1use derive_builder::Builder;
19#[cfg(test)]
20use nautilus_core::string::secret::REDACTED;
21use nautilus_core::string::secret::SecretString;
22use serde::{Deserialize, Serialize};
23use zeroize::Zeroize;
24
25use crate::common::enums::{
26 BinanceAlgoType, BinanceFuturesOrderType, BinanceIncomeType, BinanceMarginType,
27 BinancePositionSide, BinancePriceMatch, BinanceSelfTradePreventionMode, BinanceSide,
28 BinanceTimeInForce, BinanceWorkingType,
29};
30
31#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
33#[builder(setter(into, strip_option), default)]
34pub struct BinanceDepthParams {
35 pub symbol: String,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub limit: Option<u32>,
40}
41
42#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
44#[builder(setter(into, strip_option), default)]
45pub struct BinanceTradesParams {
46 pub symbol: String,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub limit: Option<u32>,
51}
52
53#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
55#[builder(setter(into, strip_option), default)]
56pub struct BinanceAggTradesParams {
57 pub symbol: String,
59 #[serde(rename = "fromId", skip_serializing_if = "Option::is_none")]
61 pub from_id: Option<i64>,
62 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
64 pub start_time: Option<i64>,
65 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
67 pub end_time: Option<i64>,
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub limit: Option<u32>,
71}
72
73#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
75#[builder(setter(into, strip_option), default)]
76pub struct BinanceKlinesParams {
77 pub symbol: String,
79 pub interval: String,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 #[serde(rename = "startTime")]
84 pub start_time: Option<i64>,
85 #[serde(skip_serializing_if = "Option::is_none")]
87 #[serde(rename = "endTime")]
88 pub end_time: Option<i64>,
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub limit: Option<u32>,
92}
93
94#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
96#[builder(default)]
97#[builder(setter(into, strip_option))]
98pub struct BinanceTicker24hrParams {
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub symbol: Option<String>,
102}
103
104#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
106#[builder(default)]
107#[builder(setter(into, strip_option))]
108pub struct BinanceBookTickerParams {
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub symbol: Option<String>,
112}
113
114#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
116#[builder(default)]
117#[builder(setter(into, strip_option))]
118pub struct BinanceMarkPriceParams {
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub symbol: Option<String>,
122}
123
124#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
126#[builder(default)]
127#[builder(setter(into, strip_option))]
128pub struct BinanceFundingRateParams {
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub symbol: Option<String>,
132 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
134 pub start_time: Option<i64>,
135 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
137 pub end_time: Option<i64>,
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub limit: Option<u32>,
141}
142
143#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
145#[builder(setter(into))]
146pub struct BinanceOpenInterestParams {
147 pub symbol: String,
149}
150
151#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
153#[builder(setter(into, strip_option), default)]
154pub struct BinanceOpenInterestHistParams {
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub symbol: Option<String>,
158 #[serde(skip_serializing_if = "Option::is_none")]
160 pub pair: Option<String>,
161 #[serde(rename = "contractType", skip_serializing_if = "Option::is_none")]
163 pub contract_type: Option<String>,
164 pub period: String,
166 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
168 pub start_time: Option<i64>,
169 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
171 pub end_time: Option<i64>,
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub limit: Option<u32>,
175}
176
177#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
179#[builder(default)]
180#[builder(setter(into, strip_option))]
181pub struct BinanceFuturesBalanceParams {
182 #[serde(skip_serializing_if = "Option::is_none")]
184 pub asset: Option<String>,
185 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
187 pub recv_window: Option<u64>,
188}
189
190#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
192#[builder(default)]
193#[builder(setter(into, strip_option))]
194pub struct BinancePositionRiskParams {
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub symbol: Option<String>,
198 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
200 pub recv_window: Option<u64>,
201}
202
203#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
205#[builder(setter(into))]
206pub struct BinanceCommissionRateParams {
207 pub symbol: String,
209}
210
211#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
213#[builder(default)]
214#[builder(setter(into, strip_option))]
215pub struct BinanceIncomeHistoryParams {
216 #[serde(skip_serializing_if = "Option::is_none")]
218 pub symbol: Option<String>,
219 #[serde(rename = "incomeType", skip_serializing_if = "Option::is_none")]
221 pub income_type: Option<BinanceIncomeType>,
222 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
224 pub start_time: Option<i64>,
225 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
227 pub end_time: Option<i64>,
228 #[serde(skip_serializing_if = "Option::is_none")]
230 pub limit: Option<u32>,
231 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
233 pub recv_window: Option<u64>,
234}
235
236#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
238#[builder(setter(into, strip_option), default)]
239pub struct BinanceUserTradesParams {
240 pub symbol: String,
242 #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
244 pub order_id: Option<i64>,
245 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
247 pub start_time: Option<i64>,
248 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
250 pub end_time: Option<i64>,
251 #[serde(rename = "fromId", skip_serializing_if = "Option::is_none")]
253 pub from_id: Option<i64>,
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub limit: Option<u32>,
257 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
259 pub recv_window: Option<u64>,
260}
261
262#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
264#[builder(default)]
265#[builder(setter(into, strip_option))]
266pub struct BinanceOpenOrdersParams {
267 #[serde(skip_serializing_if = "Option::is_none")]
269 pub symbol: Option<String>,
270 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
272 pub recv_window: Option<u64>,
273}
274
275#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
277#[builder(setter(into, strip_option), default)]
278pub struct BinanceOrderQueryParams {
279 pub symbol: String,
281 #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
283 pub order_id: Option<i64>,
284 #[serde(rename = "origClientOrderId", skip_serializing_if = "Option::is_none")]
286 pub orig_client_order_id: Option<String>,
287 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
289 pub recv_window: Option<u64>,
290}
291
292#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
294#[builder(setter(into, strip_option))]
295pub struct BinanceNewOrderParams {
296 pub symbol: String,
298 pub side: BinanceSide,
300 #[serde(rename = "type")]
302 pub order_type: BinanceFuturesOrderType,
303 #[serde(rename = "positionSide", skip_serializing_if = "Option::is_none")]
305 #[builder(default)]
306 pub position_side: Option<BinancePositionSide>,
307 #[serde(rename = "timeInForce", skip_serializing_if = "Option::is_none")]
309 #[builder(default)]
310 pub time_in_force: Option<BinanceTimeInForce>,
311 #[serde(skip_serializing_if = "Option::is_none")]
313 #[builder(default)]
314 pub quantity: Option<String>,
315 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
317 #[builder(default)]
318 pub reduce_only: Option<bool>,
319 #[serde(skip_serializing_if = "Option::is_none")]
321 #[builder(default)]
322 pub price: Option<String>,
323 #[serde(rename = "newClientOrderId", skip_serializing_if = "Option::is_none")]
325 #[builder(default)]
326 pub new_client_order_id: Option<String>,
327 #[serde(rename = "stopPrice", skip_serializing_if = "Option::is_none")]
329 #[builder(default)]
330 pub stop_price: Option<String>,
331 #[serde(rename = "closePosition", skip_serializing_if = "Option::is_none")]
333 #[builder(default)]
334 pub close_position: Option<bool>,
335 #[serde(rename = "activationPrice", skip_serializing_if = "Option::is_none")]
337 #[builder(default)]
338 pub activation_price: Option<String>,
339 #[serde(rename = "callbackRate", skip_serializing_if = "Option::is_none")]
341 #[builder(default)]
342 pub callback_rate: Option<String>,
343 #[serde(rename = "workingType", skip_serializing_if = "Option::is_none")]
345 #[builder(default)]
346 pub working_type: Option<BinanceWorkingType>,
347 #[serde(rename = "priceProtect", skip_serializing_if = "Option::is_none")]
349 #[builder(default)]
350 pub price_protect: Option<bool>,
351 #[serde(rename = "newOrderRespType", skip_serializing_if = "Option::is_none")]
353 #[builder(default)]
354 pub new_order_resp_type: Option<String>,
355 #[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
357 #[builder(default)]
358 pub good_till_date: Option<i64>,
359 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
361 #[builder(default)]
362 pub recv_window: Option<u64>,
363 #[serde(rename = "priceMatch", skip_serializing_if = "Option::is_none")]
365 #[builder(default)]
366 pub price_match: Option<BinancePriceMatch>,
367 #[serde(
369 rename = "selfTradePreventionMode",
370 skip_serializing_if = "Option::is_none"
371 )]
372 #[builder(default)]
373 pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
374}
375
376#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
378#[builder(setter(into, strip_option), default)]
379pub struct BinanceCancelOrderParams {
380 pub symbol: String,
382 #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
384 pub order_id: Option<i64>,
385 #[serde(rename = "origClientOrderId", skip_serializing_if = "Option::is_none")]
387 pub orig_client_order_id: Option<String>,
388 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
390 pub recv_window: Option<u64>,
391}
392
393#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
395#[builder(setter(into, strip_option), default)]
396pub struct BinanceCancelAllOrdersParams {
397 pub symbol: String,
399 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
401 pub recv_window: Option<u64>,
402}
403
404#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
406#[builder(setter(into, strip_option))]
407pub struct BinanceModifyOrderParams {
408 pub symbol: String,
410 #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
412 #[builder(default)]
413 pub order_id: Option<i64>,
414 #[serde(rename = "origClientOrderId", skip_serializing_if = "Option::is_none")]
416 #[builder(default)]
417 pub orig_client_order_id: Option<String>,
418 pub side: BinanceSide,
420 pub quantity: String,
422 pub price: String,
424 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
426 #[builder(default)]
427 pub recv_window: Option<u64>,
428}
429
430#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
432#[builder(setter(into, strip_option), default)]
433pub struct BinanceAllOrdersParams {
434 pub symbol: String,
436 #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
438 pub order_id: Option<i64>,
439 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
441 pub start_time: Option<i64>,
442 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
444 pub end_time: Option<i64>,
445 #[serde(skip_serializing_if = "Option::is_none")]
447 pub limit: Option<u32>,
448 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
450 pub recv_window: Option<u64>,
451}
452
453#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
455#[builder(setter(into))]
456pub struct BinanceSetLeverageParams {
457 pub symbol: String,
459 pub leverage: u32,
461 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
463 #[builder(default)]
464 pub recv_window: Option<u64>,
465}
466
467#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
469#[builder(setter(into))]
470pub struct BinanceSetMarginTypeParams {
471 pub symbol: String,
473 #[serde(rename = "marginType")]
475 pub margin_type: BinanceMarginType,
476 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
478 #[builder(default)]
479 pub recv_window: Option<u64>,
480}
481
482#[derive(Clone, Debug, Serialize)]
484#[serde(rename_all = "camelCase")]
485pub struct BatchOrderItem {
486 pub symbol: String,
488 pub side: String,
490 #[serde(rename = "type")]
492 pub order_type: String,
493 #[serde(skip_serializing_if = "Option::is_none")]
495 pub time_in_force: Option<String>,
496 #[serde(skip_serializing_if = "Option::is_none")]
498 pub quantity: Option<String>,
499 #[serde(skip_serializing_if = "Option::is_none")]
501 pub price: Option<String>,
502 #[serde(skip_serializing_if = "Option::is_none")]
504 pub reduce_only: Option<bool>,
505 #[serde(skip_serializing_if = "Option::is_none")]
507 pub new_client_order_id: Option<String>,
508 #[serde(skip_serializing_if = "Option::is_none")]
510 pub stop_price: Option<String>,
511 #[serde(skip_serializing_if = "Option::is_none")]
513 pub position_side: Option<String>,
514 #[serde(skip_serializing_if = "Option::is_none")]
516 pub activation_price: Option<String>,
517 #[serde(skip_serializing_if = "Option::is_none")]
519 pub callback_rate: Option<String>,
520 #[serde(skip_serializing_if = "Option::is_none")]
522 pub working_type: Option<String>,
523 #[serde(skip_serializing_if = "Option::is_none")]
525 pub price_protect: Option<bool>,
526 #[serde(skip_serializing_if = "Option::is_none")]
528 pub close_position: Option<bool>,
529 #[serde(skip_serializing_if = "Option::is_none")]
531 pub good_till_date: Option<i64>,
532 #[serde(skip_serializing_if = "Option::is_none")]
534 pub price_match: Option<String>,
535 #[serde(skip_serializing_if = "Option::is_none")]
537 pub self_trade_prevention_mode: Option<String>,
538}
539
540#[derive(Clone, Debug, Serialize)]
542#[serde(rename_all = "camelCase")]
543pub struct BatchCancelItem {
544 pub symbol: String,
546 #[serde(skip_serializing_if = "Option::is_none")]
548 pub order_id: Option<i64>,
549 #[serde(skip_serializing_if = "Option::is_none")]
551 pub orig_client_order_id: Option<String>,
552}
553
554impl BatchCancelItem {
555 #[must_use]
557 pub fn by_order_id(symbol: impl Into<String>, order_id: i64) -> Self {
558 Self {
559 symbol: symbol.into(),
560 order_id: Some(order_id),
561 orig_client_order_id: None,
562 }
563 }
564
565 #[must_use]
567 pub fn by_client_order_id(
568 symbol: impl Into<String>,
569 client_order_id: impl Into<String>,
570 ) -> Self {
571 Self {
572 symbol: symbol.into(),
573 order_id: None,
574 orig_client_order_id: Some(client_order_id.into()),
575 }
576 }
577}
578
579#[derive(Clone, Debug, Serialize)]
581#[serde(rename_all = "camelCase")]
582pub struct BatchModifyItem {
583 pub symbol: String,
585 #[serde(
587 skip_serializing_if = "Option::is_none",
588 serialize_with = "serialize_batch_order_id"
589 )]
590 pub order_id: Option<i64>,
591 #[serde(skip_serializing_if = "Option::is_none")]
593 pub orig_client_order_id: Option<String>,
594 pub side: String,
596 pub quantity: String,
598 pub price: String,
600}
601
602#[derive(Debug, Clone, Serialize, Zeroize)]
604#[serde(rename_all = "camelCase")]
605pub struct ListenKeyParams {
606 pub listen_key: SecretString,
608}
609
610#[derive(Clone, Debug, Serialize, Builder)]
616#[builder(setter(into, strip_option))]
617#[serde(rename_all = "camelCase")]
618pub struct BinanceNewAlgoOrderParams {
619 pub symbol: String,
621 pub side: BinanceSide,
623 #[serde(rename = "type")]
625 pub order_type: BinanceFuturesOrderType,
626 #[serde(rename = "algoType")]
628 pub algo_type: BinanceAlgoType,
629 #[serde(rename = "positionSide", skip_serializing_if = "Option::is_none")]
631 #[builder(default)]
632 pub position_side: Option<BinancePositionSide>,
633 #[serde(skip_serializing_if = "Option::is_none")]
635 #[builder(default)]
636 pub quantity: Option<String>,
637 #[serde(skip_serializing_if = "Option::is_none")]
639 #[builder(default)]
640 pub price: Option<String>,
641 #[serde(rename = "triggerPrice", skip_serializing_if = "Option::is_none")]
643 #[builder(default)]
644 pub trigger_price: Option<String>,
645 #[serde(rename = "timeInForce", skip_serializing_if = "Option::is_none")]
647 #[builder(default)]
648 pub time_in_force: Option<BinanceTimeInForce>,
649 #[serde(rename = "workingType", skip_serializing_if = "Option::is_none")]
651 #[builder(default)]
652 pub working_type: Option<BinanceWorkingType>,
653 #[serde(rename = "closePosition", skip_serializing_if = "Option::is_none")]
655 #[builder(default)]
656 pub close_position: Option<bool>,
657 #[serde(rename = "priceProtect", skip_serializing_if = "Option::is_none")]
659 #[builder(default)]
660 pub price_protect: Option<bool>,
661 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
663 #[builder(default)]
664 pub reduce_only: Option<bool>,
665 #[serde(rename = "activatePrice", skip_serializing_if = "Option::is_none")]
667 #[builder(default)]
668 pub activation_price: Option<String>,
669 #[serde(rename = "callbackRate", skip_serializing_if = "Option::is_none")]
671 #[builder(default)]
672 pub callback_rate: Option<String>,
673 #[serde(rename = "clientAlgoId", skip_serializing_if = "Option::is_none")]
675 #[builder(default)]
676 pub client_algo_id: Option<String>,
677 #[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
679 #[builder(default)]
680 pub good_till_date: Option<i64>,
681 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
683 #[builder(default)]
684 pub recv_window: Option<u64>,
685}
686
687#[derive(Clone, Debug, Default, Serialize, Builder)]
689#[builder(setter(into, strip_option), default)]
690#[serde(rename_all = "camelCase")]
691pub struct BinanceAlgoOrderQueryParams {
692 #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
694 pub algo_id: Option<i64>,
695 #[serde(rename = "clientAlgoId", skip_serializing_if = "Option::is_none")]
697 pub client_algo_id: Option<String>,
698 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
700 pub recv_window: Option<u64>,
701}
702
703#[derive(Clone, Debug, Default, Serialize, Builder)]
705#[builder(setter(into, strip_option), default)]
706#[serde(rename_all = "camelCase")]
707pub struct BinanceOpenAlgoOrdersParams {
708 #[serde(skip_serializing_if = "Option::is_none")]
710 pub symbol: Option<String>,
711 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
713 pub recv_window: Option<u64>,
714}
715
716#[derive(Clone, Debug, Serialize, Builder)]
718#[builder(setter(into, strip_option))]
719#[serde(rename_all = "camelCase")]
720pub struct BinanceAllAlgoOrdersParams {
721 pub symbol: String,
723 #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
725 #[builder(default)]
726 pub algo_id: Option<i64>,
727 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
729 #[builder(default)]
730 pub start_time: Option<i64>,
731 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
733 #[builder(default)]
734 pub end_time: Option<i64>,
735 #[serde(skip_serializing_if = "Option::is_none")]
737 #[builder(default)]
738 pub page: Option<u32>,
739 #[serde(skip_serializing_if = "Option::is_none")]
741 #[builder(default)]
742 pub limit: Option<u32>,
743 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
745 #[builder(default)]
746 pub recv_window: Option<u64>,
747}
748
749#[derive(Clone, Debug, Serialize, Builder)]
751#[builder(setter(into, strip_option))]
752#[serde(rename_all = "camelCase")]
753pub struct BinanceCancelAllAlgoOrdersParams {
754 pub symbol: String,
756 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
758 #[builder(default)]
759 pub recv_window: Option<u64>,
760}
761
762fn serialize_batch_order_id<S: serde::Serializer>(
763 order_id: &Option<i64>,
764 serializer: S,
765) -> Result<S::Ok, S::Error> {
766 match order_id {
767 Some(id) => serializer.collect_str(id),
768 None => serializer.serialize_none(),
769 }
770}
771
772#[cfg(test)]
773mod tests {
774 use rstest::rstest;
775 use zeroize::Zeroize;
776
777 use super::*;
778
779 #[rstest]
780 fn test_listen_key_params_preserve_wire_value_and_redact_debug() {
781 let mut params = ListenKeyParams {
782 listen_key: SecretString::from("listen-key-secret"),
783 };
784
785 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
786 let debug = format!("{params:?}");
787
788 assert_eq!(serialized, "listenKey=listen-key-secret");
789 assert!(debug.contains(REDACTED));
790 assert!(!debug.contains(params.listen_key.expose_secret()));
791
792 params.zeroize();
793 assert!(params.listen_key.expose_secret().is_empty());
794 }
795
796 #[rstest]
797 fn test_depth_params_builder() {
798 let params = BinanceDepthParamsBuilder::default()
799 .symbol("BTCUSDT")
800 .limit(100u32)
801 .build()
802 .unwrap();
803
804 assert_eq!(params.symbol, "BTCUSDT");
805 assert_eq!(params.limit, Some(100));
806 }
807
808 #[rstest]
809 fn test_ticker_params_serialization() {
810 let params = BinanceTicker24hrParams {
811 symbol: Some("BTCUSDT".to_string()),
812 };
813
814 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
815 assert_eq!(serialized, "symbol=BTCUSDT");
816 }
817
818 #[rstest]
819 fn test_agg_trades_params_serialization() {
820 let params = BinanceAggTradesParams {
821 symbol: "BTCUSDT".to_string(),
822 from_id: Some(123),
823 start_time: Some(1_700_000_000_001),
824 end_time: Some(1_700_000_000_999),
825 limit: Some(456),
826 };
827
828 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
829
830 assert_eq!(
831 serialized,
832 "symbol=BTCUSDT&fromId=123&startTime=1700000000001&endTime=1700000000999&limit=456"
833 );
834 }
835
836 #[rstest]
837 fn test_order_query_params_builder() {
838 let params = BinanceOrderQueryParamsBuilder::default()
839 .symbol("BTCUSDT")
840 .order_id(12345_i64)
841 .recv_window(5_000_u64)
842 .build()
843 .unwrap();
844
845 assert_eq!(params.symbol, "BTCUSDT");
846 assert_eq!(params.order_id, Some(12345));
847 assert_eq!(params.recv_window, Some(5_000));
848 }
849
850 #[rstest]
851 fn test_income_history_params_serialization() {
852 let params = BinanceIncomeHistoryParamsBuilder::default()
853 .symbol("ETHUSDT")
854 .income_type(BinanceIncomeType::FundingFee)
855 .limit(50_u32)
856 .build()
857 .unwrap();
858
859 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
860 assert_eq!(serialized, "symbol=ETHUSDT&incomeType=FUNDING_FEE&limit=50");
861 }
862
863 #[rstest]
864 fn test_open_orders_params_builder() {
865 let params = BinanceOpenOrdersParamsBuilder::default()
866 .symbol("BNBUSDT")
867 .build()
868 .unwrap();
869
870 assert_eq!(params.symbol.as_deref(), Some("BNBUSDT"));
871 assert!(params.recv_window.is_none());
872 }
873
874 #[rstest]
875 fn test_new_algo_order_params_serialization_uses_activate_price() {
876 let params = BinanceNewAlgoOrderParamsBuilder::default()
877 .symbol("ETHUSDT")
878 .side(BinanceSide::Sell)
879 .order_type(BinanceFuturesOrderType::TrailingStopMarket)
880 .algo_type(BinanceAlgoType::Conditional)
881 .quantity("0.1")
882 .activation_price("10000.00")
883 .callback_rate("0.25")
884 .build()
885 .unwrap();
886
887 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
888 let query: std::collections::HashMap<String, String> =
889 serde_urlencoded::from_str(&serialized).unwrap();
890
891 assert_eq!(query.get("activatePrice"), Some(&"10000.00".to_string()));
892 assert_eq!(query.get("callbackRate"), Some(&"0.25".to_string()));
893 assert!(!query.contains_key("activationPrice"));
894 }
895
896 #[rstest]
897 fn test_new_order_params_with_price_match_serializes_correctly() {
898 let params = BinanceNewOrderParams {
899 symbol: "BTCUSDT".to_string(),
900 side: BinanceSide::Buy,
901 order_type: BinanceFuturesOrderType::Limit,
902 time_in_force: Some(BinanceTimeInForce::Gtc),
903 quantity: Some("0.001".to_string()),
904 price: None,
905 new_client_order_id: Some("test-order-001".to_string()),
906 stop_price: None,
907 reduce_only: None,
908 position_side: None,
909 close_position: None,
910 activation_price: None,
911 callback_rate: None,
912 working_type: None,
913 price_protect: None,
914 new_order_resp_type: None,
915 good_till_date: None,
916 recv_window: None,
917 price_match: Some(BinancePriceMatch::Opponent5),
918 self_trade_prevention_mode: None,
919 };
920
921 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
922 let query: std::collections::HashMap<String, String> =
923 serde_urlencoded::from_str(&serialized).unwrap();
924
925 assert_eq!(query.get("priceMatch"), Some(&"OPPONENT_5".to_string()));
926 assert!(!query.contains_key("price"));
927 assert_eq!(query.get("symbol"), Some(&"BTCUSDT".to_string()));
928 assert_eq!(query.get("side"), Some(&"BUY".to_string()));
929 assert_eq!(query.get("type"), Some(&"LIMIT".to_string()));
930 }
931
932 #[rstest]
933 fn test_new_order_params_without_price_match_omits_field() {
934 let params = BinanceNewOrderParams {
935 symbol: "BTCUSDT".to_string(),
936 side: BinanceSide::Buy,
937 order_type: BinanceFuturesOrderType::Limit,
938 time_in_force: Some(BinanceTimeInForce::Gtc),
939 quantity: Some("0.001".to_string()),
940 price: Some("50000.00".to_string()),
941 new_client_order_id: Some("test-order-002".to_string()),
942 stop_price: None,
943 reduce_only: None,
944 position_side: None,
945 close_position: None,
946 activation_price: None,
947 callback_rate: None,
948 working_type: None,
949 price_protect: None,
950 new_order_resp_type: None,
951 good_till_date: None,
952 recv_window: None,
953 price_match: None,
954 self_trade_prevention_mode: None,
955 };
956
957 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
958 let query: std::collections::HashMap<String, String> =
959 serde_urlencoded::from_str(&serialized).unwrap();
960
961 assert!(!query.contains_key("priceMatch"));
962 assert_eq!(query.get("price"), Some(&"50000.00".to_string()));
963 }
964}