finance_query/streaming/
trades.rs1use std::sync::Arc;
8use std::time::Duration;
9
10use serde::{Deserialize, Serialize};
11
12use super::client::StreamResult;
13use super::handle::{RECONNECT_BACKOFF, SourceStream, stream_builder, stream_handle};
14use super::polygon::{AssetClass, PolygonTradeSource};
15use super::source::ReconnectConfig;
16
17const CHANNEL_CAPACITY: usize = 4096;
19
20#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23#[non_exhaustive]
24pub struct TradeTick {
25 pub symbol: String,
27 pub price: f64,
29 pub size: f64,
31 pub exchange: Option<i32>,
33 pub conditions: Vec<i32>,
35 pub trade_id: Option<String>,
37 pub time: i64,
39}
40
41impl TradeTick {
42 pub fn notional(&self) -> f64 {
44 self.price * self.size
45 }
46}
47
48stream_handle! {
49 TradeStream(TradeTick);
72 add: add_symbols = "Add symbols to the subscription.",
73 remove: remove_symbols = "Remove symbols from the subscription.",
74}
75
76impl TradeStream {
77 pub async fn subscribe<S, I>(symbols: I) -> StreamResult<Self>
79 where
80 S: Into<String>,
81 I: IntoIterator<Item = S>,
82 {
83 TradeStreamBuilder::new().symbols(symbols).build().await
84 }
85}
86
87pub struct TradeStreamBuilder {
89 symbols: Vec<String>,
90 asset_class: AssetClass,
91 retry_delay: Duration,
92 max_reconnect_attempts: Option<u32>,
93}
94
95impl TradeStreamBuilder {
96 pub fn new() -> Self {
98 Self {
99 symbols: Vec::new(),
100 asset_class: AssetClass::Stocks,
101 retry_delay: RECONNECT_BACKOFF,
102 max_reconnect_attempts: None,
103 }
104 }
105
106 pub fn asset_class(mut self, class: AssetClass) -> Self {
111 self.asset_class = class;
112 self
113 }
114
115 pub async fn build(self) -> StreamResult<TradeStream> {
122 let source = Arc::new(PolygonTradeSource::new(self.asset_class)?);
123 let reconnect =
124 ReconnectConfig::new(self.retry_delay).max_attempts(self.max_reconnect_attempts);
125 Ok(TradeStream {
126 inner: SourceStream::start(source, self.symbols, reconnect, CHANNEL_CAPACITY),
127 })
128 }
129}
130
131stream_builder!(TradeStreamBuilder, symbols = "Add symbols to subscribe to.");
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[tokio::test]
138 async fn classes_without_trade_prints_are_rejected() {
139 for class in [AssetClass::Forex, AssetClass::Indices] {
140 assert!(
141 TradeStreamBuilder::new()
142 .symbols(["X"])
143 .asset_class(class)
144 .build()
145 .await
146 .is_err()
147 );
148 }
149 }
150
151 #[test]
152 fn notional_multiplies_price_by_size() {
153 let tick = TradeTick {
154 price: 10.0,
155 size: 25.0,
156 ..Default::default()
157 };
158 assert!((tick.notional() - 250.0).abs() < 1e-9);
159 }
160}