Skip to main content

alpaca_data/stocks/
request.rs

1use alpaca_core::{QueryWriter, pagination::PaginatedRequest};
2
3use crate::Error;
4use crate::symbols::display_stock_symbol;
5
6use super::{Adjustment, AuctionFeed, Currency, DataFeed, Sort, Tape, TickType, TimeFrame};
7
8#[derive(Clone, Debug, Default)]
9pub struct BarsRequest {
10    pub symbols: Vec<String>,
11    pub timeframe: TimeFrame,
12    pub start: Option<String>,
13    pub end: Option<String>,
14    pub limit: Option<u32>,
15    pub adjustment: Option<Adjustment>,
16    pub feed: Option<DataFeed>,
17    pub sort: Option<Sort>,
18    pub asof: Option<String>,
19    pub currency: Option<Currency>,
20    pub page_token: Option<String>,
21}
22
23#[derive(Clone, Debug, Default)]
24pub struct AuctionsRequest {
25    pub symbols: Vec<String>,
26    pub start: Option<String>,
27    pub end: Option<String>,
28    pub limit: Option<u32>,
29    pub asof: Option<String>,
30    pub feed: Option<AuctionFeed>,
31    pub currency: Option<Currency>,
32    pub page_token: Option<String>,
33    pub sort: Option<Sort>,
34}
35
36#[derive(Clone, Debug, Default)]
37pub struct QuotesRequest {
38    pub symbols: Vec<String>,
39    pub start: Option<String>,
40    pub end: Option<String>,
41    pub limit: Option<u32>,
42    pub feed: Option<DataFeed>,
43    pub sort: Option<Sort>,
44    pub asof: Option<String>,
45    pub currency: Option<Currency>,
46    pub page_token: Option<String>,
47}
48
49#[derive(Clone, Debug, Default)]
50pub struct TradesRequest {
51    pub symbols: Vec<String>,
52    pub start: Option<String>,
53    pub end: Option<String>,
54    pub limit: Option<u32>,
55    pub feed: Option<DataFeed>,
56    pub sort: Option<Sort>,
57    pub asof: Option<String>,
58    pub currency: Option<Currency>,
59    pub page_token: Option<String>,
60}
61
62#[derive(Clone, Debug, Default)]
63pub struct LatestBarsRequest {
64    pub symbols: Vec<String>,
65    pub feed: Option<DataFeed>,
66    pub currency: Option<Currency>,
67}
68
69#[derive(Clone, Debug, Default)]
70pub struct LatestQuotesRequest {
71    pub symbols: Vec<String>,
72    pub feed: Option<DataFeed>,
73    pub currency: Option<Currency>,
74}
75
76#[derive(Clone, Debug, Default)]
77pub struct LatestTradesRequest {
78    pub symbols: Vec<String>,
79    pub feed: Option<DataFeed>,
80    pub currency: Option<Currency>,
81}
82
83#[derive(Clone, Debug, Default)]
84pub struct SnapshotsRequest {
85    pub symbols: Vec<String>,
86    pub feed: Option<DataFeed>,
87    pub currency: Option<Currency>,
88}
89
90#[derive(Clone, Debug, Default)]
91pub struct ConditionCodesRequest {
92    pub ticktype: TickType,
93    pub tape: Tape,
94}
95
96impl BarsRequest {
97    pub(crate) fn validate(&self) -> Result<(), Error> {
98        validate_required_symbols(&self.symbols)?;
99        validate_limit(self.limit, 1, 10_000)
100    }
101
102    pub(crate) fn into_query(self) -> Vec<(String, String)> {
103        let mut query = QueryWriter::default();
104        query.push_csv("symbols", normalized_stock_symbols(&self.symbols));
105        query.push_opt("timeframe", Some(self.timeframe));
106        query.push_opt("start", self.start);
107        query.push_opt("end", self.end);
108        query.push_opt("limit", self.limit);
109        query.push_opt("adjustment", self.adjustment);
110        query.push_opt("feed", self.feed);
111        query.push_opt("sort", self.sort);
112        query.push_opt("asof", self.asof);
113        query.push_opt("currency", self.currency);
114        query.push_opt("page_token", self.page_token);
115        query.finish()
116    }
117}
118
119impl AuctionsRequest {
120    pub(crate) fn validate(&self) -> Result<(), Error> {
121        validate_required_symbols(&self.symbols)?;
122        validate_limit(self.limit, 1, 10_000)
123    }
124
125    pub(crate) fn into_query(self) -> Vec<(String, String)> {
126        let mut query = QueryWriter::default();
127        query.push_csv("symbols", normalized_stock_symbols(&self.symbols));
128        query.push_opt("start", self.start);
129        query.push_opt("end", self.end);
130        query.push_opt("limit", self.limit);
131        query.push_opt("asof", self.asof);
132        query.push_opt("feed", self.feed);
133        query.push_opt("currency", self.currency);
134        query.push_opt("page_token", self.page_token);
135        query.push_opt("sort", self.sort);
136        query.finish()
137    }
138}
139
140impl QuotesRequest {
141    pub(crate) fn validate(&self) -> Result<(), Error> {
142        validate_required_symbols(&self.symbols)?;
143        validate_limit(self.limit, 1, 10_000)
144    }
145
146    pub(crate) fn into_query(self) -> Vec<(String, String)> {
147        let mut query = QueryWriter::default();
148        query.push_csv("symbols", normalized_stock_symbols(&self.symbols));
149        query.push_opt("start", self.start);
150        query.push_opt("end", self.end);
151        query.push_opt("limit", self.limit);
152        query.push_opt("feed", self.feed);
153        query.push_opt("sort", self.sort);
154        query.push_opt("asof", self.asof);
155        query.push_opt("currency", self.currency);
156        query.push_opt("page_token", self.page_token);
157        query.finish()
158    }
159}
160
161impl TradesRequest {
162    pub(crate) fn validate(&self) -> Result<(), Error> {
163        validate_required_symbols(&self.symbols)?;
164        validate_limit(self.limit, 1, 10_000)
165    }
166
167    pub(crate) fn into_query(self) -> Vec<(String, String)> {
168        let mut query = QueryWriter::default();
169        query.push_csv("symbols", normalized_stock_symbols(&self.symbols));
170        query.push_opt("start", self.start);
171        query.push_opt("end", self.end);
172        query.push_opt("limit", self.limit);
173        query.push_opt("feed", self.feed);
174        query.push_opt("sort", self.sort);
175        query.push_opt("asof", self.asof);
176        query.push_opt("currency", self.currency);
177        query.push_opt("page_token", self.page_token);
178        query.finish()
179    }
180}
181
182impl LatestBarsRequest {
183    pub(crate) fn validate(&self) -> Result<(), Error> {
184        validate_required_symbols(&self.symbols)
185    }
186
187    pub(crate) fn into_query(self) -> Vec<(String, String)> {
188        latest_batch_query(self.symbols, self.feed, self.currency)
189    }
190}
191
192impl LatestQuotesRequest {
193    pub(crate) fn validate(&self) -> Result<(), Error> {
194        validate_required_symbols(&self.symbols)
195    }
196
197    pub(crate) fn into_query(self) -> Vec<(String, String)> {
198        latest_batch_query(self.symbols, self.feed, self.currency)
199    }
200}
201
202impl LatestTradesRequest {
203    pub(crate) fn validate(&self) -> Result<(), Error> {
204        validate_required_symbols(&self.symbols)
205    }
206
207    pub(crate) fn into_query(self) -> Vec<(String, String)> {
208        latest_batch_query(self.symbols, self.feed, self.currency)
209    }
210}
211
212impl SnapshotsRequest {
213    pub(crate) fn validate(&self) -> Result<(), Error> {
214        validate_required_symbols(&self.symbols)
215    }
216
217    pub(crate) fn into_query(self) -> Vec<(String, String)> {
218        latest_batch_query(self.symbols, self.feed, self.currency)
219    }
220}
221
222impl ConditionCodesRequest {
223    pub(crate) fn into_query(self) -> Vec<(String, String)> {
224        let mut query = QueryWriter::default();
225        query.push_opt("tape", Some(self.tape));
226        query.finish()
227    }
228}
229
230impl PaginatedRequest for BarsRequest {
231    fn with_page_token(&self, page_token: Option<String>) -> Self {
232        let mut next = self.clone();
233        next.page_token = page_token;
234        next
235    }
236}
237
238impl PaginatedRequest for AuctionsRequest {
239    fn with_page_token(&self, page_token: Option<String>) -> Self {
240        let mut next = self.clone();
241        next.page_token = page_token;
242        next
243    }
244}
245
246impl PaginatedRequest for QuotesRequest {
247    fn with_page_token(&self, page_token: Option<String>) -> Self {
248        let mut next = self.clone();
249        next.page_token = page_token;
250        next
251    }
252}
253
254impl PaginatedRequest for TradesRequest {
255    fn with_page_token(&self, page_token: Option<String>) -> Self {
256        let mut next = self.clone();
257        next.page_token = page_token;
258        next
259    }
260}
261
262fn latest_batch_query(
263    symbols: Vec<String>,
264    feed: Option<DataFeed>,
265    currency: Option<Currency>,
266) -> Vec<(String, String)> {
267    let mut query = QueryWriter::default();
268    query.push_csv("symbols", normalized_stock_symbols(&symbols));
269    query.push_opt("feed", feed);
270    query.push_opt("currency", currency);
271    query.finish()
272}
273
274fn validate_required_symbols(symbols: &[String]) -> Result<(), Error> {
275    if symbols.is_empty() {
276        return Err(Error::InvalidRequest(
277            "symbols are invalid: must not be empty".to_owned(),
278        ));
279    }
280
281    if symbols
282        .iter()
283        .any(|symbol| normalized_stock_symbol(symbol).is_empty())
284    {
285        return Err(Error::InvalidRequest(
286            "symbols are invalid: must not contain empty or whitespace-only entries".to_owned(),
287        ));
288    }
289
290    Ok(())
291}
292
293fn normalized_stock_symbol(symbol: &str) -> String {
294    display_stock_symbol(symbol)
295}
296
297fn normalized_stock_symbols(symbols: &[String]) -> Vec<String> {
298    symbols
299        .iter()
300        .map(|symbol| normalized_stock_symbol(symbol))
301        .collect()
302}
303
304fn validate_limit(limit: Option<u32>, min: u32, max: u32) -> Result<(), Error> {
305    if let Some(limit) = limit
306        && !(min..=max).contains(&limit)
307    {
308        return Err(Error::InvalidRequest(format!(
309            "limit must be between {min} and {max}"
310        )));
311    }
312
313    Ok(())
314}