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}