jquants_api_client/api/
futures_prices.rs

1//! Futures OHLC (/derivatives/futures) 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            futures_code::FuturesCode,
16        },
17    },
18    JQuantsApiClient, JQuantsPlanClient,
19};
20
21/// Builder for Futures (OHLC) Data API.
22#[derive(Clone, Serialize)]
23pub struct FuturesPricesBuilder {
24    #[serde(skip)]
25    client: JQuantsApiClient,
26
27    /// Category of data
28    #[serde(skip_serializing_if = "Option::is_none")]
29    category: Option<FuturesCode>,
30
31    /// Date of data (e.g., "20210901" or "2021-09-01")
32    date: String,
33
34    /// Central contract month flag
35    #[serde(skip_serializing_if = "Option::is_none")]
36    central_contract_month_flag: Option<String>,
37
38    /// Pagination key.
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pagination_key: Option<String>,
41}
42
43impl JQuantsBuilder<FuturesPricesResponse> for FuturesPricesBuilder {
44    async fn send(self) -> Result<FuturesPricesResponse, crate::JQuantsError> {
45        self.send_ref().await
46    }
47
48    async fn send_ref(&self) -> Result<FuturesPricesResponse, crate::JQuantsError> {
49        self.client.inner.get("derivatives/futures", self).await
50    }
51}
52
53impl Paginatable<FuturesPricesResponse> for FuturesPricesBuilder {
54    fn pagination_key(mut self, pagination_key: impl Into<String>) -> Self {
55        self.pagination_key = Some(pagination_key.into());
56        self
57    }
58}
59
60impl FuturesPricesBuilder {
61    /// Create a new builder.
62    pub(crate) fn new(client: JQuantsApiClient, date: String) -> Self {
63        Self {
64            client,
65            category: None,
66            date,
67            central_contract_month_flag: None,
68            pagination_key: None,
69        }
70    }
71
72    /// Set the category of data.
73    pub fn category(mut self, category: impl Into<FuturesCode>) -> Self {
74        self.category = Some(category.into());
75        self
76    }
77
78    /// Set the date of data (e.g., "20210901" or "2021-09-01")
79    pub fn date(mut self, date: impl Into<String>) -> Self {
80        self.date = date.into();
81        self
82    }
83
84    /// Set the central contract month flag.
85    pub fn central_contract_month_flag(mut self, flag: impl Into<String>) -> Self {
86        self.central_contract_month_flag = Some(flag.into());
87        self
88    }
89
90    /// Set pagination key for fetching the next set of data.
91    pub fn pagination_key(mut self, pagination_key: impl Into<String>) -> Self {
92        self.pagination_key = Some(pagination_key.into());
93        self
94    }
95}
96
97/// Trait for Futures (OHLC) Data API.
98pub trait FuturesPricesApi: JQuantsPlanClient {
99    /// Get API builder for Futures (OHLC) Data.
100    ///
101    /// Use [Futures (OHLC) (/derivatives/futures) API](https://jpx.gitbook.io/j-quants-en/api-reference/futures)
102    fn get_futures_prices(&self, date: impl Into<String>) -> FuturesPricesBuilder {
103        FuturesPricesBuilder::new(self.get_api_client().clone(), date.into())
104    }
105}
106
107/// Futures (OHLC) Data API response.
108///
109/// See: [API Reference](https://jpx.gitbook.io/j-quants-en/api-reference/futures)
110#[derive(Debug, Clone, PartialEq, Deserialize)]
111pub struct FuturesPricesResponse {
112    /// List of Futures prices
113    pub futures: Vec<FuturesPricesItem>,
114    /// Pagination key for fetching next set of data
115    pub pagination_key: Option<String>,
116}
117
118impl HasPaginationKey for FuturesPricesResponse {
119    fn get_pagination_key(&self) -> Option<&str> {
120        self.pagination_key.as_deref()
121    }
122}
123
124impl MergePage for FuturesPricesResponse {
125    fn merge_page(
126        page: Result<Vec<Self>, crate::JQuantsError>,
127    ) -> Result<Self, crate::JQuantsError> {
128        let mut page = page?;
129        let mut merged = page.pop().unwrap();
130        for p in page {
131            merged.futures.extend(p.futures);
132        }
133        merged.pagination_key = None;
134
135        Ok(merged)
136    }
137}
138
139/// Represents a single Futures price record.
140#[derive(Debug, Clone, PartialEq, Deserialize)]
141pub struct FuturesPricesItem {
142    /// Issue code
143    #[serde(rename = "Code")]
144    pub code: String,
145
146    /// Derivative Product Category
147    #[serde(rename = "DerivativesProductCategory")]
148    pub derivatives_product_category: String,
149
150    /// Trading day (YYYY-MM-DD)
151    #[serde(rename = "Date")]
152    pub date: String,
153
154    /// Whole day open price
155    #[serde(rename = "WholeDayOpen")]
156    pub whole_day_open: f64,
157
158    /// Whole day high price
159    #[serde(rename = "WholeDayHigh")]
160    pub whole_day_high: f64,
161
162    /// Whole day low price
163    #[serde(rename = "WholeDayLow")]
164    pub whole_day_low: f64,
165
166    /// Whole day close price
167    #[serde(rename = "WholeDayClose")]
168    pub whole_day_close: f64,
169
170    /// Morning session open price
171    #[serde(
172        rename = "MorningSessionOpen",
173        deserialize_with = "deserialize_f64_or_none"
174    )]
175    pub morning_session_open: Option<f64>,
176
177    /// Morning session high price
178    #[serde(
179        rename = "MorningSessionHigh",
180        deserialize_with = "deserialize_f64_or_none"
181    )]
182    pub morning_session_high: Option<f64>,
183
184    /// Morning session low price
185    #[serde(
186        rename = "MorningSessionLow",
187        deserialize_with = "deserialize_f64_or_none"
188    )]
189    pub morning_session_low: Option<f64>,
190
191    /// Morning session close price
192    #[serde(
193        rename = "MorningSessionClose",
194        deserialize_with = "deserialize_f64_or_none"
195    )]
196    pub morning_session_close: Option<f64>,
197
198    /// Night session open price
199    #[serde(
200        rename = "NightSessionOpen",
201        deserialize_with = "deserialize_f64_or_none"
202    )]
203    pub night_session_open: Option<f64>,
204
205    /// Night session high price
206    #[serde(
207        rename = "NightSessionHigh",
208        deserialize_with = "deserialize_f64_or_none"
209    )]
210    pub night_session_high: Option<f64>,
211
212    /// Night session low price
213    #[serde(
214        rename = "NightSessionLow",
215        deserialize_with = "deserialize_f64_or_none"
216    )]
217    pub night_session_low: Option<f64>,
218
219    /// Night session close price
220    #[serde(
221        rename = "NightSessionClose",
222        deserialize_with = "deserialize_f64_or_none"
223    )]
224    pub night_session_close: Option<f64>,
225
226    /// Day session open price
227    #[serde(rename = "DaySessionOpen")]
228    pub day_session_open: f64,
229
230    /// Day session high price
231    #[serde(rename = "DaySessionHigh")]
232    pub day_session_high: f64,
233
234    /// Day session low price
235    #[serde(rename = "DaySessionLow")]
236    pub day_session_low: f64,
237
238    /// Day session close price
239    #[serde(rename = "DaySessionClose")]
240    pub day_session_close: f64,
241
242    /// Volume
243    #[serde(rename = "Volume")]
244    pub volume: f64,
245
246    /// Open interest
247    #[serde(rename = "OpenInterest")]
248    pub open_interest: f64,
249
250    /// Turnover value
251    #[serde(rename = "TurnoverValue")]
252    pub turnover_value: f64,
253
254    /// Contract month (YYYY-MM)
255    #[serde(rename = "ContractMonth")]
256    pub contract_month: String,
257
258    /// Volume only auction
259    #[serde(
260        rename = "Volume(OnlyAuction)",
261        deserialize_with = "deserialize_f64_or_none"
262    )]
263    pub volume_only_auction: Option<f64>,
264
265    /// Emergency margin trigger division
266    #[serde(rename = "EmergencyMarginTriggerDivision")]
267    pub emergency_margin_trigger_division: EmergencyMarginTriggerDivision,
268
269    /// Last trading day (YYYY-MM-DD)
270    #[serde(
271        rename = "LastTradingDay",
272        deserialize_with = "empty_string_or_null_as_none"
273    )]
274    pub last_trading_day: Option<String>,
275
276    /// Special quotation day (YYYY-MM-DD)
277    #[serde(
278        rename = "SpecialQuotationDay",
279        deserialize_with = "empty_string_or_null_as_none"
280    )]
281    pub special_quotation_day: Option<String>,
282
283    /// Settlement price
284    #[serde(
285        rename = "SettlementPrice",
286        deserialize_with = "deserialize_f64_or_none"
287    )]
288    pub settlement_price: Option<f64>,
289
290    /// Central contract month flag
291    #[serde(
292        rename = "CentralContractMonthFlag",
293        deserialize_with = "empty_string_or_null_as_none"
294    )]
295    pub central_contract_month_flag: Option<CentralContractMonthFlag>,
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301
302    #[test]
303    fn test_deserialize_futures_prices_response() {
304        let json_data = r#"
305        {
306            "futures": [
307                {
308                    "Code": "169090005",
309                    "DerivativesProductCategory": "TOPIXF",
310                    "Date": "2024-07-23", 
311                    "WholeDayOpen": 2825.5, 
312                    "WholeDayHigh": 2853.0, 
313                    "WholeDayLow": 2825.5, 
314                    "WholeDayClose": 2829.0, 
315                    "MorningSessionOpen": "", 
316                    "MorningSessionHigh": "", 
317                    "MorningSessionLow": "", 
318                    "MorningSessionClose": "", 
319                    "NightSessionOpen": 2825.5, 
320                    "NightSessionHigh": 2850.0, 
321                    "NightSessionLow": 2825.5, 
322                    "NightSessionClose": 2845.0, 
323                    "DaySessionOpen": 2850.5, 
324                    "DaySessionHigh": 2853.0, 
325                    "DaySessionLow": 2826.0, 
326                    "DaySessionClose": 2829.0, 
327                    "Volume": 42910.0, 
328                    "OpenInterest": 479812.0, 
329                    "TurnoverValue": 1217918971856.0, 
330                    "ContractMonth": "2024-09", 
331                    "Volume(OnlyAuction)": 40405.0, 
332                    "EmergencyMarginTriggerDivision": "002", 
333                    "LastTradingDay": "2024-09-12", 
334                    "SpecialQuotationDay": "2024-09-13", 
335                    "SettlementPrice": 2829.0, 
336                    "CentralContractMonthFlag": "1"
337                }
338            ],
339            "pagination_key": "value1.value2."
340        }
341        "#;
342
343        let response: FuturesPricesResponse = serde_json::from_str(json_data).unwrap();
344
345        let expected_futures = vec![FuturesPricesItem {
346            code: "169090005".to_string(),
347            derivatives_product_category: "TOPIXF".to_string(),
348            date: "2024-07-23".to_string(),
349            whole_day_open: 2825.5,
350            whole_day_high: 2853.0,
351            whole_day_low: 2825.5,
352            whole_day_close: 2829.0,
353            morning_session_open: None,
354            morning_session_high: None,
355            morning_session_low: None,
356            morning_session_close: None,
357            night_session_open: Some(2825.5),
358            night_session_high: Some(2850.0),
359            night_session_low: Some(2825.5),
360            night_session_close: Some(2845.0),
361            day_session_open: 2850.5,
362            day_session_high: 2853.0,
363            day_session_low: 2826.0,
364            day_session_close: 2829.0,
365            volume: 42910.0,
366            open_interest: 479812.0,
367            turnover_value: 1217918971856.0,
368            contract_month: "2024-09".to_string(),
369            volume_only_auction: Some(40405.0),
370            emergency_margin_trigger_division: EmergencyMarginTriggerDivision::Calculated,
371            last_trading_day: Some("2024-09-12".to_string()),
372            special_quotation_day: Some("2024-09-13".to_string()),
373            settlement_price: Some(2829.0),
374            central_contract_month_flag: Some(CentralContractMonthFlag::CentralContractMonth),
375        }];
376
377        let expected_response = FuturesPricesResponse {
378            futures: expected_futures,
379            pagination_key: Some("value1.value2.".to_string()),
380        };
381
382        pretty_assertions::assert_eq!(response, expected_response);
383    }
384
385    #[test]
386    fn test_deserialize_futures_prices_response_with_missing_optional_fields() {
387        let json_data = r#"
388        {
389            "futures": [
390                {
391                    "Code": "169090005",
392                    "DerivativesProductCategory": "TOPIXF",
393                    "Date": "2024-07-23", 
394                    "WholeDayOpen": 2825.5, 
395                    "WholeDayHigh": 2853.0, 
396                    "WholeDayLow": 2825.5, 
397                    "WholeDayClose": 2829.0, 
398                    "MorningSessionOpen": "", 
399                    "MorningSessionHigh": "", 
400                    "MorningSessionLow": "", 
401                    "MorningSessionClose": "", 
402                    "NightSessionOpen": "",
403                    "NightSessionHigh": "", 
404                    "NightSessionLow": "", 
405                    "NightSessionClose": "",
406                    "DaySessionOpen": 2850.5, 
407                    "DaySessionHigh": 2853.0, 
408                    "DaySessionLow": 2826.0, 
409                    "DaySessionClose": 2829.0,
410                    "Volume": 42910.0,
411                    "OpenInterest": 479812.0,
412                    "TurnoverValue": 1217918971856.0,
413                    "ContractMonth": "2024-09",
414                    "Volume(OnlyAuction)": "",
415                    "EmergencyMarginTriggerDivision": "002",
416                    "LastTradingDay": "",
417                    "SpecialQuotationDay": "",
418                    "SettlementPrice": "",
419                    "CentralContractMonthFlag": ""
420                }
421            ],
422            "pagination_key": "value1.value2."
423        }
424        "#;
425
426        let response: FuturesPricesResponse = serde_json::from_str(json_data).unwrap();
427
428        let expected_futures = vec![FuturesPricesItem {
429            code: "169090005".to_string(),
430            derivatives_product_category: "TOPIXF".to_string(),
431            date: "2024-07-23".to_string(),
432            whole_day_open: 2825.5,
433            whole_day_high: 2853.0,
434            whole_day_low: 2825.5,
435            whole_day_close: 2829.0,
436            morning_session_open: None,
437            morning_session_high: None,
438            morning_session_low: None,
439            morning_session_close: None,
440            night_session_open: None,
441            night_session_high: None,
442            night_session_low: None,
443            night_session_close: None,
444            day_session_open: 2850.5,
445            day_session_high: 2853.0,
446            day_session_low: 2826.0,
447            day_session_close: 2829.0,
448            volume: 42910.0,
449            open_interest: 479812.0,
450            turnover_value: 1217918971856.0,
451            contract_month: "2024-09".to_string(),
452            volume_only_auction: None,
453            emergency_margin_trigger_division: EmergencyMarginTriggerDivision::Calculated,
454            last_trading_day: None,
455            special_quotation_day: None,
456            settlement_price: None,
457            central_contract_month_flag: None,
458        }];
459
460        let expected_response = FuturesPricesResponse {
461            futures: expected_futures,
462            pagination_key: Some("value1.value2.".to_string()),
463        };
464
465        pretty_assertions::assert_eq!(response, expected_response);
466    }
467
468    #[test]
469    fn test_deserialize_futures_prices_response_multiple_items() {
470        let json_data = r#"
471        {
472            "futures": [
473                {
474                    "Code": "169090005",
475                    "DerivativesProductCategory": "TOPIXF",
476                    "Date": "2024-07-23", 
477                    "WholeDayOpen": 2825.5, 
478                    "WholeDayHigh": 2853.0, 
479                    "WholeDayLow": 2825.5, 
480                    "WholeDayClose": 2829.0, 
481                    "MorningSessionOpen": "", 
482                    "MorningSessionHigh": "", 
483                    "MorningSessionLow": "", 
484                    "MorningSessionClose": "", 
485                    "NightSessionOpen": 2825.5, 
486                    "NightSessionHigh": 2850.0, 
487                    "NightSessionLow": 2825.5, 
488                    "NightSessionClose": 2845.0, 
489                    "DaySessionOpen": 2850.5, 
490                    "DaySessionHigh": 2853.0, 
491                    "DaySessionLow": 2826.0, 
492                    "DaySessionClose": 2829.0, 
493                    "Volume": 42910.0, 
494                    "OpenInterest": 479812.0, 
495                    "TurnoverValue": 1217918971856.0, 
496                    "ContractMonth": "2024-09", 
497                    "Volume(OnlyAuction)": 40405.0, 
498                    "EmergencyMarginTriggerDivision": "002", 
499                    "LastTradingDay": "2024-09-12", 
500                    "SpecialQuotationDay": "2024-09-13", 
501                    "SettlementPrice": 2829.0, 
502                    "CentralContractMonthFlag": "1"
503                },
504                {
505                    "Code": "169090006",
506                    "DerivativesProductCategory": "NK225F",
507                    "Date": "2024-07-24",
508                    "WholeDayOpen": 3000.0,
509                    "WholeDayHigh": 3050.0,
510                    "WholeDayLow": 2950.0,
511                    "WholeDayClose": 3025.0,
512                    "MorningSessionOpen": 3010.0,
513                    "MorningSessionHigh": 3040.0,
514                    "MorningSessionLow": 2955.0,
515                    "MorningSessionClose": 3030.0,
516                    "NightSessionOpen": 3025.5,
517                    "NightSessionHigh": 3050.0,
518                    "NightSessionLow": 3000.0,
519                    "NightSessionClose": 3045.0,
520                    "DaySessionOpen": 3050.5,
521                    "DaySessionHigh": 3053.0,
522                    "DaySessionLow": 3006.0,
523                    "DaySessionClose": 3029.0,
524                    "Volume": 52910.0,
525                    "OpenInterest": 579812.0,
526                    "TurnoverValue": 1317918971856.0,
527                    "ContractMonth": "2024-10",
528                    "Volume(OnlyAuction)": 50405.0,
529                    "EmergencyMarginTriggerDivision": "001",
530                    "LastTradingDay": "2024-10-12",
531                    "SpecialQuotationDay": "2024-10-13",
532                    "SettlementPrice": 3029.0,
533                    "CentralContractMonthFlag": "0"
534                }
535            ],
536            "pagination_key": "value3.value4."
537        }
538        "#;
539
540        let response: FuturesPricesResponse = serde_json::from_str(json_data).unwrap();
541
542        let expected_futures = vec![
543            FuturesPricesItem {
544                code: "169090005".to_string(),
545                derivatives_product_category: "TOPIXF".to_string(),
546                date: "2024-07-23".to_string(),
547                whole_day_open: 2825.5,
548                whole_day_high: 2853.0,
549                whole_day_low: 2825.5,
550                whole_day_close: 2829.0,
551                morning_session_open: None,
552                morning_session_high: None,
553                morning_session_low: None,
554                morning_session_close: None,
555                night_session_open: Some(2825.5),
556                night_session_high: Some(2850.0),
557                night_session_low: Some(2825.5),
558                night_session_close: Some(2845.0),
559                day_session_open: 2850.5,
560                day_session_high: 2853.0,
561                day_session_low: 2826.0,
562                day_session_close: 2829.0,
563                volume: 42910.0,
564                open_interest: 479812.0,
565                turnover_value: 1217918971856.0,
566                contract_month: "2024-09".to_string(),
567                volume_only_auction: Some(40405.0),
568                emergency_margin_trigger_division: EmergencyMarginTriggerDivision::Calculated,
569                last_trading_day: Some("2024-09-12".to_string()),
570                special_quotation_day: Some("2024-09-13".to_string()),
571                settlement_price: Some(2829.0),
572                central_contract_month_flag: Some(CentralContractMonthFlag::CentralContractMonth),
573            },
574            FuturesPricesItem {
575                code: "169090006".to_string(),
576                derivatives_product_category: "NK225F".to_string(),
577                date: "2024-07-24".to_string(),
578                whole_day_open: 3000.0,
579                whole_day_high: 3050.0,
580                whole_day_low: 2950.0,
581                whole_day_close: 3025.0,
582                morning_session_open: Some(3010.0),
583                morning_session_high: Some(3040.0),
584                morning_session_low: Some(2955.0),
585                morning_session_close: Some(3030.0),
586                night_session_open: Some(3025.5),
587                night_session_high: Some(3050.0),
588                night_session_low: Some(3000.0),
589                night_session_close: Some(3045.0),
590                day_session_open: 3050.5,
591                day_session_high: 3053.0,
592                day_session_low: 3006.0,
593                day_session_close: 3029.0,
594                volume: 52910.0,
595                open_interest: 579812.0,
596                turnover_value: 1317918971856.0,
597                contract_month: "2024-10".to_string(),
598                volume_only_auction: Some(50405.0),
599                emergency_margin_trigger_division: EmergencyMarginTriggerDivision::Triggered,
600                last_trading_day: Some("2024-10-12".to_string()),
601                special_quotation_day: Some("2024-10-13".to_string()),
602                settlement_price: Some(3029.0),
603                central_contract_month_flag: Some(CentralContractMonthFlag::Others),
604            },
605        ];
606
607        let expected_response = FuturesPricesResponse {
608            futures: expected_futures,
609            pagination_key: Some("value3.value4.".to_string()),
610        };
611
612        pretty_assertions::assert_eq!(response, expected_response);
613    }
614
615    #[test]
616    fn test_deserialize_futures_prices_response_no_pagination_key() {
617        let json_data = r#"
618        {
619            "futures": [
620                {
621                    "Code": "169090005",
622                    "DerivativesProductCategory": "TOPIXF",
623                    "Date": "2024-07-23", 
624                    "WholeDayOpen": 2825.5, 
625                    "WholeDayHigh": 2853.0, 
626                    "WholeDayLow": 2825.5, 
627                    "WholeDayClose": 2829.0, 
628                    "MorningSessionOpen": "", 
629                    "MorningSessionHigh": "", 
630                    "MorningSessionLow": "", 
631                    "MorningSessionClose": "", 
632                    "NightSessionOpen": 2825.5, 
633                    "NightSessionHigh": 2850.0, 
634                    "NightSessionLow": 2825.5, 
635                    "NightSessionClose": 2845.0, 
636                    "DaySessionOpen": 2850.5, 
637                    "DaySessionHigh": 2853.0, 
638                    "DaySessionLow": 2826.0, 
639                    "DaySessionClose": 2829.0, 
640                    "Volume": 42910.0, 
641                    "OpenInterest": 479812.0, 
642                    "TurnoverValue": 1217918971856.0, 
643                    "ContractMonth": "2024-09", 
644                    "Volume(OnlyAuction)": 40405.0, 
645                    "EmergencyMarginTriggerDivision": "002", 
646                    "LastTradingDay": "2024-09-12", 
647                    "SpecialQuotationDay": "2024-09-13", 
648                    "SettlementPrice": 2829.0, 
649                    "CentralContractMonthFlag": "1"
650                }
651            ]
652        }
653        "#;
654
655        let response: FuturesPricesResponse = serde_json::from_str(json_data).unwrap();
656
657        let expected_futures = vec![FuturesPricesItem {
658            code: "169090005".to_string(),
659            derivatives_product_category: "TOPIXF".to_string(),
660            date: "2024-07-23".to_string(),
661            whole_day_open: 2825.5,
662            whole_day_high: 2853.0,
663            whole_day_low: 2825.5,
664            whole_day_close: 2829.0,
665            morning_session_open: None,
666            morning_session_high: None,
667            morning_session_low: None,
668            morning_session_close: None,
669            night_session_open: Some(2825.5),
670            night_session_high: Some(2850.0),
671            night_session_low: Some(2825.5),
672            night_session_close: Some(2845.0),
673            day_session_open: 2850.5,
674            day_session_high: 2853.0,
675            day_session_low: 2826.0,
676            day_session_close: 2829.0,
677            volume: 42910.0,
678            open_interest: 479812.0,
679            turnover_value: 1217918971856.0,
680            contract_month: "2024-09".to_string(),
681            volume_only_auction: Some(40405.0),
682            emergency_margin_trigger_division: EmergencyMarginTriggerDivision::Calculated,
683            last_trading_day: Some("2024-09-12".to_string()),
684            special_quotation_day: Some("2024-09-13".to_string()),
685            settlement_price: Some(2829.0),
686            central_contract_month_flag: Some(CentralContractMonthFlag::CentralContractMonth),
687        }];
688
689        let expected_response = FuturesPricesResponse {
690            futures: expected_futures,
691            pagination_key: None,
692        };
693
694        pretty_assertions::assert_eq!(response, expected_response);
695    }
696
697    #[test]
698    fn test_deserialize_futures_prices_response_no_data() {
699        let json_data = r#"
700        {
701            "futures": []
702        }
703        "#;
704
705        let response: FuturesPricesResponse = serde_json::from_str(json_data).unwrap();
706        let expected_response = FuturesPricesResponse {
707            futures: vec![],
708            pagination_key: None,
709        };
710
711        pretty_assertions::assert_eq!(response, expected_response);
712    }
713}