1use 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#[derive(Clone, Serialize)]
24pub struct OptionsPricesBuilder {
25 #[serde(skip)]
26 client: JQuantsApiClient,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 category: Option<String>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 code: Option<OptionsCode>,
35
36 date: String,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 contract_flag: Option<String>,
42
43 #[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 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 pub fn category(mut self, category: impl Into<String>) -> Self {
80 self.category = Some(category.into());
81 self
82 }
83
84 pub fn code(mut self, code: impl Into<OptionsCode>) -> Self {
86 self.code = Some(code.into());
87 self
88 }
89
90 pub fn date(mut self, date: impl Into<String>) -> Self {
92 self.date = date.into();
93 self
94 }
95
96 pub fn contract_flag(mut self, flag: impl Into<String>) -> Self {
98 self.contract_flag = Some(flag.into());
99 self
100 }
101
102 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
109pub trait OptionsPricesApi: JQuantsPlanClient {
111 fn get_options_prices(&self, date: impl Into<String>) -> OptionsPricesBuilder {
115 OptionsPricesBuilder::new(self.get_api_client().clone(), date.into())
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Deserialize)]
123pub struct OptionsPricesResponse {
124 pub options: Vec<OptionsPricesItem>,
126 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#[derive(Debug, Clone, PartialEq, Deserialize)]
153pub struct OptionsPricesItem {
154 #[serde(rename = "Code")]
156 pub code: String,
157
158 #[serde(rename = "DerivativesProductCategory")]
160 pub derivatives_product_category: String,
161
162 #[serde(rename = "UnderlyingSSO")]
164 pub underlying_sso: UnderlyingSSO,
165
166 #[serde(rename = "Date")]
168 pub date: String,
169
170 #[serde(rename = "WholeDayOpen")]
172 pub whole_day_open: f64,
173
174 #[serde(rename = "WholeDayHigh")]
176 pub whole_day_high: f64,
177
178 #[serde(rename = "WholeDayLow")]
180 pub whole_day_low: f64,
181
182 #[serde(rename = "WholeDayClose")]
184 pub whole_day_close: f64,
185
186 #[serde(
188 rename = "MorningSessionOpen",
189 deserialize_with = "deserialize_f64_or_none"
190 )]
191 pub morning_session_open: Option<f64>,
192
193 #[serde(
195 rename = "MorningSessionHigh",
196 deserialize_with = "deserialize_f64_or_none"
197 )]
198 pub morning_session_high: Option<f64>,
199
200 #[serde(
202 rename = "MorningSessionLow",
203 deserialize_with = "deserialize_f64_or_none"
204 )]
205 pub morning_session_low: Option<f64>,
206
207 #[serde(
209 rename = "MorningSessionClose",
210 deserialize_with = "deserialize_f64_or_none"
211 )]
212 pub morning_session_close: Option<f64>,
213
214 #[serde(
216 rename = "NightSessionOpen",
217 deserialize_with = "deserialize_f64_or_none"
218 )]
219 pub night_session_open: Option<f64>,
220
221 #[serde(
223 rename = "NightSessionHigh",
224 deserialize_with = "deserialize_f64_or_none"
225 )]
226 pub night_session_high: Option<f64>,
227
228 #[serde(
230 rename = "NightSessionLow",
231 deserialize_with = "deserialize_f64_or_none"
232 )]
233 pub night_session_low: Option<f64>,
234
235 #[serde(
237 rename = "NightSessionClose",
238 deserialize_with = "deserialize_f64_or_none"
239 )]
240 pub night_session_close: Option<f64>,
241
242 #[serde(
244 rename = "DaySessionOpen",
245 deserialize_with = "deserialize_f64_or_none"
246 )]
247 pub day_session_open: Option<f64>,
248
249 #[serde(rename = "DaySessionHigh")]
251 pub day_session_high: f64,
252
253 #[serde(rename = "DaySessionLow")]
255 pub day_session_low: f64,
256
257 #[serde(rename = "DaySessionClose")]
259 pub day_session_close: f64,
260
261 #[serde(rename = "Volume")]
263 pub volume: f64,
264
265 #[serde(rename = "OpenInterest")]
267 pub open_interest: f64,
268
269 #[serde(rename = "TurnoverValue")]
271 pub turnover_value: f64,
272
273 #[serde(rename = "ContractMonth")]
275 pub contract_month: String,
276
277 #[serde(rename = "StrikePrice")]
279 pub strike_price: f64,
280
281 #[serde(
283 rename = "Volume(OnlyAuction)",
284 deserialize_with = "deserialize_f64_or_none"
285 )]
286 pub volume_only_auction: Option<f64>,
287
288 #[serde(rename = "EmergencyMarginTriggerDivision")]
290 pub emergency_margin_trigger_division: EmergencyMarginTriggerDivision,
291
292 #[serde(rename = "PutCallDivision")]
294 pub put_call_division: PutCallDivision,
295
296 #[serde(
298 rename = "LastTradingDay",
299 deserialize_with = "empty_string_or_null_as_none"
300 )]
301 pub last_trading_day: Option<String>,
302
303 #[serde(
305 rename = "SpecialQuotationDay",
306 deserialize_with = "empty_string_or_null_as_none"
307 )]
308 pub special_quotation_day: Option<String>,
309
310 #[serde(
312 rename = "SettlementPrice",
313 deserialize_with = "deserialize_f64_or_none"
314 )]
315 pub settlement_price: Option<f64>,
316
317 #[serde(
319 rename = "TheoreticalPrice",
320 deserialize_with = "deserialize_f64_or_none"
321 )]
322 pub theoretical_price: Option<f64>,
323
324 #[serde(
326 rename = "BaseVolatility",
327 deserialize_with = "deserialize_f64_or_none"
328 )]
329 pub base_volatility: Option<f64>,
330
331 #[serde(
333 rename = "UnderlyingPrice",
334 deserialize_with = "deserialize_f64_or_none"
335 )]
336 pub underlying_price: Option<f64>,
337
338 #[serde(
340 rename = "ImpliedVolatility",
341 deserialize_with = "deserialize_f64_or_none"
342 )]
343 pub implied_volatility: Option<f64>,
344
345 #[serde(rename = "InterestRate", deserialize_with = "deserialize_f64_or_none")]
347 pub interest_rate: Option<f64>,
348
349 #[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}