1#![allow(missing_docs)]
4
5use alpaca_base::types::*;
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(tag = "T")]
12pub enum WebSocketMessage {
13 #[serde(rename = "auth")]
15 Auth(AuthMessage),
16
17 #[serde(rename = "subscribe")]
19 Subscribe(SubscribeMessage),
20
21 #[serde(rename = "unsubscribe")]
23 Unsubscribe(UnsubscribeMessage),
24
25 #[serde(rename = "t")]
27 Trade(TradeMessage),
28
29 #[serde(rename = "q")]
30 Quote(QuoteMessage),
31
32 #[serde(rename = "b")]
33 Bar(BarMessage),
34
35 #[serde(rename = "trade_updates")]
37 TradeUpdate(Box<TradeUpdateMessage>),
38
39 #[serde(rename = "success")]
41 Success(SuccessMessage),
42
43 #[serde(rename = "error")]
44 Error(ErrorMessage),
45
46 #[serde(rename = "connection")]
48 Connection(ConnectionMessage),
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct AuthMessage {
54 pub key: String,
55 pub secret: String,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct SubscribeMessage {
61 pub trades: Option<Vec<String>>,
62 pub quotes: Option<Vec<String>>,
63 pub bars: Option<Vec<String>>,
64 pub trade_updates: Option<bool>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct UnsubscribeMessage {
70 pub trades: Option<Vec<String>>,
71 pub quotes: Option<Vec<String>>,
72 pub bars: Option<Vec<String>>,
73 pub trade_updates: Option<bool>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct TradeMessage {
79 #[serde(rename = "S")]
80 pub symbol: String,
81 #[serde(rename = "t")]
82 pub timestamp: DateTime<Utc>,
83 #[serde(rename = "p")]
84 pub price: f64,
85 #[serde(rename = "s")]
86 pub size: u32,
87 #[serde(rename = "x")]
88 pub exchange: String,
89 #[serde(rename = "c")]
90 pub conditions: Vec<String>,
91 #[serde(rename = "i")]
92 pub id: u64,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct QuoteMessage {
98 #[serde(rename = "S")]
99 pub symbol: String,
100 #[serde(rename = "t")]
101 pub timestamp: DateTime<Utc>,
102 #[serde(rename = "bp")]
103 pub bid_price: f64,
104 #[serde(rename = "bs")]
105 pub bid_size: u32,
106 #[serde(rename = "ap")]
107 pub ask_price: f64,
108 #[serde(rename = "as")]
109 pub ask_size: u32,
110 #[serde(rename = "bx")]
111 pub bid_exchange: String,
112 #[serde(rename = "ax")]
113 pub ask_exchange: String,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct BarMessage {
119 #[serde(rename = "S")]
120 pub symbol: String,
121 #[serde(rename = "t")]
122 pub timestamp: DateTime<Utc>,
123 #[serde(rename = "o")]
124 pub open: f64,
125 #[serde(rename = "h")]
126 pub high: f64,
127 #[serde(rename = "l")]
128 pub low: f64,
129 #[serde(rename = "c")]
130 pub close: f64,
131 #[serde(rename = "v")]
132 pub volume: u64,
133 #[serde(rename = "n")]
134 pub trade_count: Option<u64>,
135 #[serde(rename = "vw")]
136 pub vwap: Option<f64>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct TradeUpdateMessage {
142 pub event: TradeUpdateEvent,
143 pub order: Order,
144 pub timestamp: DateTime<Utc>,
145 pub position_qty: Option<String>,
146 pub price: Option<String>,
147 pub qty: Option<String>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152#[serde(rename_all = "snake_case")]
153pub enum TradeUpdateEvent {
154 New,
155 Fill,
156 PartialFill,
157 Canceled,
158 Expired,
159 DoneForDay,
160 Replaced,
161 Rejected,
162 PendingNew,
163 Stopped,
164 PendingCancel,
165 PendingReplace,
166 Calculated,
167 Suspended,
168 OrderReplacePending,
169 OrderCancelPending,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct SuccessMessage {
175 pub msg: String,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct ErrorMessage {
181 pub code: u16,
182 pub msg: String,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct ConnectionMessage {
188 pub status: ConnectionStatus,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
193#[serde(rename_all = "snake_case")]
194pub enum ConnectionStatus {
195 Connected,
196 Authenticated,
197 AuthenticationFailed,
198 Disconnected,
199 Reconnecting,
200}
201
202#[derive(Debug, Default)]
204pub struct SubscriptionBuilder {
205 trades: Vec<String>,
206 quotes: Vec<String>,
207 bars: Vec<String>,
208 trade_updates: bool,
209}
210
211impl SubscriptionBuilder {
212 pub fn new() -> Self {
214 Self::default()
215 }
216
217 pub fn trades<I, S>(mut self, symbols: I) -> Self
219 where
220 I: IntoIterator<Item = S>,
221 S: Into<String>,
222 {
223 self.trades.extend(symbols.into_iter().map(|s| s.into()));
224 self
225 }
226
227 pub fn quotes<I, S>(mut self, symbols: I) -> Self
229 where
230 I: IntoIterator<Item = S>,
231 S: Into<String>,
232 {
233 self.quotes.extend(symbols.into_iter().map(|s| s.into()));
234 self
235 }
236
237 pub fn bars<I, S>(mut self, symbols: I) -> Self
239 where
240 I: IntoIterator<Item = S>,
241 S: Into<String>,
242 {
243 self.bars.extend(symbols.into_iter().map(|s| s.into()));
244 self
245 }
246
247 pub fn trade_updates(mut self) -> Self {
249 self.trade_updates = true;
250 self
251 }
252
253 pub fn build(self) -> SubscribeMessage {
255 SubscribeMessage {
256 trades: if self.trades.is_empty() {
257 None
258 } else {
259 Some(self.trades)
260 },
261 quotes: if self.quotes.is_empty() {
262 None
263 } else {
264 Some(self.quotes)
265 },
266 bars: if self.bars.is_empty() {
267 None
268 } else {
269 Some(self.bars)
270 },
271 trade_updates: if self.trade_updates { Some(true) } else { None },
272 }
273 }
274}
275
276impl From<TradeMessage> for Trade {
277 fn from(msg: TradeMessage) -> Self {
278 Trade {
279 timestamp: msg.timestamp,
280 price: msg.price,
281 size: msg.size,
282 exchange: msg.exchange,
283 conditions: msg.conditions,
284 id: msg.id,
285 }
286 }
287}
288
289impl From<QuoteMessage> for Quote {
290 fn from(msg: QuoteMessage) -> Self {
291 Quote {
292 timestamp: msg.timestamp,
293 timeframe: "real-time".to_string(),
294 bid_price: msg.bid_price,
295 bid_size: msg.bid_size,
296 ask_price: msg.ask_price,
297 ask_size: msg.ask_size,
298 bid_exchange: msg.bid_exchange,
299 ask_exchange: msg.ask_exchange,
300 }
301 }
302}
303
304impl From<BarMessage> for Bar {
305 fn from(msg: BarMessage) -> Self {
306 Bar {
307 timestamp: msg.timestamp,
308 open: msg.open,
309 high: msg.high,
310 low: msg.low,
311 close: msg.close,
312 volume: msg.volume,
313 trade_count: msg.trade_count,
314 vwap: msg.vwap,
315 }
316 }
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct CryptoTradeMessage {
326 #[serde(rename = "S")]
328 pub symbol: String,
329 #[serde(rename = "t")]
331 pub timestamp: DateTime<Utc>,
332 #[serde(rename = "p")]
334 pub price: f64,
335 #[serde(rename = "s")]
337 pub size: f64,
338 #[serde(rename = "tks")]
340 pub taker_side: String,
341 #[serde(rename = "i")]
343 pub id: u64,
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct CryptoQuoteMessage {
349 #[serde(rename = "S")]
351 pub symbol: String,
352 #[serde(rename = "t")]
354 pub timestamp: DateTime<Utc>,
355 #[serde(rename = "bp")]
357 pub bid_price: f64,
358 #[serde(rename = "bs")]
360 pub bid_size: f64,
361 #[serde(rename = "ap")]
363 pub ask_price: f64,
364 #[serde(rename = "as")]
366 pub ask_size: f64,
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct CryptoBarMessage {
372 #[serde(rename = "S")]
374 pub symbol: String,
375 #[serde(rename = "t")]
377 pub timestamp: DateTime<Utc>,
378 #[serde(rename = "o")]
380 pub open: f64,
381 #[serde(rename = "h")]
383 pub high: f64,
384 #[serde(rename = "l")]
386 pub low: f64,
387 #[serde(rename = "c")]
389 pub close: f64,
390 #[serde(rename = "v")]
392 pub volume: f64,
393 #[serde(rename = "n")]
395 pub trade_count: Option<u64>,
396 #[serde(rename = "vw")]
398 pub vwap: Option<f64>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
403pub struct OptionTradeMessage {
404 #[serde(rename = "S")]
406 pub symbol: String,
407 #[serde(rename = "t")]
409 pub timestamp: DateTime<Utc>,
410 #[serde(rename = "p")]
412 pub price: f64,
413 #[serde(rename = "s")]
415 pub size: u32,
416 #[serde(rename = "x")]
418 pub exchange: String,
419 #[serde(rename = "c", default)]
421 pub conditions: Option<String>,
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
426pub struct OptionQuoteMessage {
427 #[serde(rename = "S")]
429 pub symbol: String,
430 #[serde(rename = "t")]
432 pub timestamp: DateTime<Utc>,
433 #[serde(rename = "bp")]
435 pub bid_price: f64,
436 #[serde(rename = "bs")]
438 pub bid_size: u32,
439 #[serde(rename = "ap")]
441 pub ask_price: f64,
442 #[serde(rename = "as")]
444 pub ask_size: u32,
445 #[serde(rename = "bx")]
447 pub bid_exchange: String,
448 #[serde(rename = "ax")]
450 pub ask_exchange: String,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
455pub struct NewsMessage {
456 pub id: u64,
458 pub headline: String,
460 pub summary: Option<String>,
462 pub author: Option<String>,
464 pub created_at: DateTime<Utc>,
466 pub updated_at: DateTime<Utc>,
468 pub url: Option<String>,
470 pub symbols: Vec<String>,
472 pub source: String,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize)]
478pub struct LuldMessage {
479 #[serde(rename = "S")]
481 pub symbol: String,
482 #[serde(rename = "t")]
484 pub timestamp: DateTime<Utc>,
485 #[serde(rename = "i")]
487 pub indicator: String,
488 #[serde(rename = "u")]
490 pub limit_up_price: f64,
491 #[serde(rename = "d")]
493 pub limit_down_price: f64,
494}
495
496#[derive(Debug, Clone, Serialize, Deserialize)]
498pub struct TradingStatusMessage {
499 #[serde(rename = "S")]
501 pub symbol: String,
502 #[serde(rename = "t")]
504 pub timestamp: DateTime<Utc>,
505 #[serde(rename = "sc")]
507 pub status_code: String,
508 #[serde(rename = "sm")]
510 pub status_message: String,
511 #[serde(rename = "rc")]
513 pub reason_code: String,
514 #[serde(rename = "rm")]
516 pub reason_message: String,
517}
518
519#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct CorrectionMessage {
522 #[serde(rename = "S")]
524 pub symbol: String,
525 #[serde(rename = "t")]
527 pub timestamp: DateTime<Utc>,
528 #[serde(rename = "x")]
530 pub original_id: u64,
531 #[serde(rename = "op")]
533 pub original_price: f64,
534 #[serde(rename = "os")]
536 pub original_size: u32,
537 #[serde(rename = "cp")]
539 pub corrected_price: f64,
540 #[serde(rename = "cs")]
542 pub corrected_size: u32,
543}
544
545#[derive(Debug, Clone, Serialize, Deserialize)]
547pub struct CancelErrorMessage {
548 #[serde(rename = "S")]
550 pub symbol: String,
551 #[serde(rename = "t")]
553 pub timestamp: DateTime<Utc>,
554 #[serde(rename = "i")]
556 pub id: u64,
557 #[serde(rename = "p")]
559 pub price: f64,
560 #[serde(rename = "s")]
562 pub size: u32,
563}
564
565#[derive(Debug, Clone, Serialize, Deserialize)]
567pub struct DailyBarMessage {
568 #[serde(rename = "S")]
570 pub symbol: String,
571 #[serde(rename = "t")]
573 pub timestamp: DateTime<Utc>,
574 #[serde(rename = "o")]
576 pub open: f64,
577 #[serde(rename = "h")]
579 pub high: f64,
580 #[serde(rename = "l")]
582 pub low: f64,
583 #[serde(rename = "c")]
585 pub close: f64,
586 #[serde(rename = "v")]
588 pub volume: u64,
589 #[serde(rename = "vw")]
591 pub vwap: Option<f64>,
592}
593
594#[cfg(test)]
595mod tests {
596 use super::*;
597
598 #[test]
599 fn test_subscription_builder() {
600 let sub = SubscriptionBuilder::new()
601 .trades(["AAPL", "MSFT"])
602 .quotes(["GOOGL"])
603 .trade_updates()
604 .build();
605
606 assert_eq!(
607 sub.trades,
608 Some(vec!["AAPL".to_string(), "MSFT".to_string()])
609 );
610 assert_eq!(sub.quotes, Some(vec!["GOOGL".to_string()]));
611 assert_eq!(sub.trade_updates, Some(true));
612 }
613
614 #[test]
615 fn test_trade_update_event_serialization() {
616 let event = TradeUpdateEvent::Fill;
617 let json = serde_json::to_string(&event).unwrap();
618 assert_eq!(json, "\"fill\"");
619 }
620
621 #[test]
622 fn test_connection_status_serialization() {
623 let status = ConnectionStatus::Connected;
624 let json = serde_json::to_string(&status).unwrap();
625 assert_eq!(json, "\"connected\"");
626 }
627}