Skip to main content

alpaca_data/options/
request.rs

1use crate::common::query::QueryWriter;
2use crate::transport::pagination::PaginatedRequest;
3
4use super::{ContractType, OptionsFeed, Sort, TickType, TimeFrame};
5
6#[derive(Clone, Debug, Default)]
7pub struct BarsRequest {
8    pub symbols: Vec<String>,
9    pub timeframe: TimeFrame,
10    pub start: Option<String>,
11    pub end: Option<String>,
12    pub limit: Option<u32>,
13    pub sort: Option<Sort>,
14    pub page_token: Option<String>,
15}
16
17#[derive(Clone, Debug, Default)]
18pub struct TradesRequest {
19    pub symbols: Vec<String>,
20    pub start: Option<String>,
21    pub end: Option<String>,
22    pub limit: Option<u32>,
23    pub sort: Option<Sort>,
24    pub page_token: Option<String>,
25}
26
27#[derive(Clone, Debug, Default)]
28pub struct LatestQuotesRequest {
29    pub symbols: Vec<String>,
30    pub feed: Option<OptionsFeed>,
31}
32
33#[derive(Clone, Debug, Default)]
34pub struct LatestTradesRequest {
35    pub symbols: Vec<String>,
36    pub feed: Option<OptionsFeed>,
37}
38
39#[derive(Clone, Debug, Default)]
40pub struct SnapshotsRequest {
41    pub symbols: Vec<String>,
42    pub feed: Option<OptionsFeed>,
43    pub limit: Option<u32>,
44    pub page_token: Option<String>,
45}
46
47#[derive(Clone, Debug, Default)]
48pub struct ChainRequest {
49    pub underlying_symbol: String,
50    pub feed: Option<OptionsFeed>,
51    pub r#type: Option<ContractType>,
52    pub strike_price_gte: Option<f64>,
53    pub strike_price_lte: Option<f64>,
54    pub expiration_date: Option<String>,
55    pub expiration_date_gte: Option<String>,
56    pub expiration_date_lte: Option<String>,
57    pub root_symbol: Option<String>,
58    pub updated_since: Option<String>,
59    pub limit: Option<u32>,
60    pub page_token: Option<String>,
61}
62
63#[derive(Clone, Debug, Default)]
64pub struct ConditionCodesRequest {
65    pub ticktype: TickType,
66}
67
68impl BarsRequest {
69    pub(crate) fn to_query(self) -> Vec<(String, String)> {
70        let mut query = QueryWriter::default();
71        query.push_csv("symbols", self.symbols);
72        query.push_opt("timeframe", Some(self.timeframe));
73        query.push_opt("start", self.start);
74        query.push_opt("end", self.end);
75        query.push_opt("limit", self.limit);
76        query.push_opt("page_token", self.page_token);
77        query.push_opt("sort", self.sort);
78        query.finish()
79    }
80}
81
82impl TradesRequest {
83    pub(crate) fn to_query(self) -> Vec<(String, String)> {
84        let mut query = QueryWriter::default();
85        query.push_csv("symbols", self.symbols);
86        query.push_opt("start", self.start);
87        query.push_opt("end", self.end);
88        query.push_opt("limit", self.limit);
89        query.push_opt("page_token", self.page_token);
90        query.push_opt("sort", self.sort);
91        query.finish()
92    }
93}
94
95impl LatestQuotesRequest {
96    #[allow(dead_code)]
97    pub(crate) fn to_query(self) -> Vec<(String, String)> {
98        latest_query(self.symbols, self.feed)
99    }
100}
101
102impl LatestTradesRequest {
103    #[allow(dead_code)]
104    pub(crate) fn to_query(self) -> Vec<(String, String)> {
105        latest_query(self.symbols, self.feed)
106    }
107}
108
109impl SnapshotsRequest {
110    #[allow(dead_code)]
111    pub(crate) fn to_query(self) -> Vec<(String, String)> {
112        let mut query = QueryWriter::default();
113        query.push_csv("symbols", self.symbols);
114        query.push_opt("feed", self.feed);
115        query.push_opt("limit", self.limit);
116        query.push_opt("page_token", self.page_token);
117        query.finish()
118    }
119}
120
121impl ChainRequest {
122    #[allow(dead_code)]
123    pub(crate) fn to_query(self) -> Vec<(String, String)> {
124        let mut query = QueryWriter::default();
125        query.push_opt("feed", self.feed);
126        query.push_opt("type", self.r#type);
127        query.push_opt("strike_price_gte", self.strike_price_gte);
128        query.push_opt("strike_price_lte", self.strike_price_lte);
129        query.push_opt("expiration_date", self.expiration_date);
130        query.push_opt("expiration_date_gte", self.expiration_date_gte);
131        query.push_opt("expiration_date_lte", self.expiration_date_lte);
132        query.push_opt("root_symbol", self.root_symbol);
133        query.push_opt("updated_since", self.updated_since);
134        query.push_opt("limit", self.limit);
135        query.push_opt("page_token", self.page_token);
136        query.finish()
137    }
138}
139
140impl ConditionCodesRequest {
141    pub(crate) fn ticktype(&self) -> &'static str {
142        self.ticktype.as_str()
143    }
144}
145
146impl PaginatedRequest for BarsRequest {
147    fn with_page_token(&self, page_token: Option<String>) -> Self {
148        let mut next = self.clone();
149        next.page_token = page_token;
150        next
151    }
152}
153
154impl PaginatedRequest for TradesRequest {
155    fn with_page_token(&self, page_token: Option<String>) -> Self {
156        let mut next = self.clone();
157        next.page_token = page_token;
158        next
159    }
160}
161
162impl PaginatedRequest for SnapshotsRequest {
163    fn with_page_token(&self, page_token: Option<String>) -> Self {
164        let mut next = self.clone();
165        next.page_token = page_token;
166        next
167    }
168}
169
170impl PaginatedRequest for ChainRequest {
171    fn with_page_token(&self, page_token: Option<String>) -> Self {
172        let mut next = self.clone();
173        next.page_token = page_token;
174        next
175    }
176}
177
178#[allow(dead_code)]
179fn latest_query(symbols: Vec<String>, feed: Option<OptionsFeed>) -> Vec<(String, String)> {
180    let mut query = QueryWriter::default();
181    query.push_csv("symbols", symbols);
182    query.push_opt("feed", feed);
183    query.finish()
184}
185
186#[cfg(test)]
187mod tests {
188    use super::{
189        BarsRequest, ChainRequest, ConditionCodesRequest, ContractType, LatestQuotesRequest,
190        LatestTradesRequest, OptionsFeed, SnapshotsRequest, Sort, TickType, TimeFrame,
191        TradesRequest,
192    };
193
194    #[test]
195    fn bars_request_serializes_official_query_words() {
196        let query = BarsRequest {
197            symbols: vec!["AAPL260406C00180000".into(), "AAPL260406C00185000".into()],
198            timeframe: TimeFrame::from("1Day"),
199            start: Some("2026-04-01T00:00:00Z".into()),
200            end: Some("2026-04-03T00:00:00Z".into()),
201            limit: Some(2),
202            sort: Some(Sort::Asc),
203            page_token: Some("page-2".into()),
204        }
205        .to_query();
206
207        assert_eq!(
208            query,
209            vec![
210                (
211                    "symbols".to_string(),
212                    "AAPL260406C00180000,AAPL260406C00185000".to_string(),
213                ),
214                ("timeframe".to_string(), "1Day".to_string()),
215                ("start".to_string(), "2026-04-01T00:00:00Z".to_string()),
216                ("end".to_string(), "2026-04-03T00:00:00Z".to_string()),
217                ("limit".to_string(), "2".to_string()),
218                ("page_token".to_string(), "page-2".to_string()),
219                ("sort".to_string(), "asc".to_string()),
220            ]
221        );
222    }
223
224    #[test]
225    fn trades_request_serializes_official_query_words() {
226        let query = TradesRequest {
227            symbols: vec!["AAPL260406C00180000".into()],
228            start: Some("2026-04-02T13:39:00Z".into()),
229            end: Some("2026-04-02T13:40:00Z".into()),
230            limit: Some(1),
231            sort: Some(Sort::Desc),
232            page_token: Some("page-3".into()),
233        }
234        .to_query();
235
236        assert_eq!(
237            query,
238            vec![
239                ("symbols".to_string(), "AAPL260406C00180000".to_string()),
240                ("start".to_string(), "2026-04-02T13:39:00Z".to_string()),
241                ("end".to_string(), "2026-04-02T13:40:00Z".to_string()),
242                ("limit".to_string(), "1".to_string()),
243                ("page_token".to_string(), "page-3".to_string()),
244                ("sort".to_string(), "desc".to_string()),
245            ]
246        );
247    }
248
249    #[test]
250    fn latest_requests_serialize_official_query_words() {
251        let quotes_query = LatestQuotesRequest {
252            symbols: vec!["AAPL260406C00180000".into()],
253            feed: Some(OptionsFeed::Indicative),
254        }
255        .to_query();
256        assert_eq!(
257            quotes_query,
258            vec![
259                ("symbols".to_string(), "AAPL260406C00180000".to_string()),
260                ("feed".to_string(), "indicative".to_string()),
261            ]
262        );
263
264        let trades_query = LatestTradesRequest {
265            symbols: vec!["AAPL260406C00180000".into(), "AAPL260406C00185000".into()],
266            feed: Some(OptionsFeed::Opra),
267        }
268        .to_query();
269        assert_eq!(
270            trades_query,
271            vec![
272                (
273                    "symbols".to_string(),
274                    "AAPL260406C00180000,AAPL260406C00185000".to_string(),
275                ),
276                ("feed".to_string(), "opra".to_string()),
277            ]
278        );
279    }
280
281    #[test]
282    fn snapshot_requests_serialize_official_query_words() {
283        let query = SnapshotsRequest {
284            symbols: vec!["AAPL260406C00180000".into(), "AAPL260406C00185000".into()],
285            feed: Some(OptionsFeed::Indicative),
286            limit: Some(2),
287            page_token: Some("page-2".into()),
288        }
289        .to_query();
290
291        assert_eq!(
292            query,
293            vec![
294                (
295                    "symbols".to_string(),
296                    "AAPL260406C00180000,AAPL260406C00185000".to_string(),
297                ),
298                ("feed".to_string(), "indicative".to_string()),
299                ("limit".to_string(), "2".to_string()),
300                ("page_token".to_string(), "page-2".to_string()),
301            ]
302        );
303    }
304
305    #[test]
306    fn chain_request_serializes_official_query_words() {
307        let query = ChainRequest {
308            underlying_symbol: "AAPL".into(),
309            feed: Some(OptionsFeed::Indicative),
310            r#type: Some(ContractType::Call),
311            strike_price_gte: Some(180.0),
312            strike_price_lte: Some(200.0),
313            expiration_date: Some("2026-04-06".into()),
314            expiration_date_gte: Some("2026-04-06".into()),
315            expiration_date_lte: Some("2026-04-13".into()),
316            root_symbol: Some("AAPL".into()),
317            updated_since: Some("2026-04-02T19:30:00Z".into()),
318            limit: Some(3),
319            page_token: Some("page-3".into()),
320        }
321        .to_query();
322
323        assert_eq!(
324            query,
325            vec![
326                ("feed".to_string(), "indicative".to_string()),
327                ("type".to_string(), "call".to_string()),
328                ("strike_price_gte".to_string(), "180".to_string()),
329                ("strike_price_lte".to_string(), "200".to_string()),
330                ("expiration_date".to_string(), "2026-04-06".to_string()),
331                ("expiration_date_gte".to_string(), "2026-04-06".to_string()),
332                ("expiration_date_lte".to_string(), "2026-04-13".to_string()),
333                ("root_symbol".to_string(), "AAPL".to_string()),
334                (
335                    "updated_since".to_string(),
336                    "2026-04-02T19:30:00Z".to_string()
337                ),
338                ("limit".to_string(), "3".to_string()),
339                ("page_token".to_string(), "page-3".to_string()),
340            ]
341        );
342    }
343
344    #[test]
345    fn condition_codes_request_uses_official_ticktype_word() {
346        let trade = ConditionCodesRequest {
347            ticktype: TickType::Trade,
348        };
349        assert_eq!(trade.ticktype(), "trade");
350
351        let quote = ConditionCodesRequest {
352            ticktype: TickType::Quote,
353        };
354        assert_eq!(quote.ticktype(), "quote");
355    }
356}