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}