1#![allow(unused_imports)]
15use async_trait::async_trait;
16use derive_builder::Builder;
17use reqwest;
18use rust_decimal::prelude::*;
19use serde::{Deserialize, Serialize};
20use serde_json::{Value, json};
21use std::collections::BTreeMap;
22
23use crate::common::{
24 config::ConfigurationRestApi,
25 models::{ParamBuildError, RestApiResponse},
26 utils::send_request,
27};
28use crate::stocks::rest_api::models;
29
30const HAS_TIME_UNIT: bool = false;
31
32#[async_trait]
33pub trait TradeApi: Send + Sync {
34 async fn cancel_all_equity_orders(
35 &self,
36 params: CancelAllEquityOrdersParams,
37 ) -> anyhow::Result<RestApiResponse<models::CancelAllEquityOrdersResponse>>;
38 async fn cancel_equity_order(
39 &self,
40 params: CancelEquityOrderParams,
41 ) -> anyhow::Result<RestApiResponse<models::CancelEquityOrderResponse>>;
42 async fn current_open_orders(
43 &self,
44 params: CurrentOpenOrdersParams,
45 ) -> anyhow::Result<RestApiResponse<Vec<models::CurrentOpenOrdersResponseInner>>>;
46 async fn equity_order_detail(
47 &self,
48 params: EquityOrderDetailParams,
49 ) -> anyhow::Result<RestApiResponse<models::EquityOrderDetailResponse>>;
50 async fn equity_order_history(
51 &self,
52 params: EquityOrderHistoryParams,
53 ) -> anyhow::Result<RestApiResponse<models::EquityOrderHistoryResponse>>;
54 async fn equity_trade_history(
55 &self,
56 params: EquityTradeHistoryParams,
57 ) -> anyhow::Result<RestApiResponse<models::EquityTradeHistoryResponse>>;
58 async fn place_equity_order(
59 &self,
60 params: PlaceEquityOrderParams,
61 ) -> anyhow::Result<RestApiResponse<models::PlaceEquityOrderResponse>>;
62}
63
64#[derive(Debug, Clone)]
65pub struct TradeApiClient {
66 configuration: ConfigurationRestApi,
67}
68
69impl TradeApiClient {
70 pub fn new(configuration: ConfigurationRestApi) -> Self {
71 Self { configuration }
72 }
73}
74
75#[allow(non_camel_case_types)]
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub enum EquityOrderHistoryOrderTypeEnum {
78 #[serde(rename = "MARKET")]
79 Market,
80 #[serde(rename = "LIMIT")]
81 Limit,
82}
83
84impl EquityOrderHistoryOrderTypeEnum {
85 #[must_use]
86 pub fn as_str(&self) -> &'static str {
87 match self {
88 Self::Market => "MARKET",
89 Self::Limit => "LIMIT",
90 }
91 }
92}
93
94impl std::str::FromStr for EquityOrderHistoryOrderTypeEnum {
95 type Err = Box<dyn std::error::Error + Send + Sync>;
96
97 fn from_str(s: &str) -> Result<Self, Self::Err> {
98 match s {
99 "MARKET" => Ok(Self::Market),
100 "LIMIT" => Ok(Self::Limit),
101 other => Err(format!("invalid EquityOrderHistoryOrderTypeEnum: {}", other).into()),
102 }
103 }
104}
105
106#[allow(non_camel_case_types)]
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub enum EquityOrderHistorySideEnum {
109 #[serde(rename = "BUY")]
110 Buy,
111 #[serde(rename = "SELL")]
112 Sell,
113}
114
115impl EquityOrderHistorySideEnum {
116 #[must_use]
117 pub fn as_str(&self) -> &'static str {
118 match self {
119 Self::Buy => "BUY",
120 Self::Sell => "SELL",
121 }
122 }
123}
124
125impl std::str::FromStr for EquityOrderHistorySideEnum {
126 type Err = Box<dyn std::error::Error + Send + Sync>;
127
128 fn from_str(s: &str) -> Result<Self, Self::Err> {
129 match s {
130 "BUY" => Ok(Self::Buy),
131 "SELL" => Ok(Self::Sell),
132 other => Err(format!("invalid EquityOrderHistorySideEnum: {}", other).into()),
133 }
134 }
135}
136
137#[allow(non_camel_case_types)]
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum EquityTradeHistorySideEnum {
140 #[serde(rename = "BUY")]
141 Buy,
142 #[serde(rename = "SELL")]
143 Sell,
144}
145
146impl EquityTradeHistorySideEnum {
147 #[must_use]
148 pub fn as_str(&self) -> &'static str {
149 match self {
150 Self::Buy => "BUY",
151 Self::Sell => "SELL",
152 }
153 }
154}
155
156impl std::str::FromStr for EquityTradeHistorySideEnum {
157 type Err = Box<dyn std::error::Error + Send + Sync>;
158
159 fn from_str(s: &str) -> Result<Self, Self::Err> {
160 match s {
161 "BUY" => Ok(Self::Buy),
162 "SELL" => Ok(Self::Sell),
163 other => Err(format!("invalid EquityTradeHistorySideEnum: {}", other).into()),
164 }
165 }
166}
167
168#[allow(non_camel_case_types)]
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub enum PlaceEquityOrderSideEnum {
171 #[serde(rename = "BUY")]
172 Buy,
173 #[serde(rename = "SELL")]
174 Sell,
175}
176
177impl PlaceEquityOrderSideEnum {
178 #[must_use]
179 pub fn as_str(&self) -> &'static str {
180 match self {
181 Self::Buy => "BUY",
182 Self::Sell => "SELL",
183 }
184 }
185}
186
187impl std::str::FromStr for PlaceEquityOrderSideEnum {
188 type Err = Box<dyn std::error::Error + Send + Sync>;
189
190 fn from_str(s: &str) -> Result<Self, Self::Err> {
191 match s {
192 "BUY" => Ok(Self::Buy),
193 "SELL" => Ok(Self::Sell),
194 other => Err(format!("invalid PlaceEquityOrderSideEnum: {}", other).into()),
195 }
196 }
197}
198
199#[allow(non_camel_case_types)]
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub enum PlaceEquityOrderOrderTypeEnum {
202 #[serde(rename = "MARKET")]
203 Market,
204 #[serde(rename = "LIMIT")]
205 Limit,
206}
207
208impl PlaceEquityOrderOrderTypeEnum {
209 #[must_use]
210 pub fn as_str(&self) -> &'static str {
211 match self {
212 Self::Market => "MARKET",
213 Self::Limit => "LIMIT",
214 }
215 }
216}
217
218impl std::str::FromStr for PlaceEquityOrderOrderTypeEnum {
219 type Err = Box<dyn std::error::Error + Send + Sync>;
220
221 fn from_str(s: &str) -> Result<Self, Self::Err> {
222 match s {
223 "MARKET" => Ok(Self::Market),
224 "LIMIT" => Ok(Self::Limit),
225 other => Err(format!("invalid PlaceEquityOrderOrderTypeEnum: {}", other).into()),
226 }
227 }
228}
229
230#[allow(non_camel_case_types)]
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub enum PlaceEquityOrderTimeInForceEnum {
233 #[serde(rename = "DAY")]
234 Day,
235 #[serde(rename = "GTC")]
236 Gtc,
237}
238
239impl PlaceEquityOrderTimeInForceEnum {
240 #[must_use]
241 pub fn as_str(&self) -> &'static str {
242 match self {
243 Self::Day => "DAY",
244 Self::Gtc => "GTC",
245 }
246 }
247}
248
249impl std::str::FromStr for PlaceEquityOrderTimeInForceEnum {
250 type Err = Box<dyn std::error::Error + Send + Sync>;
251
252 fn from_str(s: &str) -> Result<Self, Self::Err> {
253 match s {
254 "DAY" => Ok(Self::Day),
255 "GTC" => Ok(Self::Gtc),
256 other => Err(format!("invalid PlaceEquityOrderTimeInForceEnum: {}", other).into()),
257 }
258 }
259}
260
261#[allow(non_camel_case_types)]
262#[derive(Debug, Clone, Serialize, Deserialize)]
263pub enum PlaceEquityOrderTradingSessionEnum {
264 #[serde(rename = "RTH")]
265 Rth,
266 #[serde(rename = "EXTENDED")]
267 Extended,
268 #[serde(rename = "24H")]
269 TradingSession24H,
270}
271
272impl PlaceEquityOrderTradingSessionEnum {
273 #[must_use]
274 pub fn as_str(&self) -> &'static str {
275 match self {
276 Self::Rth => "RTH",
277 Self::Extended => "EXTENDED",
278 Self::TradingSession24H => "24H",
279 }
280 }
281}
282
283impl std::str::FromStr for PlaceEquityOrderTradingSessionEnum {
284 type Err = Box<dyn std::error::Error + Send + Sync>;
285
286 fn from_str(s: &str) -> Result<Self, Self::Err> {
287 match s {
288 "RTH" => Ok(Self::Rth),
289 "EXTENDED" => Ok(Self::Extended),
290 "24H" => Ok(Self::TradingSession24H),
291 other => Err(format!("invalid PlaceEquityOrderTradingSessionEnum: {}", other).into()),
292 }
293 }
294}
295
296#[allow(non_camel_case_types)]
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub enum PlaceEquityOrderWalletTypeEnum {
299 #[serde(rename = "CARD")]
300 Card,
301 #[serde(rename = "MAIN")]
302 Main,
303}
304
305impl PlaceEquityOrderWalletTypeEnum {
306 #[must_use]
307 pub fn as_str(&self) -> &'static str {
308 match self {
309 Self::Card => "CARD",
310 Self::Main => "MAIN",
311 }
312 }
313}
314
315impl std::str::FromStr for PlaceEquityOrderWalletTypeEnum {
316 type Err = Box<dyn std::error::Error + Send + Sync>;
317
318 fn from_str(s: &str) -> Result<Self, Self::Err> {
319 match s {
320 "CARD" => Ok(Self::Card),
321 "MAIN" => Ok(Self::Main),
322 other => Err(format!("invalid PlaceEquityOrderWalletTypeEnum: {}", other).into()),
323 }
324 }
325}
326
327#[derive(Clone, Debug, Builder, Deserialize, Default)]
332#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
333pub struct CancelAllEquityOrdersParams {
334 #[builder(setter(into), default)]
338 #[serde(rename = "recvWindow", default)]
339 pub recv_window: Option<i64>,
340}
341
342impl CancelAllEquityOrdersParams {
343 #[must_use]
346 pub fn builder() -> CancelAllEquityOrdersParamsBuilder {
347 CancelAllEquityOrdersParamsBuilder::default()
348 }
349}
350#[derive(Clone, Debug, Builder, Deserialize)]
355#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
356pub struct CancelEquityOrderParams {
357 #[builder(setter(into))]
361 #[serde(rename = "orderId")]
362 pub order_id: String,
363 #[builder(setter(into), default)]
367 #[serde(rename = "recvWindow", default)]
368 pub recv_window: Option<i64>,
369}
370
371impl CancelEquityOrderParams {
372 #[must_use]
379 pub fn builder(order_id: String) -> CancelEquityOrderParamsBuilder {
380 CancelEquityOrderParamsBuilder::default().order_id(order_id)
381 }
382}
383#[derive(Clone, Debug, Builder, Deserialize, Default)]
388#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
389pub struct CurrentOpenOrdersParams {
390 #[builder(setter(into), default)]
394 #[serde(rename = "recvWindow", default)]
395 pub recv_window: Option<i64>,
396}
397
398impl CurrentOpenOrdersParams {
399 #[must_use]
402 pub fn builder() -> CurrentOpenOrdersParamsBuilder {
403 CurrentOpenOrdersParamsBuilder::default()
404 }
405}
406#[derive(Clone, Debug, Builder, Deserialize, Default)]
411#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
412pub struct EquityOrderDetailParams {
413 #[builder(setter(into), default)]
417 #[serde(rename = "orderId", default)]
418 pub order_id: Option<String>,
419 #[builder(setter(into), default)]
423 #[serde(rename = "clientOrderId", default)]
424 pub client_order_id: Option<String>,
425 #[builder(setter(into), default)]
429 #[serde(rename = "recvWindow", default)]
430 pub recv_window: Option<i64>,
431}
432
433impl EquityOrderDetailParams {
434 #[must_use]
437 pub fn builder() -> EquityOrderDetailParamsBuilder {
438 EquityOrderDetailParamsBuilder::default()
439 }
440}
441#[derive(Clone, Debug, Builder, Deserialize)]
446#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
447pub struct EquityOrderHistoryParams {
448 #[builder(setter(into))]
452 #[serde(rename = "startTime")]
453 pub start_time: i64,
454 #[builder(setter(into))]
458 #[serde(rename = "endTime")]
459 pub end_time: i64,
460 #[builder(setter(into), default)]
464 #[serde(rename = "symbol", default)]
465 pub symbol: Option<String>,
466 #[builder(setter(into), default)]
470 #[serde(rename = "orderType", default)]
471 pub order_type: Option<EquityOrderHistoryOrderTypeEnum>,
472 #[builder(setter(into), default)]
476 #[serde(rename = "side", default)]
477 pub side: Option<EquityOrderHistorySideEnum>,
478 #[builder(setter(into), default)]
482 #[serde(rename = "orderStatus", default)]
483 pub order_status: Option<String>,
484 #[builder(setter(into), default)]
488 #[serde(rename = "current", default)]
489 pub current: Option<i32>,
490 #[builder(setter(into), default)]
494 #[serde(rename = "size", default)]
495 pub size: Option<i32>,
496 #[builder(setter(into), default)]
500 #[serde(rename = "recvWindow", default)]
501 pub recv_window: Option<i64>,
502}
503
504impl EquityOrderHistoryParams {
505 #[must_use]
513 pub fn builder(start_time: i64, end_time: i64) -> EquityOrderHistoryParamsBuilder {
514 EquityOrderHistoryParamsBuilder::default()
515 .start_time(start_time)
516 .end_time(end_time)
517 }
518}
519#[derive(Clone, Debug, Builder, Deserialize)]
524#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
525pub struct EquityTradeHistoryParams {
526 #[builder(setter(into))]
530 #[serde(rename = "startTime")]
531 pub start_time: i64,
532 #[builder(setter(into))]
536 #[serde(rename = "endTime")]
537 pub end_time: i64,
538 #[builder(setter(into), default)]
542 #[serde(rename = "symbol", default)]
543 pub symbol: Option<String>,
544 #[builder(setter(into), default)]
548 #[serde(rename = "side", default)]
549 pub side: Option<EquityTradeHistorySideEnum>,
550 #[builder(setter(into), default)]
554 #[serde(rename = "orderId", default)]
555 pub order_id: Option<String>,
556 #[builder(setter(into), default)]
560 #[serde(rename = "current", default)]
561 pub current: Option<i32>,
562 #[builder(setter(into), default)]
566 #[serde(rename = "size", default)]
567 pub size: Option<i32>,
568 #[builder(setter(into), default)]
572 #[serde(rename = "recvWindow", default)]
573 pub recv_window: Option<i64>,
574}
575
576impl EquityTradeHistoryParams {
577 #[must_use]
585 pub fn builder(start_time: i64, end_time: i64) -> EquityTradeHistoryParamsBuilder {
586 EquityTradeHistoryParamsBuilder::default()
587 .start_time(start_time)
588 .end_time(end_time)
589 }
590}
591#[derive(Clone, Debug, Builder, Deserialize)]
596#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
597pub struct PlaceEquityOrderParams {
598 #[builder(setter(into))]
602 #[serde(rename = "symbol")]
603 pub symbol: String,
604 #[builder(setter(into))]
608 #[serde(rename = "side")]
609 pub side: PlaceEquityOrderSideEnum,
610 #[builder(setter(into))]
614 #[serde(rename = "orderType")]
615 pub order_type: PlaceEquityOrderOrderTypeEnum,
616 #[builder(setter(into), default)]
620 #[serde(rename = "quoteAsset", default)]
621 pub quote_asset: Option<String>,
622 #[builder(setter(into), default)]
626 #[serde(rename = "price", default)]
627 pub price: Option<String>,
628 #[builder(setter(into), default)]
632 #[serde(rename = "quantity", default)]
633 pub quantity: Option<String>,
634 #[builder(setter(into), default)]
638 #[serde(rename = "notional", default)]
639 pub notional: Option<String>,
640 #[builder(setter(into), default)]
644 #[serde(rename = "timeInForce", default)]
645 pub time_in_force: Option<PlaceEquityOrderTimeInForceEnum>,
646 #[builder(setter(into), default)]
650 #[serde(rename = "tradingSession", default)]
651 pub trading_session: Option<PlaceEquityOrderTradingSessionEnum>,
652 #[builder(setter(into), default)]
656 #[serde(rename = "walletType", default)]
657 pub wallet_type: Option<PlaceEquityOrderWalletTypeEnum>,
658 #[builder(setter(into), default)]
662 #[serde(rename = "clientOrderId", default)]
663 pub client_order_id: Option<String>,
664 #[builder(setter(into), default)]
668 #[serde(rename = "tokenize", default)]
669 pub tokenize: Option<bool>,
670 #[builder(setter(into), default)]
674 #[serde(rename = "recvWindow", default)]
675 pub recv_window: Option<i64>,
676}
677
678impl PlaceEquityOrderParams {
679 #[must_use]
688 pub fn builder(
689 symbol: String,
690 side: PlaceEquityOrderSideEnum,
691 order_type: PlaceEquityOrderOrderTypeEnum,
692 ) -> PlaceEquityOrderParamsBuilder {
693 PlaceEquityOrderParamsBuilder::default()
694 .symbol(symbol)
695 .side(side)
696 .order_type(order_type)
697 }
698}
699
700#[async_trait]
701impl TradeApi for TradeApiClient {
702 async fn cancel_all_equity_orders(
703 &self,
704 params: CancelAllEquityOrdersParams,
705 ) -> anyhow::Result<RestApiResponse<models::CancelAllEquityOrdersResponse>> {
706 let CancelAllEquityOrdersParams { recv_window } = params;
707
708 let mut query_params = BTreeMap::new();
709 let body_params = BTreeMap::new();
710
711 if let Some(rw) = recv_window {
712 query_params.insert("recvWindow".to_string(), json!(rw));
713 }
714
715 send_request::<models::CancelAllEquityOrdersResponse>(
716 &self.configuration,
717 "/sapi/v1/equity/order/cancel-all",
718 reqwest::Method::POST,
719 query_params,
720 body_params,
721 if HAS_TIME_UNIT {
722 self.configuration.time_unit
723 } else {
724 None
725 },
726 true,
727 )
728 .await
729 }
730
731 async fn cancel_equity_order(
732 &self,
733 params: CancelEquityOrderParams,
734 ) -> anyhow::Result<RestApiResponse<models::CancelEquityOrderResponse>> {
735 let CancelEquityOrderParams {
736 order_id,
737 recv_window,
738 } = params;
739
740 let mut query_params = BTreeMap::new();
741 let body_params = BTreeMap::new();
742
743 query_params.insert("orderId".to_string(), json!(order_id));
744
745 if let Some(rw) = recv_window {
746 query_params.insert("recvWindow".to_string(), json!(rw));
747 }
748
749 send_request::<models::CancelEquityOrderResponse>(
750 &self.configuration,
751 "/sapi/v1/equity/order/cancel",
752 reqwest::Method::POST,
753 query_params,
754 body_params,
755 if HAS_TIME_UNIT {
756 self.configuration.time_unit
757 } else {
758 None
759 },
760 true,
761 )
762 .await
763 }
764
765 async fn current_open_orders(
766 &self,
767 params: CurrentOpenOrdersParams,
768 ) -> anyhow::Result<RestApiResponse<Vec<models::CurrentOpenOrdersResponseInner>>> {
769 let CurrentOpenOrdersParams { recv_window } = params;
770
771 let mut query_params = BTreeMap::new();
772 let body_params = BTreeMap::new();
773
774 if let Some(rw) = recv_window {
775 query_params.insert("recvWindow".to_string(), json!(rw));
776 }
777
778 send_request::<Vec<models::CurrentOpenOrdersResponseInner>>(
779 &self.configuration,
780 "/sapi/v1/equity/order/open-orders",
781 reqwest::Method::GET,
782 query_params,
783 body_params,
784 if HAS_TIME_UNIT {
785 self.configuration.time_unit
786 } else {
787 None
788 },
789 true,
790 )
791 .await
792 }
793
794 async fn equity_order_detail(
795 &self,
796 params: EquityOrderDetailParams,
797 ) -> anyhow::Result<RestApiResponse<models::EquityOrderDetailResponse>> {
798 let EquityOrderDetailParams {
799 order_id,
800 client_order_id,
801 recv_window,
802 } = params;
803
804 let mut query_params = BTreeMap::new();
805 let body_params = BTreeMap::new();
806
807 if let Some(rw) = order_id {
808 query_params.insert("orderId".to_string(), json!(rw));
809 }
810
811 if let Some(rw) = client_order_id {
812 query_params.insert("clientOrderId".to_string(), json!(rw));
813 }
814
815 if let Some(rw) = recv_window {
816 query_params.insert("recvWindow".to_string(), json!(rw));
817 }
818
819 send_request::<models::EquityOrderDetailResponse>(
820 &self.configuration,
821 "/sapi/v1/equity/order/detail",
822 reqwest::Method::GET,
823 query_params,
824 body_params,
825 if HAS_TIME_UNIT {
826 self.configuration.time_unit
827 } else {
828 None
829 },
830 true,
831 )
832 .await
833 }
834
835 async fn equity_order_history(
836 &self,
837 params: EquityOrderHistoryParams,
838 ) -> anyhow::Result<RestApiResponse<models::EquityOrderHistoryResponse>> {
839 let EquityOrderHistoryParams {
840 start_time,
841 end_time,
842 symbol,
843 order_type,
844 side,
845 order_status,
846 current,
847 size,
848 recv_window,
849 } = params;
850
851 let mut query_params = BTreeMap::new();
852 let body_params = BTreeMap::new();
853
854 if let Some(rw) = symbol {
855 query_params.insert("symbol".to_string(), json!(rw));
856 }
857
858 if let Some(rw) = order_type {
859 query_params.insert("orderType".to_string(), json!(rw));
860 }
861
862 if let Some(rw) = side {
863 query_params.insert("side".to_string(), json!(rw));
864 }
865
866 if let Some(rw) = order_status {
867 query_params.insert("orderStatus".to_string(), json!(rw));
868 }
869
870 query_params.insert("startTime".to_string(), json!(start_time));
871
872 query_params.insert("endTime".to_string(), json!(end_time));
873
874 if let Some(rw) = current {
875 query_params.insert("current".to_string(), json!(rw));
876 }
877
878 if let Some(rw) = size {
879 query_params.insert("size".to_string(), json!(rw));
880 }
881
882 if let Some(rw) = recv_window {
883 query_params.insert("recvWindow".to_string(), json!(rw));
884 }
885
886 send_request::<models::EquityOrderHistoryResponse>(
887 &self.configuration,
888 "/sapi/v1/equity/order/history",
889 reqwest::Method::GET,
890 query_params,
891 body_params,
892 if HAS_TIME_UNIT {
893 self.configuration.time_unit
894 } else {
895 None
896 },
897 true,
898 )
899 .await
900 }
901
902 async fn equity_trade_history(
903 &self,
904 params: EquityTradeHistoryParams,
905 ) -> anyhow::Result<RestApiResponse<models::EquityTradeHistoryResponse>> {
906 let EquityTradeHistoryParams {
907 start_time,
908 end_time,
909 symbol,
910 side,
911 order_id,
912 current,
913 size,
914 recv_window,
915 } = params;
916
917 let mut query_params = BTreeMap::new();
918 let body_params = BTreeMap::new();
919
920 if let Some(rw) = symbol {
921 query_params.insert("symbol".to_string(), json!(rw));
922 }
923
924 if let Some(rw) = side {
925 query_params.insert("side".to_string(), json!(rw));
926 }
927
928 if let Some(rw) = order_id {
929 query_params.insert("orderId".to_string(), json!(rw));
930 }
931
932 query_params.insert("startTime".to_string(), json!(start_time));
933
934 query_params.insert("endTime".to_string(), json!(end_time));
935
936 if let Some(rw) = current {
937 query_params.insert("current".to_string(), json!(rw));
938 }
939
940 if let Some(rw) = size {
941 query_params.insert("size".to_string(), json!(rw));
942 }
943
944 if let Some(rw) = recv_window {
945 query_params.insert("recvWindow".to_string(), json!(rw));
946 }
947
948 send_request::<models::EquityTradeHistoryResponse>(
949 &self.configuration,
950 "/sapi/v1/equity/trade/history",
951 reqwest::Method::GET,
952 query_params,
953 body_params,
954 if HAS_TIME_UNIT {
955 self.configuration.time_unit
956 } else {
957 None
958 },
959 true,
960 )
961 .await
962 }
963
964 async fn place_equity_order(
965 &self,
966 params: PlaceEquityOrderParams,
967 ) -> anyhow::Result<RestApiResponse<models::PlaceEquityOrderResponse>> {
968 let PlaceEquityOrderParams {
969 symbol,
970 side,
971 order_type,
972 quote_asset,
973 price,
974 quantity,
975 notional,
976 time_in_force,
977 trading_session,
978 wallet_type,
979 client_order_id,
980 tokenize,
981 recv_window,
982 } = params;
983
984 let mut query_params = BTreeMap::new();
985 let body_params = BTreeMap::new();
986
987 query_params.insert("symbol".to_string(), json!(symbol));
988
989 if let Some(rw) = quote_asset {
990 query_params.insert("quoteAsset".to_string(), json!(rw));
991 }
992
993 query_params.insert("side".to_string(), json!(side));
994
995 query_params.insert("orderType".to_string(), json!(order_type));
996
997 if let Some(rw) = price {
998 query_params.insert("price".to_string(), json!(rw));
999 }
1000
1001 if let Some(rw) = quantity {
1002 query_params.insert("quantity".to_string(), json!(rw));
1003 }
1004
1005 if let Some(rw) = notional {
1006 query_params.insert("notional".to_string(), json!(rw));
1007 }
1008
1009 if let Some(rw) = time_in_force {
1010 query_params.insert("timeInForce".to_string(), json!(rw));
1011 }
1012
1013 if let Some(rw) = trading_session {
1014 query_params.insert("tradingSession".to_string(), json!(rw));
1015 }
1016
1017 if let Some(rw) = wallet_type {
1018 query_params.insert("walletType".to_string(), json!(rw));
1019 }
1020
1021 if let Some(rw) = client_order_id {
1022 query_params.insert("clientOrderId".to_string(), json!(rw));
1023 }
1024
1025 if let Some(rw) = tokenize {
1026 query_params.insert("tokenize".to_string(), json!(rw));
1027 }
1028
1029 if let Some(rw) = recv_window {
1030 query_params.insert("recvWindow".to_string(), json!(rw));
1031 }
1032
1033 send_request::<models::PlaceEquityOrderResponse>(
1034 &self.configuration,
1035 "/sapi/v1/equity/order/place",
1036 reqwest::Method::POST,
1037 query_params,
1038 body_params,
1039 if HAS_TIME_UNIT {
1040 self.configuration.time_unit
1041 } else {
1042 None
1043 },
1044 true,
1045 )
1046 .await
1047 }
1048}
1049
1050#[cfg(all(test, feature = "stocks"))]
1051mod tests {
1052 use super::*;
1053 use crate::TOKIO_SHARED_RT;
1054 use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
1055 use async_trait::async_trait;
1056 use std::collections::HashMap;
1057
1058 struct DummyRestApiResponse<T> {
1059 inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
1060 status: u16,
1061 headers: HashMap<String, String>,
1062 rate_limits: Option<Vec<RestApiRateLimit>>,
1063 }
1064
1065 impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
1066 fn from(dummy: DummyRestApiResponse<T>) -> Self {
1067 Self {
1068 data_fn: dummy.inner,
1069 status: dummy.status,
1070 headers: dummy.headers,
1071 rate_limits: dummy.rate_limits,
1072 }
1073 }
1074 }
1075
1076 struct MockTradeApiClient {
1077 force_error: bool,
1078 }
1079
1080 #[async_trait]
1081 impl TradeApi for MockTradeApiClient {
1082 async fn cancel_all_equity_orders(
1083 &self,
1084 _params: CancelAllEquityOrdersParams,
1085 ) -> anyhow::Result<RestApiResponse<models::CancelAllEquityOrdersResponse>> {
1086 if self.force_error {
1087 return Err(ConnectorError::ConnectorClientError {
1088 msg: "ResponseError".to_string(),
1089 code: None,
1090 }
1091 .into());
1092 }
1093
1094 let resp_json: Value = serde_json::from_str(r#"{"success":true}"#)
1095 .unwrap_or_else(|_| serde_json::json!({}));
1096 let dummy_response: models::CancelAllEquityOrdersResponse =
1097 serde_json::from_value(resp_json.clone())
1098 .expect("should parse into models::CancelAllEquityOrdersResponse");
1099
1100 let dummy = DummyRestApiResponse {
1101 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1102 status: 200,
1103 headers: HashMap::new(),
1104 rate_limits: None,
1105 };
1106
1107 Ok(dummy.into())
1108 }
1109
1110 async fn cancel_equity_order(
1111 &self,
1112 _params: CancelEquityOrderParams,
1113 ) -> anyhow::Result<RestApiResponse<models::CancelEquityOrderResponse>> {
1114 if self.force_error {
1115 return Err(ConnectorError::ConnectorClientError {
1116 msg: "ResponseError".to_string(),
1117 code: None,
1118 }
1119 .into());
1120 }
1121
1122 let resp_json: Value = serde_json::from_str(
1123 r#"{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","status":"S"}"#,
1124 )
1125 .unwrap_or_else(|_| serde_json::json!({}));
1126 let dummy_response: models::CancelEquityOrderResponse =
1127 serde_json::from_value(resp_json.clone())
1128 .expect("should parse into models::CancelEquityOrderResponse");
1129
1130 let dummy = DummyRestApiResponse {
1131 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1132 status: 200,
1133 headers: HashMap::new(),
1134 rate_limits: None,
1135 };
1136
1137 Ok(dummy.into())
1138 }
1139
1140 async fn current_open_orders(
1141 &self,
1142 _params: CurrentOpenOrdersParams,
1143 ) -> anyhow::Result<RestApiResponse<Vec<models::CurrentOpenOrdersResponseInner>>> {
1144 if self.force_error {
1145 return Err(ConnectorError::ConnectorClientError {
1146 msg: "ResponseError".to_string(),
1147 code: None,
1148 }
1149 .into());
1150 }
1151
1152 let resp_json: Value = serde_json::from_str(r#"[{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"avgFilledPrice","qty":"1","notional":"notional","filledQty":"0","filledTotal":"filledTotal","fee":"0","session":"RTH","status":"NEW","createdAt":1735900000000,"updatedAt":1735900000000}]"#).unwrap_or_else(|_| serde_json::json!({}));
1153 let dummy_response: Vec<models::CurrentOpenOrdersResponseInner> =
1154 serde_json::from_value(resp_json.clone())
1155 .expect("should parse into Vec<models::CurrentOpenOrdersResponseInner>");
1156
1157 let dummy = DummyRestApiResponse {
1158 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1159 status: 200,
1160 headers: HashMap::new(),
1161 rate_limits: None,
1162 };
1163
1164 Ok(dummy.into())
1165 }
1166
1167 async fn equity_order_detail(
1168 &self,
1169 _params: EquityOrderDetailParams,
1170 ) -> anyhow::Result<RestApiResponse<models::EquityOrderDetailResponse>> {
1171 if self.force_error {
1172 return Err(ConnectorError::ConnectorClientError {
1173 msg: "ResponseError".to_string(),
1174 code: None,
1175 }
1176 .into());
1177 }
1178
1179 let resp_json: Value = serde_json::from_str(r#"{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","clientOrderId":"web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"180.48","qty":"1","notional":"notional","filledQty":"1","filledTotal":"filledTotal","fee":"0.10","session":"RTH","status":"FILLED","createdAt":1735900000000,"updatedAt":1735900120000,"trades":[{"executionId":"exec-20260504-0001","executionAt":1735900115000,"price":"180.48","qty":"1"}]}"#).unwrap_or_else(|_| serde_json::json!({}));
1180 let dummy_response: models::EquityOrderDetailResponse =
1181 serde_json::from_value(resp_json.clone())
1182 .expect("should parse into models::EquityOrderDetailResponse");
1183
1184 let dummy = DummyRestApiResponse {
1185 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1186 status: 200,
1187 headers: HashMap::new(),
1188 rate_limits: None,
1189 };
1190
1191 Ok(dummy.into())
1192 }
1193
1194 async fn equity_order_history(
1195 &self,
1196 _params: EquityOrderHistoryParams,
1197 ) -> anyhow::Result<RestApiResponse<models::EquityOrderHistoryResponse>> {
1198 if self.force_error {
1199 return Err(ConnectorError::ConnectorClientError {
1200 msg: "ResponseError".to_string(),
1201 code: None,
1202 }
1203 .into());
1204 }
1205
1206 let resp_json: Value = serde_json::from_str(r#"{"total":2,"page":1,"size":20,"rows":[{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"180.48","qty":"1","notional":"notional","filledQty":"1","filledTotal":"filledTotal","fee":"0.10","session":"RTH","status":"FILLED","createdAt":1735900000000,"updatedAt":1735900120000}]}"#).unwrap_or_else(|_| serde_json::json!({}));
1207 let dummy_response: models::EquityOrderHistoryResponse =
1208 serde_json::from_value(resp_json.clone())
1209 .expect("should parse into models::EquityOrderHistoryResponse");
1210
1211 let dummy = DummyRestApiResponse {
1212 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1213 status: 200,
1214 headers: HashMap::new(),
1215 rate_limits: None,
1216 };
1217
1218 Ok(dummy.into())
1219 }
1220
1221 async fn equity_trade_history(
1222 &self,
1223 _params: EquityTradeHistoryParams,
1224 ) -> anyhow::Result<RestApiResponse<models::EquityTradeHistoryResponse>> {
1225 if self.force_error {
1226 return Err(ConnectorError::ConnectorClientError {
1227 msg: "ResponseError".to_string(),
1228 code: None,
1229 }
1230 .into());
1231 }
1232
1233 let resp_json: Value = serde_json::from_str(r#"{"total":1,"page":1,"size":20,"rows":[{"executionId":"exec-20260504-0001","orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","price":"180.50","qty":"1","total":"180.50","executionAt":1735900115000,"updatedAt":1735900115200}]}"#).unwrap_or_else(|_| serde_json::json!({}));
1234 let dummy_response: models::EquityTradeHistoryResponse =
1235 serde_json::from_value(resp_json.clone())
1236 .expect("should parse into models::EquityTradeHistoryResponse");
1237
1238 let dummy = DummyRestApiResponse {
1239 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1240 status: 200,
1241 headers: HashMap::new(),
1242 rate_limits: None,
1243 };
1244
1245 Ok(dummy.into())
1246 }
1247
1248 async fn place_equity_order(
1249 &self,
1250 _params: PlaceEquityOrderParams,
1251 ) -> anyhow::Result<RestApiResponse<models::PlaceEquityOrderResponse>> {
1252 if self.force_error {
1253 return Err(ConnectorError::ConnectorClientError {
1254 msg: "ResponseError".to_string(),
1255 code: None,
1256 }
1257 .into());
1258 }
1259
1260 let resp_json: Value = serde_json::from_str(r#"{"status":"S","orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","clientOrderId":"web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a"}"#).unwrap_or_else(|_| serde_json::json!({}));
1261 let dummy_response: models::PlaceEquityOrderResponse =
1262 serde_json::from_value(resp_json.clone())
1263 .expect("should parse into models::PlaceEquityOrderResponse");
1264
1265 let dummy = DummyRestApiResponse {
1266 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
1267 status: 200,
1268 headers: HashMap::new(),
1269 rate_limits: None,
1270 };
1271
1272 Ok(dummy.into())
1273 }
1274 }
1275
1276 #[test]
1277 fn cancel_all_equity_orders_required_params_success() {
1278 TOKIO_SHARED_RT.block_on(async {
1279 let client = MockTradeApiClient { force_error: false };
1280
1281 let params = CancelAllEquityOrdersParams::builder().build().unwrap();
1282
1283 let resp_json: Value = serde_json::from_str(r#"{"success":true}"#)
1284 .unwrap_or_else(|_| serde_json::json!({}));
1285 let expected_response: models::CancelAllEquityOrdersResponse =
1286 serde_json::from_value(resp_json.clone())
1287 .expect("should parse into models::CancelAllEquityOrdersResponse");
1288
1289 let resp = client
1290 .cancel_all_equity_orders(params)
1291 .await
1292 .expect("Expected a response");
1293 let data_future = resp.data();
1294 let actual_response = data_future.await.unwrap();
1295 assert_eq!(actual_response, expected_response);
1296 });
1297 }
1298
1299 #[test]
1300 fn cancel_all_equity_orders_optional_params_success() {
1301 TOKIO_SHARED_RT.block_on(async {
1302 let client = MockTradeApiClient { force_error: false };
1303
1304 let params = CancelAllEquityOrdersParams::builder()
1305 .recv_window(5000)
1306 .build()
1307 .unwrap();
1308
1309 let resp_json: Value = serde_json::from_str(r#"{"success":true}"#)
1310 .unwrap_or_else(|_| serde_json::json!({}));
1311 let expected_response: models::CancelAllEquityOrdersResponse =
1312 serde_json::from_value(resp_json.clone())
1313 .expect("should parse into models::CancelAllEquityOrdersResponse");
1314
1315 let resp = client
1316 .cancel_all_equity_orders(params)
1317 .await
1318 .expect("Expected a response");
1319 let data_future = resp.data();
1320 let actual_response = data_future.await.unwrap();
1321 assert_eq!(actual_response, expected_response);
1322 });
1323 }
1324
1325 #[test]
1326 fn cancel_all_equity_orders_response_error() {
1327 TOKIO_SHARED_RT.block_on(async {
1328 let client = MockTradeApiClient { force_error: true };
1329
1330 let params = CancelAllEquityOrdersParams::builder().build().unwrap();
1331
1332 match client.cancel_all_equity_orders(params).await {
1333 Ok(_) => panic!("Expected an error"),
1334 Err(err) => {
1335 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1336 }
1337 }
1338 });
1339 }
1340
1341 #[test]
1342 fn cancel_equity_order_required_params_success() {
1343 TOKIO_SHARED_RT.block_on(async {
1344 let client = MockTradeApiClient { force_error: false };
1345
1346 let params = CancelEquityOrderParams::builder(
1347 "c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71".to_string(),
1348 )
1349 .build()
1350 .unwrap();
1351
1352 let resp_json: Value = serde_json::from_str(
1353 r#"{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","status":"S"}"#,
1354 )
1355 .unwrap_or_else(|_| serde_json::json!({}));
1356 let expected_response: models::CancelEquityOrderResponse =
1357 serde_json::from_value(resp_json.clone())
1358 .expect("should parse into models::CancelEquityOrderResponse");
1359
1360 let resp = client
1361 .cancel_equity_order(params)
1362 .await
1363 .expect("Expected a response");
1364 let data_future = resp.data();
1365 let actual_response = data_future.await.unwrap();
1366 assert_eq!(actual_response, expected_response);
1367 });
1368 }
1369
1370 #[test]
1371 fn cancel_equity_order_optional_params_success() {
1372 TOKIO_SHARED_RT.block_on(async {
1373 let client = MockTradeApiClient { force_error: false };
1374
1375 let params = CancelEquityOrderParams::builder(
1376 "c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71".to_string(),
1377 )
1378 .recv_window(5000)
1379 .build()
1380 .unwrap();
1381
1382 let resp_json: Value = serde_json::from_str(
1383 r#"{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","status":"S"}"#,
1384 )
1385 .unwrap_or_else(|_| serde_json::json!({}));
1386 let expected_response: models::CancelEquityOrderResponse =
1387 serde_json::from_value(resp_json.clone())
1388 .expect("should parse into models::CancelEquityOrderResponse");
1389
1390 let resp = client
1391 .cancel_equity_order(params)
1392 .await
1393 .expect("Expected a response");
1394 let data_future = resp.data();
1395 let actual_response = data_future.await.unwrap();
1396 assert_eq!(actual_response, expected_response);
1397 });
1398 }
1399
1400 #[test]
1401 fn cancel_equity_order_response_error() {
1402 TOKIO_SHARED_RT.block_on(async {
1403 let client = MockTradeApiClient { force_error: true };
1404
1405 let params = CancelEquityOrderParams::builder(
1406 "c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71".to_string(),
1407 )
1408 .build()
1409 .unwrap();
1410
1411 match client.cancel_equity_order(params).await {
1412 Ok(_) => panic!("Expected an error"),
1413 Err(err) => {
1414 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1415 }
1416 }
1417 });
1418 }
1419
1420 #[test]
1421 fn current_open_orders_required_params_success() {
1422 TOKIO_SHARED_RT.block_on(async {
1423 let client = MockTradeApiClient { force_error: false };
1424
1425 let params = CurrentOpenOrdersParams::builder().build().unwrap();
1426
1427 let resp_json: Value = serde_json::from_str(r#"[{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"avgFilledPrice","qty":"1","notional":"notional","filledQty":"0","filledTotal":"filledTotal","fee":"0","session":"RTH","status":"NEW","createdAt":1735900000000,"updatedAt":1735900000000}]"#).unwrap_or_else(|_| serde_json::json!({}));
1428 let expected_response : Vec<models::CurrentOpenOrdersResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::CurrentOpenOrdersResponseInner>");
1429
1430 let resp = client.current_open_orders(params).await.expect("Expected a response");
1431 let data_future = resp.data();
1432 let actual_response = data_future.await.unwrap();
1433 assert_eq!(actual_response, expected_response);
1434 });
1435 }
1436
1437 #[test]
1438 fn current_open_orders_optional_params_success() {
1439 TOKIO_SHARED_RT.block_on(async {
1440 let client = MockTradeApiClient { force_error: false };
1441
1442 let params = CurrentOpenOrdersParams::builder().recv_window(5000).build().unwrap();
1443
1444 let resp_json: Value = serde_json::from_str(r#"[{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"avgFilledPrice","qty":"1","notional":"notional","filledQty":"0","filledTotal":"filledTotal","fee":"0","session":"RTH","status":"NEW","createdAt":1735900000000,"updatedAt":1735900000000}]"#).unwrap_or_else(|_| serde_json::json!({}));
1445 let expected_response : Vec<models::CurrentOpenOrdersResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::CurrentOpenOrdersResponseInner>");
1446
1447 let resp = client.current_open_orders(params).await.expect("Expected a response");
1448 let data_future = resp.data();
1449 let actual_response = data_future.await.unwrap();
1450 assert_eq!(actual_response, expected_response);
1451 });
1452 }
1453
1454 #[test]
1455 fn current_open_orders_response_error() {
1456 TOKIO_SHARED_RT.block_on(async {
1457 let client = MockTradeApiClient { force_error: true };
1458
1459 let params = CurrentOpenOrdersParams::builder().build().unwrap();
1460
1461 match client.current_open_orders(params).await {
1462 Ok(_) => panic!("Expected an error"),
1463 Err(err) => {
1464 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1465 }
1466 }
1467 });
1468 }
1469
1470 #[test]
1471 fn equity_order_detail_required_params_success() {
1472 TOKIO_SHARED_RT.block_on(async {
1473 let client = MockTradeApiClient { force_error: false };
1474
1475 let params = EquityOrderDetailParams::builder().build().unwrap();
1476
1477 let resp_json: Value = serde_json::from_str(r#"{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","clientOrderId":"web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"180.48","qty":"1","notional":"notional","filledQty":"1","filledTotal":"filledTotal","fee":"0.10","session":"RTH","status":"FILLED","createdAt":1735900000000,"updatedAt":1735900120000,"trades":[{"executionId":"exec-20260504-0001","executionAt":1735900115000,"price":"180.48","qty":"1"}]}"#).unwrap_or_else(|_| serde_json::json!({}));
1478 let expected_response : models::EquityOrderDetailResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::EquityOrderDetailResponse");
1479
1480 let resp = client.equity_order_detail(params).await.expect("Expected a response");
1481 let data_future = resp.data();
1482 let actual_response = data_future.await.unwrap();
1483 assert_eq!(actual_response, expected_response);
1484 });
1485 }
1486
1487 #[test]
1488 fn equity_order_detail_optional_params_success() {
1489 TOKIO_SHARED_RT.block_on(async {
1490 let client = MockTradeApiClient { force_error: false };
1491
1492 let params = EquityOrderDetailParams::builder().order_id("c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71".to_string()).client_order_id("web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a".to_string()).recv_window(5000).build().unwrap();
1493
1494 let resp_json: Value = serde_json::from_str(r#"{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","clientOrderId":"web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"180.48","qty":"1","notional":"notional","filledQty":"1","filledTotal":"filledTotal","fee":"0.10","session":"RTH","status":"FILLED","createdAt":1735900000000,"updatedAt":1735900120000,"trades":[{"executionId":"exec-20260504-0001","executionAt":1735900115000,"price":"180.48","qty":"1"}]}"#).unwrap_or_else(|_| serde_json::json!({}));
1495 let expected_response : models::EquityOrderDetailResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::EquityOrderDetailResponse");
1496
1497 let resp = client.equity_order_detail(params).await.expect("Expected a response");
1498 let data_future = resp.data();
1499 let actual_response = data_future.await.unwrap();
1500 assert_eq!(actual_response, expected_response);
1501 });
1502 }
1503
1504 #[test]
1505 fn equity_order_detail_response_error() {
1506 TOKIO_SHARED_RT.block_on(async {
1507 let client = MockTradeApiClient { force_error: true };
1508
1509 let params = EquityOrderDetailParams::builder().build().unwrap();
1510
1511 match client.equity_order_detail(params).await {
1512 Ok(_) => panic!("Expected an error"),
1513 Err(err) => {
1514 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1515 }
1516 }
1517 });
1518 }
1519
1520 #[test]
1521 fn equity_order_history_required_params_success() {
1522 TOKIO_SHARED_RT.block_on(async {
1523 let client = MockTradeApiClient { force_error: false };
1524
1525 let params = EquityOrderHistoryParams::builder(1735800000000,1735900000000,).build().unwrap();
1526
1527 let resp_json: Value = serde_json::from_str(r#"{"total":2,"page":1,"size":20,"rows":[{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"180.48","qty":"1","notional":"notional","filledQty":"1","filledTotal":"filledTotal","fee":"0.10","session":"RTH","status":"FILLED","createdAt":1735900000000,"updatedAt":1735900120000}]}"#).unwrap_or_else(|_| serde_json::json!({}));
1528 let expected_response : models::EquityOrderHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::EquityOrderHistoryResponse");
1529
1530 let resp = client.equity_order_history(params).await.expect("Expected a response");
1531 let data_future = resp.data();
1532 let actual_response = data_future.await.unwrap();
1533 assert_eq!(actual_response, expected_response);
1534 });
1535 }
1536
1537 #[test]
1538 fn equity_order_history_optional_params_success() {
1539 TOKIO_SHARED_RT.block_on(async {
1540 let client = MockTradeApiClient { force_error: false };
1541
1542 let params = EquityOrderHistoryParams::builder(1735800000000,1735900000000,).symbol("NVDA".to_string()).order_type(EquityOrderHistoryOrderTypeEnum::Market).side(EquityOrderHistorySideEnum::Buy).order_status("FILLED,CANCELED".to_string()).current(1).size(20).recv_window(5000).build().unwrap();
1543
1544 let resp_json: Value = serde_json::from_str(r#"{"total":2,"page":1,"size":20,"rows":[{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"180.48","qty":"1","notional":"notional","filledQty":"1","filledTotal":"filledTotal","fee":"0.10","session":"RTH","status":"FILLED","createdAt":1735900000000,"updatedAt":1735900120000}]}"#).unwrap_or_else(|_| serde_json::json!({}));
1545 let expected_response : models::EquityOrderHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::EquityOrderHistoryResponse");
1546
1547 let resp = client.equity_order_history(params).await.expect("Expected a response");
1548 let data_future = resp.data();
1549 let actual_response = data_future.await.unwrap();
1550 assert_eq!(actual_response, expected_response);
1551 });
1552 }
1553
1554 #[test]
1555 fn equity_order_history_response_error() {
1556 TOKIO_SHARED_RT.block_on(async {
1557 let client = MockTradeApiClient { force_error: true };
1558
1559 let params = EquityOrderHistoryParams::builder(1735800000000, 1735900000000)
1560 .build()
1561 .unwrap();
1562
1563 match client.equity_order_history(params).await {
1564 Ok(_) => panic!("Expected an error"),
1565 Err(err) => {
1566 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1567 }
1568 }
1569 });
1570 }
1571
1572 #[test]
1573 fn equity_trade_history_required_params_success() {
1574 TOKIO_SHARED_RT.block_on(async {
1575 let client = MockTradeApiClient { force_error: false };
1576
1577 let params = EquityTradeHistoryParams::builder(1735800000000,1735900000000,).build().unwrap();
1578
1579 let resp_json: Value = serde_json::from_str(r#"{"total":1,"page":1,"size":20,"rows":[{"executionId":"exec-20260504-0001","orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","price":"180.50","qty":"1","total":"180.50","executionAt":1735900115000,"updatedAt":1735900115200}]}"#).unwrap_or_else(|_| serde_json::json!({}));
1580 let expected_response : models::EquityTradeHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::EquityTradeHistoryResponse");
1581
1582 let resp = client.equity_trade_history(params).await.expect("Expected a response");
1583 let data_future = resp.data();
1584 let actual_response = data_future.await.unwrap();
1585 assert_eq!(actual_response, expected_response);
1586 });
1587 }
1588
1589 #[test]
1590 fn equity_trade_history_optional_params_success() {
1591 TOKIO_SHARED_RT.block_on(async {
1592 let client = MockTradeApiClient { force_error: false };
1593
1594 let params = EquityTradeHistoryParams::builder(1735800000000,1735900000000,).symbol("NVDA".to_string()).side(EquityTradeHistorySideEnum::Buy).order_id("c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71".to_string()).current(1).size(20).recv_window(5000).build().unwrap();
1595
1596 let resp_json: Value = serde_json::from_str(r#"{"total":1,"page":1,"size":20,"rows":[{"executionId":"exec-20260504-0001","orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","price":"180.50","qty":"1","total":"180.50","executionAt":1735900115000,"updatedAt":1735900115200}]}"#).unwrap_or_else(|_| serde_json::json!({}));
1597 let expected_response : models::EquityTradeHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::EquityTradeHistoryResponse");
1598
1599 let resp = client.equity_trade_history(params).await.expect("Expected a response");
1600 let data_future = resp.data();
1601 let actual_response = data_future.await.unwrap();
1602 assert_eq!(actual_response, expected_response);
1603 });
1604 }
1605
1606 #[test]
1607 fn equity_trade_history_response_error() {
1608 TOKIO_SHARED_RT.block_on(async {
1609 let client = MockTradeApiClient { force_error: true };
1610
1611 let params = EquityTradeHistoryParams::builder(1735800000000, 1735900000000)
1612 .build()
1613 .unwrap();
1614
1615 match client.equity_trade_history(params).await {
1616 Ok(_) => panic!("Expected an error"),
1617 Err(err) => {
1618 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1619 }
1620 }
1621 });
1622 }
1623
1624 #[test]
1625 fn place_equity_order_required_params_success() {
1626 TOKIO_SHARED_RT.block_on(async {
1627 let client = MockTradeApiClient { force_error: false };
1628
1629 let params = PlaceEquityOrderParams::builder("AAPL".to_string(),PlaceEquityOrderSideEnum::Buy,PlaceEquityOrderOrderTypeEnum::Market,).build().unwrap();
1630
1631 let resp_json: Value = serde_json::from_str(r#"{"status":"S","orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","clientOrderId":"web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a"}"#).unwrap_or_else(|_| serde_json::json!({}));
1632 let expected_response : models::PlaceEquityOrderResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::PlaceEquityOrderResponse");
1633
1634 let resp = client.place_equity_order(params).await.expect("Expected a response");
1635 let data_future = resp.data();
1636 let actual_response = data_future.await.unwrap();
1637 assert_eq!(actual_response, expected_response);
1638 });
1639 }
1640
1641 #[test]
1642 fn place_equity_order_optional_params_success() {
1643 TOKIO_SHARED_RT.block_on(async {
1644 let client = MockTradeApiClient { force_error: false };
1645
1646 let params = PlaceEquityOrderParams::builder("AAPL".to_string(),PlaceEquityOrderSideEnum::Buy,PlaceEquityOrderOrderTypeEnum::Market,).quote_asset("USDC".to_string()).price("180.50".to_string()).quantity("1".to_string()).notional("1000.00".to_string()).time_in_force(PlaceEquityOrderTimeInForceEnum::Day).trading_session(PlaceEquityOrderTradingSessionEnum::Rth).wallet_type(PlaceEquityOrderWalletTypeEnum::Card).client_order_id("web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a".to_string()).tokenize(true).recv_window(5000).build().unwrap();
1647
1648 let resp_json: Value = serde_json::from_str(r#"{"status":"S","orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","clientOrderId":"web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a"}"#).unwrap_or_else(|_| serde_json::json!({}));
1649 let expected_response : models::PlaceEquityOrderResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::PlaceEquityOrderResponse");
1650
1651 let resp = client.place_equity_order(params).await.expect("Expected a response");
1652 let data_future = resp.data();
1653 let actual_response = data_future.await.unwrap();
1654 assert_eq!(actual_response, expected_response);
1655 });
1656 }
1657
1658 #[test]
1659 fn place_equity_order_response_error() {
1660 TOKIO_SHARED_RT.block_on(async {
1661 let client = MockTradeApiClient { force_error: true };
1662
1663 let params = PlaceEquityOrderParams::builder(
1664 "AAPL".to_string(),
1665 PlaceEquityOrderSideEnum::Buy,
1666 PlaceEquityOrderOrderTypeEnum::Market,
1667 )
1668 .build()
1669 .unwrap();
1670
1671 match client.place_equity_order(params).await {
1672 Ok(_) => panic!("Expected an error"),
1673 Err(err) => {
1674 assert_eq!(err.to_string(), "Connector client error: ResponseError");
1675 }
1676 }
1677 });
1678 }
1679}