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(skip_serializing_if = "Option::is_none")]
587 pub order_id: Option<i64>,
588 #[serde(skip_serializing_if = "Option::is_none")]
590 pub orig_client_order_id: Option<String>,
591 pub side: String,
593 pub quantity: String,
595 pub price: String,
597}
598
599#[derive(Debug, Clone, Serialize, Zeroize)]
601#[serde(rename_all = "camelCase")]
602pub struct ListenKeyParams {
603 pub listen_key: SecretString,
605}
606
607#[derive(Clone, Debug, Serialize, Builder)]
613#[builder(setter(into, strip_option))]
614#[serde(rename_all = "camelCase")]
615pub struct BinanceNewAlgoOrderParams {
616 pub symbol: String,
618 pub side: BinanceSide,
620 #[serde(rename = "type")]
622 pub order_type: BinanceFuturesOrderType,
623 #[serde(rename = "algoType")]
625 pub algo_type: BinanceAlgoType,
626 #[serde(rename = "positionSide", skip_serializing_if = "Option::is_none")]
628 #[builder(default)]
629 pub position_side: Option<BinancePositionSide>,
630 #[serde(skip_serializing_if = "Option::is_none")]
632 #[builder(default)]
633 pub quantity: Option<String>,
634 #[serde(skip_serializing_if = "Option::is_none")]
636 #[builder(default)]
637 pub price: Option<String>,
638 #[serde(rename = "triggerPrice", skip_serializing_if = "Option::is_none")]
640 #[builder(default)]
641 pub trigger_price: Option<String>,
642 #[serde(rename = "timeInForce", skip_serializing_if = "Option::is_none")]
644 #[builder(default)]
645 pub time_in_force: Option<BinanceTimeInForce>,
646 #[serde(rename = "workingType", skip_serializing_if = "Option::is_none")]
648 #[builder(default)]
649 pub working_type: Option<BinanceWorkingType>,
650 #[serde(rename = "closePosition", skip_serializing_if = "Option::is_none")]
652 #[builder(default)]
653 pub close_position: Option<bool>,
654 #[serde(rename = "priceProtect", skip_serializing_if = "Option::is_none")]
656 #[builder(default)]
657 pub price_protect: Option<bool>,
658 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
660 #[builder(default)]
661 pub reduce_only: Option<bool>,
662 #[serde(rename = "activatePrice", skip_serializing_if = "Option::is_none")]
664 #[builder(default)]
665 pub activation_price: Option<String>,
666 #[serde(rename = "callbackRate", skip_serializing_if = "Option::is_none")]
668 #[builder(default)]
669 pub callback_rate: Option<String>,
670 #[serde(rename = "clientAlgoId", skip_serializing_if = "Option::is_none")]
672 #[builder(default)]
673 pub client_algo_id: Option<String>,
674 #[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
676 #[builder(default)]
677 pub good_till_date: Option<i64>,
678 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
680 #[builder(default)]
681 pub recv_window: Option<u64>,
682}
683
684#[derive(Clone, Debug, Default, Serialize, Builder)]
686#[builder(setter(into, strip_option), default)]
687#[serde(rename_all = "camelCase")]
688pub struct BinanceAlgoOrderQueryParams {
689 #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
691 pub algo_id: Option<i64>,
692 #[serde(rename = "clientAlgoId", skip_serializing_if = "Option::is_none")]
694 pub client_algo_id: Option<String>,
695 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
697 pub recv_window: Option<u64>,
698}
699
700#[derive(Clone, Debug, Default, Serialize, Builder)]
702#[builder(setter(into, strip_option), default)]
703#[serde(rename_all = "camelCase")]
704pub struct BinanceOpenAlgoOrdersParams {
705 #[serde(skip_serializing_if = "Option::is_none")]
707 pub symbol: Option<String>,
708 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
710 pub recv_window: Option<u64>,
711}
712
713#[derive(Clone, Debug, Serialize, Builder)]
715#[builder(setter(into, strip_option))]
716#[serde(rename_all = "camelCase")]
717pub struct BinanceAllAlgoOrdersParams {
718 pub symbol: String,
720 #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
722 #[builder(default)]
723 pub algo_id: Option<i64>,
724 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
726 #[builder(default)]
727 pub start_time: Option<i64>,
728 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
730 #[builder(default)]
731 pub end_time: Option<i64>,
732 #[serde(skip_serializing_if = "Option::is_none")]
734 #[builder(default)]
735 pub page: Option<u32>,
736 #[serde(skip_serializing_if = "Option::is_none")]
738 #[builder(default)]
739 pub limit: Option<u32>,
740 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
742 #[builder(default)]
743 pub recv_window: Option<u64>,
744}
745
746#[derive(Clone, Debug, Serialize, Builder)]
748#[builder(setter(into, strip_option))]
749#[serde(rename_all = "camelCase")]
750pub struct BinanceCancelAllAlgoOrdersParams {
751 pub symbol: String,
753 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
755 #[builder(default)]
756 pub recv_window: Option<u64>,
757}
758
759#[cfg(test)]
760mod tests {
761 use rstest::rstest;
762 use zeroize::Zeroize;
763
764 use super::*;
765
766 #[rstest]
767 fn test_listen_key_params_preserve_wire_value_and_redact_debug() {
768 let mut params = ListenKeyParams {
769 listen_key: SecretString::from("listen-key-secret"),
770 };
771
772 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
773 let debug = format!("{params:?}");
774
775 assert_eq!(serialized, "listenKey=listen-key-secret");
776 assert!(debug.contains(REDACTED));
777 assert!(!debug.contains(params.listen_key.expose_secret()));
778
779 params.zeroize();
780 assert!(params.listen_key.expose_secret().is_empty());
781 }
782
783 #[rstest]
784 fn test_depth_params_builder() {
785 let params = BinanceDepthParamsBuilder::default()
786 .symbol("BTCUSDT")
787 .limit(100u32)
788 .build()
789 .unwrap();
790
791 assert_eq!(params.symbol, "BTCUSDT");
792 assert_eq!(params.limit, Some(100));
793 }
794
795 #[rstest]
796 fn test_ticker_params_serialization() {
797 let params = BinanceTicker24hrParams {
798 symbol: Some("BTCUSDT".to_string()),
799 };
800
801 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
802 assert_eq!(serialized, "symbol=BTCUSDT");
803 }
804
805 #[rstest]
806 fn test_agg_trades_params_serialization() {
807 let params = BinanceAggTradesParams {
808 symbol: "BTCUSDT".to_string(),
809 from_id: Some(123),
810 start_time: Some(1_700_000_000_001),
811 end_time: Some(1_700_000_000_999),
812 limit: Some(456),
813 };
814
815 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
816
817 assert_eq!(
818 serialized,
819 "symbol=BTCUSDT&fromId=123&startTime=1700000000001&endTime=1700000000999&limit=456"
820 );
821 }
822
823 #[rstest]
824 fn test_order_query_params_builder() {
825 let params = BinanceOrderQueryParamsBuilder::default()
826 .symbol("BTCUSDT")
827 .order_id(12345_i64)
828 .recv_window(5_000_u64)
829 .build()
830 .unwrap();
831
832 assert_eq!(params.symbol, "BTCUSDT");
833 assert_eq!(params.order_id, Some(12345));
834 assert_eq!(params.recv_window, Some(5_000));
835 }
836
837 #[rstest]
838 fn test_income_history_params_serialization() {
839 let params = BinanceIncomeHistoryParamsBuilder::default()
840 .symbol("ETHUSDT")
841 .income_type(BinanceIncomeType::FundingFee)
842 .limit(50_u32)
843 .build()
844 .unwrap();
845
846 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
847 assert_eq!(serialized, "symbol=ETHUSDT&incomeType=FUNDING_FEE&limit=50");
848 }
849
850 #[rstest]
851 fn test_open_orders_params_builder() {
852 let params = BinanceOpenOrdersParamsBuilder::default()
853 .symbol("BNBUSDT")
854 .build()
855 .unwrap();
856
857 assert_eq!(params.symbol.as_deref(), Some("BNBUSDT"));
858 assert!(params.recv_window.is_none());
859 }
860
861 #[rstest]
862 fn test_new_algo_order_params_serialization_uses_activate_price() {
863 let params = BinanceNewAlgoOrderParamsBuilder::default()
864 .symbol("ETHUSDT")
865 .side(BinanceSide::Sell)
866 .order_type(BinanceFuturesOrderType::TrailingStopMarket)
867 .algo_type(BinanceAlgoType::Conditional)
868 .quantity("0.1")
869 .activation_price("10000.00")
870 .callback_rate("0.25")
871 .build()
872 .unwrap();
873
874 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
875 let query: std::collections::HashMap<String, String> =
876 serde_urlencoded::from_str(&serialized).unwrap();
877
878 assert_eq!(query.get("activatePrice"), Some(&"10000.00".to_string()));
879 assert_eq!(query.get("callbackRate"), Some(&"0.25".to_string()));
880 assert!(!query.contains_key("activationPrice"));
881 }
882
883 #[rstest]
884 fn test_new_order_params_with_price_match_serializes_correctly() {
885 let params = BinanceNewOrderParams {
886 symbol: "BTCUSDT".to_string(),
887 side: BinanceSide::Buy,
888 order_type: BinanceFuturesOrderType::Limit,
889 time_in_force: Some(BinanceTimeInForce::Gtc),
890 quantity: Some("0.001".to_string()),
891 price: None,
892 new_client_order_id: Some("test-order-001".to_string()),
893 stop_price: None,
894 reduce_only: None,
895 position_side: None,
896 close_position: None,
897 activation_price: None,
898 callback_rate: None,
899 working_type: None,
900 price_protect: None,
901 new_order_resp_type: None,
902 good_till_date: None,
903 recv_window: None,
904 price_match: Some(BinancePriceMatch::Opponent5),
905 self_trade_prevention_mode: None,
906 };
907
908 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
909 let query: std::collections::HashMap<String, String> =
910 serde_urlencoded::from_str(&serialized).unwrap();
911
912 assert_eq!(query.get("priceMatch"), Some(&"OPPONENT_5".to_string()));
913 assert!(!query.contains_key("price"));
914 assert_eq!(query.get("symbol"), Some(&"BTCUSDT".to_string()));
915 assert_eq!(query.get("side"), Some(&"BUY".to_string()));
916 assert_eq!(query.get("type"), Some(&"LIMIT".to_string()));
917 }
918
919 #[rstest]
920 fn test_new_order_params_without_price_match_omits_field() {
921 let params = BinanceNewOrderParams {
922 symbol: "BTCUSDT".to_string(),
923 side: BinanceSide::Buy,
924 order_type: BinanceFuturesOrderType::Limit,
925 time_in_force: Some(BinanceTimeInForce::Gtc),
926 quantity: Some("0.001".to_string()),
927 price: Some("50000.00".to_string()),
928 new_client_order_id: Some("test-order-002".to_string()),
929 stop_price: None,
930 reduce_only: None,
931 position_side: None,
932 close_position: None,
933 activation_price: None,
934 callback_rate: None,
935 working_type: None,
936 price_protect: None,
937 new_order_resp_type: None,
938 good_till_date: None,
939 recv_window: None,
940 price_match: None,
941 self_trade_prevention_mode: None,
942 };
943
944 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
945 let query: std::collections::HashMap<String, String> =
946 serde_urlencoded::from_str(&serialized).unwrap();
947
948 assert!(!query.contains_key("priceMatch"));
949 assert_eq!(query.get("price"), Some(&"50000.00".to_string()));
950 }
951}