Skip to main content

alpaca_data/crypto/
request.rs

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