jquants_api_client/api/
options_prices.rs

1//! Options OHLC (/derivatives/options) API
2
3use serde::{Deserialize, Serialize};
4
5use super::{
6    shared::{
7        deserialize_utils::{deserialize_f64_or_none, empty_string_or_null_as_none},
8        traits::{
9            builder::JQuantsBuilder,
10            pagination::{HasPaginationKey, MergePage, Paginatable},
11        },
12        types::{
13            central_contract_month_flag::CentralContractMonthFlag,
14            emergency_margin_trigger_division::EmergencyMarginTriggerDivision,
15            options_code::OptionsCode, put_call_division::PutCallDivision,
16            underlying_sso::UnderlyingSSO,
17        },
18    },
19    JQuantsApiClient, JQuantsPlanClient,
20};
21
22/// Builder for Options (OHLC) Data API.
23#[derive(Clone, Serialize)]
24pub struct OptionsPricesBuilder {
25    #[serde(skip)]
26    client: JQuantsApiClient,
27
28    /// Category of data
29    #[serde(skip_serializing_if = "Option::is_none")]
30    category: Option<String>,
31
32    /// Security options code (optional)
33    #[serde(skip_serializing_if = "Option::is_none")]
34    code: Option<OptionsCode>,
35
36    /// Date of data (e.g., "20210901" or "2021-09-01")
37    date: String,
38
39    /// Central contract month flag (optional)
40    #[serde(skip_serializing_if = "Option::is_none")]
41    contract_flag: Option<String>,
42
43    /// Pagination key.
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pagination_key: Option<String>,
46}
47
48impl JQuantsBuilder<OptionsPricesResponse> for OptionsPricesBuilder {
49    async fn send(self) -> Result<OptionsPricesResponse, crate::JQuantsError> {
50        self.send_ref().await
51    }
52
53    async fn send_ref(&self) -> Result<OptionsPricesResponse, crate::JQuantsError> {
54        self.client.inner.get("derivatives/options", self).await
55    }
56}
57
58impl Paginatable<OptionsPricesResponse> for OptionsPricesBuilder {
59    fn pagination_key(mut self, pagination_key: impl Into<String>) -> Self {
60        self.pagination_key = Some(pagination_key.into());
61        self
62    }
63}
64
65impl OptionsPricesBuilder {
66    /// Create a new builder.
67    pub(crate) fn new(client: JQuantsApiClient, date: String) -> Self {
68        Self {
69            client,
70            category: None,
71            code: None,
72            date,
73            contract_flag: None,
74            pagination_key: None,
75        }
76    }
77
78    /// Set the category of data.
79    pub fn category(mut self, category: impl Into<String>) -> Self {
80        self.category = Some(category.into());
81        self
82    }
83
84    /// Set the security options code.
85    pub fn code(mut self, code: impl Into<OptionsCode>) -> Self {
86        self.code = Some(code.into());
87        self
88    }
89
90    /// Set the date of data (e.g., "20210901" or "2021-09-01")
91    pub fn date(mut self, date: impl Into<String>) -> Self {
92        self.date = date.into();
93        self
94    }
95
96    /// Set the central contract month flag.
97    pub fn contract_flag(mut self, flag: impl Into<String>) -> Self {
98        self.contract_flag = Some(flag.into());
99        self
100    }
101
102    /// Set pagination key for fetching the next set of data.
103    pub fn pagination_key(mut self, pagination_key: impl Into<String>) -> Self {
104        self.pagination_key = Some(pagination_key.into());
105        self
106    }
107}
108
109/// Trait for Options (OHLC) Data API.
110pub trait OptionsPricesApi: JQuantsPlanClient {
111    /// Get API builder for Options (OHLC) Data.
112    ///
113    /// Use [Options (OHLC) (/derivatives/options) API](https://jpx.gitbook.io/j-quants-en/api-reference/options)
114    fn get_options_prices(&self, date: impl Into<String>) -> OptionsPricesBuilder {
115        OptionsPricesBuilder::new(self.get_api_client().clone(), date.into())
116    }
117}
118
119/// Options (OHLC) Data API response.
120///
121/// See: [API Reference](https://jpx.gitbook.io/j-quants-en/api-reference/options)
122#[derive(Debug, Clone, PartialEq, Deserialize)]
123pub struct OptionsPricesResponse {
124    /// List of Options prices
125    pub options: Vec<OptionsPricesItem>,
126    /// Pagination key for fetching next set of data
127    pub pagination_key: Option<String>,
128}
129
130impl HasPaginationKey for OptionsPricesResponse {
131    fn get_pagination_key(&self) -> Option<&str> {
132        self.pagination_key.as_deref()
133    }
134}
135
136impl MergePage for OptionsPricesResponse {
137    fn merge_page(
138        page: Result<Vec<Self>, crate::JQuantsError>,
139    ) -> Result<Self, crate::JQuantsError> {
140        let mut page = page?;
141        let mut merged = page.pop().unwrap();
142        for p in page {
143            merged.options.extend(p.options);
144        }
145        merged.pagination_key = None;
146
147        Ok(merged)
148    }
149}
150
151/// Represents a single Options price record.
152#[derive(Debug, Clone, PartialEq, Deserialize)]
153pub struct OptionsPricesItem {
154    /// Issue code
155    #[serde(rename = "Code")]
156    pub code: String,
157
158    /// Derivative Product Category
159    #[serde(rename = "DerivativesProductCategory")]
160    pub derivatives_product_category: String,
161
162    /// Underlying SSO
163    #[serde(rename = "UnderlyingSSO")]
164    pub underlying_sso: UnderlyingSSO,
165
166    /// Trading day (YYYY-MM-DD)
167    #[serde(rename = "Date")]
168    pub date: String,
169
170    /// Whole day open price
171    #[serde(rename = "WholeDayOpen")]
172    pub whole_day_open: f64,
173
174    /// Whole day high price
175    #[serde(rename = "WholeDayHigh")]
176    pub whole_day_high: f64,
177
178    /// Whole day low price
179    #[serde(rename = "WholeDayLow")]
180    pub whole_day_low: f64,
181
182    /// Whole day close price
183    #[serde(rename = "WholeDayClose")]
184    pub whole_day_close: f64,
185
186    /// Morning session open price
187    #[serde(
188        rename = "MorningSessionOpen",
189        deserialize_with = "deserialize_f64_or_none"
190    )]
191    pub morning_session_open: Option<f64>,
192
193    /// Morning session high price
194    #[serde(
195        rename = "MorningSessionHigh",
196        deserialize_with = "deserialize_f64_or_none"
197    )]
198    pub morning_session_high: Option<f64>,
199
200    /// Morning session low price
201    #[serde(
202        rename = "MorningSessionLow",
203        deserialize_with = "deserialize_f64_or_none"
204    )]
205    pub morning_session_low: Option<f64>,
206
207    /// Morning session close price
208    #[serde(
209        rename = "MorningSessionClose",
210        deserialize_with = "deserialize_f64_or_none"
211    )]
212    pub morning_session_close: Option<f64>,
213
214    /// Night session open price
215    #[serde(
216        rename = "NightSessionOpen",
217        deserialize_with = "deserialize_f64_or_none"
218    )]
219    pub night_session_open: Option<f64>,
220
221    /// Night session high price
222    #[serde(
223        rename = "NightSessionHigh",
224        deserialize_with = "deserialize_f64_or_none"
225    )]
226    pub night_session_high: Option<f64>,
227
228    /// Night session low price
229    #[serde(
230        rename = "NightSessionLow",
231        deserialize_with = "deserialize_f64_or_none"
232    )]
233    pub night_session_low: Option<f64>,
234
235    /// Night session close price
236    #[serde(
237        rename = "NightSessionClose",
238        deserialize_with = "deserialize_f64_or_none"
239    )]
240    pub night_session_close: Option<f64>,
241
242    /// Day session open price
243    #[serde(
244        rename = "DaySessionOpen",
245        deserialize_with = "deserialize_f64_or_none"
246    )]
247    pub day_session_open: Option<f64>,
248
249    /// Day session high price
250    #[serde(rename = "DaySessionHigh")]
251    pub day_session_high: f64,
252
253    /// Day session low price
254    #[serde(rename = "DaySessionLow")]
255    pub day_session_low: f64,
256
257    /// Day session close price
258    #[serde(rename = "DaySessionClose")]
259    pub day_session_close: f64,
260
261    /// Volume
262    #[serde(rename = "Volume")]
263    pub volume: f64,
264
265    /// Open interest
266    #[serde(rename = "OpenInterest")]
267    pub open_interest: f64,
268
269    /// Turnover value
270    #[serde(rename = "TurnoverValue")]
271    pub turnover_value: f64,
272
273    /// Contract month (YYYY-MM)
274    #[serde(rename = "ContractMonth")]
275    pub contract_month: String,
276
277    /// Strike price
278    #[serde(rename = "StrikePrice")]
279    pub strike_price: f64,
280
281    /// Volume only auction
282    #[serde(
283        rename = "Volume(OnlyAuction)",
284        deserialize_with = "deserialize_f64_or_none"
285    )]
286    pub volume_only_auction: Option<f64>,
287
288    /// Emergency margin trigger division
289    #[serde(rename = "EmergencyMarginTriggerDivision")]
290    pub emergency_margin_trigger_division: EmergencyMarginTriggerDivision,
291
292    /// Put Call division (1: put, 2: call)
293    #[serde(rename = "PutCallDivision")]
294    pub put_call_division: PutCallDivision,
295
296    /// Last trading day (YYYY-MM-DD)
297    #[serde(
298        rename = "LastTradingDay",
299        deserialize_with = "empty_string_or_null_as_none"
300    )]
301    pub last_trading_day: Option<String>,
302
303    /// Special quotation day (YYYY-MM-DD)
304    #[serde(
305        rename = "SpecialQuotationDay",
306        deserialize_with = "empty_string_or_null_as_none"
307    )]
308    pub special_quotation_day: Option<String>,
309
310    /// Settlement price
311    #[serde(
312        rename = "SettlementPrice",
313        deserialize_with = "deserialize_f64_or_none"
314    )]
315    pub settlement_price: Option<f64>,
316
317    /// Theoretical price
318    #[serde(
319        rename = "TheoreticalPrice",
320        deserialize_with = "deserialize_f64_or_none"
321    )]
322    pub theoretical_price: Option<f64>,
323
324    /// Base volatility
325    #[serde(
326        rename = "BaseVolatility",
327        deserialize_with = "deserialize_f64_or_none"
328    )]
329    pub base_volatility: Option<f64>,
330
331    /// Underlying asset price
332    #[serde(
333        rename = "UnderlyingPrice",
334        deserialize_with = "deserialize_f64_or_none"
335    )]
336    pub underlying_price: Option<f64>,
337
338    /// Implied volatility
339    #[serde(
340        rename = "ImpliedVolatility",
341        deserialize_with = "deserialize_f64_or_none"
342    )]
343    pub implied_volatility: Option<f64>,
344
345    /// Interest rate for theoretical price calculation
346    #[serde(rename = "InterestRate", deserialize_with = "deserialize_f64_or_none")]
347    pub interest_rate: Option<f64>,
348
349    /// Flag of the central contract month
350    #[serde(
351        rename = "CentralContractMonthFlag",
352        deserialize_with = "empty_string_or_null_as_none"
353    )]
354    pub central_contract_month_flag: Option<CentralContractMonthFlag>,
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360
361    #[test]
362    fn test_deserialize_options_prices_response() {
363        let json_data = r#"
364        {
365            "options": [
366                {
367                    "Code": "140014505", 
368                    "DerivativesProductCategory": "TOPIXE", 
369                    "UnderlyingSSO": "-", 
370                    "Date": "2024-07-23", 
371                    "WholeDayOpen": 0.0, 
372                    "WholeDayHigh": 0.0, 
373                    "WholeDayLow": 0.0, 
374                    "WholeDayClose": 0.0, 
375                    "MorningSessionOpen": "", 
376                    "MorningSessionHigh": "", 
377                    "MorningSessionLow": "", 
378                    "MorningSessionClose": "", 
379                    "NightSessionOpen": 0.0, 
380                    "NightSessionHigh": 0.0, 
381                    "NightSessionLow": 0.0, 
382                    "NightSessionClose": 0.0, 
383                    "DaySessionOpen": 0.0, 
384                    "DaySessionHigh": 0.0, 
385                    "DaySessionLow": 0.0, 
386                    "DaySessionClose": 0.0, 
387                    "Volume": 0.0, 
388                    "OpenInterest": 0.0, 
389                    "TurnoverValue": 0.0, 
390                    "ContractMonth": "2025-01", 
391                    "StrikePrice": 2450.0, 
392                    "Volume(OnlyAuction)": 0.0, 
393                    "EmergencyMarginTriggerDivision": "002", 
394                    "PutCallDivision": "2", 
395                    "LastTradingDay": "2025-01-09", 
396                    "SpecialQuotationDay": "2025-01-10", 
397                    "SettlementPrice": 377.0, 
398                    "TheoreticalPrice": 380.3801, 
399                    "BaseVolatility": 18.115, 
400                    "UnderlyingPrice": 2833.39, 
401                    "ImpliedVolatility": 17.2955, 
402                    "InterestRate": 0.3527, 
403                    "CentralContractMonthFlag": "0"
404                }
405            ],
406            "pagination_key": "value1.value2."
407        }
408        "#;
409
410        let response: OptionsPricesResponse = serde_json::from_str(json_data).unwrap();
411
412        let expected_option = vec![OptionsPricesItem {
413            code: "140014505".to_string(),
414            derivatives_product_category: "TOPIXE".to_string(),
415            underlying_sso: UnderlyingSSO::Other,
416            date: "2024-07-23".to_string(),
417            whole_day_open: 0.0,
418            whole_day_high: 0.0,
419            whole_day_low: 0.0,
420            whole_day_close: 0.0,
421            morning_session_open: None,
422            morning_session_high: None,
423            morning_session_low: None,
424            morning_session_close: None,
425            night_session_open: Some(0.0),
426            night_session_high: Some(0.0),
427            night_session_low: Some(0.0),
428            night_session_close: Some(0.0),
429            day_session_open: Some(0.0),
430            day_session_high: 0.0,
431            day_session_low: 0.0,
432            day_session_close: 0.0,
433            volume: 0.0,
434            open_interest: 0.0,
435            turnover_value: 0.0,
436            contract_month: "2025-01".to_string(),
437            strike_price: 2450.0,
438            volume_only_auction: Some(0.0),
439            emergency_margin_trigger_division: EmergencyMarginTriggerDivision::Calculated,
440            put_call_division: PutCallDivision::Call,
441            last_trading_day: Some("2025-01-09".to_string()),
442            special_quotation_day: Some("2025-01-10".to_string()),
443            settlement_price: Some(377.0),
444            theoretical_price: Some(380.3801),
445            base_volatility: Some(18.115),
446            underlying_price: Some(2833.39),
447            implied_volatility: Some(17.2955),
448            interest_rate: Some(0.3527),
449            central_contract_month_flag: Some(CentralContractMonthFlag::Others),
450        }];
451
452        let expected_response = OptionsPricesResponse {
453            options: expected_option,
454            pagination_key: Some("value1.value2.".to_string()),
455        };
456
457        pretty_assertions::assert_eq!(response, expected_response);
458    }
459
460    #[test]
461    fn test_deserialize_options_prices_response_with_missing_optional_fields() {
462        let json_data = r#"
463        {
464            "options": [
465                {
466                    "Code": "140014505",
467                    "DerivativesProductCategory": "TOPIXE",
468                    "UnderlyingSSO": "-",
469                    "Date": "2024-07-23",
470                    "WholeDayOpen": 0.0,
471                    "WholeDayHigh": 0.0,
472                    "WholeDayLow": 0.0,
473                    "WholeDayClose": 0.0,
474                    "MorningSessionOpen": "",
475                    "MorningSessionHigh": "",
476                    "MorningSessionLow": "",
477                    "MorningSessionClose": "",
478                    "NightSessionOpen": "",
479                    "NightSessionHigh": "",
480                    "NightSessionLow": "",
481                    "NightSessionClose": "",
482                    "DaySessionOpen": "",
483                    "DaySessionHigh": 0.0,
484                    "DaySessionLow": 0.0,
485                    "DaySessionClose": 0.0,
486                    "Volume": 0.0,
487                    "OpenInterest": 0.0,
488                    "TurnoverValue": 0.0,
489                    "ContractMonth": "2025-01",
490                    "StrikePrice": 2450.0,
491                    "Volume(OnlyAuction)": "",
492                    "EmergencyMarginTriggerDivision": "001",
493                    "PutCallDivision": "2",
494                    "LastTradingDay": "2025-01-09",
495                    "SpecialQuotationDay": "2025-01-10",
496                    "SettlementPrice": "",
497                    "TheoreticalPrice": "",
498                    "BaseVolatility": "",
499                    "UnderlyingPrice": "",
500                    "ImpliedVolatility": "",
501                    "InterestRate": "",
502                    "CentralContractMonthFlag": ""
503                }
504            ],
505            "pagination_key": "value1.value2."
506        }
507        "#;
508
509        let response: OptionsPricesResponse = serde_json::from_str(json_data).unwrap();
510
511        let expected_option = vec![OptionsPricesItem {
512            code: "140014505".to_string(),
513            derivatives_product_category: "TOPIXE".to_string(),
514            underlying_sso: UnderlyingSSO::Other,
515            date: "2024-07-23".to_string(),
516            whole_day_open: 0.0,
517            whole_day_high: 0.0,
518            whole_day_low: 0.0,
519            whole_day_close: 0.0,
520            morning_session_open: None,
521            morning_session_high: None,
522            morning_session_low: None,
523            morning_session_close: None,
524            night_session_open: None,
525            night_session_high: None,
526            night_session_low: None,
527            night_session_close: None,
528            day_session_open: None,
529            day_session_high: 0.0,
530            day_session_low: 0.0,
531            day_session_close: 0.0,
532            volume: 0.0,
533            open_interest: 0.0,
534            turnover_value: 0.0,
535            contract_month: "2025-01".to_string(),
536            strike_price: 2450.0,
537            volume_only_auction: None,
538            emergency_margin_trigger_division: EmergencyMarginTriggerDivision::Triggered,
539            put_call_division: PutCallDivision::Call,
540            last_trading_day: Some("2025-01-09".to_string()),
541            special_quotation_day: Some("2025-01-10".to_string()),
542            settlement_price: None,
543            theoretical_price: None,
544            base_volatility: None,
545            underlying_price: None,
546            implied_volatility: None,
547            interest_rate: None,
548            central_contract_month_flag: None,
549        }];
550
551        let expected_response = OptionsPricesResponse {
552            options: expected_option,
553            pagination_key: Some("value1.value2.".to_string()),
554        };
555
556        pretty_assertions::assert_eq!(response, expected_response);
557    }
558
559    #[test]
560    fn test_deserialize_options_prices_response_multiple_items() {
561        let json_data = r#"
562        {
563            "options": [
564                {
565                    "Code": "140014505",
566                    "DerivativesProductCategory": "TOPIXE",
567                    "UnderlyingSSO": "-",
568                    "Date": "2024-07-23",
569                    "WholeDayOpen": 1000.0,
570                    "WholeDayHigh": 1050.0,
571                    "WholeDayLow": 990.0,
572                    "WholeDayClose": 1025.0,
573                    "MorningSessionOpen": "1005.0",
574                    "MorningSessionHigh": "1045.0",
575                    "MorningSessionLow": "995.0",
576                    "MorningSessionClose": "1020.0",
577                    "NightSessionOpen": 1010.0,
578                    "NightSessionHigh": 1040.0,
579                    "NightSessionLow": 995.0,
580                    "NightSessionClose": 1030.0,
581                    "DaySessionOpen": 1025.0,
582                    "DaySessionHigh": 1060.0,
583                    "DaySessionLow": 1000.0,
584                    "DaySessionClose": 1045.0,
585                    "Volume": 1500.0,
586                    "OpenInterest": 300.0,
587                    "TurnoverValue": 1500000.0,
588                    "ContractMonth": "2025-02",
589                    "StrikePrice": 2500.0,
590                    "Volume(OnlyAuction)": 500.0,
591                    "EmergencyMarginTriggerDivision": "001",
592                    "PutCallDivision": "1",
593                    "LastTradingDay": "2025-02-09",
594                    "SpecialQuotationDay": "2025-02-10",
595                    "SettlementPrice": 1025.0,
596                    "TheoreticalPrice": 1030.5001,
597                    "BaseVolatility": 19.200,
598                    "UnderlyingPrice": 2850.00,
599                    "ImpliedVolatility": 18.5000,
600                    "InterestRate": 0.3600,
601                    "CentralContractMonthFlag": "1"
602                },
603                {
604                    "Code": "140014506",
605                    "DerivativesProductCategory": "TOPIXE",
606                    "UnderlyingSSO": "-",
607                    "Date": "2024-07-23",
608                    "WholeDayOpen": 2000.0,
609                    "WholeDayHigh": 2050.0,
610                    "WholeDayLow": 1990.0,
611                    "WholeDayClose": 2025.0,
612                    "MorningSessionOpen": "2005.0",
613                    "MorningSessionHigh": "2045.0",
614                    "MorningSessionLow": "1995.0",
615                    "MorningSessionClose": "2020.0",
616                    "NightSessionOpen": 2010.0,
617                    "NightSessionHigh": 2040.0,
618                    "NightSessionLow": 1995.0,
619                    "NightSessionClose": 2030.0,
620                    "DaySessionOpen": 2025.0,
621                    "DaySessionHigh": 2060.0,
622                    "DaySessionLow": 2000.0,
623                    "DaySessionClose": 2045.0,
624                    "Volume": 2500.0,
625                    "OpenInterest": 400.0,
626                    "TurnoverValue": 2500000.0,
627                    "ContractMonth": "2025-03",
628                    "StrikePrice": 2550.0,
629                    "Volume(OnlyAuction)": 600.0,
630                    "EmergencyMarginTriggerDivision": "002",
631                    "PutCallDivision": "2",
632                    "LastTradingDay": "2025-03-09",
633                    "SpecialQuotationDay": "2025-03-10",
634                    "SettlementPrice": 2025.0,
635                    "TheoreticalPrice": 2030.5001,
636                    "BaseVolatility": 19.500,
637                    "UnderlyingPrice": 2855.00,
638                    "ImpliedVolatility": 18.7000,
639                    "InterestRate": 0.3650,
640                    "CentralContractMonthFlag": "1"
641                }
642            ],
643            "pagination_key": "value3.value4."
644        }
645        "#;
646
647        let response: OptionsPricesResponse = serde_json::from_str(json_data).unwrap();
648
649        let expected_options = vec![
650            OptionsPricesItem {
651                code: "140014505".to_string(),
652                derivatives_product_category: "TOPIXE".to_string(),
653                underlying_sso: UnderlyingSSO::Other,
654                date: "2024-07-23".to_string(),
655                whole_day_open: 1000.0,
656                whole_day_high: 1050.0,
657                whole_day_low: 990.0,
658                whole_day_close: 1025.0,
659                morning_session_open: Some(1005.0),
660                morning_session_high: Some(1045.0),
661                morning_session_low: Some(995.0),
662                morning_session_close: Some(1020.0),
663                night_session_open: Some(1010.0),
664                night_session_high: Some(1040.0),
665                night_session_low: Some(995.0),
666                night_session_close: Some(1030.0),
667                day_session_open: Some(1025.0),
668                day_session_high: 1060.0,
669                day_session_low: 1000.0,
670                day_session_close: 1045.0,
671                volume: 1500.0,
672                open_interest: 300.0,
673                turnover_value: 1500000.0,
674                contract_month: "2025-02".to_string(),
675                strike_price: 2500.0,
676                volume_only_auction: Some(500.0),
677                emergency_margin_trigger_division: EmergencyMarginTriggerDivision::Triggered,
678                put_call_division: PutCallDivision::Put,
679                last_trading_day: Some("2025-02-09".to_string()),
680                special_quotation_day: Some("2025-02-10".to_string()),
681                settlement_price: Some(1025.0),
682                theoretical_price: Some(1030.5001),
683                base_volatility: Some(19.200),
684                underlying_price: Some(2850.00),
685                implied_volatility: Some(18.5000),
686                interest_rate: Some(0.3600),
687                central_contract_month_flag: Some(CentralContractMonthFlag::CentralContractMonth),
688            },
689            OptionsPricesItem {
690                code: "140014506".to_string(),
691                derivatives_product_category: "TOPIXE".to_string(),
692                underlying_sso: UnderlyingSSO::Other,
693                date: "2024-07-23".to_string(),
694                whole_day_open: 2000.0,
695                whole_day_high: 2050.0,
696                whole_day_low: 1990.0,
697                whole_day_close: 2025.0,
698                morning_session_open: Some(2005.0),
699                morning_session_high: Some(2045.0),
700                morning_session_low: Some(1995.0),
701                morning_session_close: Some(2020.0),
702                night_session_open: Some(2010.0),
703                night_session_high: Some(2040.0),
704                night_session_low: Some(1995.0),
705                night_session_close: Some(2030.0),
706                day_session_open: Some(2025.0),
707                day_session_high: 2060.0,
708                day_session_low: 2000.0,
709                day_session_close: 2045.0,
710                volume: 2500.0,
711                open_interest: 400.0,
712                turnover_value: 2500000.0,
713                contract_month: "2025-03".to_string(),
714                strike_price: 2550.0,
715                volume_only_auction: Some(600.0),
716                emergency_margin_trigger_division: EmergencyMarginTriggerDivision::Calculated,
717                put_call_division: PutCallDivision::Call,
718                last_trading_day: Some("2025-03-09".to_string()),
719                special_quotation_day: Some("2025-03-10".to_string()),
720                settlement_price: Some(2025.0),
721                theoretical_price: Some(2030.5001),
722                base_volatility: Some(19.500),
723                underlying_price: Some(2855.00),
724                implied_volatility: Some(18.7000),
725                interest_rate: Some(0.3650),
726                central_contract_month_flag: Some(CentralContractMonthFlag::CentralContractMonth),
727            },
728        ];
729
730        let expected_response = OptionsPricesResponse {
731            options: expected_options,
732            pagination_key: Some("value3.value4.".to_string()),
733        };
734
735        pretty_assertions::assert_eq!(response, expected_response);
736    }
737
738    #[test]
739    fn test_deserialize_options_prices_response_no_pagination_key() {
740        let json_data = r#"
741        {
742            "options": [
743                {
744                    "Code": "140014505",
745                    "DerivativesProductCategory": "TOPIXE",
746                    "UnderlyingSSO": "-",
747                    "Date": "2024-07-23",
748                    "WholeDayOpen": 0.0,
749                    "WholeDayHigh": 0.0,
750                    "WholeDayLow": 0.0,
751                    "WholeDayClose": 0.0,
752                    "MorningSessionOpen": "",
753                    "MorningSessionHigh": "",
754                    "MorningSessionLow": "",
755                    "MorningSessionClose": "",
756                    "NightSessionOpen": 0.0,
757                    "NightSessionHigh": 0.0,
758                    "NightSessionLow": 0.0,
759                    "NightSessionClose": 0.0,
760                    "DaySessionOpen": 0.0,
761                    "DaySessionHigh": 0.0,
762                    "DaySessionLow": 0.0,
763                    "DaySessionClose": 0.0,
764                    "Volume": 0.0,
765                    "OpenInterest": 0.0,
766                    "TurnoverValue": 0.0,
767                    "ContractMonth": "2025-01",
768                    "StrikePrice": 2450.0,
769                    "Volume(OnlyAuction)": 0.0,
770                    "EmergencyMarginTriggerDivision": "002",
771                    "PutCallDivision": "2",
772                    "LastTradingDay": "2025-01-09",
773                    "SpecialQuotationDay": "2025-01-10",
774                    "SettlementPrice": 377.0,
775                    "TheoreticalPrice": 380.3801,
776                    "BaseVolatility": 18.115,
777                    "UnderlyingPrice": 2833.39,
778                    "ImpliedVolatility": 17.2955,
779                    "InterestRate": 0.3527,
780                    "CentralContractMonthFlag": "0"
781                }
782            ]
783        }
784        "#;
785
786        let response: OptionsPricesResponse = serde_json::from_str(json_data).unwrap();
787
788        let expected_option = vec![OptionsPricesItem {
789            code: "140014505".to_string(),
790            derivatives_product_category: "TOPIXE".to_string(),
791            underlying_sso: UnderlyingSSO::Other,
792            date: "2024-07-23".to_string(),
793            whole_day_open: 0.0,
794            whole_day_high: 0.0,
795            whole_day_low: 0.0,
796            whole_day_close: 0.0,
797            morning_session_open: None,
798            morning_session_high: None,
799            morning_session_low: None,
800            morning_session_close: None,
801            night_session_open: Some(0.0),
802            night_session_high: Some(0.0),
803            night_session_low: Some(0.0),
804            night_session_close: Some(0.0),
805            day_session_open: Some(0.0),
806            day_session_high: 0.0,
807            day_session_low: 0.0,
808            day_session_close: 0.0,
809            volume: 0.0,
810            open_interest: 0.0,
811            turnover_value: 0.0,
812            contract_month: "2025-01".to_string(),
813            strike_price: 2450.0,
814            volume_only_auction: Some(0.0),
815            emergency_margin_trigger_division: EmergencyMarginTriggerDivision::Calculated,
816            put_call_division: PutCallDivision::Call,
817            last_trading_day: Some("2025-01-09".to_string()),
818            special_quotation_day: Some("2025-01-10".to_string()),
819            settlement_price: Some(377.0),
820            theoretical_price: Some(380.3801),
821            base_volatility: Some(18.115),
822            underlying_price: Some(2833.39),
823            implied_volatility: Some(17.2955),
824            interest_rate: Some(0.3527),
825            central_contract_month_flag: Some(CentralContractMonthFlag::Others),
826        }];
827
828        let expected_response = OptionsPricesResponse {
829            options: expected_option,
830            pagination_key: None,
831        };
832
833        pretty_assertions::assert_eq!(response, expected_response);
834    }
835
836    #[test]
837    fn test_deserialize_options_prices_response_no_data() {
838        let json_data = r#"
839        {
840            "options": []
841        }
842        "#;
843
844        let response: OptionsPricesResponse = serde_json::from_str(json_data).unwrap();
845        let expected_response = OptionsPricesResponse {
846            options: vec![],
847            pagination_key: None,
848        };
849
850        pretty_assertions::assert_eq!(response, expected_response);
851    }
852}