tradestation_rs/account.rs
1use crate::responses::account::{StreamOrdersResp, StreamPositionsResp};
2use crate::{responses::account as responses, Client, Error};
3use serde::{Deserialize, Serialize};
4use std::{error::Error as StdErrorTrait, future::Future, pin::Pin};
5
6#[derive(Clone, Debug, Deserialize, Serialize)]
7#[serde(rename_all = "PascalCase")]
8/// TradeStation Account
9pub struct Account {
10 #[serde(rename = "AccountID")]
11 /// The main identifier for a TradeStation account.
12 account_id: String,
13 /// The currency the account is based on.
14 currency: String,
15 /// The type of account, examples: "Cash" or "Margin"
16 // TODO: Make enum for this
17 account_type: String,
18 /// The account details, stuff like options level and day trading approval
19 ///
20 /// NOTE: This will always be `None` if it's a Futures `Account`
21 account_detail: Option<AccountDetail>,
22}
23impl Account {
24 /// Get a specific TradeStation `Account` by it's account id.
25 pub async fn get(client: &mut Client, account_id: &str) -> Result<Account, Error> {
26 if let Some(account) = Account::get_all(client)
27 .await?
28 .iter()
29 .find(|account| account.account_id == account_id)
30 {
31 Ok(account.clone())
32 } else {
33 Err(Error::AccountNotFound)
34 }
35 }
36
37 /// Get all of your registered TradeStation `Account`(s).
38 pub async fn get_all(client: &mut Client) -> Result<Vec<Account>, Error> {
39 let endpoint = "brokerage/accounts";
40
41 let resp = client
42 .get(endpoint)
43 .await?
44 .json::<responses::GetAccountsResp>()
45 .await?;
46
47 Ok(resp.accounts)
48 }
49
50 /// Get the current balance of an `Account`.
51 pub async fn get_balance(&self, client: &mut Client) -> Result<Balance, Error> {
52 let endpoint = format!("brokerage/accounts/{}/balances", self.account_id);
53
54 if let Some(balance) = client
55 .get(&endpoint)
56 .await?
57 .json::<responses::GetBalanceResp>()
58 .await?
59 .balances
60 .pop()
61 {
62 Ok(balance)
63 } else {
64 Err(Error::AccountNotFound)
65 }
66 }
67
68 /// Get the current balance of all `Account`(s) by account ids.
69 ///
70 /// NOTE: If you have `Vec<Account>` you should instead use `Vec<Account>::get_balances()`
71 /// this method should only be used in cases where you ONLY have account id's.
72 pub async fn get_balances_by_ids(
73 client: &mut Client,
74 account_ids: Vec<&str>,
75 ) -> Result<Vec<Balance>, Error> {
76 let endpoint = format!("brokerage/accounts/{}/balances", account_ids.join(","));
77
78 let resp = client
79 .get(&endpoint)
80 .await?
81 .json::<responses::GetBalanceResp>()
82 .await?;
83
84 Ok(resp.balances)
85 }
86
87 /// Get the beginning of day balance of an `Account`.
88 pub async fn get_bod_balance(&self, client: &mut Client) -> Result<BODBalance, Error> {
89 let endpoint = format!("brokerage/accounts/{}/bodbalances", self.account_id);
90
91 if let Some(balance) = client
92 .get(&endpoint)
93 .await?
94 .json::<responses::GetBODBalanceResp>()
95 .await?
96 .bod_balances
97 .pop()
98 {
99 Ok(balance)
100 } else {
101 Err(Error::AccountNotFound)
102 }
103 }
104
105 /// Get the beginning of day balances for multiple `Account`(s) by account id.
106 ///
107 /// NOTE: If you have `Vec<Account>` you should instead use `Vec<Account>::get_bod_balances()`
108 /// this method should only be used if you ONLY have account id's.
109 pub async fn get_bod_balances_by_ids(
110 client: &mut Client,
111 account_ids: Vec<&str>,
112 ) -> Result<Vec<BODBalance>, Error> {
113 let endpoint = format!("brokerage/accounts/{}/bodbalances", account_ids.join(","));
114
115 let resp = client
116 .get(&endpoint)
117 .await?
118 .json::<responses::GetBODBalanceResp>()
119 .await?;
120
121 Ok(resp.bod_balances)
122 }
123
124 /// Fetches Historical `Order`(s) since a specific date for the given `Account`.
125 ///
126 /// NOTE: Date format is {YEAR-MONTH-DAY} ex: `"2024-07-09"`, and is limited to 90
127 /// days prior to the current date.
128 ///
129 /// NOTE: Excludes open `Order`(s) and is sorted in descending order of time closed.
130 pub async fn get_historic_orders(
131 &self,
132 client: &mut Client,
133 since_date: &str,
134 ) -> Result<Vec<Order>, Error> {
135 let endpoint = format!(
136 "brokerage/accounts/{}/historicalorders?since={}",
137 self.account_id, since_date
138 );
139
140 let resp = client
141 .get(&endpoint)
142 .await?
143 .json::<responses::GetOrdersResp>()
144 .await?;
145
146 Ok(resp.orders)
147 }
148
149 /// Fetches Historical `Order`(s) for the given `Account`(s) by id.
150 ///
151 /// NOTE: Date format is {YEAR-MONTH-DAY} ex: `"2024-07-09"`, and is limited to 90
152 /// days prior to the current date.
153 ///
154 /// NOTE: Excludes open `Order`(s) and is sorted in descending order of time closed.
155 pub async fn get_historic_orders_by_ids(
156 client: &mut Client,
157 account_ids: Vec<&str>,
158 since_date: &str,
159 ) -> Result<Vec<Order>, Error> {
160 let endpoint = format!(
161 "brokerage/accounts/{}/historicalorders?since={}",
162 account_ids.join(","),
163 since_date,
164 );
165
166 let resp = client
167 .get(&endpoint)
168 .await?
169 .json::<responses::GetOrdersResp>()
170 .await?;
171
172 Ok(resp.orders)
173 }
174
175 /// Fetches orders for the given `Account`.
176 ///
177 /// # Example
178 /// ---
179 ///
180 /// Grab all the orders for a specific account. Say you need to go
181 /// through all the orders your algorithm placed today and filter out
182 /// only the orders that were filled for data storage purposes.
183 ///
184 /// ```ignore
185 /// // Initialize the client
186 /// let mut client = ClientBuilder::new()?
187 /// .credentials("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")?
188 /// .token(Token {
189 /// access_token: String::from("YOUR_ACCESS_TOKEN"),
190 /// refresh_token: String::from("YOUR_REFRESH_TOKEN"),
191 /// id_token: String::from("YOUR_ID_TOKEN"),
192 /// token_type: String::from("Bearer"),
193 /// scope: String::from("YOUR_SCOPES SPACE_SEPERATED FOR_EACH_SCOPE"),
194 /// expires_in: 1200,
195 /// })?
196 /// .build()
197 /// .await?;
198 ///
199 /// // Grab your accounts and specify an account the orders were placed in
200 /// let accounts = client.get_accounts().await?;
201 /// if let Some(specific_account) = accounts.find_by_id("YOUR_ACCOUNT_ID") {
202 /// // Get all the orders from today for a specific account
203 /// let orders = specific_account.get_orders( &mut client).await?;
204 ///
205 /// // Filter out only filled orders
206 /// let filled_orders: Vec<Order> = orders
207 /// .into_iter()
208 /// .filter(|order| order.status == "FLL")
209 /// .collect();
210 ///
211 /// // Do something with your filled orders
212 /// for order in filled_orders {
213 /// println!("Filled Order: {order:?}");
214 /// }
215 /// }
216 /// ```
217 pub async fn get_orders(&self, client: &mut Client) -> Result<Vec<Order>, Error> {
218 let endpoint = format!("brokerage/accounts/{}/orders", self.account_id);
219
220 let resp = client
221 .get(&endpoint)
222 .await?
223 .json::<responses::GetOrdersResp>()
224 .await?;
225
226 Ok(resp.orders)
227 }
228
229 /// NOTE: Same as `get_orders` but for multiple accounts
230 /// NOTE: For internal use only. Use `Account::get_orders_by_id()`
231 /// to access this functionality.
232 async fn get_orders_for_accounts<S: Into<String>>(
233 account_ids: Vec<S>,
234 client: &mut Client,
235 ) -> Result<Vec<Order>, Error> {
236 let account_ids: Vec<String> = account_ids
237 .into_iter()
238 .map(|account_id| account_id.into())
239 .collect();
240
241 let endpoint = format!("brokerage/accounts/{}/orders", account_ids.join(","));
242
243 let resp = client
244 .get(&endpoint)
245 .await?
246 .json::<responses::GetOrdersResp>()
247 .await?;
248
249 Ok(resp.orders)
250 }
251
252 /// Fetches orders by order id for the given `Account`.
253 ///
254 /// # Example
255 /// ---
256 ///
257 /// Grab 2 specific orders by their id's, say you have a stop loss order
258 /// and a take profit order you want to check the status on, this is how.
259 ///
260 /// ```ignore
261 /// // Initialize the client
262 /// let mut client = ClientBuilder::new()?
263 /// .credentials("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")?
264 /// .token(Token {
265 /// access_token: String::from("YOUR_ACCESS_TOKEN"),
266 /// refresh_token: String::from("YOUR_REFRESH_TOKEN"),
267 /// id_token: String::from("YOUR_ID_TOKEN"),
268 /// token_type: String::from("Bearer"),
269 /// scope: String::from("YOUR_SCOPES SPACE_SEPERATED FOR_EACH_SCOPE"),
270 /// expires_in: 1200,
271 /// })?
272 /// .build()
273 /// .await?;
274 ///
275 /// // Grab your accounts and specify an account the orders were placed in
276 /// let accounts = client.get_accounts().await?;
277 /// if let Some(specific_account) = accounts.find_by_id("YOUR_ACCOUNT_ID") {
278 /// // Get some specific orders by their order id's
279 /// let orders = specific_account.
280 /// get_orders_by_id(vec!["1115661503", "1115332365"], &mut client)
281 /// .await?;
282 ///
283 /// // Log the status of the order's
284 /// for order in orders {
285 /// println!("Order ID ({}) status: {}", order.order_id, order.status);
286 /// }
287 /// }
288 /// ```
289 pub async fn get_orders_by_id<S: Into<String>>(
290 &self,
291 order_ids: Vec<S>,
292 client: &mut Client,
293 ) -> Result<Vec<Order>, Error> {
294 let order_ids: Vec<String> = order_ids.into_iter().map(|id| id.into()).collect();
295
296 let endpoint = format!(
297 "brokerage/accounts/{}/orders/{}",
298 self.account_id,
299 &order_ids.join(",")
300 );
301
302 let resp = client
303 .get(&endpoint)
304 .await?
305 .json::<responses::GetOrdersResp>()
306 .await?;
307
308 Ok(resp.orders)
309 }
310
311 /// NOTE: Same as `get_orders_by_id` but for multiple accounts
312 /// NOTE: For internal use only. Use `Account::get_orders_by_id()`
313 /// to access this functionality.
314 async fn get_orders_by_id_for_accounts<S: Into<String>>(
315 account_ids: Vec<S>,
316 order_ids: Vec<S>,
317 client: &mut Client,
318 ) -> Result<Vec<Order>, Error> {
319 let account_ids: Vec<String> = account_ids
320 .into_iter()
321 .map(|account_id| account_id.into())
322 .collect();
323
324 let order_ids: Vec<String> = order_ids
325 .into_iter()
326 .map(|order_id| order_id.into())
327 .collect();
328
329 let endpoint = format!(
330 "brokerage/accounts/{}/orders/{}",
331 account_ids.join(","),
332 &order_ids.join(",")
333 );
334
335 let resp = client
336 .get(&endpoint)
337 .await?
338 .json::<responses::GetOrdersResp>()
339 .await?;
340
341 Ok(resp.orders)
342 }
343
344 /// Fetches positions for the given `Account`.
345 pub async fn get_positions(&self, client: &mut Client) -> Result<Vec<Position>, Error> {
346 let endpoint = format!("brokerage/accounts/{}/positions", self.account_id);
347
348 let resp = client
349 .get(&endpoint)
350 .await?
351 .json::<responses::GetPositionsResp>()
352 .await?;
353
354 Ok(resp.positions)
355 }
356
357 /// Fetches positions for the given `Account`.
358 ///
359 /// NOTE: symbol should be a str of valid symbols in comma separated format;
360 /// for example: `"MSFT,MSFT *,AAPL"`.
361 ///
362 /// NOTE: You can use an * as wildcard to make more complex filters.
363 pub async fn get_positions_in_symbols(
364 &self,
365 symbols: &str,
366 client: &mut Client,
367 ) -> Result<Vec<Position>, Error> {
368 let endpoint = format!(
369 "brokerage/accounts/{}/positions?symbol={}",
370 self.account_id, symbols
371 );
372
373 let resp = client
374 .get(&endpoint)
375 .await?
376 .json::<responses::GetPositionsResp>()
377 .await?;
378
379 Ok(resp.positions)
380 }
381
382 /// Fetches positions for the given `Account`.
383 ///
384 /// NOTE: If you have `Vec<Account>` you should instead use `Vec<Account>::get_positions()`
385 /// this method should only be used if you ONLY have account id's.
386 pub async fn get_positions_by_ids(
387 client: &mut Client,
388 account_ids: Vec<&str>,
389 ) -> Result<Vec<Position>, Error> {
390 let endpoint = format!("brokerage/accounts/{}/positions", account_ids.join(","));
391
392 let resp = client
393 .get(&endpoint)
394 .await?
395 .json::<responses::GetPositionsResp>()
396 .await?;
397
398 Ok(resp.positions)
399 }
400
401 /// Fetches positions for the given `Account`.
402 ///
403 /// NOTE: If you have `Vec<Account>` you should instead use `Vec<Account>::get_positions_in_symbols()`
404 /// this method should only be used if you ONLY have account id's.
405 ///
406 /// NOTE: symbol should be a str of valid symbols in comma separated format;
407 /// for example: `"MSFT,MSFT *,AAPL"`.
408 ///
409 /// NOTE: You can use an * as wildcard to make more complex filters.
410 pub async fn get_positions_in_symbols_by_ids(
411 client: &mut Client,
412 symbols: &str,
413 account_ids: Vec<&str>,
414 ) -> Result<Vec<Position>, Error> {
415 let endpoint = format!(
416 "brokerage/accounts/{}/positions?symbol={}",
417 account_ids.join(","),
418 symbols
419 );
420
421 let resp = client
422 .get(&endpoint)
423 .await?
424 .json::<responses::GetPositionsResp>()
425 .await?;
426
427 Ok(resp.positions)
428 }
429
430 /// Stream `Order`(s) for the given `Account`.
431 ///
432 /// NOTE: You need to pass a closure function that will handle
433 /// each chunk of data (`StreamOrdersResp`) as it's streamed in.
434 ///
435 /// # Example
436 /// ---
437 ///
438 /// Get the amount of funds allocated to open orders.
439 /// ```ignore
440 /// let mut funds_allocated_to_open_orders = 0.00;
441 /// specific_account
442 /// .stream_orders(&mut client, |stream_data| {
443 /// // The response type is `responses::account::StreamOrdersResp`
444 /// // which has multiple variants the main one you care about is
445 /// // `Order` which will contain order data sent from the stream.
446 /// match stream_data {
447 /// StreamOrdersResp::Order(order) => {
448 /// // Response for an `Order` streamed in
449 /// println!("{order:?}");
450 ///
451 /// // keep a live sum of all the funds allocated to open orders
452 /// let order_value = order.price_used_for_buying_power.parse::<f64>();
453 /// if let Ok(value) = order_value {
454 /// funds_allocated_to_open_orders += value;
455 /// }
456 /// }
457 /// StreamOrdersResp::Heartbeat(heartbeat) => {
458 /// // Response for periodic signals letting you know the connection is
459 /// // still alive. A heartbeat is sent every 5 seconds of inactivity.
460 /// println!("{heartbeat:?}");
461 ///
462 /// // for the sake of this example after we recieve the
463 /// // tenth heartbeat, we will stop the stream session.
464 /// if heartbeat.heartbeat > 10 {
465 /// // Example: stopping a stream connection
466 /// return Err(Error::StopStream);
467 /// }
468 /// }
469 /// StreamOrdersResp::Status(status) => {
470 /// // Signal sent on state changes in the stream
471 /// // (closed, opened, paused, resumed)
472 /// println!("{status:?}");
473 /// }
474 /// StreamOrdersResp::Error(err) => {
475 /// // Response for when an error was encountered,
476 /// // with details on the error
477 /// println!("{err:?}");
478 /// }
479 /// }
480 ///
481 /// Ok(())
482 /// })
483 /// .await?;
484 /// ```
485 pub async fn stream_orders<F>(
486 &self,
487 client: &mut Client,
488 mut on_chunk: F,
489 ) -> Result<Vec<Order>, Error>
490 where
491 F: FnMut(StreamOrdersResp) -> Result<(), Error>,
492 {
493 let endpoint = format!("brokerage/stream/accounts/{}/orders", self.account_id);
494
495 let mut collected_orders: Vec<Order> = Vec::new();
496 client
497 .stream(&endpoint, |chunk| {
498 let parsed_chunk = serde_json::from_value::<StreamOrdersResp>(chunk)?;
499 on_chunk(parsed_chunk.clone())?;
500
501 // Only collect orders, so when the stream is done
502 // all the orders that were streamed can be returned
503 if let StreamOrdersResp::Order(order) = parsed_chunk {
504 collected_orders.push(*order);
505 }
506
507 Ok(())
508 })
509 .await?;
510
511 Ok(collected_orders)
512 }
513
514 /// Stream `Order`(s) by order id's for the given `Account`.
515 ///
516 /// NOTE: order ids should be a comma delimited string slice `"xxxxx,xxxxx,xxxxx"`
517 ///
518 /// NOTE: You need to pass a closure function that will handle
519 /// each chunk of data `StreamOrdersResp` as it's streamed in.
520 ///
521 /// # Example
522 /// ---
523 ///
524 /// Do something until all order's in a trade are filled.
525 ///
526 /// ```ignore
527 /// let mut some_trades_order_statuses: HashMap<String, String> = HashMap::new();
528 /// specific_account
529 /// // NOTE: The order ids "1111,1112,1113,1114" are fake and not to be used.
530 /// .stream_orders_by_id(&mut client, "1111,1112,1113,1114", |stream_data| {
531 /// // The response type is `responses::account::StreamOrdersResp`
532 /// // which has multiple variants the main one you care about is
533 /// // `Order` which will contain order data sent from the stream.
534 /// match stream_data {
535 /// StreamOrdersResp::Order(order) => {
536 /// // Response for an `Order` streamed in
537 /// println!("{order:?}");
538 ///
539 /// some_trades_order_statuses.insert(order.order_id, order.status);
540 /// if some_trades_order_statuses
541 /// .values()
542 /// .all(|order_status| order_status.eq("FLL"))
543 /// {
544 /// // When all order's are filled stop the stream
545 /// return Err(Error::StopStream);
546 /// } else {
547 /// // Do something until all order's for a specific trade are filled
548 /// // maybe update the limit price of the unfilled order's by 1 tick?
549 /// //
550 /// // NOTE: you can also "do nothing" essentially just waiting for some
551 /// // scenario, maybe waiting for all order's to be filled to send an
552 /// // email or text alerting that the trade is fully filled.
553 /// }
554 /// }
555 /// StreamOrdersResp::Heartbeat(heartbeat) => {
556 /// // Response for periodic signals letting you know the connection is
557 /// // still alive. A heartbeat is sent every 5 seconds of inactivity.
558 /// println!("{heartbeat:?}");
559 ///
560 /// // for the sake of this example after we recieve the
561 /// // tenth heartbeat, we will stop the stream session.
562 /// if heartbeat.heartbeat > 10 {
563 /// // Example: stopping a stream connection
564 /// return Err(Error::StopStream);
565 /// }
566 /// }
567 /// StreamOrdersResp::Status(status) => {
568 /// // Signal sent on state changes in the stream
569 /// // (closed, opened, paused, resumed)
570 /// println!("{status:?}");
571 /// }
572 /// StreamOrdersResp::Error(err) => {
573 /// // Response for when an error was encountered,
574 /// // with details on the error
575 /// println!("{err:?}");
576 /// }
577 /// }
578 ///
579 /// Ok(())
580 /// })
581 /// .await?;
582 /// ```
583 pub async fn stream_orders_by_id<F>(
584 &self,
585 client: &mut Client,
586 order_ids: &str,
587 mut on_chunk: F,
588 ) -> Result<Vec<Order>, Error>
589 where
590 F: FnMut(StreamOrdersResp) -> Result<(), Error>,
591 {
592 let endpoint = format!(
593 "brokerage/stream/accounts/{}/orders/{}",
594 self.account_id, order_ids
595 );
596
597 let mut collected_orders: Vec<Order> = Vec::new();
598 client
599 .stream(&endpoint, |chunk| {
600 let parsed_chunk = serde_json::from_value::<StreamOrdersResp>(chunk)?;
601 on_chunk(parsed_chunk.clone())?;
602
603 // Only collect orders, so when the stream is done
604 // all the orders that were streamed can be returned
605 if let StreamOrdersResp::Order(order) = parsed_chunk {
606 collected_orders.push(*order);
607 }
608
609 Ok(())
610 })
611 .await?;
612
613 Ok(collected_orders)
614 }
615
616 /// Stream `Order`(s) for the given `Account`.
617 ///
618 /// NOTE: You need to pass a closure function that will handle
619 /// each chunk of data (`StreamOrdersResp`) as it's streamed in.
620 ///
621 /// # Example
622 /// ---
623 ///
624 /// Get the amount of funds allocated to open orders.
625 /// ```ignore
626 /// let mut funds_allocated_to_open_orders = 0.00;
627 /// specific_account
628 /// .stream_orders(&mut client, |stream_data| {
629 /// // The response type is `responses::account::StreamOrdersResp`
630 /// // which has multiple variants the main one you care about is
631 /// // `Order` which will contain order data sent from the stream.
632 /// match stream_data {
633 /// StreamOrdersResp::Order(order) => {
634 /// // Response for an `Order` streamed in
635 /// println!("{order:?}");
636 ///
637 /// // keep a live sum of all the funds allocated to open orders
638 /// let order_value = order.price_used_for_buying_power.parse::<f64>();
639 /// if let Ok(value) = order_value {
640 /// funds_allocated_to_open_orders += value;
641 /// }
642 /// }
643 /// StreamOrdersResp::Heartbeat(heartbeat) => {
644 /// // Response for periodic signals letting you know the connection is
645 /// // still alive. A heartbeat is sent every 5 seconds of inactivity.
646 /// println!("{heartbeat:?}");
647 ///
648 /// // for the sake of this example after we recieve the
649 /// // tenth heartbeat, we will stop the stream session.
650 /// if heartbeat.heartbeat > 10 {
651 /// // Example: stopping a stream connection
652 /// return Err(Error::StopStream);
653 /// }
654 /// }
655 /// StreamOrdersResp::Status(status) => {
656 /// // Signal sent on state changes in the stream
657 /// // (closed, opened, paused, resumed)
658 /// println!("{status:?}");
659 /// }
660 /// StreamOrdersResp::Error(err) => {
661 /// // Response for when an error was encountered,
662 /// // with details on the error
663 /// println!("{err:?}");
664 /// }
665 /// }
666 ///
667 /// Ok(())
668 /// })
669 /// .await?;
670 /// ```
671 async fn stream_orders_for_accounts<F>(
672 client: &mut Client,
673 account_ids: Vec<&str>,
674 mut on_chunk: F,
675 ) -> Result<Vec<Order>, Error>
676 where
677 F: FnMut(StreamOrdersResp) -> Result<(), Error>,
678 {
679 let endpoint = format!("brokerage/stream/accounts/{}/orders", account_ids.join(","));
680
681 let mut collected_orders: Vec<Order> = Vec::new();
682 client
683 .stream(&endpoint, |chunk| {
684 let parsed_chunk = serde_json::from_value::<StreamOrdersResp>(chunk)?;
685 on_chunk(parsed_chunk.clone())?;
686
687 // Only collect orders so when the stream is done
688 // all the orders that were streamed can be returned
689 if let StreamOrdersResp::Order(order) = parsed_chunk {
690 collected_orders.push(*order);
691 }
692
693 Ok(())
694 })
695 .await?;
696
697 Ok(collected_orders)
698 }
699
700 /// Stream `Order`s by order id's for the given `Account`(s).
701 ///
702 /// NOTE: order ids should be a comma delimited string slice `"xxxxx,xxxxx,xxxxx"`
703 ///
704 /// NOTE: You need to pass a closure function that will handle
705 /// each chunk of data (`StreamOrdersResp`) as it's streamed in.
706 ///
707 /// # Example
708 /// ---
709 ///
710 /// Do something until all order's in a trade are filled.
711 /// ```ignore
712 /// let mut some_trades_order_statuses: HashMap<String, String> = HashMap::new();
713 /// specific_account
714 /// // NOTE: The order ids "1111,1112,1113,1114" are fake and not to be used.
715 /// .stream_orders_by_id(&mut client, "1111,1112,1113,1114", |stream_data| {
716 /// // The response type is `responses::account::StreamOrdersResp`
717 /// // which has multiple variants the main one you care about is
718 /// // `Order` which will contain order data sent from the stream.
719 /// match stream_data {
720 /// StreamOrdersResp::Order(order) => {
721 /// // Response for an `Order` streamed in
722 /// println!("{order:?}");
723 ///
724 /// some_trades_order_statuses.insert(order.order_id, order.status);
725 /// if some_trades_order_statuses
726 /// .values()
727 /// .all(|order_status| order_status.eq("FLL"))
728 /// {
729 /// // When all order's are filled stop the stream
730 /// return Err(Error::StopStream);
731 /// } else {
732 /// // Do something until all order's for a specific trade are filled
733 /// // maybe update the limit price of the unfilled order's by 1 tick?
734 /// //
735 /// // NOTE: you can also "do nothing" essentially just waiting for some
736 /// // scenario, maybe waiting for all order's to be filled to send an
737 /// // email or text alerting that the trade is fully filled.
738 /// }
739 /// }
740 /// StreamOrdersResp::Heartbeat(heartbeat) => {
741 /// // Response for periodic signals letting you know the connection is
742 /// // still alive. A heartbeat is sent every 5 seconds of inactivity.
743 /// println!("{heartbeat:?}");
744 ///
745 /// // for the sake of this example after we recieve the
746 /// // tenth heartbeat, we will stop the stream session.
747 /// if heartbeat.heartbeat > 10 {
748 /// // Example: stopping a stream connection
749 /// return Err(Error::StopStream);
750 /// }
751 /// }
752 /// StreamOrdersResp::Status(status) => {
753 /// // Signal sent on state changes in the stream
754 /// // (closed, opened, paused, resumed)
755 /// println!("{status:?}");
756 /// }
757 /// StreamOrdersResp::Error(err) => {
758 /// // Response for when an error was encountered,
759 /// // with details on the error
760 /// println!("{err:?}");
761 /// }
762 /// }
763 ///
764 /// Ok(())
765 /// })
766 /// .await?;
767 /// ```
768 async fn stream_orders_by_id_for_accounts<F>(
769 client: &mut Client,
770 order_ids: &str,
771 account_ids: Vec<&str>,
772 mut on_chunk: F,
773 ) -> Result<Vec<Order>, Error>
774 where
775 F: FnMut(StreamOrdersResp) -> Result<(), Error>,
776 {
777 let endpoint = format!(
778 "brokerage/stream/accounts/{}/orders/{}",
779 account_ids.join(","),
780 order_ids
781 );
782
783 let mut collected_orders: Vec<Order> = Vec::new();
784 client
785 .stream(&endpoint, |chunk| {
786 let parsed_chunk = serde_json::from_value::<StreamOrdersResp>(chunk)?;
787 on_chunk(parsed_chunk.clone())?;
788
789 // Only collect orders so when the stream is done
790 // all the orders that were streamed can be returned
791 if let StreamOrdersResp::Order(order) = parsed_chunk {
792 collected_orders.push(*order);
793 }
794
795 Ok(())
796 })
797 .await?;
798
799 Ok(collected_orders)
800 }
801
802 /// Stream `Position`s for the given `Account`.
803 ///
804 /// NOTE: TODO: Currently does NOT support streaming `Position` changes.
805 ///
806 /// # Example
807 /// ---
808 ///
809 /// Collect losing trades into a vector and do something with them.
810 /// ```ignore
811 /// let mut losing_positions: Vec<Position> = Vec::new();
812 /// specific_account
813 /// .stream_positions(&mut client, |stream_data| {
814 /// // the response type is `responses::account::StreamPositionsResp`
815 /// // which has multiple variants the main one you care about is
816 /// // `order` which will contain order data sent from the stream.
817 /// match stream_data {
818 /// StreamPositionsResp::Position(position) => {
819 /// // response for an `position` streamed in
820 /// println!("{position:?}");
821 ///
822 /// if position.long_short == PositionType::Long {
823 /// if position.last < position.average_price {
824 /// losing_positions.push(*position)
825 /// }
826 /// } else if position.long_short == PositionType::Short {
827 /// if position.last > position.average_price {
828 /// losing_positions.push(*position)
829 /// }
830 /// }
831 ///
832 /// // do something with the list of losing trades
833 /// // maybe send email or text of the positions
834 /// println!("{losing_positions:?}");
835 /// }
836 /// StreamPositionsResp::Heartbeat(heartbeat) => {
837 /// // response for periodic signals letting you know the connection is
838 /// // still alive. a heartbeat is sent every 5 seconds of inactivity.
839 /// println!("{heartbeat:?}");
840 ///
841 /// // for the sake of this example after we recieve the
842 /// // tenth heartbeat, we will stop the stream session.
843 /// if heartbeat.heartbeat > 10 {
844 /// // example: stopping a stream connection
845 /// return Err(Error::StopStream);
846 /// }
847 /// }
848 /// StreamPositionsResp::Status(status) => {
849 /// // signal sent on state changes in the stream
850 /// // (closed, opened, paused, resumed)
851 /// println!("{status:?}");
852 /// }
853 /// StreamPositionsResp::Error(err) => {
854 /// // response for when an error was encountered,
855 /// // with details on the error
856 /// println!("{err:?}");
857 /// }
858 /// }
859 ///
860 /// Ok(())
861 /// })
862 /// .await?;
863 /// ```
864 pub async fn stream_positions<F>(
865 &self,
866 client: &mut Client,
867 mut on_chunk: F,
868 ) -> Result<Vec<Position>, Error>
869 where
870 F: FnMut(StreamPositionsResp) -> Result<(), Error>,
871 {
872 let endpoint = format!("brokerage/stream/accounts/{}/positions", self.account_id);
873
874 let mut collected_positions: Vec<Position> = Vec::new();
875 client
876 .stream(&endpoint, |chunk| {
877 let parsed_chunk = serde_json::from_value::<StreamPositionsResp>(chunk)?;
878 on_chunk(parsed_chunk.clone())?;
879
880 // Only collect orders, so when the stream is done
881 // all the orders that were streamed can be returned
882 if let StreamPositionsResp::Position(position) = parsed_chunk {
883 collected_positions.push(*position);
884 }
885
886 Ok(())
887 })
888 .await?;
889
890 Ok(collected_positions)
891 }
892
893 /// Stream `Position`s for the given `Account`(s).
894 ///
895 /// NOTE: TODO: Currently does NOT support streaming `Position` changes.
896 ///
897 /// # Example
898 /// ---
899 ///
900 /// Collect losing trades into a vector and do something with them.
901 /// ```ignore
902 /// let mut losing_positions: Vec<Position> = Vec::new();
903 /// specific_account
904 /// .stream_positions(&mut client, |stream_data| {
905 /// // the response type is `responses::account::StreamPositionsResp`
906 /// // which has multiple variants the main one you care about is
907 /// // `order` which will contain order data sent from the stream.
908 /// match stream_data {
909 /// StreamPositionsResp::Position(position) => {
910 /// // response for an `position` streamed in
911 /// println!("{position:?}");
912 ///
913 /// if position.long_short == PositionType::Long {
914 /// if position.last < position.average_price {
915 /// losing_positions.push(*position)
916 /// }
917 /// } else if position.long_short == PositionType::Short {
918 /// if position.last > position.average_price {
919 /// losing_positions.push(*position)
920 /// }
921 /// }
922 ///
923 /// // do something with the list of losing trades
924 /// // maybe send email or text of the positions
925 /// println!("{losing_positions:?}");
926 /// }
927 /// StreamPositionsResp::Heartbeat(heartbeat) => {
928 /// // response for periodic signals letting you know the connection is
929 /// // still alive. a heartbeat is sent every 5 seconds of inactivity.
930 /// println!("{heartbeat:?}");
931 ///
932 /// // for the sake of this example after we recieve the
933 /// // tenth heartbeat, we will stop the stream session.
934 /// if heartbeat.heartbeat > 10 {
935 /// // example: stopping a stream connection
936 /// return Err(Error::StopStream);
937 /// }
938 /// }
939 /// StreamPositionsResp::Status(status) => {
940 /// // signal sent on state changes in the stream
941 /// // (closed, opened, paused, resumed)
942 /// println!("{status:?}");
943 /// }
944 /// StreamPositionsResp::Error(err) => {
945 /// // response for when an error was encountered,
946 /// // with details on the error
947 /// println!("{err:?}");
948 /// }
949 /// }
950 ///
951 /// Ok(())
952 /// })
953 /// .await?;
954 /// ```
955 pub async fn stream_positions_for_accounts<F>(
956 client: &mut Client,
957 account_ids: Vec<&str>,
958 mut on_chunk: F,
959 ) -> Result<Vec<Position>, Error>
960 where
961 F: FnMut(StreamPositionsResp) -> Result<(), Error>,
962 {
963 let endpoint = format!(
964 "brokerage/stream/accounts/{}/positions",
965 account_ids.join(",")
966 );
967
968 let mut collected_positions: Vec<Position> = Vec::new();
969 client
970 .stream(&endpoint, |chunk| {
971 let parsed_chunk = serde_json::from_value::<StreamPositionsResp>(chunk)?;
972 on_chunk(parsed_chunk.clone())?;
973
974 // Only collect orders, so when the stream is done
975 // all the orders that were streamed can be returned
976 if let StreamPositionsResp::Position(position) = parsed_chunk {
977 collected_positions.push(*position);
978 }
979
980 Ok(())
981 })
982 .await?;
983
984 Ok(collected_positions)
985 }
986}
987
988/// Trait to allow calling methods on multiple accounts `Vec<Account>`.
989pub trait MultipleAccounts {
990 /// Find an `Account` by it's id.
991 fn find_by_id(&self, id: &str) -> Option<Account>;
992
993 type GetOrdersFuture<'a>: Future<Output = Result<Vec<Order>, Box<dyn StdErrorTrait + Send + Sync>>>
994 + Send
995 + 'a
996 where
997 Self: 'a;
998 /// Get `Order`(s) for multiple `Account`(s).
999 ///
1000 /// # Example
1001 /// ---
1002 ///
1003 /// Grab all the orders for a specific account. Say you need to go
1004 /// through all the orders your algorithm placed today and filter out
1005 /// only the orders that were filled for data storage purposes.
1006 ///
1007 /// ```ignore
1008 /// // Initialize the client
1009 /// let mut client = ClientBuilder::new()?
1010 /// .credentials("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")?
1011 /// .token(Token {
1012 /// access_token: String::from("YOUR_ACCESS_TOKEN"),
1013 /// refresh_token: String::from("YOUR_REFRESH_TOKEN"),
1014 /// id_token: String::from("YOUR_ID_TOKEN"),
1015 /// token_type: String::from("Bearer"),
1016 /// scope: String::from("YOUR_SCOPES SPACE_SEPERATED FOR_EACH_SCOPE"),
1017 /// expires_in: 1200,
1018 /// })?
1019 /// .build()
1020 /// .await?;
1021 ///
1022 /// // Grab your accounts and specify an account the orders were placed in
1023 /// let accounts = client.get_accounts().await?;
1024 /// if let Some(specific_account) = accounts.find_by_id("YOUR_ACCOUNT_ID") {
1025 /// // Get all the orders from today for a specific account
1026 /// let orders = specific_account.get_orders( &mut client).await?;
1027 ///
1028 /// // Filter out only filled orders
1029 /// let filled_orders: Vec<Order> = orders
1030 /// .into_iter()
1031 /// .filter(|order| order.status == "FLL")
1032 /// .collect();
1033 ///
1034 /// // Do something with your filled orders
1035 /// for order in filled_orders {
1036 /// println!("Filled Order: {order:?}");
1037 /// }
1038 /// }
1039 /// ```
1040 fn get_orders<'a>(&'a self, client: &'a mut Client) -> Self::GetOrdersFuture<'a>;
1041
1042 /// Get specific `Order`(s) by their id's for multiple `Account`(s).
1043 ///
1044 /// # Example
1045 /// ---
1046 ///
1047 /// Grab 2 specific orders by their id's, say you have a stop loss order
1048 /// and a take profit order you want to check the status on, this is how.
1049 ///
1050 /// ```ignore
1051 /// // Initialize the client
1052 /// let mut client = ClientBuilder::new()?
1053 /// .credentials("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")?
1054 /// .token(Token {
1055 /// access_token: String::from("YOUR_ACCESS_TOKEN"),
1056 /// refresh_token: String::from("YOUR_REFRESH_TOKEN"),
1057 /// id_token: String::from("YOUR_ID_TOKEN"),
1058 /// token_type: String::from("Bearer"),
1059 /// scope: String::from("YOUR_SCOPES SPACE_SEPERATED FOR_EACH_SCOPE"),
1060 /// expires_in: 1200,
1061 /// })?
1062 /// .build()
1063 /// .await?;
1064 ///
1065 /// // Grab your accounts and specify an account the orders were placed in
1066 /// let accounts = client.get_accounts().await?;
1067 /// if let Some(specific_account) = accounts.find_by_id("YOUR_ACCOUNT_ID") {
1068 /// // Get some specific orders by their order id's
1069 /// let orders = specific_account.
1070 /// get_orders_by_id(vec!["1115661503", "1115332365"], &mut client)
1071 /// .await?;
1072 ///
1073 /// // Log the status of the order's
1074 /// for order in orders {
1075 /// println!("Order ID ({}) status: {}", order.order_id, order.status);
1076 /// }
1077 /// }
1078 /// ```
1079 fn get_orders_by_id<'a>(
1080 &'a self,
1081 order_ids: &'a [&str],
1082 client: &'a mut Client,
1083 ) -> Self::GetOrdersFuture<'a>;
1084
1085 type GetBalanceFuture<'a>: Future<Output = Result<Vec<Balance>, Box<dyn StdErrorTrait + Send + Sync>>>
1086 + Send
1087 + 'a
1088 where
1089 Self: 'a;
1090 /// Get the current balance of multiple `Account`(s).
1091 fn get_balances<'a>(&'a self, client: &'a mut Client) -> Self::GetBalanceFuture<'a>;
1092
1093 type GetBODBalanceFuture<'a>: Future<Output = Result<Vec<BODBalance>, Box<dyn StdErrorTrait + Send + Sync>>>
1094 + Send
1095 + 'a
1096 where
1097 Self: 'a;
1098 /// Get the beginning of day balances for multiple `Account`(s) by account id.
1099 fn get_bod_balances<'a>(&'a self, client: &'a mut Client) -> Self::GetBODBalanceFuture<'a>;
1100
1101 type GetHistoricOrdersFuture<'a>: Future<Output = Result<Vec<Order>, Box<dyn StdErrorTrait + Send + Sync>>>
1102 + Send
1103 + 'a
1104 where
1105 Self: 'a;
1106 /// Get the historical `Order`(s) for multiple `Account`(s).
1107 ///
1108 /// NOTE: Date format is {YEAR-MONTH-DAY} ex: `"2024-07-09"`, and is limited to 90
1109 /// days prior to the current date.
1110 ///
1111 /// NOTE: Excludes open `Order`(s) and is sorted in descending order of time closed.
1112 fn get_historic_orders<'a>(
1113 &'a self,
1114 client: &'a mut Client,
1115 since_date: &'a str,
1116 ) -> Self::GetHistoricOrdersFuture<'a>;
1117
1118 type GetPositionsFuture<'a>: Future<Output = Result<Vec<Position>, Box<dyn StdErrorTrait + Send + Sync>>>
1119 + Send
1120 + 'a
1121 where
1122 Self: 'a;
1123 /// Get the `Position`(s) for multiple `Account`(s).
1124 fn get_positions<'a>(&'a self, client: &'a mut Client) -> Self::GetPositionsFuture<'a>;
1125
1126 type GetPositionsInSymbolsFuture<'a>: Future<Output = Result<Vec<Position>, Box<dyn StdErrorTrait + Send + Sync>>>
1127 + Send
1128 + 'a
1129 where
1130 Self: 'a;
1131 /// Get the `Position`(s) in specific symbols for multiple `Account`(s).
1132 fn get_positions_in_symbols<'a>(
1133 &'a self,
1134 symbols: &'a str,
1135 client: &'a mut Client,
1136 ) -> Self::GetPositionsFuture<'a>;
1137
1138 type StreamOrdersFuture<'a>: Future<Output = Result<Vec<Order>, Box<dyn StdErrorTrait + Send + Sync>>>
1139 + Send
1140 + 'a
1141 where
1142 Self: 'a;
1143 /// Stream `Order`(s) for the given `Account`.
1144 ///
1145 /// NOTE: You need to pass a closure function that will handle
1146 /// each chunk of data (`StreamOrdersResp`) as it's streamed in.
1147 ///
1148 /// # Example
1149 /// ---
1150 ///
1151 /// Get the amount of funds allocated to open orders.
1152 /// ```ignore
1153 /// let mut funds_allocated_to_open_orders = 0.00;
1154 /// specific_account
1155 /// .stream_orders(&mut client, |stream_data| {
1156 /// // The response type is `responses::account::StreamOrdersResp`
1157 /// // which has multiple variants the main one you care about is
1158 /// // `Order` which will contain order data sent from the stream.
1159 /// match stream_data {
1160 /// StreamOrdersResp::Order(order) => {
1161 /// // Response for an `Order` streamed in
1162 /// println!("{order:?}");
1163 ///
1164 /// // keep a live sum of all the funds allocated to open orders
1165 /// let order_value = order.price_used_for_buying_power.parse::<f64>();
1166 /// if let Ok(value) = order_value {
1167 /// funds_allocated_to_open_orders += value;
1168 /// }
1169 /// }
1170 /// StreamOrdersResp::Heartbeat(heartbeat) => {
1171 /// // Response for periodic signals letting you know the connection is
1172 /// // still alive. A heartbeat is sent every 5 seconds of inactivity.
1173 /// println!("{heartbeat:?}");
1174 ///
1175 /// // for the sake of this example after we recieve the
1176 /// // tenth heartbeat, we will stop the stream session.
1177 /// if heartbeat.heartbeat > 10 {
1178 /// // Example: stopping a stream connection
1179 /// return Err(Error::StopStream);
1180 /// }
1181 /// }
1182 /// StreamOrdersResp::Status(status) => {
1183 /// // Signal sent on state changes in the stream
1184 /// // (closed, opened, paused, resumed)
1185 /// println!("{status:?}");
1186 /// }
1187 /// StreamOrdersResp::Error(err) => {
1188 /// // Response for when an error was encountered,
1189 /// // with details on the error
1190 /// println!("{err:?}");
1191 /// }
1192 /// }
1193 ///
1194 /// Ok(())
1195 /// })
1196 /// .await?;
1197 /// ```
1198 fn stream_orders<'a, F>(
1199 &'a self,
1200 on_chunk: &'a mut F,
1201 client: &'a mut Client,
1202 ) -> Self::StreamOrdersFuture<'a>
1203 where
1204 F: FnMut(StreamOrdersResp) -> Result<(), Error> + Send + 'a;
1205
1206 type StreamOrdersByIdFuture<'a>: Future<Output = Result<Vec<Order>, Box<dyn StdErrorTrait + Send + Sync>>>
1207 + Send
1208 + 'a
1209 where
1210 Self: 'a;
1211 /// Stream `Order`s by order id's for the given `Account`(s).
1212 ///
1213 /// NOTE: order ids should be a comma delimited string slice `"xxxxx,xxxxx,xxxxx"`.
1214 ///
1215 /// NOTE: You need to pass a closure function that will handle
1216 /// each chunk of data (`StreamOrdersResp`) as it's streamed in.
1217 ///
1218 /// # Example
1219 /// ---
1220 ///
1221 /// Do something until all order's in a trade are filled.
1222 /// ```ignore
1223 /// let mut some_trades_order_statuses: HashMap<String, String> = HashMap::new();
1224 /// specific_account
1225 /// // NOTE: The order ids "1111,1112,1113,1114" are fake and not to be used.
1226 /// .stream_orders_by_id(&mut client, "1111,1112,1113,1114", |stream_data| {
1227 /// // The response type is `responses::account::StreamOrdersResp`
1228 /// // which has multiple variants the main one you care about is
1229 /// // `Order` which will contain order data sent from the stream.
1230 /// match stream_data {
1231 /// StreamOrdersResp::Order(order) => {
1232 /// // Response for an `Order` streamed in
1233 /// println!("{order:?}");
1234 ///
1235 /// some_trades_order_statuses.insert(order.order_id, order.status);
1236 /// if some_trades_order_statuses
1237 /// .values()
1238 /// .all(|order_status| order_status.eq("FLL"))
1239 /// {
1240 /// // When all order's are filled stop the stream
1241 /// return Err(Error::StopStream);
1242 /// } else {
1243 /// // Do something until all order's for a specific trade are filled
1244 /// // maybe update the limit price of the unfilled order's by 1 tick?
1245 /// //
1246 /// // NOTE: you can also "do nothing" essentially just waiting for some
1247 /// // scenario, maybe waiting for all order's to be filled to send an
1248 /// // email or text alerting that the trade is fully filled.
1249 /// }
1250 /// }
1251 /// StreamOrdersResp::Heartbeat(heartbeat) => {
1252 /// // Response for periodic signals letting you know the connection is
1253 /// // still alive. A heartbeat is sent every 5 seconds of inactivity.
1254 /// println!("{heartbeat:?}");
1255 ///
1256 /// // for the sake of this example after we recieve the
1257 /// // tenth heartbeat, we will stop the stream session.
1258 /// if heartbeat.heartbeat > 10 {
1259 /// // Example: stopping a stream connection
1260 /// return Err(Error::StopStream);
1261 /// }
1262 /// }
1263 /// StreamOrdersResp::Status(status) => {
1264 /// // Signal sent on state changes in the stream
1265 /// // (closed, opened, paused, resumed)
1266 /// println!("{status:?}");
1267 /// }
1268 /// StreamOrdersResp::Error(err) => {
1269 /// // Response for when an error was encountered,
1270 /// // with details on the error
1271 /// println!("{err:?}");
1272 /// }
1273 /// }
1274 ///
1275 /// Ok(())
1276 /// })
1277 /// .await?;
1278 /// ```
1279 fn stream_orders_by_id<'a, F>(
1280 &'a self,
1281 order_ids: &'a str,
1282 on_chunk: &'a mut F,
1283 client: &'a mut Client,
1284 ) -> Self::StreamOrdersByIdFuture<'a>
1285 where
1286 F: FnMut(StreamOrdersResp) -> Result<(), Error> + Send + 'a;
1287
1288 type StreamPositionsFuture<'a>: Future<Output = Result<Vec<Position>, Box<dyn StdErrorTrait + Send + Sync>>>
1289 + Send
1290 + 'a
1291 where
1292 Self: 'a;
1293 /// Stream `Position`s for the given `Account`(s).
1294 ///
1295 /// NOTE: TODO: Currently does NOT support streaming `Position` changes.
1296 ///
1297 /// # Example
1298 /// ---
1299 ///
1300 /// Collect losing trades into a vector and do something with them.
1301 /// ```ignore
1302 /// let mut losing_positions: Vec<Position> = Vec::new();
1303 /// specific_account
1304 /// .stream_positions(&mut client, |stream_data| {
1305 /// // the response type is `responses::account::StreamPositionsResp`
1306 /// // which has multiple variants the main one you care about is
1307 /// // `order` which will contain order data sent from the stream.
1308 /// match stream_data {
1309 /// StreamPositionsResp::Position(position) => {
1310 /// // response for an `position` streamed in
1311 /// println!("{position:?}");
1312 ///
1313 /// if position.long_short == PositionType::Long {
1314 /// if position.last < position.average_price {
1315 /// losing_positions.push(*position)
1316 /// }
1317 /// } else if position.long_short == PositionType::Short {
1318 /// if position.last > position.average_price {
1319 /// losing_positions.push(*position)
1320 /// }
1321 /// }
1322 ///
1323 /// // do something with the list of losing trades
1324 /// // maybe send email or text of the positions
1325 /// println!("{losing_positions:?}");
1326 /// }
1327 /// StreamPositionsResp::Heartbeat(heartbeat) => {
1328 /// // response for periodic signals letting you know the connection is
1329 /// // still alive. a heartbeat is sent every 5 seconds of inactivity.
1330 /// println!("{heartbeat:?}");
1331 ///
1332 /// // for the sake of this example after we recieve the
1333 /// // tenth heartbeat, we will stop the stream session.
1334 /// if heartbeat.heartbeat > 10 {
1335 /// // example: stopping a stream connection
1336 /// return Err(Error::StopStream);
1337 /// }
1338 /// }
1339 /// StreamPositionsResp::Status(status) => {
1340 /// // signal sent on state changes in the stream
1341 /// // (closed, opened, paused, resumed)
1342 /// println!("{status:?}");
1343 /// }
1344 /// StreamPositionsResp::Error(err) => {
1345 /// // response for when an error was encountered,
1346 /// // with details on the error
1347 /// println!("{err:?}");
1348 /// }
1349 /// }
1350 ///
1351 /// Ok(())
1352 /// })
1353 /// .await?;
1354 /// ```
1355 fn stream_positions<'a, F>(
1356 &'a self,
1357 on_chunk: &'a mut F,
1358 client: &'a mut Client,
1359 ) -> Self::StreamPositionsFuture<'a>
1360 where
1361 F: FnMut(StreamPositionsResp) -> Result<(), Error> + Send + 'a;
1362}
1363impl MultipleAccounts for Vec<Account> {
1364 /// Find a specific account by a given account id from
1365 /// a `Vec<Account>`.
1366 fn find_by_id(&self, id: &str) -> Option<Account> {
1367 self.iter()
1368 .filter(|account| account.account_id == id)
1369 .collect::<Vec<&Account>>()
1370 .pop()
1371 .cloned()
1372 }
1373
1374 type GetOrdersFuture<'a> = Pin<
1375 Box<
1376 dyn Future<Output = Result<Vec<Order>, Box<dyn StdErrorTrait + Send + Sync>>>
1377 + Send
1378 + 'a,
1379 >,
1380 >;
1381 fn get_orders<'a>(&'a self, client: &'a mut Client) -> Self::GetOrdersFuture<'a> {
1382 let account_ids: Vec<&str> = self
1383 .iter()
1384 .map(|account| account.account_id.as_str())
1385 .collect();
1386
1387 Box::pin(async move {
1388 let orders = Account::get_orders_for_accounts(account_ids, client).await?;
1389 Ok(orders)
1390 })
1391 }
1392
1393 fn get_orders_by_id<'a>(
1394 &'a self,
1395 order_ids: &'a [&str],
1396 client: &'a mut Client,
1397 ) -> Self::GetOrdersFuture<'a> {
1398 let account_ids: Vec<&str> = self
1399 .iter()
1400 .map(|account| account.account_id.as_str())
1401 .collect();
1402
1403 Box::pin(async move {
1404 let orders =
1405 Account::get_orders_by_id_for_accounts(account_ids, order_ids.to_vec(), client)
1406 .await?;
1407
1408 Ok(orders)
1409 })
1410 }
1411
1412 type GetBalanceFuture<'a> = Pin<
1413 Box<
1414 dyn Future<Output = Result<Vec<Balance>, Box<dyn StdErrorTrait + Send + Sync>>>
1415 + Send
1416 + 'a,
1417 >,
1418 >;
1419 /// Get the beginning of day balances for multiple `Account`(s).
1420 fn get_balances<'a>(&'a self, client: &'a mut Client) -> Self::GetBalanceFuture<'a> {
1421 let account_ids: Vec<&str> = self
1422 .iter()
1423 .map(|account| account.account_id.as_str())
1424 .collect();
1425
1426 Box::pin(async move {
1427 let balances = Account::get_balances_by_ids(client, account_ids).await?;
1428 Ok(balances)
1429 })
1430 }
1431
1432 type GetBODBalanceFuture<'a> = Pin<
1433 Box<
1434 dyn Future<Output = Result<Vec<BODBalance>, Box<dyn StdErrorTrait + Send + Sync>>>
1435 + Send
1436 + 'a,
1437 >,
1438 >;
1439 /// Get the beginning of day balances for multiple `Account`(s)
1440 fn get_bod_balances<'a>(&'a self, client: &'a mut Client) -> Self::GetBODBalanceFuture<'a> {
1441 let account_ids: Vec<&str> = self
1442 .iter()
1443 .map(|account| account.account_id.as_str())
1444 .collect();
1445
1446 Box::pin(async move {
1447 let balances = Account::get_bod_balances_by_ids(client, account_ids).await?;
1448 Ok(balances)
1449 })
1450 }
1451
1452 type GetHistoricOrdersFuture<'a> = Pin<
1453 Box<
1454 dyn Future<Output = Result<Vec<Order>, Box<dyn StdErrorTrait + Send + Sync>>>
1455 + Send
1456 + 'a,
1457 >,
1458 >;
1459 /// Get the historical `Order`(s) for multiple `Account`(s).
1460 fn get_historic_orders<'a>(
1461 &'a self,
1462 client: &'a mut Client,
1463 since_date: &'a str,
1464 ) -> Self::GetHistoricOrdersFuture<'a> {
1465 let account_ids: Vec<&str> = self
1466 .iter()
1467 .map(|account| account.account_id.as_str())
1468 .collect();
1469
1470 Box::pin(async move {
1471 let balances =
1472 Account::get_historic_orders_by_ids(client, account_ids, since_date).await?;
1473 Ok(balances)
1474 })
1475 }
1476
1477 type GetPositionsFuture<'a> = Pin<
1478 Box<
1479 dyn Future<Output = Result<Vec<Position>, Box<dyn StdErrorTrait + Send + Sync>>>
1480 + Send
1481 + 'a,
1482 >,
1483 >;
1484 /// Get the `Position`(s) for multiple `Account`(s).
1485 fn get_positions<'a>(&'a self, client: &'a mut Client) -> Self::GetPositionsFuture<'a> {
1486 let account_ids: Vec<&str> = self
1487 .iter()
1488 .map(|account| account.account_id.as_str())
1489 .collect();
1490
1491 Box::pin(async move {
1492 let positions = Account::get_positions_by_ids(client, account_ids).await?;
1493 Ok(positions)
1494 })
1495 }
1496
1497 type GetPositionsInSymbolsFuture<'a> = Pin<
1498 Box<
1499 dyn Future<Output = Result<Vec<Position>, Box<dyn StdErrorTrait + Send + Sync>>>
1500 + Send
1501 + 'a,
1502 >,
1503 >;
1504 /// Get the `Position`(s) in specific symbols for multiple `Account`(s).
1505 fn get_positions_in_symbols<'a>(
1506 &'a self,
1507 symbols: &'a str,
1508 client: &'a mut Client,
1509 ) -> Self::GetPositionsFuture<'a> {
1510 let account_ids: Vec<&str> = self
1511 .iter()
1512 .map(|account| account.account_id.as_str())
1513 .collect();
1514
1515 Box::pin(async move {
1516 let positions =
1517 Account::get_positions_in_symbols_by_ids(client, symbols, account_ids).await?;
1518 Ok(positions)
1519 })
1520 }
1521
1522 type StreamOrdersFuture<'a> = Pin<
1523 Box<
1524 dyn Future<Output = Result<Vec<Order>, Box<dyn StdErrorTrait + Send + Sync>>>
1525 + Send
1526 + 'a,
1527 >,
1528 >;
1529 /// Stream `Order`(s) for the given `Account`
1530 ///
1531 /// NOTE: You need to pass a closure function that will handle
1532 /// each chunk of data (`StreamOrdersResp`) as it's streamed in.
1533 ///
1534 /// # Example
1535 /// ---
1536 ///
1537 /// Get the amount of funds allocated to open orders.
1538 /// ```ignore
1539 /// let mut funds_allocated_to_open_orders = 0.00;
1540 /// specific_account
1541 /// .stream_orders(&mut client, |stream_data| {
1542 /// // The response type is `responses::account::StreamOrdersResp`
1543 /// // which has multiple variants the main one you care about is
1544 /// // `Order` which will contain order data sent from the stream.
1545 /// match stream_data {
1546 /// StreamOrdersResp::Order(order) => {
1547 /// // Response for an `Order` streamed in
1548 /// println!("{order:?}");
1549 ///
1550 /// // keep a live sum of all the funds allocated to open orders
1551 /// let order_value = order.price_used_for_buying_power.parse::<f64>();
1552 /// if let Ok(value) = order_value {
1553 /// funds_allocated_to_open_orders += value;
1554 /// }
1555 /// }
1556 /// StreamOrdersResp::Heartbeat(heartbeat) => {
1557 /// // Response for periodic signals letting you know the connection is
1558 /// // still alive. A heartbeat is sent every 5 seconds of inactivity.
1559 /// println!("{heartbeat:?}");
1560 ///
1561 /// // for the sake of this example after we recieve the
1562 /// // tenth heartbeat, we will stop the stream session.
1563 /// if heartbeat.heartbeat > 10 {
1564 /// // Example: stopping a stream connection
1565 /// return Err(Error::StopStream);
1566 /// }
1567 /// }
1568 /// StreamOrdersResp::Status(status) => {
1569 /// // Signal sent on state changes in the stream
1570 /// // (closed, opened, paused, resumed)
1571 /// println!("{status:?}");
1572 /// }
1573 /// StreamOrdersResp::Error(err) => {
1574 /// // Response for when an error was encountered,
1575 /// // with details on the error
1576 /// println!("{err:?}");
1577 /// }
1578 /// }
1579 ///
1580 /// Ok(())
1581 /// })
1582 /// .await?;
1583 /// ```
1584 fn stream_orders<'a, F>(
1585 &'a self,
1586 mut on_chunk: &'a mut F,
1587 client: &'a mut Client,
1588 ) -> Self::StreamOrdersFuture<'a>
1589 where
1590 F: FnMut(StreamOrdersResp) -> Result<(), Error> + Send + 'a,
1591 {
1592 let account_ids: Vec<&str> = self
1593 .iter()
1594 .map(|account| account.account_id.as_str())
1595 .collect();
1596
1597 Box::pin(async move {
1598 let orders =
1599 Account::stream_orders_for_accounts(client, account_ids, &mut on_chunk).await?;
1600 Ok(orders)
1601 })
1602 }
1603
1604 type StreamOrdersByIdFuture<'a> = Pin<
1605 Box<
1606 dyn Future<Output = Result<Vec<Order>, Box<dyn StdErrorTrait + Send + Sync>>>
1607 + Send
1608 + 'a,
1609 >,
1610 >;
1611 /// Stream `Order`s by order id's for the given `Account`(s)
1612 ///
1613 /// NOTE: order ids should be a comma delimited string slice `"xxxxx,xxxxx,xxxxx"`
1614 ///
1615 /// NOTE: You need to pass a closure function that will handle
1616 /// each chunk of data (`StreamOrdersResp`) as it's streamed in.
1617 ///
1618 /// # Example
1619 /// ---
1620 ///
1621 /// Do something until all order's in a trade are filled.
1622 /// ```ignore
1623 /// let mut some_trades_order_statuses: HashMap<String, String> = HashMap::new();
1624 /// specific_account
1625 /// // NOTE: The order ids "1111,1112,1113,1114" are fake and not to be used.
1626 /// .stream_orders_by_id(&mut client, "1111,1112,1113,1114", |stream_data| {
1627 /// // The response type is `responses::account::StreamOrdersResp`
1628 /// // which has multiple variants the main one you care about is
1629 /// // `Order` which will contain order data sent from the stream.
1630 /// match stream_data {
1631 /// StreamOrdersResp::Order(order) => {
1632 /// // Response for an `Order` streamed in
1633 /// println!("{order:?}");
1634 ///
1635 /// some_trades_order_statuses.insert(order.order_id, order.status);
1636 /// if some_trades_order_statuses
1637 /// .values()
1638 /// .all(|order_status| order_status.eq("FLL"))
1639 /// {
1640 /// // When all order's are filled stop the stream
1641 /// return Err(Error::StopStream);
1642 /// } else {
1643 /// // Do something until all order's for a specific trade are filled
1644 /// // maybe update the limit price of the unfilled order's by 1 tick?
1645 /// //
1646 /// // NOTE: you can also "do nothing" essentially just waiting for some
1647 /// // scenario, maybe waiting for all order's to be filled to send an
1648 /// // email or text alerting that the trade is fully filled.
1649 /// }
1650 /// }
1651 /// StreamOrdersResp::Heartbeat(heartbeat) => {
1652 /// // Response for periodic signals letting you know the connection is
1653 /// // still alive. A heartbeat is sent every 5 seconds of inactivity.
1654 /// println!("{heartbeat:?}");
1655 ///
1656 /// // for the sake of this example after we recieve the
1657 /// // tenth heartbeat, we will stop the stream session.
1658 /// if heartbeat.heartbeat > 10 {
1659 /// // Example: stopping a stream connection
1660 /// return Err(Error::StopStream);
1661 /// }
1662 /// }
1663 /// StreamOrdersResp::Status(status) => {
1664 /// // Signal sent on state changes in the stream
1665 /// // (closed, opened, paused, resumed)
1666 /// println!("{status:?}");
1667 /// }
1668 /// StreamOrdersResp::Error(err) => {
1669 /// // Response for when an error was encountered,
1670 /// // with details on the error
1671 /// println!("{err:?}");
1672 /// }
1673 /// }
1674 ///
1675 /// Ok(())
1676 /// })
1677 /// .await?;
1678 /// ```
1679 fn stream_orders_by_id<'a, F>(
1680 &'a self,
1681 order_ids: &'a str,
1682 mut on_chunk: &'a mut F,
1683 client: &'a mut Client,
1684 ) -> Self::StreamOrdersByIdFuture<'a>
1685 where
1686 F: FnMut(StreamOrdersResp) -> Result<(), Error> + Send + 'a,
1687 {
1688 let account_ids: Vec<&str> = self
1689 .iter()
1690 .map(|account| account.account_id.as_str())
1691 .collect();
1692
1693 Box::pin(async move {
1694 let orders = Account::stream_orders_by_id_for_accounts(
1695 client,
1696 order_ids,
1697 account_ids,
1698 &mut on_chunk,
1699 )
1700 .await?;
1701 Ok(orders)
1702 })
1703 }
1704
1705 type StreamPositionsFuture<'a> = Pin<
1706 Box<
1707 dyn Future<Output = Result<Vec<Position>, Box<dyn StdErrorTrait + Send + Sync>>>
1708 + Send
1709 + 'a,
1710 >,
1711 >;
1712 /// Stream `Position`s for the given `Account`(s).
1713 ///
1714 /// NOTE: TODO: Currently does NOT support streaming `Position` changes.
1715 ///
1716 /// # Example
1717 /// ---
1718 ///
1719 /// Collect losing trades into a vector and do something with them.
1720 /// ```ignore
1721 /// let mut losing_positions: Vec<Position> = Vec::new();
1722 /// specific_account
1723 /// .stream_positions(&mut client, |stream_data| {
1724 /// // the response type is `responses::account::StreamPositionsResp`
1725 /// // which has multiple variants the main one you care about is
1726 /// // `order` which will contain order data sent from the stream.
1727 /// match stream_data {
1728 /// StreamPositionsResp::Position(position) => {
1729 /// // response for an `position` streamed in
1730 /// println!("{position:?}");
1731 ///
1732 /// if position.long_short == PositionType::Long {
1733 /// if position.last < position.average_price {
1734 /// losing_positions.push(*position)
1735 /// }
1736 /// } else if position.long_short == PositionType::Short {
1737 /// if position.last > position.average_price {
1738 /// losing_positions.push(*position)
1739 /// }
1740 /// }
1741 ///
1742 /// // do something with the list of losing trades
1743 /// // maybe send email or text of the positions
1744 /// println!("{losing_positions:?}");
1745 /// }
1746 /// StreamPositionsResp::Heartbeat(heartbeat) => {
1747 /// // response for periodic signals letting you know the connection is
1748 /// // still alive. a heartbeat is sent every 5 seconds of inactivity.
1749 /// println!("{heartbeat:?}");
1750 ///
1751 /// // for the sake of this example after we recieve the
1752 /// // tenth heartbeat, we will stop the stream session.
1753 /// if heartbeat.heartbeat > 10 {
1754 /// // example: stopping a stream connection
1755 /// return Err(Error::StopStream);
1756 /// }
1757 /// }
1758 /// StreamPositionsResp::Status(status) => {
1759 /// // signal sent on state changes in the stream
1760 /// // (closed, opened, paused, resumed)
1761 /// println!("{status:?}");
1762 /// }
1763 /// StreamPositionsResp::Error(err) => {
1764 /// // response for when an error was encountered,
1765 /// // with details on the error
1766 /// println!("{err:?}");
1767 /// }
1768 /// }
1769 ///
1770 /// Ok(())
1771 /// })
1772 /// .await?;
1773 /// ```
1774 fn stream_positions<'a, F>(
1775 &'a self,
1776 mut on_chunk: &'a mut F,
1777 client: &'a mut Client,
1778 ) -> Self::StreamPositionsFuture<'a>
1779 where
1780 F: FnMut(StreamPositionsResp) -> Result<(), Error> + Send + 'a,
1781 {
1782 let account_ids: Vec<&str> = self
1783 .iter()
1784 .map(|account| account.account_id.as_str())
1785 .collect();
1786
1787 Box::pin(async move {
1788 let positions =
1789 Account::stream_positions_for_accounts(client, account_ids, &mut on_chunk).await?;
1790 Ok(positions)
1791 })
1792 }
1793}
1794
1795#[derive(Clone, Debug, Deserialize, Serialize)]
1796#[serde(rename_all = "PascalCase")]
1797/// Deeper Details for an `Account`
1798pub struct AccountDetail {
1799 /// Can account locate securities for borrowing?
1800 ///
1801 /// For example if you want to short a stock, you need
1802 /// to "locate" shares to borrow and sell.
1803 is_stock_locate_eligible: bool,
1804 /// Is account enrolled with Regulation T ?
1805 ///
1806 /// Regulation T governs cash accounts and the amount of credit that
1807 /// broker-dealers can extend to investors for the purchase of securities.
1808 enrolled_in_reg_t_program: bool,
1809 /// Does the account require a buying power warning before order execution?
1810 ///
1811 /// TradeStation uses the greater of Overnight Buying Power or Day Trade
1812 /// Buying Power to determine if an order should be accepted or rejected.
1813 ///
1814 /// In cases where an order exceeds both values, the order will be rejected.
1815 /// If the order exceeds only one of the values, a Buying Power Warning will
1816 /// appear to notify you that the order could result in a margin call.
1817 requires_buying_power_warning: bool,
1818 /// Is the `Account` qualified for day trading?
1819 ///
1820 /// An `Account` MUST maintain a minimum equity balance of $25,000
1821 /// to be qualified for day trades. *(As per TradeStation compliance rules)*
1822 day_trading_qualified: bool,
1823 /// What options level is the `Account` approved for?
1824 ///
1825 /// The option approval level will determine what options strategies you will
1826 /// be able to employ on `Account`. In general terms, the levels are defined as:
1827 /// Level 0: No options trading allowed.
1828 /// Level 1: Writing of Covered Calls, Buying Protective Puts.
1829 /// Level 2: Level 1 + Buying Calls, Buying Puts, Writing Covered Puts.
1830 /// Level 3: Level 2 + Stock Option Spreads, Index Option Spreads, Butterfly Spreads, Condor Spreads, Iron Butterfly Spreads, Iron Condor Spreads.
1831 /// Level 4: Level 3 + Writing of Naked Puts (Stock Options).
1832 /// Level 5: Level 4 + Writing of Naked Puts (Index Options), Writing of Naked Calls (Stock Options), Writing of Naked Calls (Index Options).
1833 ///
1834 /// These levels vary depending on the funding and type of account.
1835 // TODO: Make enum for this
1836 option_approval_level: u8,
1837 /// Is the `Account` a Pattern Day Trader?
1838 ///
1839 /// As per FINRA rules, an `Account` will be considered a pattern day trader
1840 /// if it day-trades 4 or more times in 5 business days and it's day-trading
1841 /// activities are greater than 6 percent of it's total trading activity for
1842 /// that same five-day period.
1843 ///
1844 /// A pattern day trader must maintain minimum equity of $25,000 on any day
1845 /// that the customer day trades. If the account falls below the $25,000
1846 /// requirement, the pattern day trader will not be permitted to day trade
1847 /// until the account is restored to the $25,000 minimum equity level.
1848 pattern_day_trader: bool,
1849 /// Is the `Account` enabled to trade crypto?
1850 ///
1851 /// NOTE: As of 2024 TradeStation no longer offer's crypto trading.
1852 crypto_enabled: bool,
1853}
1854
1855#[derive(Clone, Debug, Deserialize, Serialize)]
1856#[serde(rename_all = "PascalCase")]
1857/// The real time balance of an `Account`.
1858pub struct Balance {
1859 #[serde(rename = "AccountID")]
1860 /// The main identifier for a TradeStation account
1861 pub account_id: String,
1862 /// The type of account, examples: "Cash" or "Margin"
1863 pub account_type: String,
1864 /// The real time Cash Balance value for the `Account`
1865 pub cash_balance: String,
1866 /// The real time Buying Power value for the `Account`
1867 pub buying_power: String,
1868 /// The real time Equity value for the `Account`
1869 pub equity: String,
1870 /// The real time Market Value for the `Account`
1871 pub market_value: String,
1872 #[serde(rename = "TodaysProfitLoss")]
1873 /// The real time (profit - loss) value for the `Account` over a 24 hour period
1874 pub todays_pnl: String,
1875 /// The value of uncleared funds for the `Account`
1876 pub uncleared_deposit: String,
1877 /// Deeper details on the `Balance` of an `Account`
1878 pub balance_detail: BalanceDetail,
1879 /// The amount paid in brokerage commissions.
1880 ///
1881 /// NOTE: This value does not include slippage.
1882 pub commission: String,
1883}
1884#[derive(Clone, Debug, Deserialize, Serialize)]
1885#[serde(rename_all = "PascalCase")]
1886/// Real time balance information for an `Account`.
1887pub struct BalanceDetail {
1888 /// The real time cost for all positions open in the `Account`
1889 ///
1890 /// NOTE: Positions are based on the actual entry price
1891 pub cost_of_positions: Option<String>,
1892 /// The number of day trades the `Account` has taken over the previous 4 days
1893 ///
1894 /// NOTE: This updates daily
1895 ///
1896 /// NOTE: This is always None for futures `Account`.
1897 pub day_trades: Option<String>,
1898 /// The real time dollar amount of required funds for `Account` margin maintenance
1899 ///
1900 /// NOTE: SUM(maintenance margin of all open positions in the account).
1901 ///
1902 /// NOTE: This is always None for futures `Account`.
1903 pub maintenance_rate: Option<String>,
1904 /// The real time value of intraday buying power for options
1905 ///
1906 /// NOTE: This is always None for futures `Account`.
1907 pub option_buying_power: Option<String>,
1908 /// The real time Market Value of current open option positions in an `Account`.
1909 pub options_market_value: Option<String>,
1910 /// The real time Buying Power value that can be held overnight w/o triggering a margin call.
1911 ///
1912 /// NOTE: (Equity - Overnight Requirement %) / 50 %.
1913 pub overnight_buying_power: Option<String>,
1914 /// The real time dollar value of open order Day Trade Margins for an `Account`.
1915 ///
1916 /// NOTE: SUM(Day Trade Margin of all open orders in the account).
1917 ///
1918 /// NOTE: Always `None` for cash & margin accounts
1919 pub day_trade_open_order_margin: Option<String>,
1920 /// The real time dollar value of open order Initial Margin for an `Account`.
1921 ///
1922 /// NOTE: SUM(Initial Margin of all open orders in the account).
1923 ///
1924 /// NOTE: Always `None` for cash & margin accounts.
1925 pub open_order_margin: Option<String>,
1926 /// The real time dollar value of Initial Margin for an `Account`.
1927 ///
1928 /// NOTE: SUM(Initial Margin of all open positions in the account).
1929 pub initial_margin: Option<String>,
1930 /// The real time dollar value of Maintenance Margin for an `Account`.
1931 ///
1932 /// NOTE: SUM(Maintenance Margins of all open positions in the account).
1933 ///
1934 /// NOTE: Always `None` for cash & margin accounts.
1935 pub maintenance_margin: Option<String>,
1936 /// The real time dollar amount of Trade Equity for an `Account`.
1937 ///
1938 /// NOTE: Always `None` for cash & margin accounts.
1939 pub trade_equity: Option<String>,
1940 /// The value of special securities deposited with the clearing firm
1941 /// for the sole purpose of increasing purchasing power in `Account`
1942 ///
1943 /// NOTE: This number will be reset daily by the account balances clearing file.
1944 ///
1945 /// NOTE: The entire value of this field will increase purchasing power.
1946 ///
1947 /// NOTE: Always `None` for cash & margin accounts.
1948 pub security_on_deposit: Option<String>,
1949 /// The real time dollar value of Today's Trade Equity for an `Account`.
1950 ///
1951 /// NOTE: (Beginning Day Trade Equity - Real Time Trade Equity).
1952 pub today_real_time_trade_equity: Option<String>,
1953 /// Deeper details on base currency.
1954 ///
1955 /// NOTE: Always `None` for cash & margin accounts.
1956 pub currency_details: Option<CurrencyDetails>,
1957 /// The real time amount of required funds for `Account` margin maintenance.
1958 ///
1959 /// NOTE: The currency denomination is dependant on `Account::currency`.
1960 ///
1961 /// NOTE: SUM(maintenance margin of all open positions in the account).
1962 ///
1963 /// NOTE: Always `None` for futures accounts.
1964 pub required_margin: Option<String>,
1965 /// Funds received by TradeStation that are not settled from a transaction in the `Account`.
1966 ///
1967 /// NOTE: Always `None` for futures accounts.
1968 pub unsettled_funds: Option<String>,
1969 /// Maintenance Excess.
1970 ///
1971 /// NOTE: (Cash Balance + Long Market Value + Short Credit - Maintenance Requirement - Margin Debt - Short Market Value).
1972 pub day_trade_excess: String,
1973 #[serde(rename = "RealizedProfitLoss")]
1974 /// The net Realized Profit or Loss of an `Account` for the current trading day.
1975 ///
1976 /// NOTE: This includes all commissions and routing fees.
1977 pub realized_pnl: String,
1978 #[serde(rename = "UnrealizedProfitLoss")]
1979 /// The net Unrealized Profit or Loss of an `Account` for all currently open positions.
1980 ///
1981 /// NOTE: This does not include commissions or routing fees.
1982 pub unrealized_pnl: String,
1983}
1984
1985#[derive(Clone, Debug, Deserialize, Serialize)]
1986#[serde(rename_all = "PascalCase")]
1987/// The beginning of day balance of an `Account`.
1988pub struct BODBalance {
1989 #[serde(rename = "AccountID")]
1990 /// The main identifier for a TradeStation account.
1991 pub account_id: String,
1992 /// The type of account, examples: "Cash" or "Margin".
1993 pub account_type: String,
1994 /// Deeper details on the `Balance` of an `Account`.
1995 pub balance_detail: BODBalanceDetail,
1996 /// Deeper details on the `Currency` local of an `Account`.
1997 ///
1998 /// NOTE: Only applies to futures.
1999 pub currency_details: Option<Vec<BODCurrencyDetails>>,
2000}
2001#[derive(Clone, Debug, Deserialize, Serialize)]
2002#[serde(rename_all = "PascalCase")]
2003/// The beginning of day balance information of an `Account`.
2004pub struct BODBalanceDetail {
2005 /// The amount of cash in the account at the beginning of the day.
2006 ///
2007 /// NOTE: Only applies to equities.
2008 pub account_balance: Option<String>,
2009 /// Beginning of day value for cash available to withdraw
2010 pub cash_available_to_withdraw: Option<String>,
2011 /// The number of day trades placed in the account within the previous
2012 /// 4 trading days.
2013 ///
2014 /// NOTE: Only applies to equities.
2015 pub day_trades: Option<String>,
2016 /// The Intraday Buying Power with which the account started the trading day.
2017 ///
2018 /// NOTE: Only applies to equities.
2019 pub day_trading_marginable_buying_power: Option<String>,
2020 /// The total amount of equity with which you started the current trading day.
2021 pub equity: String,
2022 /// The amount of cash in the account at the beginning of the day.
2023 pub net_cash: String,
2024 /// Unrealized profit and loss at the beginning of the day.
2025 ///
2026 /// NOTE: Only applies to futures.
2027 pub open_trade_equity: Option<String>,
2028 /// Option buying power at the start of the trading day.
2029 ///
2030 /// NOTE: Only applies to equities.
2031 pub option_buying_power: Option<String>,
2032 /// Intraday liquidation value of option positions.
2033 ///
2034 /// NOTE: Only applies to equities.
2035 pub option_value: Option<String>,
2036 /// Overnight Buying Power (Regulation T) at the start of the trading day.
2037 ///
2038 /// NOTE: Only applies to equities.
2039 pub overnight_buying_power: Option<String>,
2040 /// The value of special securities that are deposited by the customer with
2041 /// the clearing firm for the sole purpose of increasing purchasing power in
2042 /// their trading account.
2043 ///
2044 /// NOTE: Only applies to futures.
2045 pub security_on_deposit: Option<String>,
2046}
2047#[derive(Clone, Debug, Deserialize, Serialize)]
2048#[serde(rename_all = "PascalCase")]
2049/// The beginning of day currency information.
2050///
2051/// NOTE: Only applies to futures.
2052pub struct BODCurrencyDetails {
2053 /// The dollar amount of Beginning Day Margin for the given forex account
2054 pub account_margin_requirement: Option<String>,
2055 /// The dollar amount of Beginning Day Trade Equity for the given account
2056 pub account_open_trade_equity: String,
2057 /// The value of special securities that are deposited by the customer with
2058 /// the clearing firm for the sole purpose of increasing purchasing power in
2059 /// their trading account.
2060 ///
2061 /// NOTE: This number will be reset daily by the account balances
2062 /// clearing file.
2063 ///
2064 /// NOTE: The entire value of this field will increase purchasing power
2065 pub account_securities: String,
2066 /// The dollar amount of the Beginning Day Cash Balance for the given account
2067 pub cash_balance: String,
2068 /// The currency of the entity
2069 pub currency: String,
2070 /// The dollar amount of Beginning Day Margin for the given forex account
2071 pub margin_requirement: Option<String>,
2072 /// The dollar amount of Beginning Day Trade Equity for the given account
2073 pub open_trade_equity: String,
2074 /// Indicates the dollar amount of Beginning Day Securities
2075 pub securities: String,
2076}
2077
2078#[derive(Clone, Debug, Deserialize, Serialize)]
2079#[serde(rename_all = "PascalCase")]
2080/// The properties that describe balance characteristics in different currencies.
2081///
2082/// NOTE: Only applies to futures.
2083pub struct CurrencyDetails {
2084 /// Base currency.
2085 currency: String,
2086 /// The net Unrealized Profit or Loss for all currently open positions.
2087 ///
2088 /// NOTE: This does not include commissions or routing fees.
2089 commission: String,
2090 /// The real time value of an `Account`(s) Cash Balance.
2091 cash_balance: String,
2092 #[serde(rename = "RealizedProfitLoss")]
2093 /// The net Realized Profit or Loss of an `Account` for the current trading day.
2094 ///
2095 /// NOTE: This includes all commissions and routing fees.
2096 realized_pnl: String,
2097 #[serde(rename = "UnrealizedProfitLoss")]
2098 /// The net Unrealized Profit or Loss of an `Account` for all currently open positions.
2099 ///
2100 /// NOTE: This does not include commissions or routing fees.
2101 unrealized_pnl: String,
2102 /// The real time dollar value of Initial Margin for an `Account`.
2103 ///
2104 /// NOTE: SUM(Initial Margin of all open positions in the account).
2105 initial_margin: String,
2106 /// The real time dollar value of Maintenance Margin for an `Account`.
2107 ///
2108 /// NOTE: SUM(Maintenance Margins of all open positions in the account).
2109 maintenance_margin: String,
2110 /// The real time conversion rate used to translate value from symbol currency to `Account` currency.
2111 account_conversion_rate: String,
2112}
2113
2114#[derive(Clone, Debug, Deserialize, Serialize)]
2115#[serde(rename_all = "PascalCase")]
2116/// An order to open, close, add, or trim positions.
2117pub struct Order {
2118 #[serde(rename = "AccountID")]
2119 /// The `Account` id to the this `Order` belongs to.
2120 pub account_id: String,
2121 /// The `Order rules` or brackets.
2122 pub advanced_options: Option<String>,
2123 /// The Closed Date Time of this `Order`.
2124 pub closed_date_time: Option<String>,
2125 /// The actual brokerage commission cost and routing fees
2126 /// for a trade based on the number of shares or contracts.
2127 pub commission_fee: String,
2128 /// The relationship between linked `Order`(s) in a group
2129 /// and this `Order` in specific.
2130 pub conditional_orders: Option<ConditionalOrder>,
2131 /// The rate used to convert from the currency of
2132 /// the symbol to the currency of the account.
2133 pub conversion_rate: Option<String>,
2134 /// The currency used to complete the `Order`.
2135 pub currency: String,
2136 /// The amount of time for which the `Order` is valid.
2137 pub duration: String,
2138 /// At the top level, this is the average fill price.
2139 ///
2140 /// At the expanded levels, this is the actual execution price.
2141 pub filled_price: Option<String>,
2142 /// The expiration date-time for the `Order`
2143 ///
2144 /// NOTE: The time portion, if `"T:00:00:00Z"`, should be ignored.
2145 pub good_till_date: Option<String>,
2146 /// An identifier for `Order`(s) that are part of the same bracket.
2147 pub group_name: Option<String>,
2148 /// Legs (multi step/part trade) associated with this `Order`
2149 pub legs: Vec<OrderLeg>,
2150 /// Allows you to specify when an order will be placed based on
2151 /// the price action of one or more symbols.
2152 // TODO: Should I convert None to empty vector ?
2153 pub market_activation_rules: Option<Vec<MarketActivationRule>>,
2154 /// Allows you to specify a time that an `Order` will be placed.
2155 // TODO: Should I convert None to empty vector ?
2156 pub time_activation_rules: Option<Vec<TimeActivationRule>>,
2157 /// The limit price for Limit and Stop Limit `Order`(s).
2158 pub limit_price: Option<String>,
2159 /// Time the `Order` was placed.
2160 pub opened_date_time: String,
2161 #[serde(rename = "OrderID")]
2162 /// The `Order` id.
2163 pub order_id: String,
2164 /// The type of `Order` this is.
2165 pub order_type: OrderType,
2166 /// Price used for the buying power calculation of the `Order`.
2167 pub price_used_for_buying_power: String,
2168 /// Identifies the routing selection made by the customer when
2169 /// placing the `Order`.
2170 ///
2171 /// NOTE: ONLY valid for Equities.
2172 pub routing: Option<String>,
2173 /// Hides the true number of shares intended to be bought or sold.
2174 ///
2175 /// NOTE: ONLY valid for `OrderType::Limit` or `Order::Type::StopLimit`
2176 /// `Order`(s).
2177 ///
2178 /// NOTE: Not valid for all exchanges.
2179 pub show_only_quantity: Option<String>,
2180 /// The spread type for an option `Order`
2181 pub spread: Option<String>,
2182 /// The status of an `Order`
2183 // TODO: make enum for this.
2184 pub status: String,
2185 /// Description of the `Order` status
2186 pub status_description: String,
2187 /// The stop price for `OrderType::StopLimit` and
2188 /// `OrderType::StopMarket` orders.
2189 pub stop_price: Option<String>,
2190 /// TrailingStop offset.
2191 ///
2192 /// NOTE: amount or percent.
2193 pub trailing_stop: Option<TrailingStop>,
2194 /// Only applies to equities.
2195 ///
2196 /// NOTE: Will contain a value if the order has received a routing fee.
2197 pub unbundled_route_fee: Option<String>,
2198}
2199
2200#[derive(Clone, Debug, Deserialize, Serialize)]
2201#[serde(rename_all = "PascalCase")]
2202pub struct TrailingStop {
2203 /// Currency Offset from current price.
2204 ///
2205 /// NOTE: Mutually exclusive with Percent.
2206 pub amount: Option<String>,
2207 /// Percentage offset from current price.
2208 ///
2209 /// NOTE: Mutually exclusive with Amount.
2210 pub percent: Option<String>,
2211}
2212
2213#[derive(Clone, Debug, Deserialize, Serialize)]
2214/// Types of `Order`(s).
2215pub enum OrderType {
2216 /// Limit Order
2217 Limit,
2218 /// Market Order
2219 Market,
2220 /// Stop Loss At Market Order
2221 StopMarket,
2222 /// Stop Loss At Limit Order
2223 StopLimit,
2224}
2225
2226#[derive(Clone, Debug, Deserialize, Serialize)]
2227#[serde(rename_all = "PascalCase")]
2228/// Allows you to specify a time that an `Order` will be placed.
2229pub struct TimeActivationRule {
2230 /// Timestamp represented as an RFC3339 formatted date.
2231 time_utc: String,
2232}
2233
2234#[derive(Clone, Debug, Deserialize, Serialize)]
2235#[serde(rename_all = "PascalCase")]
2236/// Allows you to specify when an order will be placed
2237/// based on the price action of one or more symbols.
2238pub struct MarketActivationRule {
2239 /// The type of activation rule.
2240 ///
2241 /// NOTE: Currently only supports `"Price"` for now.
2242 pub rule_type: String,
2243 /// The symbol that the rule is based on.
2244 pub symbol: String,
2245 /// The type of comparison predicate the rule is based on.
2246 pub predicate: Predicate,
2247 /// The ticks behavior for the activation rule.
2248 pub tigger_key: TickTrigger,
2249 /// The price at which the rule will trigger.
2250 pub price: Option<String>,
2251 /// Relation with the previous activation rule.
2252 ///
2253 /// NOTE: The first rule will never have a logic operator.
2254 pub logic_operator: Option<LogicOp>,
2255}
2256
2257#[derive(Clone, Debug, Deserialize, Serialize)]
2258/// Logic Operators
2259pub enum LogicOp {
2260 And,
2261 Or,
2262}
2263
2264#[derive(Clone, Debug, Deserialize, Serialize)]
2265/// Types of tick triggers
2266pub enum TickTrigger {
2267 /// Single Trade Tick
2268 STT,
2269 /// Single Trade Tick Within NBBO
2270 STTN,
2271 /// Single Bid/Ask Tick
2272 SBA,
2273 /// Single Ask/Bid Tick
2274 SAB,
2275 /// Double Trade Tick
2276 DTT,
2277 /// Double Trade Tick Within NBBO
2278 DTTN,
2279 /// Double Bid/Ask Tick
2280 DBA,
2281 /// Double Ask/Bid Tick
2282 DAB,
2283 /// Triple Trade Tick
2284 TTT,
2285 /// Triple Trade Tick Within NBBO
2286 TTTN,
2287 /// Triple Bid/Ask Tick
2288 TBA,
2289 /// Triple Ask/Bid Tick
2290 TAB,
2291}
2292
2293#[derive(Clone, Debug, Deserialize, Serialize)]
2294/// Types of comparison predicates
2295pub enum Predicate {
2296 /// Less than
2297 Lt,
2298 /// Less than or Equal
2299 Lte,
2300 /// Greater than
2301 Gt,
2302 /// Greater than or Equal
2303 Gte,
2304}
2305
2306#[derive(Clone, Debug, Deserialize, Serialize)]
2307#[serde(rename_all = "PascalCase")]
2308/// Order Leg associated with this `Order`
2309pub struct OrderLeg {
2310 /// Indicates the asset type of the `Order`.
2311 pub asset_type: AssetType,
2312 /// identifier for the `Order` action (buying or selling)
2313 pub buy_or_sell: OrderAction,
2314 /// Number of shares or contracts that have been executed.
2315 pub exec_quantity: String,
2316 /// The price at which `Order` execution occurred.
2317 pub execution_price: Option<String>,
2318 /// The expiration date of the future or option contract.
2319 pub expiration_date: Option<String>,
2320 /// The stage of the `Order` , is it opening or closing?
2321 pub open_or_close: Option<OrderStage>,
2322 /// The type of option
2323 pub option_type: Option<OptionType>,
2324 /// Number of shares or contracts being purchased or sold.
2325 pub quantity_ordered: String,
2326 /// In a partially filled `Order` , this is the number of shares
2327 /// or contracts the have NOT yet been filled.
2328 pub quantity_remaining: String,
2329 /// The price at which the holder of an options contract can buy
2330 /// or sell the underlying asset.
2331 ///
2332 /// NOTE: ONLY for option `Order`(s).
2333 pub strike_price: Option<String>,
2334 /// The securities symbol the `Order` is for.
2335 pub symbol: String,
2336 /// The underlying securities symbol the `Order` is for.
2337 ///
2338 /// NOTE: ONLY for futures and options.
2339 pub underlying: Option<String>,
2340}
2341
2342#[derive(Clone, Debug, Deserialize, Serialize)]
2343/// The type of option
2344pub enum OptionType {
2345 #[serde(rename = "CALL")]
2346 /// Call Option
2347 Call,
2348 #[serde(rename = "PUT")]
2349 /// Put Option
2350 Put,
2351}
2352
2353#[derive(Clone, Debug, Deserialize, Serialize)]
2354/// The stage of the `Order` , is it opening or closing?
2355pub enum OrderStage {
2356 /// Order to open position.
2357 Open,
2358 /// Order to close position.
2359 Close,
2360}
2361
2362#[derive(Clone, Debug, Deserialize, Serialize)]
2363/// The different types of order actions.
2364pub enum OrderAction {
2365 /// Buying to open.
2366 Buy,
2367 /// Selling to close.
2368 Sell,
2369 /// Open a short position.
2370 SellShort,
2371 /// Closing a short position.
2372 BuyToCover,
2373}
2374
2375#[derive(Clone, Debug, Deserialize, Serialize)]
2376/// The different types of asset's.
2377pub enum AssetType {
2378 #[serde(rename = "UNKNOWN")]
2379 Unknown,
2380 #[serde(rename = "STOCK")]
2381 Stock,
2382 #[serde(rename = "STOCKOPTION")]
2383 StockOption,
2384 #[serde(rename = "FUTURE")]
2385 Future,
2386 #[serde(rename = "FUTUREOPTION")]
2387 FutureOption,
2388 #[serde(rename = "FOREX")]
2389 Forex,
2390 #[serde(rename = "CURRENCYOPTION")]
2391 CurrencyOption,
2392 #[serde(rename = "INDEX")]
2393 Index,
2394 #[serde(rename = "INDEXOPTION")]
2395 IndexOption,
2396}
2397
2398#[derive(Clone, Debug, Deserialize, Serialize)]
2399#[serde(rename_all = "PascalCase")]
2400/// Describes the relationship between linked
2401/// orders in a group and this order.
2402pub struct ConditionalOrder {
2403 #[serde(rename = "OrderID")]
2404 /// The id of the linked `Order`.
2405 pub order_id: String,
2406 /// The relationship of a linked order within a group order
2407 /// to the current returned `Order`.
2408 pub relationship: OrderRelationship,
2409}
2410
2411#[derive(Clone, Debug, Deserialize, Serialize)]
2412/// Types of `Order` relationships
2413pub enum OrderRelationship {
2414 BRK,
2415 OSP,
2416 OSO,
2417 OCO,
2418}
2419
2420#[derive(Clone, Debug, Deserialize, Serialize)]
2421#[serde(rename_all = "PascalCase")]
2422/// The open trades (positons).
2423pub struct Position {
2424 #[serde(rename = "AccountID")]
2425 /// The `Account` id the `Position` belongs to.
2426 pub account_id: String,
2427 /// Indicates the asset type of the position.
2428 // NOTE: use enum
2429 pub asset_type: String,
2430 /// The average price of the position currently held.
2431 pub average_price: String,
2432 /// The highest price a prospective buyer is prepared to pay at
2433 /// a particular time for a trading unit of a given symbol.
2434 pub bid: String,
2435 /// The price at which a security, futures contract, or other
2436 /// financial instrument is offered for sale.
2437 pub ask: String,
2438 /// The currency conversion rate that is used in order to convert
2439 /// from the currency of the symbol to the currency of the account.
2440 pub conversion_rate: String,
2441 /// DayTradeMargin used on open positions.
2442 ///
2443 /// NOTE: Currently only calculated for futures positions.
2444 /// Other asset classes will have a 0 for this value.
2445 pub day_trade_requirement: String,
2446 /// The UTC formatted expiration date of the future or option symbol,
2447 /// in the country the contract is traded in.
2448 ///
2449 /// NOTE: The time portion of the value should be ignored.
2450 pub expiration_date: Option<String>,
2451 /// The margin account balance denominated in the symbol currency required
2452 /// for entering a position on margin.
2453 ///
2454 /// NOTE: Only applies to future and option positions.
2455 pub initial_requirement: String,
2456 /// The last price at which the symbol traded.
2457 pub last: String,
2458 /// Specifies if the position is Long or Short.
2459 pub long_short: PositionType,
2460 /// The MarkToMarketPrice value is the weighted average of the previous close
2461 /// price for the position quantity held overnight and the purchase price of the
2462 /// position quantity opened during the current market session.
2463 ///
2464 /// NOTE: This value is used to calculate TodaysProfitLoss.
2465 ///
2466 /// NOTE: Only applies to equity and option positions.
2467 pub mark_to_market_price: String,
2468 /// The actual market value denominated in the symbol currency of the open position.
2469 ///
2470 /// NOTE: This value is updated in real-time.
2471 pub market_value: String,
2472 #[serde(rename = "PositionID")]
2473 /// A unique identifier for the position.
2474 pub position_id: String,
2475 /// The number of shares or contracts for a particular position.
2476 ///
2477 /// NOTE: This value is negative for short positions.
2478 pub quantity: String,
2479 /// Symbol of the position.
2480 pub symbol: String,
2481 /// Time the position was entered.
2482 pub timestamp: String,
2483 /// The unrealized profit or loss denominated in the account currency on the position
2484 /// held, calculated using the MarkToMarketPrice.
2485 ///
2486 /// NOTE: Only applies to equity and option positions.
2487 #[serde(rename = "TodaysProfitLoss")]
2488 pub todays_pnl: String,
2489 /// The total cost denominated in the account currency of the open position.
2490 pub total_cost: String,
2491 #[serde(rename = "UnrealizedProfitLoss")]
2492 /// The unrealized profit or loss denominated in the symbol currency on the position
2493 /// held, calculated based on the average price of the position.
2494 pub unrealized_pnl: String,
2495 #[serde(rename = "UnrealizedProfitLossPercent")]
2496 /// The unrealized profit or loss on the position expressed as a percentage of the
2497 /// initial value of the position.
2498 pub unrealized_pnl_percent: String,
2499 #[serde(rename = "UnrealizedProfitLossQty")]
2500 /// The unrealized profit or loss denominated in the account currency divided by the
2501 /// number of shares, contracts or units held.
2502 pub unrealized_pnl_qty: String,
2503}
2504
2505#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
2506/// A position type can either be short or long
2507pub enum PositionType {
2508 /// Long a share, or futures/options contract
2509 Long,
2510 /// Short a share, or futures/options contract
2511 Short,
2512}
2513
2514impl Client {
2515 /// Get all of your registered TradeStation `Accounts`
2516 pub async fn get_accounts(&mut self) -> Result<Vec<Account>, Error> {
2517 Account::get_all(self).await
2518 }
2519
2520 /// Get a specific TradeStation `Account` by it's account id
2521 pub async fn get_account(&mut self, account_id: &str) -> Result<Account, Error> {
2522 Account::get(self, account_id).await
2523 }
2524
2525 /// Get the current balance of a specific `Account` by it's account id
2526 pub async fn get_account_balance(&mut self, account_id: &str) -> Result<Balance, Error> {
2527 let mut balances = Account::get_balances_by_ids(self, vec![account_id]).await?;
2528 if balances.len() == 1 {
2529 // NOTE: This unwrap is panic safe due to invariant above
2530 let balance = balances.pop().unwrap();
2531 Ok(balance)
2532 } else {
2533 Err(Error::AccountNotFound)
2534 }
2535 }
2536
2537 /// Get the current balance of all `Account`(s)
2538 pub async fn get_account_balances(
2539 &mut self,
2540 account_ids: Vec<&str>,
2541 ) -> Result<Vec<Balance>, Error> {
2542 Account::get_balances_by_ids(self, account_ids).await
2543 }
2544}