Skip to main content

tradestation_api/streaming/
dto.rs

1//! Stream DTOs (newline-delimited JSON wire types) for the TradeStation
2//! v3 streaming surface, split out of the single-file `streaming` module
3//! to keep each file focused.
4
5use serde::Deserialize;
6
7/// Stream status messages from TradeStation.
8///
9/// These appear inline in the stream data to signal events like end-of-snapshot
10/// or a server-initiated disconnect.
11#[derive(Debug, Clone, Deserialize)]
12#[serde(rename_all = "PascalCase")]
13pub struct StreamStatus {
14    /// Status code: "EndSnapshot", "GoAway", etc.
15    pub status: String,
16    /// Optional message with additional details.
17    #[serde(default)]
18    pub message: Option<String>,
19}
20
21/// A streaming quote update.
22///
23/// Contains real-time quote data or a stream status message. Use [`StreamQuote::is_status`]
24/// to distinguish between the two.
25#[derive(Debug, Clone, Deserialize)]
26#[serde(rename_all = "PascalCase")]
27pub struct StreamQuote {
28    /// Ticker symbol.
29    pub symbol: Option<String>,
30    /// Last traded price.
31    pub last: Option<String>,
32    /// Best ask price.
33    pub ask: Option<String>,
34    /// Best bid price.
35    pub bid: Option<String>,
36    /// Cumulative volume.
37    pub volume: Option<String>,
38    /// Time of the last trade.
39    #[serde(default)]
40    pub trade_time: Option<String>,
41    /// Stream status (present only for status messages).
42    #[serde(default)]
43    pub status: Option<String>,
44}
45
46impl StreamQuote {
47    /// Whether this is a status message rather than a quote update.
48    pub fn is_status(&self) -> bool {
49        self.status.is_some()
50    }
51
52    /// Whether this is a GoAway message indicating reconnection is needed.
53    pub fn is_go_away(&self) -> bool {
54        self.status.as_deref() == Some("GoAway")
55    }
56}
57
58/// A streaming bar (OHLCV) update.
59///
60/// Delivered via [`Client::stream_bars`](crate::Client::stream_bars). Contains partial or completed bar data.
61#[derive(Debug, Clone, Deserialize)]
62#[serde(rename_all = "PascalCase")]
63pub struct StreamBar {
64    /// Highest price during the bar period.
65    pub high: Option<String>,
66    /// Lowest price during the bar period.
67    pub low: Option<String>,
68    /// Opening price.
69    pub open: Option<String>,
70    /// Closing price (updates in real time for the current bar).
71    pub close: Option<String>,
72    /// Bar timestamp.
73    pub time_stamp: Option<String>,
74    /// Total volume during the bar period.
75    pub total_volume: Option<String>,
76    /// Stream status (present only for top-level status messages).
77    #[serde(default)]
78    pub status: Option<String>,
79    /// Per-bar finalization status: `"Open"` while the minute's bar is
80    /// still forming (TS re-emits it on every trade under the same
81    /// `TimeStamp`) or `"Closed"` once the bar is finalized. Distinct from
82    /// `status` (the top-level stream control line). `rename_all =
83    /// "PascalCase"` maps this field to the wire `BarStatus`.
84    #[serde(default)]
85    pub bar_status: Option<String>,
86}
87
88impl StreamBar {
89    /// Whether this is a status message rather than bar data.
90    pub fn is_status(&self) -> bool {
91        self.status.is_some()
92    }
93}
94
95/// A streaming market depth (Level 2) quote.
96///
97/// Delivered via [`Client::stream_market_depth_quotes`](crate::Client::stream_market_depth_quotes).
98#[derive(Debug, Clone, Deserialize)]
99#[serde(rename_all = "PascalCase")]
100pub struct StreamMarketDepthQuote {
101    /// Ticker symbol.
102    pub symbol: Option<String>,
103    /// Ask price at this depth level.
104    pub ask: Option<String>,
105    /// Ask size at this depth level.
106    pub ask_size: Option<String>,
107    /// Bid price at this depth level.
108    pub bid: Option<String>,
109    /// Bid size at this depth level.
110    pub bid_size: Option<String>,
111    /// Side of the book ("Ask" or "Bid").
112    #[serde(default)]
113    pub side: Option<String>,
114    /// Stream status (present only for status messages).
115    #[serde(default)]
116    pub status: Option<String>,
117}
118
119impl StreamMarketDepthQuote {
120    /// Whether this is a status message rather than depth data.
121    pub fn is_status(&self) -> bool {
122        self.status.is_some()
123    }
124}
125
126/// A streaming market depth aggregate summary.
127///
128/// Delivered via [`Client::stream_market_depth_aggregates`](crate::Client::stream_market_depth_aggregates).
129#[derive(Debug, Clone, Deserialize)]
130#[serde(rename_all = "PascalCase")]
131pub struct StreamMarketDepthAggregate {
132    /// Ticker symbol.
133    pub symbol: Option<String>,
134    /// Total ask size across all levels.
135    pub total_ask_size: Option<String>,
136    /// Total bid size across all levels.
137    pub total_bid_size: Option<String>,
138    /// Number of price levels.
139    #[serde(default)]
140    pub levels: Option<u32>,
141    /// Stream status (present only for status messages).
142    #[serde(default)]
143    pub status: Option<String>,
144}
145
146impl StreamMarketDepthAggregate {
147    /// Whether this is a status message rather than aggregate data.
148    pub fn is_status(&self) -> bool {
149        self.status.is_some()
150    }
151}
152
153/// A streaming option chain update.
154///
155/// Delivered via [`Client::stream_option_chains`](crate::Client::stream_option_chains).
156#[derive(Debug, Clone, Deserialize)]
157#[serde(rename_all = "PascalCase")]
158pub struct StreamOptionChain {
159    /// Option symbol.
160    pub symbol: Option<String>,
161    /// Underlying ticker symbol.
162    pub underlying: Option<String>,
163    /// Option type ("Call" or "Put").
164    #[serde(default, rename = "Type")]
165    pub option_type: Option<String>,
166    /// Strike price.
167    pub strike_price: Option<String>,
168    /// Expiration date.
169    pub expiration_date: Option<String>,
170    /// Best bid price.
171    pub bid: Option<String>,
172    /// Best ask price.
173    pub ask: Option<String>,
174    /// Last traded price.
175    pub last: Option<String>,
176    /// Stream status (present only for status messages).
177    #[serde(default)]
178    pub status: Option<String>,
179}
180
181impl StreamOptionChain {
182    /// Whether this is a status message rather than chain data.
183    pub fn is_status(&self) -> bool {
184        self.status.is_some()
185    }
186}
187
188/// A streaming option quote update.
189///
190/// Delivered via [`Client::stream_option_quotes`](crate::Client::stream_option_quotes).
191#[derive(Debug, Clone, Deserialize)]
192#[serde(rename_all = "PascalCase")]
193pub struct StreamOptionQuote {
194    /// Option symbol.
195    pub symbol: Option<String>,
196    /// Best bid price.
197    pub bid: Option<String>,
198    /// Best ask price.
199    pub ask: Option<String>,
200    /// Last traded price.
201    pub last: Option<String>,
202    /// Cumulative volume.
203    pub volume: Option<String>,
204    /// Open interest.
205    #[serde(default)]
206    pub open_interest: Option<String>,
207    /// Stream status (present only for status messages).
208    #[serde(default)]
209    pub status: Option<String>,
210}
211
212impl StreamOptionQuote {
213    /// Whether this is a status message rather than quote data.
214    pub fn is_status(&self) -> bool {
215        self.status.is_some()
216    }
217}
218
219/// A streaming order status update.
220///
221/// Delivered via [`Client::stream_orders`](crate::Client::stream_orders) and [`Client::stream_orders_by_id`](crate::Client::stream_orders_by_id).
222#[derive(Debug, Clone, Deserialize)]
223#[serde(rename_all = "PascalCase")]
224pub struct StreamOrder {
225    /// Order identifier.
226    pub order_id: Option<String>,
227    /// Account this order belongs to.
228    pub account_id: Option<String>,
229    /// Ticker symbol.
230    pub symbol: Option<String>,
231    /// Ordered quantity.
232    pub quantity: Option<String>,
233    /// Order type.
234    pub order_type: Option<String>,
235    /// Current order status.
236    #[serde(default)]
237    pub order_status: Option<String>,
238    /// Filled quantity.
239    #[serde(default)]
240    pub filled_quantity: Option<String>,
241    /// Stream status (present only for status messages).
242    #[serde(default)]
243    pub status: Option<String>,
244}
245
246impl StreamOrder {
247    /// Whether this is a status message rather than an order update.
248    pub fn is_status(&self) -> bool {
249        self.status.is_some()
250    }
251}
252
253/// A streaming position update.
254///
255/// Delivered via [`Client::stream_positions`](crate::Client::stream_positions).
256#[derive(Debug, Clone, Deserialize)]
257#[serde(rename_all = "PascalCase")]
258pub struct StreamPosition {
259    /// Account holding this position.
260    pub account_id: Option<String>,
261    /// Ticker symbol.
262    pub symbol: Option<String>,
263    /// Current position quantity.
264    pub quantity: Option<String>,
265    /// Average entry price.
266    pub average_price: Option<String>,
267    /// Last traded price.
268    pub last: Option<String>,
269    /// Unrealized P&L.
270    #[serde(default)]
271    pub unrealized_profit_loss: Option<String>,
272    /// Stream status (present only for status messages).
273    #[serde(default)]
274    pub status: Option<String>,
275}
276
277impl StreamPosition {
278    /// Whether this is a status message rather than a position update.
279    pub fn is_status(&self) -> bool {
280        self.status.is_some()
281    }
282}