Skip to main content

finance_query/adapters/polygon/futures/
contracts.rs

1//! Futures reference data endpoints: contracts, products, schedules.
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::{FinanceError, Result};
6
7use super::super::build_client;
8use super::super::models::PaginatedResponse;
9
10/// A futures contract from the reference endpoint.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[non_exhaustive]
13pub struct FuturesContract {
14    /// Ticker symbol.
15    pub ticker: Option<String>,
16    /// Name of the contract.
17    pub name: Option<String>,
18    /// Market for this contract.
19    pub market: Option<String>,
20    /// Asset class.
21    pub asset_class: Option<String>,
22    /// Expiration date.
23    pub expiration_date: Option<String>,
24    /// First trade date.
25    pub first_trade_date: Option<String>,
26    /// Last trade date.
27    pub last_trade_date: Option<String>,
28    /// Contract size.
29    pub contract_size: Option<f64>,
30    /// Contract unit.
31    pub contract_unit: Option<String>,
32    /// Tick size.
33    pub tick_size: Option<f64>,
34    /// Underlying ticker.
35    pub underlying_ticker: Option<String>,
36}
37
38/// A futures product from the reference endpoint.
39#[derive(Debug, Clone, Serialize, Deserialize)]
40#[non_exhaustive]
41pub struct FuturesProduct {
42    /// Product ticker.
43    pub ticker: Option<String>,
44    /// Product name.
45    pub name: Option<String>,
46    /// Asset class.
47    pub asset_class: Option<String>,
48    /// Market.
49    pub market: Option<String>,
50    /// Exchange.
51    pub exchange: Option<String>,
52    /// Contract size.
53    pub contract_size: Option<f64>,
54    /// Contract unit.
55    pub contract_unit: Option<String>,
56    /// Tick size.
57    pub tick_size: Option<f64>,
58}
59
60/// A futures schedule from the reference endpoint.
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[non_exhaustive]
63pub struct FuturesSchedule {
64    /// Ticker symbol.
65    pub ticker: Option<String>,
66    /// Session type.
67    pub session_type: Option<String>,
68    /// Start time.
69    pub start_time: Option<String>,
70    /// End time.
71    pub end_time: Option<String>,
72    /// Timezone.
73    pub timezone: Option<String>,
74    /// Day of week.
75    pub day_of_week: Option<String>,
76}
77
78/// Fetch futures contracts reference data.
79///
80/// * `params` - Optional query params: `ticker`, `underlying_ticker`, `expiration_date`, `order`, `limit`, `sort`
81pub async fn futures_contracts(
82    params: &[(&str, &str)],
83) -> Result<PaginatedResponse<FuturesContract>> {
84    let client = build_client()?;
85    let path = "/v3/reference/futures/contracts";
86    let json = client.get_raw(path, params).await?;
87    serde_json::from_value(json).map_err(|e| FinanceError::ResponseStructureError {
88        field: "futures_contracts".to_string(),
89        context: format!("Failed to parse futures contracts response: {e}"),
90    })
91}
92
93/// Fetch futures products reference data.
94///
95/// * `params` - Optional query params: `ticker`, `asset_class`, `exchange`, `order`, `limit`, `sort`
96pub async fn futures_products(
97    params: &[(&str, &str)],
98) -> Result<PaginatedResponse<FuturesProduct>> {
99    let client = build_client()?;
100    let path = "/v3/reference/futures/products";
101    let json = client.get_raw(path, params).await?;
102    serde_json::from_value(json).map_err(|e| FinanceError::ResponseStructureError {
103        field: "futures_products".to_string(),
104        context: format!("Failed to parse futures products response: {e}"),
105    })
106}
107
108/// Fetch futures schedules reference data.
109///
110/// * `params` - Optional query params: `ticker`, `session_type`, `order`, `limit`, `sort`
111pub async fn futures_schedules(
112    params: &[(&str, &str)],
113) -> Result<PaginatedResponse<FuturesSchedule>> {
114    let client = build_client()?;
115    let path = "/v3/reference/futures/schedules";
116    let json = client.get_raw(path, params).await?;
117    serde_json::from_value(json).map_err(|e| FinanceError::ResponseStructureError {
118        field: "futures_schedules".to_string(),
119        context: format!("Failed to parse futures schedules response: {e}"),
120    })
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[tokio::test]
128    async fn test_futures_contracts_mock() {
129        let mut server = mockito::Server::new_async().await;
130        let _mock = server
131            .mock("GET", "/v3/reference/futures/contracts")
132            .match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
133                "apiKey".into(),
134                "test-key".into(),
135            )]))
136            .with_status(200)
137            .with_header("content-type", "application/json")
138            .with_body(
139                serde_json::json!({
140                    "status": "OK",
141                    "request_id": "abc123",
142                    "results": [
143                        {
144                            "ticker": "ESZ4",
145                            "name": "E-mini S&P 500 Dec 2024",
146                            "market": "futures",
147                            "asset_class": "equity_index",
148                            "expiration_date": "2024-12-20",
149                            "first_trade_date": "2024-06-21",
150                            "contract_size": 50.0,
151                            "contract_unit": "USD",
152                            "tick_size": 0.25,
153                            "underlying_ticker": "I:SPX"
154                        }
155                    ],
156                    "resultsCount": 1
157                })
158                .to_string(),
159            )
160            .create_async()
161            .await;
162
163        let client = super::super::super::build_test_client(&server.url()).unwrap();
164        let json = client
165            .get_raw("/v3/reference/futures/contracts", &[])
166            .await
167            .unwrap();
168
169        let resp: PaginatedResponse<FuturesContract> = serde_json::from_value(json).unwrap();
170        let results = resp.results.unwrap();
171        assert_eq!(results.len(), 1);
172        assert_eq!(results[0].ticker.as_deref(), Some("ESZ4"));
173        assert_eq!(results[0].name.as_deref(), Some("E-mini S&P 500 Dec 2024"));
174    }
175}