#![allow(missing_docs)]
use alpaca_base::types::*;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "T")]
pub enum WebSocketMessage {
#[serde(rename = "auth")]
Auth(AuthMessage),
#[serde(rename = "subscribe")]
Subscribe(SubscribeMessage),
#[serde(rename = "unsubscribe")]
Unsubscribe(UnsubscribeMessage),
#[serde(rename = "t")]
Trade(TradeMessage),
#[serde(rename = "q")]
Quote(QuoteMessage),
#[serde(rename = "b")]
Bar(BarMessage),
#[serde(rename = "trade_updates")]
TradeUpdate(Box<TradeUpdateMessage>),
#[serde(rename = "success")]
Success(SuccessMessage),
#[serde(rename = "error")]
Error(ErrorMessage),
#[serde(rename = "connection")]
Connection(ConnectionMessage),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthMessage {
pub key: String,
pub secret: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscribeMessage {
pub trades: Option<Vec<String>>,
pub quotes: Option<Vec<String>>,
pub bars: Option<Vec<String>>,
pub trade_updates: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnsubscribeMessage {
pub trades: Option<Vec<String>>,
pub quotes: Option<Vec<String>>,
pub bars: Option<Vec<String>>,
pub trade_updates: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "p")]
pub price: f64,
#[serde(rename = "s")]
pub size: u32,
#[serde(rename = "x")]
pub exchange: String,
#[serde(rename = "c")]
pub conditions: Vec<String>,
#[serde(rename = "i")]
pub id: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuoteMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "bp")]
pub bid_price: f64,
#[serde(rename = "bs")]
pub bid_size: u32,
#[serde(rename = "ap")]
pub ask_price: f64,
#[serde(rename = "as")]
pub ask_size: u32,
#[serde(rename = "bx")]
pub bid_exchange: String,
#[serde(rename = "ax")]
pub ask_exchange: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BarMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "o")]
pub open: f64,
#[serde(rename = "h")]
pub high: f64,
#[serde(rename = "l")]
pub low: f64,
#[serde(rename = "c")]
pub close: f64,
#[serde(rename = "v")]
pub volume: u64,
#[serde(rename = "n")]
pub trade_count: Option<u64>,
#[serde(rename = "vw")]
pub vwap: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeUpdateMessage {
pub event: TradeUpdateEvent,
pub order: Order,
pub timestamp: DateTime<Utc>,
pub position_qty: Option<String>,
pub price: Option<String>,
pub qty: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TradeUpdateEvent {
New,
Fill,
PartialFill,
Canceled,
Expired,
DoneForDay,
Replaced,
Rejected,
PendingNew,
Stopped,
PendingCancel,
PendingReplace,
Calculated,
Suspended,
OrderReplacePending,
OrderCancelPending,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SuccessMessage {
pub msg: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorMessage {
pub code: u16,
pub msg: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionMessage {
pub status: ConnectionStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConnectionStatus {
Connected,
Authenticated,
AuthenticationFailed,
Disconnected,
Reconnecting,
}
#[derive(Debug, Default)]
pub struct SubscriptionBuilder {
trades: Vec<String>,
quotes: Vec<String>,
bars: Vec<String>,
trade_updates: bool,
}
impl SubscriptionBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn trades<I, S>(mut self, symbols: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.trades.extend(symbols.into_iter().map(|s| s.into()));
self
}
pub fn quotes<I, S>(mut self, symbols: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.quotes.extend(symbols.into_iter().map(|s| s.into()));
self
}
pub fn bars<I, S>(mut self, symbols: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.bars.extend(symbols.into_iter().map(|s| s.into()));
self
}
pub fn trade_updates(mut self) -> Self {
self.trade_updates = true;
self
}
pub fn build(self) -> SubscribeMessage {
SubscribeMessage {
trades: if self.trades.is_empty() {
None
} else {
Some(self.trades)
},
quotes: if self.quotes.is_empty() {
None
} else {
Some(self.quotes)
},
bars: if self.bars.is_empty() {
None
} else {
Some(self.bars)
},
trade_updates: if self.trade_updates { Some(true) } else { None },
}
}
}
impl From<TradeMessage> for Trade {
fn from(msg: TradeMessage) -> Self {
Trade {
timestamp: msg.timestamp,
price: msg.price,
size: msg.size,
exchange: msg.exchange,
conditions: msg.conditions,
id: msg.id,
}
}
}
impl From<QuoteMessage> for Quote {
fn from(msg: QuoteMessage) -> Self {
Quote {
timestamp: msg.timestamp,
timeframe: "real-time".to_string(),
bid_price: msg.bid_price,
bid_size: msg.bid_size,
ask_price: msg.ask_price,
ask_size: msg.ask_size,
bid_exchange: msg.bid_exchange,
ask_exchange: msg.ask_exchange,
}
}
}
impl From<BarMessage> for Bar {
fn from(msg: BarMessage) -> Self {
Bar {
timestamp: msg.timestamp,
open: msg.open,
high: msg.high,
low: msg.low,
close: msg.close,
volume: msg.volume,
trade_count: msg.trade_count,
vwap: msg.vwap,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CryptoTradeMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "p")]
pub price: f64,
#[serde(rename = "s")]
pub size: f64,
#[serde(rename = "tks")]
pub taker_side: String,
#[serde(rename = "i")]
pub id: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CryptoQuoteMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "bp")]
pub bid_price: f64,
#[serde(rename = "bs")]
pub bid_size: f64,
#[serde(rename = "ap")]
pub ask_price: f64,
#[serde(rename = "as")]
pub ask_size: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CryptoBarMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "o")]
pub open: f64,
#[serde(rename = "h")]
pub high: f64,
#[serde(rename = "l")]
pub low: f64,
#[serde(rename = "c")]
pub close: f64,
#[serde(rename = "v")]
pub volume: f64,
#[serde(rename = "n")]
pub trade_count: Option<u64>,
#[serde(rename = "vw")]
pub vwap: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptionTradeMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "p")]
pub price: f64,
#[serde(rename = "s")]
pub size: u32,
#[serde(rename = "x")]
pub exchange: String,
#[serde(rename = "c", default)]
pub conditions: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptionQuoteMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "bp")]
pub bid_price: f64,
#[serde(rename = "bs")]
pub bid_size: u32,
#[serde(rename = "ap")]
pub ask_price: f64,
#[serde(rename = "as")]
pub ask_size: u32,
#[serde(rename = "bx")]
pub bid_exchange: String,
#[serde(rename = "ax")]
pub ask_exchange: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NewsMessage {
pub id: u64,
pub headline: String,
pub summary: Option<String>,
pub author: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub url: Option<String>,
pub symbols: Vec<String>,
pub source: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LuldMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "i")]
pub indicator: String,
#[serde(rename = "u")]
pub limit_up_price: f64,
#[serde(rename = "d")]
pub limit_down_price: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradingStatusMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "sc")]
pub status_code: String,
#[serde(rename = "sm")]
pub status_message: String,
#[serde(rename = "rc")]
pub reason_code: String,
#[serde(rename = "rm")]
pub reason_message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CorrectionMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "x")]
pub original_id: u64,
#[serde(rename = "op")]
pub original_price: f64,
#[serde(rename = "os")]
pub original_size: u32,
#[serde(rename = "cp")]
pub corrected_price: f64,
#[serde(rename = "cs")]
pub corrected_size: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelErrorMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "i")]
pub id: u64,
#[serde(rename = "p")]
pub price: f64,
#[serde(rename = "s")]
pub size: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DailyBarMessage {
#[serde(rename = "S")]
pub symbol: String,
#[serde(rename = "t")]
pub timestamp: DateTime<Utc>,
#[serde(rename = "o")]
pub open: f64,
#[serde(rename = "h")]
pub high: f64,
#[serde(rename = "l")]
pub low: f64,
#[serde(rename = "c")]
pub close: f64,
#[serde(rename = "v")]
pub volume: u64,
#[serde(rename = "vw")]
pub vwap: Option<f64>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subscription_builder() {
let sub = SubscriptionBuilder::new()
.trades(["AAPL", "MSFT"])
.quotes(["GOOGL"])
.trade_updates()
.build();
assert_eq!(
sub.trades,
Some(vec!["AAPL".to_string(), "MSFT".to_string()])
);
assert_eq!(sub.quotes, Some(vec!["GOOGL".to_string()]));
assert_eq!(sub.trade_updates, Some(true));
}
#[test]
fn test_trade_update_event_serialization() {
let event = TradeUpdateEvent::Fill;
let json = serde_json::to_string(&event).unwrap();
assert_eq!(json, "\"fill\"");
}
#[test]
fn test_connection_status_serialization() {
let status = ConnectionStatus::Connected;
let json = serde_json::to_string(&status).unwrap();
assert_eq!(json, "\"connected\"");
}
}