jquants_api_client/api/
daily_stock_prices.rs

1//! Prices daily quotes API.
2use std::{fmt, marker::PhantomData};
3
4use serde::{de::DeserializeOwned, Deserialize, Serialize};
5
6use crate::PriceLimit;
7
8use super::{
9    shared::traits::{
10        builder::JQuantsBuilder,
11        pagination::{HasPaginationKey, MergePage, Paginatable},
12    },
13    JQuantsApiClient, JQuantsPlanClient,
14};
15
16/// Builder for Daily Stock Prices (OHLC) API.
17#[derive(Clone, Serialize)]
18pub struct DailyStockPricesBuilder<R: DeserializeOwned + fmt::Debug + Clone> {
19    #[serde(skip)]
20    client: JQuantsApiClient,
21    #[serde(skip)]
22    phantom: PhantomData<R>,
23
24    /// Issue code (e.g. 27800 or 2780)
25    ///
26    /// If a 4-character issue code is specified,  
27    /// only the data of common stock will be obtained for the issue on which both common and preferred stocks are listed.
28    #[serde(skip_serializing_if = "Option::is_none")]
29    code: Option<String>,
30    /// Starting point of data period (e.g. 20210901 or 2021-09-01)
31    #[serde(skip_serializing_if = "Option::is_none")]
32    from: Option<String>,
33    /// End point of data period (e.g. 20210907 or 2021-09-07)
34    #[serde(skip_serializing_if = "Option::is_none")]
35    to: Option<String>,
36    /// Date of data (e.g. 20210907 or 2021-09-07)
37    ///
38    /// Used when `from` and `to` are not specified.
39    #[serde(skip_serializing_if = "Option::is_none")]
40    date: Option<String>,
41
42    /// Pagination key.
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pagination_key: Option<String>,
45}
46
47impl<R: DeserializeOwned + fmt::Debug + Clone> JQuantsBuilder<R> for DailyStockPricesBuilder<R> {
48    async fn send(self) -> Result<R, crate::JQuantsError> {
49        self.send_ref().await
50    }
51
52    async fn send_ref(&self) -> Result<R, crate::JQuantsError> {
53        self.client.inner.get("prices/daily_quotes", self).await
54    }
55}
56
57impl<R: DeserializeOwned + fmt::Debug + Clone + HasPaginationKey + MergePage> Paginatable<R>
58    for DailyStockPricesBuilder<R>
59{
60    fn pagination_key(mut self, pagination_key: impl Into<String>) -> Self {
61        self.pagination_key = Some(pagination_key.into());
62        self
63    }
64}
65
66impl<R: DeserializeOwned + fmt::Debug + Clone> DailyStockPricesBuilder<R> {
67    /// Create a new builder.
68    pub(crate) fn new(client: JQuantsApiClient) -> Self {
69        Self {
70            client,
71            phantom: PhantomData,
72            code: None,
73            from: None,
74            to: None,
75            date: None,
76            pagination_key: None,
77        }
78    }
79
80    /// Set issue code (e.g. 27800 or 2780)
81    pub fn code(mut self, code: impl Into<String>) -> Self {
82        self.code = Some(code.into());
83        self
84    }
85
86    /// Set starting point of data period (e.g. 20210901 or 2021-09-01)
87    pub fn from(mut self, from: impl Into<String>) -> Self {
88        self.from = Some(from.into());
89        self
90    }
91
92    /// Set end point of data period (e.g. 20210907 or 2021-09-07)
93    pub fn to(mut self, to: impl Into<String>) -> Self {
94        self.to = Some(to.into());
95        self
96    }
97
98    /// Set date of data (e.g. 20210907 or 2021-09-07)
99    pub fn date(mut self, date: impl Into<String>) -> Self {
100        self.date = Some(date.into());
101        self
102    }
103}
104
105/// Builder for Daily Stock Prices (OHLC) API.
106pub trait DailyStockPricesApi: JQuantsPlanClient {
107    /// Response type for listed info API.
108    type Response: DeserializeOwned + fmt::Debug + Clone;
109
110    /// Get api builder for daily stock prices.
111    ///
112    /// Use [Daily Stock Prices (OHLC)(/prices/daily_quotes) API](https://jpx.gitbook.io/j-quants-en/api-reference/daily_quotes)
113    fn get_daily_stock_prices(&self) -> DailyStockPricesBuilder<Self::Response> {
114        DailyStockPricesBuilder::new(self.get_api_client().clone())
115    }
116}
117
118/// Daily Stock prices (OHLC) response for free plan.
119///
120/// See: [API Reference](https://jpx.gitbook.io/j-quants-en/api-reference/daily_quotes)
121pub type DailyStockPricesFreePlanResponse = DailyStockPricesStandardPlanResponse;
122
123/// Daily Stock prices (OHLC) response for light plan.
124///
125/// See: [API Reference](https://jpx.gitbook.io/j-quants-en/api-reference/daily_quotes)
126pub type DailyStockPricesLightPlanResponse = DailyStockPricesStandardPlanResponse;
127
128/// Daily Stock prices (OHLC) response for standard plan.
129///
130/// See: [API Reference](https://jpx.gitbook.io/j-quants-en/api-reference/daily_quotes)
131#[derive(Debug, Clone, PartialEq, Deserialize)]
132pub struct DailyStockPricesStandardPlanResponse {
133    /// List of daily quotes
134    pub daily_quotes: Vec<DailyQuoteStandardPlanItem>,
135
136    /// Pagination key for fetching next set of data
137    pub pagination_key: Option<String>,
138}
139impl HasPaginationKey for DailyStockPricesStandardPlanResponse {
140    fn get_pagination_key(&self) -> Option<&str> {
141        self.pagination_key.as_deref()
142    }
143}
144impl MergePage for DailyStockPricesStandardPlanResponse {
145    fn merge_page(
146        page: Result<Vec<Self>, crate::JQuantsError>,
147    ) -> Result<Self, crate::JQuantsError> {
148        let mut page = page?;
149        let mut merged = page.pop().unwrap();
150        for p in page {
151            merged.daily_quotes.extend(p.daily_quotes);
152        }
153        merged.pagination_key = None;
154
155        Ok(merged)
156    }
157}
158
159/// Daily Stock prices (OHLC) response for premium plan.
160///
161/// See: [API Reference](https://jpx.gitbook.io/j-quants-en/api-reference/daily_quotes)
162#[derive(Debug, Clone, PartialEq, Deserialize)]
163pub struct DailyStockPricesPremiumPlanResponse {
164    /// List of daily quotes
165    pub daily_quotes: Vec<DailyQuotePremiumPlanItem>,
166
167    /// Pagination key for fetching next set of data
168    pub pagination_key: Option<String>,
169}
170impl HasPaginationKey for DailyStockPricesPremiumPlanResponse {
171    fn get_pagination_key(&self) -> Option<&str> {
172        self.pagination_key.as_deref()
173    }
174}
175impl MergePage for DailyStockPricesPremiumPlanResponse {
176    fn merge_page(
177        page: Result<Vec<Self>, crate::JQuantsError>,
178    ) -> Result<Self, crate::JQuantsError> {
179        let mut page = page?;
180        let mut merged = page.pop().unwrap();
181        for p in page {
182            merged.daily_quotes.extend(p.daily_quotes);
183        }
184        merged.pagination_key = None;
185
186        Ok(merged)
187    }
188}
189
190/// Daily Quote for standard plan.
191#[derive(Debug, Clone, PartialEq, Deserialize)]
192pub struct DailyQuoteStandardPlanItem {
193    /// The common structure for daily quote
194    #[serde(flatten)]
195    pub common: DailyQuoteCommonItem,
196}
197
198/// Daily Quote for premium plan.
199#[derive(Debug, Clone, PartialEq, Deserialize)]
200pub struct DailyQuotePremiumPlanItem {
201    /// The common structure for daily quote
202    #[serde(flatten)]
203    pub common: DailyQuoteCommonItem,
204
205    /// Open price of the morning session (before Adjustment)
206    #[serde(rename = "MorningOpen")]
207    pub morning_open: Option<f64>,
208
209    /// High price of the morning session (before Adjustment)
210    #[serde(rename = "MorningHigh")]
211    pub morning_high: Option<f64>,
212
213    /// Low price of the morning session (before Adjustment)
214    #[serde(rename = "MorningLow")]
215    pub morning_low: Option<f64>,
216
217    /// Close price of the morning session (before Adjustment)
218    #[serde(rename = "MorningClose")]
219    pub morning_close: Option<f64>,
220
221    /// Flag of hitting the upper price limit of the day in morning session
222    #[serde(rename = "MorningUpperLimit")]
223    pub morning_upper_limit: PriceLimit,
224
225    /// Flag of hitting the lower price limit of the day in morning session
226    #[serde(rename = "MorningLowerLimit")]
227    pub morning_lower_limit: PriceLimit,
228
229    /// Trading volume of the morning session (before Adjustment)
230    #[serde(rename = "MorningVolume")]
231    pub morning_volume: Option<f64>,
232
233    /// Trading value of the morning session
234    #[serde(rename = "MorningTurnoverValue")]
235    pub morning_turnover_value: Option<f64>,
236
237    /// Adjusted open price of the morning session
238    #[serde(rename = "MorningAdjustmentOpen")]
239    pub morning_adjustment_open: Option<f64>,
240
241    /// Adjusted high price of the morning session
242    #[serde(rename = "MorningAdjustmentHigh")]
243    pub morning_adjustment_high: Option<f64>,
244
245    /// Adjusted low price of the morning session
246    #[serde(rename = "MorningAdjustmentLow")]
247    pub morning_adjustment_low: Option<f64>,
248
249    /// Adjusted close price of the morning session
250    #[serde(rename = "MorningAdjustmentClose")]
251    pub morning_adjustment_close: Option<f64>,
252
253    /// Adjusted trading volume of the morning session
254    #[serde(rename = "MorningAdjustmentVolume")]
255    pub morning_adjustment_volume: Option<f64>,
256
257    /// Open price of the afternoon session (before Adjustment)
258    #[serde(rename = "AfternoonOpen")]
259    pub afternoon_open: Option<f64>,
260
261    /// High price of the afternoon session (before Adjustment)
262    #[serde(rename = "AfternoonHigh")]
263    pub afternoon_high: Option<f64>,
264
265    /// Low price of the afternoon session (before Adjustment)
266    #[serde(rename = "AfternoonLow")]
267    pub afternoon_low: Option<f64>,
268
269    /// Close price of the afternoon session (before Adjustment)
270    #[serde(rename = "AfternoonClose")]
271    pub afternoon_close: Option<f64>,
272
273    /// Flag of hitting the upper price limit of the day in afternoon session
274    #[serde(rename = "AfternoonUpperLimit")]
275    pub afternoon_upper_limit: PriceLimit,
276
277    /// Flag of hitting the lower price limit of the day in afternoon session
278    #[serde(rename = "AfternoonLowerLimit")]
279    pub afternoon_lower_limit: PriceLimit,
280
281    /// Trading volume of the afternoon session (before Adjustment)
282    #[serde(rename = "AfternoonVolume")]
283    pub afternoon_volume: Option<f64>,
284
285    /// Trading value of the afternoon session
286    #[serde(rename = "AfternoonTurnoverValue")]
287    pub afternoon_turnover_value: Option<f64>,
288
289    /// Adjusted open price of the afternoon session
290    #[serde(rename = "AfternoonAdjustmentOpen")]
291    pub afternoon_adjustment_open: Option<f64>,
292
293    /// Adjusted high price of the afternoon session
294    #[serde(rename = "AfternoonAdjustmentHigh")]
295    pub afternoon_adjustment_high: Option<f64>,
296
297    /// Adjusted low price of the afternoon session
298    #[serde(rename = "AfternoonAdjustmentLow")]
299    pub afternoon_adjustment_low: Option<f64>,
300
301    /// Adjusted close price of the afternoon session
302    #[serde(rename = "AfternoonAdjustmentClose")]
303    pub afternoon_adjustment_close: Option<f64>,
304
305    /// Adjusted trading volume of the afternoon session
306    #[serde(rename = "AfternoonAdjustmentVolume")]
307    pub afternoon_adjustment_volume: Option<f64>,
308}
309
310/// Represents a single daily quote
311#[derive(Debug, Clone, PartialEq, Deserialize)]
312pub struct DailyQuoteCommonItem {
313    /// Date (YYYY-MM-DD).
314    #[serde(rename = "Date")]
315    pub date: String,
316
317    /// Issue code
318    #[serde(rename = "Code")]
319    pub code: String,
320
321    /// Open Price (before adjustment)
322    #[serde(rename = "Open")]
323    pub open: Option<f64>,
324
325    /// High price (before adjustment)
326    #[serde(rename = "High")]
327    pub high: Option<f64>,
328
329    /// Low price (before adjustment)
330    #[serde(rename = "Low")]
331    pub low: Option<f64>,
332
333    /// Close price (before adjustment)
334    #[serde(rename = "Close")]
335    pub close: Option<f64>,
336
337    /// Flag of hitting the upper price limit of the day
338    #[serde(rename = "UpperLimit")]
339    pub upper_limit: PriceLimit,
340
341    /// Flag of hitting the lower price limit of the day
342    #[serde(rename = "LowerLimit")]
343    pub lower_limit: PriceLimit,
344
345    /// Trading volume (before Adjustment)
346    #[serde(rename = "Volume")]
347    pub volume: Option<f64>,
348
349    /// Trading value
350    #[serde(rename = "TurnoverValue")]
351    pub turnover_value: Option<f64>,
352
353    /// Adjustment factor
354    #[serde(rename = "AdjustmentFactor")]
355    pub adjustment_factor: f64,
356
357    /// Adjusted open price
358    #[serde(rename = "AdjustmentOpen")]
359    pub adjustment_open: Option<f64>,
360
361    /// Adjusted high price
362    #[serde(rename = "AdjustmentHigh")]
363    pub adjustment_high: Option<f64>,
364
365    /// Adjusted low price
366    #[serde(rename = "AdjustmentLow")]
367    pub adjustment_low: Option<f64>,
368
369    /// Adjusted close price
370    #[serde(rename = "AdjustmentClose")]
371    pub adjustment_close: Option<f64>,
372
373    /// Adjusted volume
374    #[serde(rename = "AdjustmentVolume")]
375    pub adjustment_volume: Option<f64>,
376}
377
378#[cfg(test)]
379mod tests {
380    use crate::{
381        api::daily_stock_prices::{
382            DailyQuoteCommonItem, DailyQuotePremiumPlanItem, DailyQuoteStandardPlanItem,
383            DailyStockPricesPremiumPlanResponse, DailyStockPricesStandardPlanResponse,
384        },
385        PriceLimit,
386    };
387
388    #[test]
389    fn test_deserialize_daily_stock_prices_standard_plan_response() {
390        let json = r#"
391            {
392                "daily_quotes": [
393                    {
394                        "Date": "2023-03-24",
395                        "Code": "86970",
396                        "Open": 2047.0,
397                        "High": 2069.0,
398                        "Low": 2035.0,
399                        "Close": 2045.0,
400                        "UpperLimit": "0",
401                        "LowerLimit": "0",
402                        "Volume": 2202500.0,
403                        "TurnoverValue": 4507051850.0,
404                        "AdjustmentFactor": 1.0,
405                        "AdjustmentOpen": 2047.0,
406                        "AdjustmentHigh": 2069.0,
407                        "AdjustmentLow": 2035.0,
408                        "AdjustmentClose": 2045.0,
409                        "AdjustmentVolume": 2202500.0
410                    }
411                ],
412                "pagination_key": "value1.value2."
413            }
414        "#;
415
416        let response: DailyStockPricesStandardPlanResponse = serde_json::from_str(json).unwrap();
417        let expected_response = DailyStockPricesStandardPlanResponse {
418            daily_quotes: vec![DailyQuoteStandardPlanItem {
419                common: DailyQuoteCommonItem {
420                    date: "2023-03-24".to_string(),
421                    code: "86970".to_string(),
422                    open: Some(2047.0),
423                    high: Some(2069.0),
424                    low: Some(2035.0),
425                    close: Some(2045.0),
426                    upper_limit: PriceLimit::NotHit,
427                    lower_limit: PriceLimit::NotHit,
428                    volume: Some(2202500.0),
429                    turnover_value: Some(4507051850.0),
430                    adjustment_factor: 1.0,
431                    adjustment_open: Some(2047.0),
432                    adjustment_high: Some(2069.0),
433                    adjustment_low: Some(2035.0),
434                    adjustment_close: Some(2045.0),
435                    adjustment_volume: Some(2202500.0),
436                },
437            }],
438            pagination_key: Some("value1.value2.".to_string()),
439        };
440
441        pretty_assertions::assert_eq!(response, expected_response);
442    }
443
444    #[test]
445    fn test_deserialize_daily_stock_prices_standard_plan_response_no_pagination_key() {
446        let json = r#"
447            {
448                "daily_quotes": [
449                    {
450                        "Date": "2023-03-24",
451                        "Code": "86970",
452                        "Open": 2047.0,
453                        "High": 2069.0,
454                        "Low": 2035.0,
455                        "Close": 2045.0,
456                        "UpperLimit": "0",
457                        "LowerLimit": "0",
458                        "Volume": 2202500.0,
459                        "TurnoverValue": 4507051850.0,
460                        "AdjustmentFactor": 1.0,
461                        "AdjustmentOpen": 2047.0,
462                        "AdjustmentHigh": 2069.0,
463                        "AdjustmentLow": 2035.0,
464                        "AdjustmentClose": 2045.0,
465                        "AdjustmentVolume": 2202500.0
466                    }
467                ]
468            }
469        "#;
470
471        let response: DailyStockPricesStandardPlanResponse = serde_json::from_str(json).unwrap();
472        let expected_response = DailyStockPricesStandardPlanResponse {
473            daily_quotes: vec![DailyQuoteStandardPlanItem {
474                common: DailyQuoteCommonItem {
475                    date: "2023-03-24".to_string(),
476                    code: "86970".to_string(),
477                    open: Some(2047.0),
478                    high: Some(2069.0),
479                    low: Some(2035.0),
480                    close: Some(2045.0),
481                    upper_limit: PriceLimit::NotHit,
482                    lower_limit: PriceLimit::NotHit,
483                    volume: Some(2202500.0),
484                    turnover_value: Some(4507051850.0),
485                    adjustment_factor: 1.0,
486                    adjustment_open: Some(2047.0),
487                    adjustment_high: Some(2069.0),
488                    adjustment_low: Some(2035.0),
489                    adjustment_close: Some(2045.0),
490                    adjustment_volume: Some(2202500.0),
491                },
492            }],
493            pagination_key: None,
494        };
495
496        pretty_assertions::assert_eq!(response, expected_response);
497    }
498
499    #[test]
500    fn test_deserialize_daily_stock_prices_premium_plan_response() {
501        let json = r#"
502            {
503                "daily_quotes": [
504                    {
505                        "Date": "2023-03-24",
506                        "Code": "86970",
507                        "Open": 2047.0,
508                        "High": 2069.0,
509                        "Low": 2035.0,
510                        "Close": 2045.0,
511                        "UpperLimit": "1",
512                        "LowerLimit": "1",
513                        "Volume": 2202500.0,
514                        "TurnoverValue": 4507051850.0,
515                        "AdjustmentFactor": 1.0,
516                        "AdjustmentOpen": 2047.0,
517                        "AdjustmentHigh": 2069.0,
518                        "AdjustmentLow": 2035.0,
519                        "AdjustmentClose": 2045.0,
520                        "AdjustmentVolume": 2202500.0,
521                        "MorningOpen": 2047.0,
522                        "MorningHigh": 2069.0,
523                        "MorningLow": 2040.0,
524                        "MorningClose": 2045.5,
525                        "MorningUpperLimit": "1",
526                        "MorningLowerLimit": "1",
527                        "MorningVolume": 1121200.0,
528                        "MorningTurnoverValue": 2297525850.0,
529                        "MorningAdjustmentOpen": 2047.0,
530                        "MorningAdjustmentHigh": 2069.0,
531                        "MorningAdjustmentLow": 2040.0,
532                        "MorningAdjustmentClose": 2045.5,
533                        "MorningAdjustmentVolume": 1121200.0,
534                        "AfternoonOpen": 2047.0,
535                        "AfternoonHigh": 2047.0,
536                        "AfternoonLow": 2035.0,
537                        "AfternoonClose": 2045.0,
538                        "AfternoonUpperLimit": "1",
539                        "AfternoonLowerLimit": "1",
540                        "AfternoonVolume": 1081300.0,
541                        "AfternoonTurnoverValue": 2209526000.0,
542                        "AfternoonAdjustmentOpen": 2047.0,
543                        "AfternoonAdjustmentHigh": 2047.0,
544                        "AfternoonAdjustmentLow": 2035.0,
545                        "AfternoonAdjustmentClose": 2045.0,
546                        "AfternoonAdjustmentVolume": 1081300.0
547                    }
548                ],
549                "pagination_key": "value1.value2."
550            }
551        "#;
552
553        let response: DailyStockPricesPremiumPlanResponse = serde_json::from_str(json).unwrap();
554        let expected_response = DailyStockPricesPremiumPlanResponse {
555            daily_quotes: vec![DailyQuotePremiumPlanItem {
556                common: DailyQuoteCommonItem {
557                    date: "2023-03-24".to_string(),
558                    code: "86970".to_string(),
559                    open: Some(2047.0),
560                    high: Some(2069.0),
561                    low: Some(2035.0),
562                    close: Some(2045.0),
563                    upper_limit: PriceLimit::Hit,
564                    lower_limit: PriceLimit::Hit,
565                    volume: Some(2202500.0),
566                    turnover_value: Some(4507051850.0),
567                    adjustment_factor: 1.0,
568                    adjustment_open: Some(2047.0),
569                    adjustment_high: Some(2069.0),
570                    adjustment_low: Some(2035.0),
571                    adjustment_close: Some(2045.0),
572                    adjustment_volume: Some(2202500.0),
573                },
574                morning_open: Some(2047.0),
575                morning_high: Some(2069.0),
576                morning_low: Some(2040.0),
577                morning_close: Some(2045.5),
578                morning_upper_limit: PriceLimit::Hit,
579                morning_lower_limit: PriceLimit::Hit,
580                morning_volume: Some(1121200.0),
581                morning_turnover_value: Some(2297525850.0),
582                morning_adjustment_open: Some(2047.0),
583                morning_adjustment_high: Some(2069.0),
584                morning_adjustment_low: Some(2040.0),
585                morning_adjustment_close: Some(2045.5),
586                morning_adjustment_volume: Some(1121200.0),
587                afternoon_open: Some(2047.0),
588                afternoon_high: Some(2047.0),
589                afternoon_low: Some(2035.0),
590                afternoon_close: Some(2045.0),
591                afternoon_upper_limit: PriceLimit::Hit,
592                afternoon_lower_limit: PriceLimit::Hit,
593                afternoon_volume: Some(1081300.0),
594                afternoon_turnover_value: Some(2209526000.0),
595                afternoon_adjustment_open: Some(2047.0),
596                afternoon_adjustment_high: Some(2047.0),
597                afternoon_adjustment_low: Some(2035.0),
598                afternoon_adjustment_close: Some(2045.0),
599                afternoon_adjustment_volume: Some(1081300.0),
600            }],
601            pagination_key: Some("value1.value2.".to_string()),
602        };
603
604        pretty_assertions::assert_eq!(response, expected_response);
605    }
606
607    #[test]
608    fn test_deserialize_daily_stock_prices_premium_plan_response_no_data() {
609        let json = r#"
610            {
611                "daily_quotes": [
612                    {
613                        "Date": "2023-03-24",
614                        "Code": "86970",
615                        "Open": null,
616                        "High": null,
617                        "Low": null,
618                        "Close": null,
619                        "UpperLimit": "0",
620                        "LowerLimit": "0",
621                        "Volume": null,
622                        "TurnoverValue": null,
623                        "AdjustmentFactor": 1.0,
624                        "AdjustmentOpen": null,
625                        "AdjustmentHigh": null,
626                        "AdjustmentLow": null,
627                        "AdjustmentClose": null,
628                        "AdjustmentVolume": null,
629                        "MorningOpen": null,
630                        "MorningHigh": null,
631                        "MorningLow": null,
632                        "MorningClose": null,
633                        "MorningUpperLimit": "0",
634                        "MorningLowerLimit": "0",
635                        "MorningVolume": null,
636                        "MorningTurnoverValue": null,
637                        "MorningAdjustmentOpen": null,
638                        "MorningAdjustmentHigh": null,
639                        "MorningAdjustmentLow": null,
640                        "MorningAdjustmentClose": null,
641                        "MorningAdjustmentVolume": null,
642                        "AfternoonOpen": null,
643                        "AfternoonHigh": null,
644                        "AfternoonLow": null,
645                        "AfternoonClose": null,
646                        "AfternoonUpperLimit": "0",
647                        "AfternoonLowerLimit": "0",
648                        "AfternoonVolume": null,
649                        "AfternoonTurnoverValue": null,
650                        "AfternoonAdjustmentOpen": null,
651                        "AfternoonAdjustmentHigh": null,
652                        "AfternoonAdjustmentLow": null,
653                        "AfternoonAdjustmentClose": null,
654                        "AfternoonAdjustmentVolume": null
655                    }
656                ],
657                "pagination_key": "value1.value2."
658            }
659        "#;
660
661        let response: DailyStockPricesPremiumPlanResponse = serde_json::from_str(json).unwrap();
662        let expected_response = DailyStockPricesPremiumPlanResponse {
663            daily_quotes: vec![DailyQuotePremiumPlanItem {
664                common: DailyQuoteCommonItem {
665                    date: "2023-03-24".to_string(),
666                    code: "86970".to_string(),
667                    open: None,
668                    high: None,
669                    low: None,
670                    close: None,
671                    upper_limit: PriceLimit::NotHit,
672                    lower_limit: PriceLimit::NotHit,
673                    volume: None,
674                    turnover_value: None,
675                    adjustment_factor: 1.0,
676                    adjustment_open: None,
677                    adjustment_high: None,
678                    adjustment_low: None,
679                    adjustment_close: None,
680                    adjustment_volume: None,
681                },
682                morning_open: None,
683                morning_high: None,
684                morning_low: None,
685                morning_close: None,
686                morning_upper_limit: PriceLimit::NotHit,
687                morning_lower_limit: PriceLimit::NotHit,
688                morning_volume: None,
689                morning_turnover_value: None,
690                morning_adjustment_open: None,
691                morning_adjustment_high: None,
692                morning_adjustment_low: None,
693                morning_adjustment_close: None,
694                morning_adjustment_volume: None,
695                afternoon_open: None,
696                afternoon_high: None,
697                afternoon_low: None,
698                afternoon_close: None,
699                afternoon_upper_limit: PriceLimit::NotHit,
700                afternoon_lower_limit: PriceLimit::NotHit,
701                afternoon_volume: None,
702                afternoon_turnover_value: None,
703                afternoon_adjustment_open: None,
704                afternoon_adjustment_high: None,
705                afternoon_adjustment_low: None,
706                afternoon_adjustment_close: None,
707                afternoon_adjustment_volume: None,
708            }],
709            pagination_key: Some("value1.value2.".to_string()),
710        };
711
712        pretty_assertions::assert_eq!(response, expected_response);
713    }
714
715    #[test]
716    fn test_deserialize_daily_stock_prices_premium_plan_response_no_pagination_key() {
717        let json = r#"
718            {
719                "daily_quotes": [
720                    {
721                        "Date": "2023-03-24",
722                        "Code": "86970",
723                        "Open": 2047.0,
724                        "High": 2069.0,
725                        "Low": 2035.0,
726                        "Close": 2045.0,
727                        "UpperLimit": "0",
728                        "LowerLimit": "0",
729                        "Volume": 2202500.0,
730                        "TurnoverValue": 4507051850.0,
731                        "AdjustmentFactor": 1.0,
732                        "AdjustmentOpen": 2047.0,
733                        "AdjustmentHigh": 2069.0,
734                        "AdjustmentLow": 2035.0,
735                        "AdjustmentClose": 2045.0,
736                        "AdjustmentVolume": 2202500.0,
737                        "MorningOpen": 2047.0,
738                        "MorningHigh": 2069.0,
739                        "MorningLow": 2040.0,
740                        "MorningClose": 2045.5,
741                        "MorningUpperLimit": "0",
742                        "MorningLowerLimit": "0",
743                        "MorningVolume": 1121200.0,
744                        "MorningTurnoverValue": 2297525850.0,
745                        "MorningAdjustmentOpen": 2047.0,
746                        "MorningAdjustmentHigh": 2069.0,
747                        "MorningAdjustmentLow": 2040.0,
748                        "MorningAdjustmentClose": 2045.5,
749                        "MorningAdjustmentVolume": 1121200.0,
750                        "AfternoonOpen": 2047.0,
751                        "AfternoonHigh": 2047.0,
752                        "AfternoonLow": 2035.0,
753                        "AfternoonClose": 2045.0,
754                        "AfternoonUpperLimit": "0",
755                        "AfternoonLowerLimit": "0",
756                        "AfternoonVolume": 1081300.0,
757                        "AfternoonTurnoverValue": 2209526000.0,
758                        "AfternoonAdjustmentOpen": 2047.0,
759                        "AfternoonAdjustmentHigh": 2047.0,
760                        "AfternoonAdjustmentLow": 2035.0,
761                        "AfternoonAdjustmentClose": 2045.0,
762                        "AfternoonAdjustmentVolume": 1081300.0
763                    }
764                ]
765            }
766        "#;
767
768        let response: DailyStockPricesPremiumPlanResponse = serde_json::from_str(json).unwrap();
769        let expected_response = DailyStockPricesPremiumPlanResponse {
770            daily_quotes: vec![DailyQuotePremiumPlanItem {
771                common: DailyQuoteCommonItem {
772                    date: "2023-03-24".to_string(),
773                    code: "86970".to_string(),
774                    open: Some(2047.0),
775                    high: Some(2069.0),
776                    low: Some(2035.0),
777                    close: Some(2045.0),
778                    upper_limit: PriceLimit::NotHit,
779                    lower_limit: PriceLimit::NotHit,
780                    volume: Some(2202500.0),
781                    turnover_value: Some(4507051850.0),
782                    adjustment_factor: 1.0,
783                    adjustment_open: Some(2047.0),
784                    adjustment_high: Some(2069.0),
785                    adjustment_low: Some(2035.0),
786                    adjustment_close: Some(2045.0),
787                    adjustment_volume: Some(2202500.0),
788                },
789                morning_open: Some(2047.0),
790                morning_high: Some(2069.0),
791                morning_low: Some(2040.0),
792                morning_close: Some(2045.5),
793                morning_upper_limit: PriceLimit::NotHit,
794                morning_lower_limit: PriceLimit::NotHit,
795                morning_volume: Some(1121200.0),
796                morning_turnover_value: Some(2297525850.0),
797                morning_adjustment_open: Some(2047.0),
798                morning_adjustment_high: Some(2069.0),
799                morning_adjustment_low: Some(2040.0),
800                morning_adjustment_close: Some(2045.5),
801                morning_adjustment_volume: Some(1121200.0),
802                afternoon_open: Some(2047.0),
803                afternoon_high: Some(2047.0),
804                afternoon_low: Some(2035.0),
805                afternoon_close: Some(2045.0),
806                afternoon_upper_limit: PriceLimit::NotHit,
807                afternoon_lower_limit: PriceLimit::NotHit,
808                afternoon_volume: Some(1081300.0),
809                afternoon_turnover_value: Some(2209526000.0),
810                afternoon_adjustment_open: Some(2047.0),
811                afternoon_adjustment_high: Some(2047.0),
812                afternoon_adjustment_low: Some(2035.0),
813                afternoon_adjustment_close: Some(2045.0),
814                afternoon_adjustment_volume: Some(1081300.0),
815            }],
816            pagination_key: None,
817        };
818
819        pretty_assertions::assert_eq!(response, expected_response);
820    }
821}