Skip to main content

alpaca_data/crypto/
request.rs

1use alpaca_core::{QueryWriter, pagination::PaginatedRequest};
2
3use crate::Error;
4
5use super::{Location, Sort, TimeFrame};
6
7#[derive(Clone, Debug, Default)]
8pub struct BarsRequest {
9    pub location: Location,
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 sort: Option<Sort>,
16    pub page_token: Option<String>,
17}
18
19#[derive(Clone, Debug, Default)]
20pub struct QuotesRequest {
21    pub location: Location,
22    pub symbols: Vec<String>,
23    pub start: Option<String>,
24    pub end: Option<String>,
25    pub limit: Option<u32>,
26    pub sort: Option<Sort>,
27    pub page_token: Option<String>,
28}
29
30#[derive(Clone, Debug, Default)]
31pub struct TradesRequest {
32    pub location: Location,
33    pub symbols: Vec<String>,
34    pub start: Option<String>,
35    pub end: Option<String>,
36    pub limit: Option<u32>,
37    pub sort: Option<Sort>,
38    pub page_token: Option<String>,
39}
40
41#[derive(Clone, Debug, Default)]
42pub struct LatestBarsRequest {
43    pub location: Location,
44    pub symbols: Vec<String>,
45}
46
47#[derive(Clone, Debug, Default)]
48pub struct LatestQuotesRequest {
49    pub location: Location,
50    pub symbols: Vec<String>,
51}
52
53#[derive(Clone, Debug, Default)]
54pub struct LatestTradesRequest {
55    pub location: Location,
56    pub symbols: Vec<String>,
57}
58
59#[derive(Clone, Debug, Default)]
60pub struct LatestOrderbooksRequest {
61    pub location: Location,
62    pub symbols: Vec<String>,
63}
64
65#[derive(Clone, Debug, Default)]
66pub struct SnapshotsRequest {
67    pub location: Location,
68    pub symbols: Vec<String>,
69}
70
71impl BarsRequest {
72    pub(crate) fn validate(&self) -> Result<(), Error> {
73        validate_required_symbols(&self.symbols)?;
74        validate_limit(self.limit, 1, 10_000)
75    }
76
77    pub(crate) fn into_query(self) -> Vec<(String, String)> {
78        let mut query = QueryWriter::default();
79        query.push_csv("symbols", normalized_crypto_symbols(&self.symbols));
80        query.push_opt("timeframe", Some(self.timeframe));
81        query.push_opt("start", self.start);
82        query.push_opt("end", self.end);
83        query.push_opt("limit", self.limit);
84        query.push_opt("sort", self.sort);
85        query.push_opt("page_token", self.page_token);
86        query.finish()
87    }
88}
89
90impl QuotesRequest {
91    pub(crate) fn validate(&self) -> Result<(), Error> {
92        validate_required_symbols(&self.symbols)?;
93        validate_limit(self.limit, 1, 10_000)
94    }
95
96    pub(crate) fn into_query(self) -> Vec<(String, String)> {
97        let mut query = QueryWriter::default();
98        query.push_csv("symbols", normalized_crypto_symbols(&self.symbols));
99        query.push_opt("start", self.start);
100        query.push_opt("end", self.end);
101        query.push_opt("limit", self.limit);
102        query.push_opt("sort", self.sort);
103        query.push_opt("page_token", self.page_token);
104        query.finish()
105    }
106}
107
108impl TradesRequest {
109    pub(crate) fn validate(&self) -> Result<(), Error> {
110        validate_required_symbols(&self.symbols)?;
111        validate_limit(self.limit, 1, 10_000)
112    }
113
114    pub(crate) fn into_query(self) -> Vec<(String, String)> {
115        let mut query = QueryWriter::default();
116        query.push_csv("symbols", normalized_crypto_symbols(&self.symbols));
117        query.push_opt("start", self.start);
118        query.push_opt("end", self.end);
119        query.push_opt("limit", self.limit);
120        query.push_opt("sort", self.sort);
121        query.push_opt("page_token", self.page_token);
122        query.finish()
123    }
124}
125
126impl LatestBarsRequest {
127    pub(crate) fn validate(&self) -> Result<(), Error> {
128        validate_required_symbols(&self.symbols)
129    }
130
131    pub(crate) fn into_query(self) -> Vec<(String, String)> {
132        latest_batch_query(self.symbols)
133    }
134}
135
136impl LatestQuotesRequest {
137    pub(crate) fn validate(&self) -> Result<(), Error> {
138        validate_required_symbols(&self.symbols)
139    }
140
141    pub(crate) fn into_query(self) -> Vec<(String, String)> {
142        latest_batch_query(self.symbols)
143    }
144}
145
146impl LatestTradesRequest {
147    pub(crate) fn validate(&self) -> Result<(), Error> {
148        validate_required_symbols(&self.symbols)
149    }
150
151    pub(crate) fn into_query(self) -> Vec<(String, String)> {
152        latest_batch_query(self.symbols)
153    }
154}
155
156impl LatestOrderbooksRequest {
157    pub(crate) fn validate(&self) -> Result<(), Error> {
158        validate_required_symbols(&self.symbols)
159    }
160
161    pub(crate) fn into_query(self) -> Vec<(String, String)> {
162        latest_batch_query(self.symbols)
163    }
164}
165
166impl SnapshotsRequest {
167    pub(crate) fn validate(&self) -> Result<(), Error> {
168        validate_required_symbols(&self.symbols)
169    }
170
171    pub(crate) fn into_query(self) -> Vec<(String, String)> {
172        latest_batch_query(self.symbols)
173    }
174}
175
176impl PaginatedRequest for BarsRequest {
177    fn with_page_token(&self, page_token: Option<String>) -> Self {
178        let mut next = self.clone();
179        next.page_token = page_token;
180        next
181    }
182}
183
184impl PaginatedRequest for QuotesRequest {
185    fn with_page_token(&self, page_token: Option<String>) -> Self {
186        let mut next = self.clone();
187        next.page_token = page_token;
188        next
189    }
190}
191
192impl PaginatedRequest for TradesRequest {
193    fn with_page_token(&self, page_token: Option<String>) -> Self {
194        let mut next = self.clone();
195        next.page_token = page_token;
196        next
197    }
198}
199
200fn latest_batch_query(symbols: Vec<String>) -> Vec<(String, String)> {
201    let mut query = QueryWriter::default();
202    query.push_csv("symbols", normalized_crypto_symbols(&symbols));
203    query.finish()
204}
205
206fn validate_required_symbols(symbols: &[String]) -> Result<(), Error> {
207    if symbols.is_empty() {
208        return Err(Error::InvalidRequest(
209            "symbols are invalid: must not be empty".to_owned(),
210        ));
211    }
212
213    if symbols
214        .iter()
215        .any(|symbol| normalized_crypto_symbol(symbol).is_empty())
216    {
217        return Err(Error::InvalidRequest(
218            "symbols are invalid: must not contain empty or whitespace-only entries".to_owned(),
219        ));
220    }
221
222    Ok(())
223}
224
225fn normalized_crypto_symbol(symbol: &str) -> String {
226    symbol.trim().to_owned()
227}
228
229fn normalized_crypto_symbols(symbols: &[String]) -> Vec<String> {
230    symbols
231        .iter()
232        .map(|symbol| normalized_crypto_symbol(symbol))
233        .collect()
234}
235
236fn validate_limit(limit: Option<u32>, min: u32, max: u32) -> Result<(), Error> {
237    if let Some(limit) = limit
238        && !(min..=max).contains(&limit)
239    {
240        return Err(Error::InvalidRequest(format!(
241            "limit must be between {min} and {max}"
242        )));
243    }
244
245    Ok(())
246}