Skip to main content

alpaca_data/stocks/
request.rs

1use alpaca_core::{QueryWriter, pagination::PaginatedRequest};
2
3use crate::Error;
4
5use super::{Adjustment, AuctionFeed, Currency, DataFeed, Sort, Tape, TickType, TimeFrame};
6
7#[derive(Clone, Debug, Default)]
8pub struct BarsRequest {
9    pub symbols: Vec<String>,
10    pub timeframe: TimeFrame,
11    pub start: Option<String>,
12    pub end: Option<String>,
13    pub limit: Option<u32>,
14    pub adjustment: Option<Adjustment>,
15    pub feed: Option<DataFeed>,
16    pub sort: Option<Sort>,
17    pub asof: Option<String>,
18    pub currency: Option<Currency>,
19    pub page_token: Option<String>,
20}
21
22#[derive(Clone, Debug, Default)]
23pub struct BarsSingleRequest {
24    pub symbol: String,
25    pub timeframe: TimeFrame,
26    pub start: Option<String>,
27    pub end: Option<String>,
28    pub limit: Option<u32>,
29    pub adjustment: Option<Adjustment>,
30    pub feed: Option<DataFeed>,
31    pub sort: Option<Sort>,
32    pub asof: Option<String>,
33    pub currency: Option<Currency>,
34    pub page_token: Option<String>,
35}
36
37#[derive(Clone, Debug, Default)]
38pub struct AuctionsRequest {
39    pub symbols: Vec<String>,
40    pub start: Option<String>,
41    pub end: Option<String>,
42    pub limit: Option<u32>,
43    pub asof: Option<String>,
44    pub feed: Option<AuctionFeed>,
45    pub currency: Option<Currency>,
46    pub page_token: Option<String>,
47    pub sort: Option<Sort>,
48}
49
50#[derive(Clone, Debug, Default)]
51pub struct AuctionsSingleRequest {
52    pub symbol: String,
53    pub start: Option<String>,
54    pub end: Option<String>,
55    pub limit: Option<u32>,
56    pub asof: Option<String>,
57    pub feed: Option<AuctionFeed>,
58    pub currency: Option<Currency>,
59    pub page_token: Option<String>,
60    pub sort: Option<Sort>,
61}
62
63#[derive(Clone, Debug, Default)]
64pub struct QuotesRequest {
65    pub symbols: Vec<String>,
66    pub start: Option<String>,
67    pub end: Option<String>,
68    pub limit: Option<u32>,
69    pub feed: Option<DataFeed>,
70    pub sort: Option<Sort>,
71    pub asof: Option<String>,
72    pub currency: Option<Currency>,
73    pub page_token: Option<String>,
74}
75
76#[derive(Clone, Debug, Default)]
77pub struct QuotesSingleRequest {
78    pub symbol: String,
79    pub start: Option<String>,
80    pub end: Option<String>,
81    pub limit: Option<u32>,
82    pub feed: Option<DataFeed>,
83    pub sort: Option<Sort>,
84    pub asof: Option<String>,
85    pub currency: Option<Currency>,
86    pub page_token: Option<String>,
87}
88
89#[derive(Clone, Debug, Default)]
90pub struct TradesRequest {
91    pub symbols: Vec<String>,
92    pub start: Option<String>,
93    pub end: Option<String>,
94    pub limit: Option<u32>,
95    pub feed: Option<DataFeed>,
96    pub sort: Option<Sort>,
97    pub asof: Option<String>,
98    pub currency: Option<Currency>,
99    pub page_token: Option<String>,
100}
101
102#[derive(Clone, Debug, Default)]
103pub struct TradesSingleRequest {
104    pub symbol: String,
105    pub start: Option<String>,
106    pub end: Option<String>,
107    pub limit: Option<u32>,
108    pub feed: Option<DataFeed>,
109    pub sort: Option<Sort>,
110    pub asof: Option<String>,
111    pub currency: Option<Currency>,
112    pub page_token: Option<String>,
113}
114
115#[derive(Clone, Debug, Default)]
116pub struct LatestBarsRequest {
117    pub symbols: Vec<String>,
118    pub feed: Option<DataFeed>,
119    pub currency: Option<Currency>,
120}
121
122#[derive(Clone, Debug, Default)]
123pub struct LatestBarRequest {
124    pub symbol: String,
125    pub feed: Option<DataFeed>,
126    pub currency: Option<Currency>,
127}
128
129#[derive(Clone, Debug, Default)]
130pub struct LatestQuotesRequest {
131    pub symbols: Vec<String>,
132    pub feed: Option<DataFeed>,
133    pub currency: Option<Currency>,
134}
135
136#[derive(Clone, Debug, Default)]
137pub struct LatestQuoteRequest {
138    pub symbol: String,
139    pub feed: Option<DataFeed>,
140    pub currency: Option<Currency>,
141}
142
143#[derive(Clone, Debug, Default)]
144pub struct LatestTradesRequest {
145    pub symbols: Vec<String>,
146    pub feed: Option<DataFeed>,
147    pub currency: Option<Currency>,
148}
149
150#[derive(Clone, Debug, Default)]
151pub struct LatestTradeRequest {
152    pub symbol: String,
153    pub feed: Option<DataFeed>,
154    pub currency: Option<Currency>,
155}
156
157#[derive(Clone, Debug, Default)]
158pub struct SnapshotsRequest {
159    pub symbols: Vec<String>,
160    pub feed: Option<DataFeed>,
161    pub currency: Option<Currency>,
162}
163
164#[derive(Clone, Debug, Default)]
165pub struct SnapshotRequest {
166    pub symbol: String,
167    pub feed: Option<DataFeed>,
168    pub currency: Option<Currency>,
169}
170
171#[derive(Clone, Debug, Default)]
172pub struct ConditionCodesRequest {
173    pub ticktype: TickType,
174    pub tape: Tape,
175}
176
177impl BarsRequest {
178    pub(crate) fn validate(&self) -> Result<(), Error> {
179        validate_required_symbols(&self.symbols)?;
180        validate_limit(self.limit, 1, 10_000)
181    }
182
183    pub(crate) fn into_query(self) -> Vec<(String, String)> {
184        let mut query = QueryWriter::default();
185        query.push_csv("symbols", self.symbols);
186        query.push_opt("timeframe", Some(self.timeframe));
187        query.push_opt("start", self.start);
188        query.push_opt("end", self.end);
189        query.push_opt("limit", self.limit);
190        query.push_opt("adjustment", self.adjustment);
191        query.push_opt("feed", self.feed);
192        query.push_opt("sort", self.sort);
193        query.push_opt("asof", self.asof);
194        query.push_opt("currency", self.currency);
195        query.push_opt("page_token", self.page_token);
196        query.finish()
197    }
198}
199
200impl BarsSingleRequest {
201    pub(crate) fn validate(&self) -> Result<(), Error> {
202        validate_required_symbol(&self.symbol, "symbol")?;
203        validate_limit(self.limit, 1, 10_000)
204    }
205
206    pub(crate) fn into_query(self) -> Vec<(String, String)> {
207        let mut query = QueryWriter::default();
208        query.push_opt("timeframe", Some(self.timeframe));
209        query.push_opt("start", self.start);
210        query.push_opt("end", self.end);
211        query.push_opt("limit", self.limit);
212        query.push_opt("adjustment", self.adjustment);
213        query.push_opt("feed", self.feed);
214        query.push_opt("sort", self.sort);
215        query.push_opt("asof", self.asof);
216        query.push_opt("currency", self.currency);
217        query.push_opt("page_token", self.page_token);
218        query.finish()
219    }
220}
221
222impl AuctionsRequest {
223    pub(crate) fn validate(&self) -> Result<(), Error> {
224        validate_required_symbols(&self.symbols)?;
225        validate_limit(self.limit, 1, 10_000)
226    }
227
228    pub(crate) fn into_query(self) -> Vec<(String, String)> {
229        let mut query = QueryWriter::default();
230        query.push_csv("symbols", self.symbols);
231        query.push_opt("start", self.start);
232        query.push_opt("end", self.end);
233        query.push_opt("limit", self.limit);
234        query.push_opt("asof", self.asof);
235        query.push_opt("feed", self.feed);
236        query.push_opt("currency", self.currency);
237        query.push_opt("page_token", self.page_token);
238        query.push_opt("sort", self.sort);
239        query.finish()
240    }
241}
242
243impl AuctionsSingleRequest {
244    pub(crate) fn validate(&self) -> Result<(), Error> {
245        validate_required_symbol(&self.symbol, "symbol")?;
246        validate_limit(self.limit, 1, 10_000)
247    }
248
249    pub(crate) fn into_query(self) -> Vec<(String, String)> {
250        let mut query = QueryWriter::default();
251        query.push_opt("start", self.start);
252        query.push_opt("end", self.end);
253        query.push_opt("limit", self.limit);
254        query.push_opt("asof", self.asof);
255        query.push_opt("feed", self.feed);
256        query.push_opt("currency", self.currency);
257        query.push_opt("page_token", self.page_token);
258        query.push_opt("sort", self.sort);
259        query.finish()
260    }
261}
262
263impl QuotesRequest {
264    pub(crate) fn validate(&self) -> Result<(), Error> {
265        validate_required_symbols(&self.symbols)?;
266        validate_limit(self.limit, 1, 10_000)
267    }
268
269    pub(crate) fn into_query(self) -> Vec<(String, String)> {
270        let mut query = QueryWriter::default();
271        query.push_csv("symbols", self.symbols);
272        query.push_opt("start", self.start);
273        query.push_opt("end", self.end);
274        query.push_opt("limit", self.limit);
275        query.push_opt("feed", self.feed);
276        query.push_opt("sort", self.sort);
277        query.push_opt("asof", self.asof);
278        query.push_opt("currency", self.currency);
279        query.push_opt("page_token", self.page_token);
280        query.finish()
281    }
282}
283
284impl QuotesSingleRequest {
285    pub(crate) fn validate(&self) -> Result<(), Error> {
286        validate_required_symbol(&self.symbol, "symbol")?;
287        validate_limit(self.limit, 1, 10_000)
288    }
289
290    pub(crate) fn into_query(self) -> Vec<(String, String)> {
291        let mut query = QueryWriter::default();
292        query.push_opt("start", self.start);
293        query.push_opt("end", self.end);
294        query.push_opt("limit", self.limit);
295        query.push_opt("feed", self.feed);
296        query.push_opt("sort", self.sort);
297        query.push_opt("asof", self.asof);
298        query.push_opt("currency", self.currency);
299        query.push_opt("page_token", self.page_token);
300        query.finish()
301    }
302}
303
304impl TradesRequest {
305    pub(crate) fn validate(&self) -> Result<(), Error> {
306        validate_required_symbols(&self.symbols)?;
307        validate_limit(self.limit, 1, 10_000)
308    }
309
310    pub(crate) fn into_query(self) -> Vec<(String, String)> {
311        let mut query = QueryWriter::default();
312        query.push_csv("symbols", self.symbols);
313        query.push_opt("start", self.start);
314        query.push_opt("end", self.end);
315        query.push_opt("limit", self.limit);
316        query.push_opt("feed", self.feed);
317        query.push_opt("sort", self.sort);
318        query.push_opt("asof", self.asof);
319        query.push_opt("currency", self.currency);
320        query.push_opt("page_token", self.page_token);
321        query.finish()
322    }
323}
324
325impl TradesSingleRequest {
326    pub(crate) fn validate(&self) -> Result<(), Error> {
327        validate_required_symbol(&self.symbol, "symbol")?;
328        validate_limit(self.limit, 1, 10_000)
329    }
330
331    pub(crate) fn into_query(self) -> Vec<(String, String)> {
332        let mut query = QueryWriter::default();
333        query.push_opt("start", self.start);
334        query.push_opt("end", self.end);
335        query.push_opt("limit", self.limit);
336        query.push_opt("feed", self.feed);
337        query.push_opt("sort", self.sort);
338        query.push_opt("asof", self.asof);
339        query.push_opt("currency", self.currency);
340        query.push_opt("page_token", self.page_token);
341        query.finish()
342    }
343}
344
345impl LatestBarsRequest {
346    pub(crate) fn validate(&self) -> Result<(), Error> {
347        validate_required_symbols(&self.symbols)
348    }
349
350    pub(crate) fn into_query(self) -> Vec<(String, String)> {
351        latest_batch_query(self.symbols, self.feed, self.currency)
352    }
353}
354
355impl LatestBarRequest {
356    pub(crate) fn validate(&self) -> Result<(), Error> {
357        validate_required_symbol(&self.symbol, "symbol")
358    }
359
360    pub(crate) fn into_query(self) -> Vec<(String, String)> {
361        latest_single_query(self.feed, self.currency)
362    }
363}
364
365impl LatestQuotesRequest {
366    pub(crate) fn validate(&self) -> Result<(), Error> {
367        validate_required_symbols(&self.symbols)
368    }
369
370    pub(crate) fn into_query(self) -> Vec<(String, String)> {
371        latest_batch_query(self.symbols, self.feed, self.currency)
372    }
373}
374
375impl LatestQuoteRequest {
376    pub(crate) fn validate(&self) -> Result<(), Error> {
377        validate_required_symbol(&self.symbol, "symbol")
378    }
379
380    pub(crate) fn into_query(self) -> Vec<(String, String)> {
381        latest_single_query(self.feed, self.currency)
382    }
383}
384
385impl LatestTradesRequest {
386    pub(crate) fn validate(&self) -> Result<(), Error> {
387        validate_required_symbols(&self.symbols)
388    }
389
390    pub(crate) fn into_query(self) -> Vec<(String, String)> {
391        latest_batch_query(self.symbols, self.feed, self.currency)
392    }
393}
394
395impl LatestTradeRequest {
396    pub(crate) fn validate(&self) -> Result<(), Error> {
397        validate_required_symbol(&self.symbol, "symbol")
398    }
399
400    pub(crate) fn into_query(self) -> Vec<(String, String)> {
401        latest_single_query(self.feed, self.currency)
402    }
403}
404
405impl SnapshotsRequest {
406    pub(crate) fn validate(&self) -> Result<(), Error> {
407        validate_required_symbols(&self.symbols)
408    }
409
410    pub(crate) fn into_query(self) -> Vec<(String, String)> {
411        latest_batch_query(self.symbols, self.feed, self.currency)
412    }
413}
414
415impl SnapshotRequest {
416    pub(crate) fn validate(&self) -> Result<(), Error> {
417        validate_required_symbol(&self.symbol, "symbol")
418    }
419
420    pub(crate) fn into_query(self) -> Vec<(String, String)> {
421        latest_single_query(self.feed, self.currency)
422    }
423}
424
425impl ConditionCodesRequest {
426    pub(crate) fn into_query(self) -> Vec<(String, String)> {
427        let mut query = QueryWriter::default();
428        query.push_opt("tape", Some(self.tape));
429        query.finish()
430    }
431}
432
433impl PaginatedRequest for BarsRequest {
434    fn with_page_token(&self, page_token: Option<String>) -> Self {
435        let mut next = self.clone();
436        next.page_token = page_token;
437        next
438    }
439}
440
441impl PaginatedRequest for BarsSingleRequest {
442    fn with_page_token(&self, page_token: Option<String>) -> Self {
443        let mut next = self.clone();
444        next.page_token = page_token;
445        next
446    }
447}
448
449impl PaginatedRequest for AuctionsRequest {
450    fn with_page_token(&self, page_token: Option<String>) -> Self {
451        let mut next = self.clone();
452        next.page_token = page_token;
453        next
454    }
455}
456
457impl PaginatedRequest for AuctionsSingleRequest {
458    fn with_page_token(&self, page_token: Option<String>) -> Self {
459        let mut next = self.clone();
460        next.page_token = page_token;
461        next
462    }
463}
464
465impl PaginatedRequest for QuotesRequest {
466    fn with_page_token(&self, page_token: Option<String>) -> Self {
467        let mut next = self.clone();
468        next.page_token = page_token;
469        next
470    }
471}
472
473impl PaginatedRequest for QuotesSingleRequest {
474    fn with_page_token(&self, page_token: Option<String>) -> Self {
475        let mut next = self.clone();
476        next.page_token = page_token;
477        next
478    }
479}
480
481impl PaginatedRequest for TradesRequest {
482    fn with_page_token(&self, page_token: Option<String>) -> Self {
483        let mut next = self.clone();
484        next.page_token = page_token;
485        next
486    }
487}
488
489impl PaginatedRequest for TradesSingleRequest {
490    fn with_page_token(&self, page_token: Option<String>) -> Self {
491        let mut next = self.clone();
492        next.page_token = page_token;
493        next
494    }
495}
496
497fn latest_batch_query(
498    symbols: Vec<String>,
499    feed: Option<DataFeed>,
500    currency: Option<Currency>,
501) -> Vec<(String, String)> {
502    let mut query = QueryWriter::default();
503    query.push_csv("symbols", symbols);
504    query.push_opt("feed", feed);
505    query.push_opt("currency", currency);
506    query.finish()
507}
508
509fn latest_single_query(
510    feed: Option<DataFeed>,
511    currency: Option<Currency>,
512) -> Vec<(String, String)> {
513    let mut query = QueryWriter::default();
514    query.push_opt("feed", feed);
515    query.push_opt("currency", currency);
516    query.finish()
517}
518
519fn validate_required_symbol(symbol: &str, field_name: &str) -> Result<(), Error> {
520    if symbol.trim().is_empty() {
521        return Err(Error::InvalidRequest(format!(
522            "{field_name} is invalid: must not be empty or whitespace-only"
523        )));
524    }
525
526    Ok(())
527}
528
529fn validate_required_symbols(symbols: &[String]) -> Result<(), Error> {
530    if symbols.is_empty() {
531        return Err(Error::InvalidRequest(
532            "symbols are invalid: must not be empty".to_owned(),
533        ));
534    }
535
536    if symbols.iter().any(|symbol| symbol.trim().is_empty()) {
537        return Err(Error::InvalidRequest(
538            "symbols are invalid: must not contain empty or whitespace-only entries".to_owned(),
539        ));
540    }
541
542    Ok(())
543}
544
545fn validate_limit(limit: Option<u32>, min: u32, max: u32) -> Result<(), Error> {
546    if let Some(limit) = limit
547        && !(min..=max).contains(&limit)
548    {
549        return Err(Error::InvalidRequest(format!(
550            "limit must be between {min} and {max}"
551        )));
552    }
553
554    Ok(())
555}