1#[cfg(test)]
19use nautilus_core::string::secret::REDACTED;
20use nautilus_core::string::secret::SecretString;
21use serde::Serialize;
22use zeroize::Zeroize;
23
24use crate::{
25 common::enums::{BinanceSelfTradePreventionMode, BinanceSide, BinanceTimeInForce},
26 spot::enums::{BinanceCancelReplaceMode, BinanceOrderResponseType, BinanceSpotOrderType},
27};
28
29#[derive(Debug, Clone, Serialize)]
31pub struct DepthParams {
32 pub symbol: String,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub limit: Option<u32>,
37}
38
39impl DepthParams {
40 #[must_use]
42 pub fn new(symbol: impl Into<String>) -> Self {
43 Self {
44 symbol: symbol.into(),
45 limit: None,
46 }
47 }
48
49 #[must_use]
51 pub fn with_limit(mut self, limit: u32) -> Self {
52 self.limit = Some(limit);
53 self
54 }
55}
56
57#[derive(Debug, Clone, Serialize)]
59pub struct TradesParams {
60 pub symbol: String,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub limit: Option<u32>,
65}
66
67impl TradesParams {
68 #[must_use]
70 pub fn new(symbol: impl Into<String>) -> Self {
71 Self {
72 symbol: symbol.into(),
73 limit: None,
74 }
75 }
76
77 #[must_use]
79 pub fn with_limit(mut self, limit: u32) -> Self {
80 self.limit = Some(limit);
81 self
82 }
83}
84
85#[derive(Debug, Clone, Serialize)]
87pub struct AggTradesParams {
88 pub symbol: String,
90 #[serde(skip_serializing_if = "Option::is_none", rename = "fromId")]
92 pub from_id: Option<i64>,
93 #[serde(skip_serializing_if = "Option::is_none", rename = "startTime")]
95 pub start_time: Option<i64>,
96 #[serde(skip_serializing_if = "Option::is_none", rename = "endTime")]
98 pub end_time: Option<i64>,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub limit: Option<u32>,
102}
103
104#[cfg(test)]
105mod market_data_tests {
106 use rstest::rstest;
107
108 use super::*;
109
110 #[rstest]
111 fn test_agg_trades_params_serialization() {
112 let params = AggTradesParams {
113 symbol: "ETHUSDT".to_string(),
114 from_id: Some(123),
115 start_time: Some(1_700_000_000_001),
116 end_time: Some(1_700_000_000_999),
117 limit: Some(456),
118 };
119
120 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
121
122 assert_eq!(
123 serialized,
124 "symbol=ETHUSDT&fromId=123&startTime=1700000000001&endTime=1700000000999&limit=456"
125 );
126 }
127}
128
129#[derive(Debug, Clone, Serialize)]
131pub struct NewOrderParams {
132 pub symbol: String,
134 pub side: BinanceSide,
136 #[serde(rename = "type")]
138 pub order_type: BinanceSpotOrderType,
139 #[serde(skip_serializing_if = "Option::is_none", rename = "timeInForce")]
141 pub time_in_force: Option<BinanceTimeInForce>,
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub quantity: Option<String>,
145 #[serde(skip_serializing_if = "Option::is_none", rename = "quoteOrderQty")]
147 pub quote_order_qty: Option<String>,
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub price: Option<String>,
151 #[serde(skip_serializing_if = "Option::is_none", rename = "newClientOrderId")]
153 pub new_client_order_id: Option<String>,
154 #[serde(skip_serializing_if = "Option::is_none", rename = "stopPrice")]
156 pub stop_price: Option<String>,
157 #[serde(skip_serializing_if = "Option::is_none", rename = "trailingDelta")]
159 pub trailing_delta: Option<i64>,
160 #[serde(skip_serializing_if = "Option::is_none", rename = "icebergQty")]
162 pub iceberg_qty: Option<String>,
163 #[serde(skip_serializing_if = "Option::is_none", rename = "newOrderRespType")]
165 pub new_order_resp_type: Option<BinanceOrderResponseType>,
166 #[serde(
168 skip_serializing_if = "Option::is_none",
169 rename = "selfTradePreventionMode"
170 )]
171 pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
172 #[serde(skip_serializing_if = "Option::is_none", rename = "strategyId")]
174 pub strategy_id: Option<i64>,
175 #[serde(skip_serializing_if = "Option::is_none", rename = "strategyType")]
177 pub strategy_type: Option<i64>,
178}
179
180impl NewOrderParams {
181 #[must_use]
183 pub fn limit(
184 symbol: impl Into<String>,
185 side: BinanceSide,
186 quantity: impl Into<String>,
187 price: impl Into<String>,
188 ) -> Self {
189 Self {
190 symbol: symbol.into(),
191 side,
192 order_type: BinanceSpotOrderType::Limit,
193 time_in_force: Some(BinanceTimeInForce::Gtc),
194 quantity: Some(quantity.into()),
195 quote_order_qty: None,
196 price: Some(price.into()),
197 new_client_order_id: None,
198 stop_price: None,
199 trailing_delta: None,
200 iceberg_qty: None,
201 new_order_resp_type: Some(BinanceOrderResponseType::Full),
202 self_trade_prevention_mode: None,
203 strategy_id: None,
204 strategy_type: None,
205 }
206 }
207
208 #[must_use]
210 pub fn market(
211 symbol: impl Into<String>,
212 side: BinanceSide,
213 quantity: impl Into<String>,
214 ) -> Self {
215 Self {
216 symbol: symbol.into(),
217 side,
218 order_type: BinanceSpotOrderType::Market,
219 time_in_force: None,
220 quantity: Some(quantity.into()),
221 quote_order_qty: None,
222 price: None,
223 new_client_order_id: None,
224 stop_price: None,
225 trailing_delta: None,
226 iceberg_qty: None,
227 new_order_resp_type: Some(BinanceOrderResponseType::Full),
228 self_trade_prevention_mode: None,
229 strategy_id: None,
230 strategy_type: None,
231 }
232 }
233
234 #[must_use]
236 pub fn with_client_order_id(mut self, id: impl Into<String>) -> Self {
237 self.new_client_order_id = Some(id.into());
238 self
239 }
240
241 #[must_use]
243 pub fn with_time_in_force(mut self, tif: BinanceTimeInForce) -> Self {
244 self.time_in_force = Some(tif);
245 self
246 }
247
248 #[must_use]
250 pub fn with_stop_price(mut self, price: impl Into<String>) -> Self {
251 self.stop_price = Some(price.into());
252 self
253 }
254
255 #[must_use]
257 pub fn with_stp_mode(mut self, mode: BinanceSelfTradePreventionMode) -> Self {
258 self.self_trade_prevention_mode = Some(mode);
259 self
260 }
261}
262
263#[derive(Debug, Clone, Serialize)]
265pub struct CancelOrderParams {
266 pub symbol: String,
268 #[serde(skip_serializing_if = "Option::is_none", rename = "orderId")]
270 pub order_id: Option<i64>,
271 #[serde(skip_serializing_if = "Option::is_none", rename = "origClientOrderId")]
273 pub orig_client_order_id: Option<String>,
274 #[serde(skip_serializing_if = "Option::is_none", rename = "newClientOrderId")]
276 pub new_client_order_id: Option<String>,
277}
278
279impl CancelOrderParams {
280 #[must_use]
282 pub fn by_order_id(symbol: impl Into<String>, order_id: i64) -> Self {
283 Self {
284 symbol: symbol.into(),
285 order_id: Some(order_id),
286 orig_client_order_id: None,
287 new_client_order_id: None,
288 }
289 }
290
291 #[must_use]
293 pub fn by_client_order_id(
294 symbol: impl Into<String>,
295 client_order_id: impl Into<String>,
296 ) -> Self {
297 Self {
298 symbol: symbol.into(),
299 order_id: None,
300 orig_client_order_id: Some(client_order_id.into()),
301 new_client_order_id: None,
302 }
303 }
304}
305
306#[derive(Debug, Clone, Serialize)]
308pub struct CancelOpenOrdersParams {
309 pub symbol: String,
311}
312
313impl CancelOpenOrdersParams {
314 #[must_use]
316 pub fn new(symbol: impl Into<String>) -> Self {
317 Self {
318 symbol: symbol.into(),
319 }
320 }
321}
322
323#[derive(Debug, Clone, Serialize)]
325pub struct CancelReplaceOrderParams {
326 pub symbol: String,
328 pub side: BinanceSide,
330 #[serde(rename = "type")]
332 pub order_type: BinanceSpotOrderType,
333 #[serde(rename = "cancelReplaceMode")]
335 pub cancel_replace_mode: BinanceCancelReplaceMode,
336 #[serde(skip_serializing_if = "Option::is_none", rename = "timeInForce")]
338 pub time_in_force: Option<BinanceTimeInForce>,
339 #[serde(skip_serializing_if = "Option::is_none")]
341 pub quantity: Option<String>,
342 #[serde(skip_serializing_if = "Option::is_none", rename = "quoteOrderQty")]
344 pub quote_order_qty: Option<String>,
345 #[serde(skip_serializing_if = "Option::is_none")]
347 pub price: Option<String>,
348 #[serde(skip_serializing_if = "Option::is_none", rename = "cancelOrderId")]
350 pub cancel_order_id: Option<i64>,
351 #[serde(
353 skip_serializing_if = "Option::is_none",
354 rename = "cancelOrigClientOrderId"
355 )]
356 pub cancel_orig_client_order_id: Option<String>,
357 #[serde(
359 skip_serializing_if = "Option::is_none",
360 rename = "cancelNewClientOrderId"
361 )]
362 pub cancel_new_client_order_id: Option<String>,
363 #[serde(skip_serializing_if = "Option::is_none", rename = "newClientOrderId")]
365 pub new_client_order_id: Option<String>,
366 #[serde(skip_serializing_if = "Option::is_none", rename = "stopPrice")]
368 pub stop_price: Option<String>,
369 #[serde(skip_serializing_if = "Option::is_none", rename = "trailingDelta")]
371 pub trailing_delta: Option<i64>,
372 #[serde(skip_serializing_if = "Option::is_none", rename = "icebergQty")]
374 pub iceberg_qty: Option<String>,
375 #[serde(skip_serializing_if = "Option::is_none", rename = "newOrderRespType")]
377 pub new_order_resp_type: Option<BinanceOrderResponseType>,
378 #[serde(
380 skip_serializing_if = "Option::is_none",
381 rename = "selfTradePreventionMode"
382 )]
383 pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
384}
385
386#[derive(Debug, Clone, Serialize)]
388pub struct QueryOrderParams {
389 pub symbol: String,
391 #[serde(skip_serializing_if = "Option::is_none", rename = "orderId")]
393 pub order_id: Option<i64>,
394 #[serde(skip_serializing_if = "Option::is_none", rename = "origClientOrderId")]
396 pub orig_client_order_id: Option<String>,
397}
398
399impl QueryOrderParams {
400 #[must_use]
402 pub fn by_order_id(symbol: impl Into<String>, order_id: i64) -> Self {
403 Self {
404 symbol: symbol.into(),
405 order_id: Some(order_id),
406 orig_client_order_id: None,
407 }
408 }
409
410 #[must_use]
412 pub fn by_client_order_id(
413 symbol: impl Into<String>,
414 client_order_id: impl Into<String>,
415 ) -> Self {
416 Self {
417 symbol: symbol.into(),
418 order_id: None,
419 orig_client_order_id: Some(client_order_id.into()),
420 }
421 }
422}
423
424#[derive(Debug, Clone, Default, Serialize)]
426pub struct OpenOrdersParams {
427 #[serde(skip_serializing_if = "Option::is_none")]
429 pub symbol: Option<String>,
430}
431
432impl OpenOrdersParams {
433 #[must_use]
435 pub fn all() -> Self {
436 Self { symbol: None }
437 }
438
439 #[must_use]
441 pub fn for_symbol(symbol: impl Into<String>) -> Self {
442 Self {
443 symbol: Some(symbol.into()),
444 }
445 }
446}
447
448#[derive(Debug, Clone, Serialize)]
450pub struct AllOrdersParams {
451 pub symbol: String,
453 #[serde(skip_serializing_if = "Option::is_none", rename = "orderId")]
455 pub order_id: Option<i64>,
456 #[serde(skip_serializing_if = "Option::is_none", rename = "startTime")]
458 pub start_time: Option<i64>,
459 #[serde(skip_serializing_if = "Option::is_none", rename = "endTime")]
461 pub end_time: Option<i64>,
462 #[serde(skip_serializing_if = "Option::is_none")]
464 pub limit: Option<u32>,
465}
466
467impl AllOrdersParams {
468 #[must_use]
470 pub fn new(symbol: impl Into<String>) -> Self {
471 Self {
472 symbol: symbol.into(),
473 order_id: None,
474 start_time: None,
475 end_time: None,
476 limit: None,
477 }
478 }
479
480 #[must_use]
482 pub fn with_limit(mut self, limit: u32) -> Self {
483 self.limit = Some(limit);
484 self
485 }
486
487 #[must_use]
489 pub fn with_time_range(mut self, start: i64, end: i64) -> Self {
490 self.start_time = Some(start);
491 self.end_time = Some(end);
492 self
493 }
494}
495
496#[derive(Debug, Clone, Serialize)]
498pub struct NewOcoOrderParams {
499 pub symbol: String,
501 pub side: BinanceSide,
503 pub quantity: String,
505 pub price: String,
507 #[serde(rename = "stopPrice")]
509 pub stop_price: String,
510 #[serde(skip_serializing_if = "Option::is_none", rename = "stopLimitPrice")]
512 pub stop_limit_price: Option<String>,
513 #[serde(skip_serializing_if = "Option::is_none", rename = "listClientOrderId")]
515 pub list_client_order_id: Option<String>,
516 #[serde(skip_serializing_if = "Option::is_none", rename = "limitClientOrderId")]
518 pub limit_client_order_id: Option<String>,
519 #[serde(skip_serializing_if = "Option::is_none", rename = "stopClientOrderId")]
521 pub stop_client_order_id: Option<String>,
522 #[serde(skip_serializing_if = "Option::is_none", rename = "limitIcebergQty")]
524 pub limit_iceberg_qty: Option<String>,
525 #[serde(skip_serializing_if = "Option::is_none", rename = "stopIcebergQty")]
527 pub stop_iceberg_qty: Option<String>,
528 #[serde(
530 skip_serializing_if = "Option::is_none",
531 rename = "stopLimitTimeInForce"
532 )]
533 pub stop_limit_time_in_force: Option<BinanceTimeInForce>,
534 #[serde(skip_serializing_if = "Option::is_none", rename = "newOrderRespType")]
536 pub new_order_resp_type: Option<BinanceOrderResponseType>,
537 #[serde(
539 skip_serializing_if = "Option::is_none",
540 rename = "selfTradePreventionMode"
541 )]
542 pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
543}
544
545impl NewOcoOrderParams {
546 #[must_use]
548 pub fn new(
549 symbol: impl Into<String>,
550 side: BinanceSide,
551 quantity: impl Into<String>,
552 price: impl Into<String>,
553 stop_price: impl Into<String>,
554 ) -> Self {
555 Self {
556 symbol: symbol.into(),
557 side,
558 quantity: quantity.into(),
559 price: price.into(),
560 stop_price: stop_price.into(),
561 stop_limit_price: None,
562 list_client_order_id: None,
563 limit_client_order_id: None,
564 stop_client_order_id: None,
565 limit_iceberg_qty: None,
566 stop_iceberg_qty: None,
567 stop_limit_time_in_force: None,
568 new_order_resp_type: Some(BinanceOrderResponseType::Full),
569 self_trade_prevention_mode: None,
570 }
571 }
572
573 #[must_use]
575 pub fn with_stop_limit_price(mut self, price: impl Into<String>) -> Self {
576 self.stop_limit_price = Some(price.into());
577 self.stop_limit_time_in_force = Some(BinanceTimeInForce::Gtc);
578 self
579 }
580}
581
582#[derive(Debug, Clone, Serialize)]
584#[serde(rename_all = "camelCase")]
585pub struct NewOcoOrderListParams {
586 pub symbol: String,
588 #[serde(skip_serializing_if = "Option::is_none")]
590 pub list_client_order_id: Option<String>,
591 pub side: BinanceSide,
593 pub quantity: String,
595 pub above_type: BinanceSpotOrderType,
597 #[serde(skip_serializing_if = "Option::is_none")]
599 pub above_client_order_id: Option<String>,
600 #[serde(skip_serializing_if = "Option::is_none")]
602 pub above_iceberg_qty: Option<String>,
603 #[serde(skip_serializing_if = "Option::is_none")]
605 pub above_price: Option<String>,
606 #[serde(skip_serializing_if = "Option::is_none")]
608 pub above_stop_price: Option<String>,
609 #[serde(skip_serializing_if = "Option::is_none")]
611 pub above_time_in_force: Option<BinanceTimeInForce>,
612 pub below_type: BinanceSpotOrderType,
614 #[serde(skip_serializing_if = "Option::is_none")]
616 pub below_client_order_id: Option<String>,
617 #[serde(skip_serializing_if = "Option::is_none")]
619 pub below_iceberg_qty: Option<String>,
620 #[serde(skip_serializing_if = "Option::is_none")]
622 pub below_price: Option<String>,
623 #[serde(skip_serializing_if = "Option::is_none")]
625 pub below_stop_price: Option<String>,
626 #[serde(skip_serializing_if = "Option::is_none")]
628 pub below_time_in_force: Option<BinanceTimeInForce>,
629 #[serde(skip_serializing_if = "Option::is_none")]
631 pub new_order_resp_type: Option<BinanceOrderResponseType>,
632 #[serde(skip_serializing_if = "Option::is_none")]
634 pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
635}
636
637#[derive(Debug, Clone, Serialize)]
639pub struct CancelOrderListParams {
640 pub symbol: String,
642 #[serde(skip_serializing_if = "Option::is_none", rename = "orderListId")]
644 pub order_list_id: Option<i64>,
645 #[serde(skip_serializing_if = "Option::is_none", rename = "listClientOrderId")]
647 pub list_client_order_id: Option<String>,
648 #[serde(skip_serializing_if = "Option::is_none", rename = "newClientOrderId")]
650 pub new_client_order_id: Option<String>,
651}
652
653impl CancelOrderListParams {
654 #[must_use]
656 pub fn by_order_list_id(symbol: impl Into<String>, order_list_id: i64) -> Self {
657 Self {
658 symbol: symbol.into(),
659 order_list_id: Some(order_list_id),
660 list_client_order_id: None,
661 new_client_order_id: None,
662 }
663 }
664
665 #[must_use]
667 pub fn by_list_client_order_id(
668 symbol: impl Into<String>,
669 list_client_order_id: impl Into<String>,
670 ) -> Self {
671 Self {
672 symbol: symbol.into(),
673 order_list_id: None,
674 list_client_order_id: Some(list_client_order_id.into()),
675 new_client_order_id: None,
676 }
677 }
678}
679
680#[derive(Debug, Clone, Serialize)]
682pub struct QueryOrderListParams {
683 #[serde(skip_serializing_if = "Option::is_none", rename = "orderListId")]
685 pub order_list_id: Option<i64>,
686 #[serde(skip_serializing_if = "Option::is_none", rename = "origClientOrderId")]
688 pub orig_client_order_id: Option<String>,
689}
690
691impl QueryOrderListParams {
692 #[must_use]
694 pub fn by_order_list_id(order_list_id: i64) -> Self {
695 Self {
696 order_list_id: Some(order_list_id),
697 orig_client_order_id: None,
698 }
699 }
700
701 #[must_use]
703 pub fn by_client_order_id(client_order_id: impl Into<String>) -> Self {
704 Self {
705 order_list_id: None,
706 orig_client_order_id: Some(client_order_id.into()),
707 }
708 }
709}
710
711#[derive(Debug, Clone, Default, Serialize)]
713pub struct AllOrderListsParams {
714 #[serde(skip_serializing_if = "Option::is_none", rename = "startTime")]
716 pub start_time: Option<i64>,
717 #[serde(skip_serializing_if = "Option::is_none", rename = "endTime")]
719 pub end_time: Option<i64>,
720 #[serde(skip_serializing_if = "Option::is_none")]
722 pub limit: Option<u32>,
723}
724
725#[derive(Debug, Clone, Default, Serialize)]
727pub struct OpenOrderListsParams {}
728
729#[derive(Debug, Clone, Default, Serialize)]
731pub struct AccountInfoParams {
732 #[serde(skip_serializing_if = "Option::is_none", rename = "omitZeroBalances")]
734 pub omit_zero_balances: Option<bool>,
735}
736
737#[derive(Debug, Clone, Serialize)]
739pub struct AccountCommissionParams {
740 pub symbol: String,
742}
743
744impl AccountCommissionParams {
745 #[must_use]
747 pub fn new(symbol: impl Into<String>) -> Self {
748 Self {
749 symbol: symbol.into(),
750 }
751 }
752}
753
754impl AccountInfoParams {
755 #[must_use]
757 pub fn new() -> Self {
758 Self::default()
759 }
760
761 #[must_use]
763 pub fn omit_zero_balances(mut self) -> Self {
764 self.omit_zero_balances = Some(true);
765 self
766 }
767}
768
769#[derive(Debug, Clone, Serialize)]
771pub struct AccountTradesParams {
772 pub symbol: String,
774 #[serde(skip_serializing_if = "Option::is_none", rename = "orderId")]
776 pub order_id: Option<i64>,
777 #[serde(skip_serializing_if = "Option::is_none", rename = "startTime")]
779 pub start_time: Option<i64>,
780 #[serde(skip_serializing_if = "Option::is_none", rename = "endTime")]
782 pub end_time: Option<i64>,
783 #[serde(skip_serializing_if = "Option::is_none", rename = "fromId")]
785 pub from_id: Option<i64>,
786 #[serde(skip_serializing_if = "Option::is_none")]
788 pub limit: Option<u32>,
789}
790
791impl AccountTradesParams {
792 #[must_use]
794 pub fn new(symbol: impl Into<String>) -> Self {
795 Self {
796 symbol: symbol.into(),
797 order_id: None,
798 start_time: None,
799 end_time: None,
800 from_id: None,
801 limit: None,
802 }
803 }
804
805 #[must_use]
807 pub fn for_order(mut self, order_id: i64) -> Self {
808 self.order_id = Some(order_id);
809 self
810 }
811
812 #[must_use]
814 pub fn with_limit(mut self, limit: u32) -> Self {
815 self.limit = Some(limit);
816 self
817 }
818
819 #[must_use]
821 pub fn with_time_range(mut self, start: i64, end: i64) -> Self {
822 self.start_time = Some(start);
823 self.end_time = Some(end);
824 self
825 }
826}
827
828#[derive(Debug, Clone, Serialize)]
830pub struct KlinesParams {
831 pub symbol: String,
833 pub interval: String,
835 #[serde(skip_serializing_if = "Option::is_none", rename = "startTime")]
837 pub start_time: Option<i64>,
838 #[serde(skip_serializing_if = "Option::is_none", rename = "endTime")]
840 pub end_time: Option<i64>,
841 #[serde(skip_serializing_if = "Option::is_none", rename = "timeZone")]
843 pub time_zone: Option<String>,
844 #[serde(skip_serializing_if = "Option::is_none")]
846 pub limit: Option<u32>,
847}
848
849#[derive(Debug, Clone, Serialize, Zeroize)]
851pub struct ListenKeyParams {
852 #[serde(rename = "listenKey")]
854 pub listen_key: SecretString,
855}
856
857impl ListenKeyParams {
858 #[must_use]
860 pub fn new(listen_key: impl Into<SecretString>) -> Self {
861 Self {
862 listen_key: listen_key.into(),
863 }
864 }
865}
866
867#[derive(Debug, Clone, Default, Serialize)]
869pub struct TickerParams {
870 #[serde(skip_serializing_if = "Option::is_none")]
872 pub symbol: Option<String>,
873}
874
875impl TickerParams {
876 #[must_use]
878 pub fn all() -> Self {
879 Self { symbol: None }
880 }
881
882 #[must_use]
884 pub fn for_symbol(symbol: impl Into<String>) -> Self {
885 Self {
886 symbol: Some(symbol.into()),
887 }
888 }
889}
890
891#[derive(Debug, Clone, Serialize)]
893pub struct AvgPriceParams {
894 pub symbol: String,
896}
897
898impl AvgPriceParams {
899 #[must_use]
901 pub fn new(symbol: impl Into<String>) -> Self {
902 Self {
903 symbol: symbol.into(),
904 }
905 }
906}
907
908#[derive(Debug, Clone, Default, Serialize)]
910pub struct TradeFeeParams {
911 #[serde(skip_serializing_if = "Option::is_none")]
913 pub symbol: Option<String>,
914}
915
916impl TradeFeeParams {
917 #[must_use]
919 pub fn all() -> Self {
920 Self { symbol: None }
921 }
922
923 #[must_use]
925 pub fn for_symbol(symbol: impl Into<String>) -> Self {
926 Self {
927 symbol: Some(symbol.into()),
928 }
929 }
930}
931
932#[derive(Debug, Clone, Serialize)]
934#[serde(rename_all = "camelCase")]
935pub struct BatchOrderItem {
936 pub symbol: String,
938 pub side: String,
940 #[serde(rename = "type")]
942 pub order_type: String,
943 #[serde(skip_serializing_if = "Option::is_none")]
945 pub time_in_force: Option<String>,
946 #[serde(skip_serializing_if = "Option::is_none")]
948 pub quantity: Option<String>,
949 #[serde(skip_serializing_if = "Option::is_none")]
951 pub price: Option<String>,
952 #[serde(skip_serializing_if = "Option::is_none")]
954 pub new_client_order_id: Option<String>,
955 #[serde(skip_serializing_if = "Option::is_none")]
957 pub stop_price: Option<String>,
958}
959
960impl BatchOrderItem {
961 #[must_use]
963 pub fn from_params(params: &NewOrderParams) -> Self {
964 Self {
965 symbol: params.symbol.clone(),
966 side: format!("{:?}", params.side).to_uppercase(),
967 order_type: format!("{:?}", params.order_type).to_uppercase(),
968 time_in_force: params
969 .time_in_force
970 .map(|t| format!("{t:?}").to_uppercase()),
971 quantity: params.quantity.clone(),
972 price: params.price.clone(),
973 new_client_order_id: params.new_client_order_id.clone(),
974 stop_price: params.stop_price.clone(),
975 }
976 }
977}
978
979#[derive(Debug, Clone, Serialize)]
981#[serde(rename_all = "camelCase")]
982pub struct BatchCancelItem {
983 pub symbol: String,
985 #[serde(skip_serializing_if = "Option::is_none")]
987 pub order_id: Option<i64>,
988 #[serde(skip_serializing_if = "Option::is_none")]
990 pub orig_client_order_id: Option<String>,
991}
992
993impl BatchCancelItem {
994 #[must_use]
996 pub fn by_order_id(symbol: impl Into<String>, order_id: i64) -> Self {
997 Self {
998 symbol: symbol.into(),
999 order_id: Some(order_id),
1000 orig_client_order_id: None,
1001 }
1002 }
1003
1004 #[must_use]
1006 pub fn by_client_order_id(
1007 symbol: impl Into<String>,
1008 client_order_id: impl Into<String>,
1009 ) -> Self {
1010 Self {
1011 symbol: symbol.into(),
1012 order_id: None,
1013 orig_client_order_id: Some(client_order_id.into()),
1014 }
1015 }
1016}
1017
1018#[cfg(test)]
1019mod tests {
1020 use rstest::rstest;
1021 use zeroize::Zeroize;
1022
1023 use super::*;
1024
1025 #[rstest]
1026 fn test_listen_key_params_preserve_wire_value_and_redact_debug() {
1027 let mut params = ListenKeyParams::new("listen-key-secret");
1028
1029 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
1030 let debug = format!("{params:?}");
1031
1032 assert_eq!(serialized, "listenKey=listen-key-secret");
1033 assert!(debug.contains(REDACTED));
1034 assert!(!debug.contains(params.listen_key.expose_secret()));
1035
1036 params.zeroize();
1037 assert!(params.listen_key.expose_secret().is_empty());
1038 }
1039}