tradestation_api/streaming/
market_data_streams.rs1use super::*;
5use crate::{Client, Error};
6
7impl Client {
8 pub async fn stream_quotes(
13 &mut self,
14 symbols: &[&str],
15 ) -> Result<BoxStream<StreamQuote>, Error> {
16 let symbols_str = symbols.join(",");
17 let path = format!("/v3/marketdata/stream/quotes/{}", symbols_str);
18 let headers = self.auth_headers().await?;
19 let url = format!("{}{}", self.base_url(), &path);
20
21 let resp = self.http.get(&url).headers(headers).send().await?;
22
23 if !resp.status().is_success() {
24 let status = resp.status().as_u16();
25 let body = resp.text().await.unwrap_or_default();
26 return Err(Error::Api {
27 status,
28 message: body,
29 });
30 }
31
32 let stream = async_stream::try_stream! {
33 let mut bytes_stream = resp.bytes_stream();
34 let mut buffer = String::new();
35
36 use futures::StreamExt;
37 while let Some(chunk) = bytes_stream.next().await {
38 let chunk = chunk.map_err(Error::Http)?;
39 buffer.push_str(&String::from_utf8_lossy(&chunk));
40
41 while let Some(newline_pos) = buffer.find('\n') {
43 let line = buffer[..newline_pos].trim().to_string();
44 buffer = buffer[newline_pos + 1..].to_string();
45
46 if line.is_empty() {
47 continue;
48 }
49
50 match serde_json::from_str::<StreamQuote>(&line) {
51 Ok(quote) => yield quote,
52 Err(e) => {
53 tracing::warn!("Failed to parse stream quote: {e}, line: {line}");
54 }
55 }
56 }
57 }
58 };
59
60 Ok(Box::pin(stream))
61 }
62
63 pub async fn stream_bars(
71 &mut self,
72 symbol: &str,
73 interval: &str,
74 unit: &str,
75 ) -> Result<BoxStream<StreamBar>, Error> {
76 let path = format!(
77 "/v3/marketdata/stream/barcharts/{}?interval={}&unit={}",
78 symbol, interval, unit
79 );
80 let headers = self.auth_headers().await?;
81 let url = format!("{}{}", self.base_url(), &path);
82
83 let resp = self.http.get(&url).headers(headers).send().await?;
84
85 if !resp.status().is_success() {
86 let status = resp.status().as_u16();
87 let body = resp.text().await.unwrap_or_default();
88 return Err(Error::Api {
89 status,
90 message: body,
91 });
92 }
93
94 let stream = async_stream::try_stream! {
95 let mut bytes_stream = resp.bytes_stream();
96 let mut buffer = String::new();
97
98 use futures::StreamExt;
99 while let Some(chunk) = bytes_stream.next().await {
100 let chunk = chunk.map_err(Error::Http)?;
101 buffer.push_str(&String::from_utf8_lossy(&chunk));
102
103 while let Some(newline_pos) = buffer.find('\n') {
104 let line = buffer[..newline_pos].trim().to_string();
105 buffer = buffer[newline_pos + 1..].to_string();
106
107 if line.is_empty() {
108 continue;
109 }
110
111 match serde_json::from_str::<StreamBar>(&line) {
112 Ok(bar) => yield bar,
113 Err(e) => {
114 tracing::warn!("Failed to parse stream bar: {e}, line: {line}");
115 }
116 }
117 }
118 }
119 };
120
121 Ok(Box::pin(stream))
122 }
123
124 pub async fn stream_market_depth_quotes(
126 &mut self,
127 symbol: &str,
128 ) -> Result<BoxStream<StreamMarketDepthQuote>, Error> {
129 let path = format!("/v3/marketdata/stream/marketdepth/quotes/{}", symbol);
130 let headers = self.auth_headers().await?;
131 let url = format!("{}{}", self.base_url(), &path);
132
133 let resp = self.http.get(&url).headers(headers).send().await?;
134
135 if !resp.status().is_success() {
136 let status = resp.status().as_u16();
137 let body = resp.text().await.unwrap_or_default();
138 return Err(Error::Api {
139 status,
140 message: body,
141 });
142 }
143
144 let stream = async_stream::try_stream! {
145 let mut bytes_stream = resp.bytes_stream();
146 let mut buffer = String::new();
147
148 use futures::StreamExt;
149 while let Some(chunk) = bytes_stream.next().await {
150 let chunk = chunk.map_err(Error::Http)?;
151 buffer.push_str(&String::from_utf8_lossy(&chunk));
152
153 while let Some(newline_pos) = buffer.find('\n') {
154 let line = buffer[..newline_pos].trim().to_string();
155 buffer = buffer[newline_pos + 1..].to_string();
156
157 if line.is_empty() {
158 continue;
159 }
160
161 match serde_json::from_str::<StreamMarketDepthQuote>(&line) {
162 Ok(item) => yield item,
163 Err(e) => {
164 tracing::warn!("Failed to parse stream market depth quote: {e}, line: {line}");
165 }
166 }
167 }
168 }
169 };
170
171 Ok(Box::pin(stream))
172 }
173
174 pub async fn stream_market_depth_aggregates(
176 &mut self,
177 symbol: &str,
178 ) -> Result<BoxStream<StreamMarketDepthAggregate>, Error> {
179 let path = format!("/v3/marketdata/stream/marketdepth/aggregates/{}", symbol);
180 let headers = self.auth_headers().await?;
181 let url = format!("{}{}", self.base_url(), &path);
182
183 let resp = self.http.get(&url).headers(headers).send().await?;
184
185 if !resp.status().is_success() {
186 let status = resp.status().as_u16();
187 let body = resp.text().await.unwrap_or_default();
188 return Err(Error::Api {
189 status,
190 message: body,
191 });
192 }
193
194 let stream = async_stream::try_stream! {
195 let mut bytes_stream = resp.bytes_stream();
196 let mut buffer = String::new();
197
198 use futures::StreamExt;
199 while let Some(chunk) = bytes_stream.next().await {
200 let chunk = chunk.map_err(Error::Http)?;
201 buffer.push_str(&String::from_utf8_lossy(&chunk));
202
203 while let Some(newline_pos) = buffer.find('\n') {
204 let line = buffer[..newline_pos].trim().to_string();
205 buffer = buffer[newline_pos + 1..].to_string();
206
207 if line.is_empty() {
208 continue;
209 }
210
211 match serde_json::from_str::<StreamMarketDepthAggregate>(&line) {
212 Ok(item) => yield item,
213 Err(e) => {
214 tracing::warn!("Failed to parse stream market depth aggregate: {e}, line: {line}");
215 }
216 }
217 }
218 }
219 };
220
221 Ok(Box::pin(stream))
222 }
223
224 pub async fn stream_option_chains(
226 &mut self,
227 underlying: &str,
228 ) -> Result<BoxStream<StreamOptionChain>, Error> {
229 let path = format!("/v3/marketdata/stream/options/chains/{}", underlying);
230 let headers = self.auth_headers().await?;
231 let url = format!("{}{}", self.base_url(), &path);
232
233 let resp = self.http.get(&url).headers(headers).send().await?;
234
235 if !resp.status().is_success() {
236 let status = resp.status().as_u16();
237 let body = resp.text().await.unwrap_or_default();
238 return Err(Error::Api {
239 status,
240 message: body,
241 });
242 }
243
244 let stream = async_stream::try_stream! {
245 let mut bytes_stream = resp.bytes_stream();
246 let mut buffer = String::new();
247
248 use futures::StreamExt;
249 while let Some(chunk) = bytes_stream.next().await {
250 let chunk = chunk.map_err(Error::Http)?;
251 buffer.push_str(&String::from_utf8_lossy(&chunk));
252
253 while let Some(newline_pos) = buffer.find('\n') {
254 let line = buffer[..newline_pos].trim().to_string();
255 buffer = buffer[newline_pos + 1..].to_string();
256
257 if line.is_empty() {
258 continue;
259 }
260
261 match serde_json::from_str::<StreamOptionChain>(&line) {
262 Ok(item) => yield item,
263 Err(e) => {
264 tracing::warn!("Failed to parse stream option chain: {e}, line: {line}");
265 }
266 }
267 }
268 }
269 };
270
271 Ok(Box::pin(stream))
272 }
273
274 pub async fn stream_option_quotes(
276 &mut self,
277 legs: &[&str],
278 ) -> Result<BoxStream<StreamOptionQuote>, Error> {
279 let legs_str = legs.join(",");
280 let path = format!("/v3/marketdata/stream/options/quotes/{}", legs_str);
281 let headers = self.auth_headers().await?;
282 let url = format!("{}{}", self.base_url(), &path);
283
284 let resp = self.http.get(&url).headers(headers).send().await?;
285
286 if !resp.status().is_success() {
287 let status = resp.status().as_u16();
288 let body = resp.text().await.unwrap_or_default();
289 return Err(Error::Api {
290 status,
291 message: body,
292 });
293 }
294
295 let stream = async_stream::try_stream! {
296 let mut bytes_stream = resp.bytes_stream();
297 let mut buffer = String::new();
298
299 use futures::StreamExt;
300 while let Some(chunk) = bytes_stream.next().await {
301 let chunk = chunk.map_err(Error::Http)?;
302 buffer.push_str(&String::from_utf8_lossy(&chunk));
303
304 while let Some(newline_pos) = buffer.find('\n') {
305 let line = buffer[..newline_pos].trim().to_string();
306 buffer = buffer[newline_pos + 1..].to_string();
307
308 if line.is_empty() {
309 continue;
310 }
311
312 match serde_json::from_str::<StreamOptionQuote>(&line) {
313 Ok(item) => yield item,
314 Err(e) => {
315 tracing::warn!("Failed to parse stream option quote: {e}, line: {line}");
316 }
317 }
318 }
319 }
320 };
321
322 Ok(Box::pin(stream))
323 }
324}