Skip to main content

finance_query/adapters/polygon/futures/
snapshots.rs

1//! Futures snapshot endpoints.
2
3use serde::{Deserialize, Serialize};
4
5use crate::adapters::common::encode_path_segment;
6use crate::error::{FinanceError, Result};
7
8use super::super::build_client;
9
10/// Session data within a futures snapshot.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[non_exhaustive]
13pub struct FuturesSession {
14    /// Change from previous close.
15    pub change: Option<f64>,
16    /// Change percent from previous close.
17    pub change_percent: Option<f64>,
18    /// Close price.
19    pub close: Option<f64>,
20    /// High price.
21    pub high: Option<f64>,
22    /// Low price.
23    pub low: Option<f64>,
24    /// Open price.
25    pub open: Option<f64>,
26    /// Previous close price.
27    pub previous_close: Option<f64>,
28    /// Settlement price.
29    pub settlement: Option<f64>,
30    /// Volume.
31    pub volume: Option<f64>,
32}
33
34/// A single futures snapshot.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[non_exhaustive]
37pub struct FuturesSnapshot {
38    /// Ticker symbol.
39    pub ticker: Option<String>,
40    /// Name of the contract.
41    pub name: Option<String>,
42    /// Market status.
43    pub market_status: Option<String>,
44    /// Type.
45    #[serde(rename = "type")]
46    pub snapshot_type: Option<String>,
47    /// Session data.
48    pub session: Option<FuturesSession>,
49    /// Last updated timestamp.
50    pub last_updated: Option<i64>,
51}
52
53/// Response wrapper for futures snapshots.
54#[derive(Debug, Clone, Serialize, Deserialize)]
55#[non_exhaustive]
56pub struct FuturesSnapshotResponse {
57    /// Response status.
58    pub status: Option<String>,
59    /// Request identifier.
60    pub request_id: Option<String>,
61    /// Snapshot results.
62    pub results: Option<Vec<FuturesSnapshot>>,
63}
64
65/// Fetch snapshot for a futures ticker.
66///
67/// * `ticker` - Futures ticker symbol (e.g., `"ESZ4"`)
68pub async fn futures_snapshot(ticker: &str) -> Result<FuturesSnapshotResponse> {
69    let client = build_client()?;
70    let path = format!("/v3/snapshot/futures/{}", encode_path_segment(ticker));
71    let json = client.get_raw(&path, &[]).await?;
72    serde_json::from_value(json).map_err(|e| FinanceError::ResponseStructureError {
73        field: "futures_snapshot".to_string(),
74        context: format!("Failed to parse futures snapshot response: {e}"),
75    })
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[tokio::test]
83    async fn test_futures_snapshot_mock() {
84        let mut server = mockito::Server::new_async().await;
85        let _mock = server
86            .mock("GET", "/v3/snapshot/futures/ESZ4")
87            .match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
88                "apiKey".into(),
89                "test-key".into(),
90            )]))
91            .with_status(200)
92            .with_header("content-type", "application/json")
93            .with_body(
94                serde_json::json!({
95                    "status": "OK",
96                    "request_id": "abc123",
97                    "results": [
98                        {
99                            "ticker": "ESZ4",
100                            "name": "E-mini S&P 500 Dec 2024",
101                            "market_status": "open",
102                            "type": "futures",
103                            "session": {
104                                "change": 15.0,
105                                "change_percent": 0.31,
106                                "close": 4790.0,
107                                "high": 4800.0,
108                                "low": 4760.0,
109                                "open": 4775.0,
110                                "previous_close": 4775.0,
111                                "settlement": 4785.0,
112                                "volume": 1500000.0
113                            },
114                            "last_updated": 1705363200000000000_i64
115                        }
116                    ]
117                })
118                .to_string(),
119            )
120            .create_async()
121            .await;
122
123        let client = super::super::super::build_test_client(&server.url()).unwrap();
124        let json = client
125            .get_raw("/v3/snapshot/futures/ESZ4", &[])
126            .await
127            .unwrap();
128
129        let resp: FuturesSnapshotResponse = serde_json::from_value(json).unwrap();
130        assert_eq!(resp.status.as_deref(), Some("OK"));
131        let results = resp.results.unwrap();
132        assert_eq!(results.len(), 1);
133        assert_eq!(results[0].ticker.as_deref(), Some("ESZ4"));
134        let session = results[0].session.as_ref().unwrap();
135        assert!((session.change.unwrap() - 15.0).abs() < 0.01);
136        assert!((session.close.unwrap() - 4790.0).abs() < 0.01);
137    }
138}