1use derive_builder::Builder;
19use serde::{Deserialize, Serialize};
20
21use crate::common::enums::{
22 BinanceAlgoType, BinanceFuturesOrderType, BinanceIncomeType, BinanceMarginType,
23 BinancePositionSide, BinancePriceMatch, BinanceSelfTradePreventionMode, BinanceSide,
24 BinanceTimeInForce, BinanceWorkingType,
25};
26
27#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
29#[builder(setter(into, strip_option), default)]
30pub struct BinanceDepthParams {
31 pub symbol: String,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub limit: Option<u32>,
36}
37
38#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
40#[builder(setter(into, strip_option), default)]
41pub struct BinanceTradesParams {
42 pub symbol: String,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub limit: Option<u32>,
47}
48
49#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
51#[builder(setter(into, strip_option), default)]
52pub struct BinanceKlinesParams {
53 pub symbol: String,
55 pub interval: String,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 #[serde(rename = "startTime")]
60 pub start_time: Option<i64>,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 #[serde(rename = "endTime")]
64 pub end_time: Option<i64>,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub limit: Option<u32>,
68}
69
70#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
72#[builder(default)]
73#[builder(setter(into, strip_option))]
74pub struct BinanceTicker24hrParams {
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub symbol: Option<String>,
78}
79
80#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
82#[builder(default)]
83#[builder(setter(into, strip_option))]
84pub struct BinanceBookTickerParams {
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub symbol: Option<String>,
88}
89
90#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
92#[builder(default)]
93#[builder(setter(into, strip_option))]
94pub struct BinanceMarkPriceParams {
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub symbol: Option<String>,
98}
99
100#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
102#[builder(default)]
103#[builder(setter(into, strip_option))]
104pub struct BinanceFundingRateParams {
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub symbol: Option<String>,
108 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
110 pub start_time: Option<i64>,
111 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
113 pub end_time: Option<i64>,
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub limit: Option<u32>,
117}
118
119#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
121#[builder(setter(into))]
122pub struct BinanceOpenInterestParams {
123 pub symbol: String,
125}
126
127#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
129#[builder(default)]
130#[builder(setter(into, strip_option))]
131pub struct BinanceFuturesBalanceParams {
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub asset: Option<String>,
135 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
137 pub recv_window: Option<u64>,
138}
139
140#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
142#[builder(default)]
143#[builder(setter(into, strip_option))]
144pub struct BinancePositionRiskParams {
145 #[serde(skip_serializing_if = "Option::is_none")]
147 pub symbol: Option<String>,
148 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
150 pub recv_window: Option<u64>,
151}
152
153#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
155#[builder(default)]
156#[builder(setter(into, strip_option))]
157pub struct BinanceIncomeHistoryParams {
158 #[serde(skip_serializing_if = "Option::is_none")]
160 pub symbol: Option<String>,
161 #[serde(rename = "incomeType", skip_serializing_if = "Option::is_none")]
163 pub income_type: Option<BinanceIncomeType>,
164 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
166 pub start_time: Option<i64>,
167 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
169 pub end_time: Option<i64>,
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub limit: Option<u32>,
173 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
175 pub recv_window: Option<u64>,
176}
177
178#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
180#[builder(setter(into, strip_option), default)]
181pub struct BinanceUserTradesParams {
182 pub symbol: String,
184 #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
186 pub order_id: Option<i64>,
187 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
189 pub start_time: Option<i64>,
190 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
192 pub end_time: Option<i64>,
193 #[serde(rename = "fromId", skip_serializing_if = "Option::is_none")]
195 pub from_id: Option<i64>,
196 #[serde(skip_serializing_if = "Option::is_none")]
198 pub limit: Option<u32>,
199 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
201 pub recv_window: Option<u64>,
202}
203
204#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
206#[builder(default)]
207#[builder(setter(into, strip_option))]
208pub struct BinanceOpenOrdersParams {
209 #[serde(skip_serializing_if = "Option::is_none")]
211 pub symbol: Option<String>,
212 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
214 pub recv_window: Option<u64>,
215}
216
217#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
219#[builder(setter(into, strip_option), default)]
220pub struct BinanceOrderQueryParams {
221 pub symbol: String,
223 #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
225 pub order_id: Option<i64>,
226 #[serde(rename = "origClientOrderId", skip_serializing_if = "Option::is_none")]
228 pub orig_client_order_id: Option<String>,
229 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
231 pub recv_window: Option<u64>,
232}
233
234#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
236#[builder(setter(into, strip_option))]
237pub struct BinanceNewOrderParams {
238 pub symbol: String,
240 pub side: BinanceSide,
242 #[serde(rename = "type")]
244 pub order_type: BinanceFuturesOrderType,
245 #[serde(rename = "positionSide", skip_serializing_if = "Option::is_none")]
247 #[builder(default)]
248 pub position_side: Option<BinancePositionSide>,
249 #[serde(rename = "timeInForce", skip_serializing_if = "Option::is_none")]
251 #[builder(default)]
252 pub time_in_force: Option<BinanceTimeInForce>,
253 #[serde(skip_serializing_if = "Option::is_none")]
255 #[builder(default)]
256 pub quantity: Option<String>,
257 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
259 #[builder(default)]
260 pub reduce_only: Option<bool>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 #[builder(default)]
264 pub price: Option<String>,
265 #[serde(rename = "newClientOrderId", skip_serializing_if = "Option::is_none")]
267 #[builder(default)]
268 pub new_client_order_id: Option<String>,
269 #[serde(rename = "stopPrice", skip_serializing_if = "Option::is_none")]
271 #[builder(default)]
272 pub stop_price: Option<String>,
273 #[serde(rename = "closePosition", skip_serializing_if = "Option::is_none")]
275 #[builder(default)]
276 pub close_position: Option<bool>,
277 #[serde(rename = "activationPrice", skip_serializing_if = "Option::is_none")]
279 #[builder(default)]
280 pub activation_price: Option<String>,
281 #[serde(rename = "callbackRate", skip_serializing_if = "Option::is_none")]
283 #[builder(default)]
284 pub callback_rate: Option<String>,
285 #[serde(rename = "workingType", skip_serializing_if = "Option::is_none")]
287 #[builder(default)]
288 pub working_type: Option<BinanceWorkingType>,
289 #[serde(rename = "priceProtect", skip_serializing_if = "Option::is_none")]
291 #[builder(default)]
292 pub price_protect: Option<bool>,
293 #[serde(rename = "newOrderRespType", skip_serializing_if = "Option::is_none")]
295 #[builder(default)]
296 pub new_order_resp_type: Option<String>,
297 #[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
299 #[builder(default)]
300 pub good_till_date: Option<i64>,
301 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
303 #[builder(default)]
304 pub recv_window: Option<u64>,
305 #[serde(rename = "priceMatch", skip_serializing_if = "Option::is_none")]
307 #[builder(default)]
308 pub price_match: Option<BinancePriceMatch>,
309 #[serde(
311 rename = "selfTradePreventionMode",
312 skip_serializing_if = "Option::is_none"
313 )]
314 #[builder(default)]
315 pub self_trade_prevention_mode: Option<BinanceSelfTradePreventionMode>,
316}
317
318#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
320#[builder(setter(into, strip_option), default)]
321pub struct BinanceCancelOrderParams {
322 pub symbol: String,
324 #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
326 pub order_id: Option<i64>,
327 #[serde(rename = "origClientOrderId", skip_serializing_if = "Option::is_none")]
329 pub orig_client_order_id: Option<String>,
330 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
332 pub recv_window: Option<u64>,
333}
334
335#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
337#[builder(setter(into, strip_option), default)]
338pub struct BinanceCancelAllOrdersParams {
339 pub symbol: String,
341 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
343 pub recv_window: Option<u64>,
344}
345
346#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
348#[builder(setter(into, strip_option))]
349pub struct BinanceModifyOrderParams {
350 pub symbol: String,
352 #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
354 #[builder(default)]
355 pub order_id: Option<i64>,
356 #[serde(rename = "origClientOrderId", skip_serializing_if = "Option::is_none")]
358 #[builder(default)]
359 pub orig_client_order_id: Option<String>,
360 pub side: BinanceSide,
362 pub quantity: String,
364 pub price: String,
366 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
368 #[builder(default)]
369 pub recv_window: Option<u64>,
370}
371
372#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
374#[builder(setter(into, strip_option), default)]
375pub struct BinanceAllOrdersParams {
376 pub symbol: String,
378 #[serde(rename = "orderId", skip_serializing_if = "Option::is_none")]
380 pub order_id: Option<i64>,
381 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
383 pub start_time: Option<i64>,
384 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
386 pub end_time: Option<i64>,
387 #[serde(skip_serializing_if = "Option::is_none")]
389 pub limit: Option<u32>,
390 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
392 pub recv_window: Option<u64>,
393}
394
395#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
397#[builder(setter(into))]
398pub struct BinanceSetLeverageParams {
399 pub symbol: String,
401 pub leverage: u32,
403 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
405 #[builder(default)]
406 pub recv_window: Option<u64>,
407}
408
409#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
411#[builder(setter(into))]
412pub struct BinanceSetMarginTypeParams {
413 pub symbol: String,
415 #[serde(rename = "marginType")]
417 pub margin_type: BinanceMarginType,
418 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
420 #[builder(default)]
421 pub recv_window: Option<u64>,
422}
423
424#[derive(Clone, Debug, Serialize)]
426#[serde(rename_all = "camelCase")]
427pub struct BatchOrderItem {
428 pub symbol: String,
430 pub side: String,
432 #[serde(rename = "type")]
434 pub order_type: String,
435 #[serde(skip_serializing_if = "Option::is_none")]
437 pub time_in_force: Option<String>,
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub quantity: Option<String>,
441 #[serde(skip_serializing_if = "Option::is_none")]
443 pub price: Option<String>,
444 #[serde(skip_serializing_if = "Option::is_none")]
446 pub reduce_only: Option<bool>,
447 #[serde(skip_serializing_if = "Option::is_none")]
449 pub new_client_order_id: Option<String>,
450 #[serde(skip_serializing_if = "Option::is_none")]
452 pub stop_price: Option<String>,
453 #[serde(skip_serializing_if = "Option::is_none")]
455 pub position_side: Option<String>,
456 #[serde(skip_serializing_if = "Option::is_none")]
458 pub activation_price: Option<String>,
459 #[serde(skip_serializing_if = "Option::is_none")]
461 pub callback_rate: Option<String>,
462 #[serde(skip_serializing_if = "Option::is_none")]
464 pub working_type: Option<String>,
465 #[serde(skip_serializing_if = "Option::is_none")]
467 pub price_protect: Option<bool>,
468 #[serde(skip_serializing_if = "Option::is_none")]
470 pub close_position: Option<bool>,
471 #[serde(skip_serializing_if = "Option::is_none")]
473 pub good_till_date: Option<i64>,
474 #[serde(skip_serializing_if = "Option::is_none")]
476 pub price_match: Option<String>,
477 #[serde(skip_serializing_if = "Option::is_none")]
479 pub self_trade_prevention_mode: Option<String>,
480}
481
482#[derive(Clone, Debug, Serialize)]
484#[serde(rename_all = "camelCase")]
485pub struct BatchCancelItem {
486 pub symbol: String,
488 #[serde(skip_serializing_if = "Option::is_none")]
490 pub order_id: Option<i64>,
491 #[serde(skip_serializing_if = "Option::is_none")]
493 pub orig_client_order_id: Option<String>,
494}
495
496impl BatchCancelItem {
497 #[must_use]
499 pub fn by_order_id(symbol: impl Into<String>, order_id: i64) -> Self {
500 Self {
501 symbol: symbol.into(),
502 order_id: Some(order_id),
503 orig_client_order_id: None,
504 }
505 }
506
507 #[must_use]
509 pub fn by_client_order_id(
510 symbol: impl Into<String>,
511 client_order_id: impl Into<String>,
512 ) -> Self {
513 Self {
514 symbol: symbol.into(),
515 order_id: None,
516 orig_client_order_id: Some(client_order_id.into()),
517 }
518 }
519}
520
521#[derive(Clone, Debug, Serialize)]
523#[serde(rename_all = "camelCase")]
524pub struct BatchModifyItem {
525 pub symbol: String,
527 #[serde(skip_serializing_if = "Option::is_none")]
529 pub order_id: Option<i64>,
530 #[serde(skip_serializing_if = "Option::is_none")]
532 pub orig_client_order_id: Option<String>,
533 pub side: String,
535 pub quantity: String,
537 pub price: String,
539}
540
541#[derive(Debug, Clone, Serialize)]
543#[serde(rename_all = "camelCase")]
544pub struct ListenKeyParams {
545 pub listen_key: String,
547}
548
549#[derive(Clone, Debug, Serialize, Builder)]
555#[builder(setter(into, strip_option))]
556#[serde(rename_all = "camelCase")]
557pub struct BinanceNewAlgoOrderParams {
558 pub symbol: String,
560 pub side: BinanceSide,
562 #[serde(rename = "type")]
564 pub order_type: BinanceFuturesOrderType,
565 #[serde(rename = "algoType")]
567 pub algo_type: BinanceAlgoType,
568 #[serde(rename = "positionSide", skip_serializing_if = "Option::is_none")]
570 #[builder(default)]
571 pub position_side: Option<BinancePositionSide>,
572 #[serde(skip_serializing_if = "Option::is_none")]
574 #[builder(default)]
575 pub quantity: Option<String>,
576 #[serde(skip_serializing_if = "Option::is_none")]
578 #[builder(default)]
579 pub price: Option<String>,
580 #[serde(rename = "triggerPrice", skip_serializing_if = "Option::is_none")]
582 #[builder(default)]
583 pub trigger_price: Option<String>,
584 #[serde(rename = "timeInForce", skip_serializing_if = "Option::is_none")]
586 #[builder(default)]
587 pub time_in_force: Option<BinanceTimeInForce>,
588 #[serde(rename = "workingType", skip_serializing_if = "Option::is_none")]
590 #[builder(default)]
591 pub working_type: Option<BinanceWorkingType>,
592 #[serde(rename = "closePosition", skip_serializing_if = "Option::is_none")]
594 #[builder(default)]
595 pub close_position: Option<bool>,
596 #[serde(rename = "priceProtect", skip_serializing_if = "Option::is_none")]
598 #[builder(default)]
599 pub price_protect: Option<bool>,
600 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
602 #[builder(default)]
603 pub reduce_only: Option<bool>,
604 #[serde(rename = "activatePrice", skip_serializing_if = "Option::is_none")]
606 #[builder(default)]
607 pub activation_price: Option<String>,
608 #[serde(rename = "callbackRate", skip_serializing_if = "Option::is_none")]
610 #[builder(default)]
611 pub callback_rate: Option<String>,
612 #[serde(rename = "clientAlgoId", skip_serializing_if = "Option::is_none")]
614 #[builder(default)]
615 pub client_algo_id: Option<String>,
616 #[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
618 #[builder(default)]
619 pub good_till_date: Option<i64>,
620 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
622 #[builder(default)]
623 pub recv_window: Option<u64>,
624}
625
626#[derive(Clone, Debug, Default, Serialize, Builder)]
628#[builder(setter(into, strip_option), default)]
629#[serde(rename_all = "camelCase")]
630pub struct BinanceAlgoOrderQueryParams {
631 #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
633 pub algo_id: Option<i64>,
634 #[serde(rename = "clientAlgoId", skip_serializing_if = "Option::is_none")]
636 pub client_algo_id: Option<String>,
637 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
639 pub recv_window: Option<u64>,
640}
641
642#[derive(Clone, Debug, Default, Serialize, Builder)]
644#[builder(setter(into, strip_option), default)]
645#[serde(rename_all = "camelCase")]
646pub struct BinanceOpenAlgoOrdersParams {
647 #[serde(skip_serializing_if = "Option::is_none")]
649 pub symbol: Option<String>,
650 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
652 pub recv_window: Option<u64>,
653}
654
655#[derive(Clone, Debug, Serialize, Builder)]
657#[builder(setter(into, strip_option))]
658#[serde(rename_all = "camelCase")]
659pub struct BinanceAllAlgoOrdersParams {
660 pub symbol: String,
662 #[serde(rename = "startTime", skip_serializing_if = "Option::is_none")]
664 #[builder(default)]
665 pub start_time: Option<i64>,
666 #[serde(rename = "endTime", skip_serializing_if = "Option::is_none")]
668 #[builder(default)]
669 pub end_time: Option<i64>,
670 #[serde(skip_serializing_if = "Option::is_none")]
672 #[builder(default)]
673 pub page: Option<u32>,
674 #[serde(skip_serializing_if = "Option::is_none")]
676 #[builder(default)]
677 pub limit: Option<u32>,
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, Serialize, Builder)]
686#[builder(setter(into, strip_option))]
687#[serde(rename_all = "camelCase")]
688pub struct BinanceCancelAllAlgoOrdersParams {
689 pub symbol: String,
691 #[serde(rename = "recvWindow", skip_serializing_if = "Option::is_none")]
693 #[builder(default)]
694 pub recv_window: Option<u64>,
695}
696
697#[cfg(test)]
698mod tests {
699 use rstest::rstest;
700
701 use super::*;
702
703 #[rstest]
704 fn test_depth_params_builder() {
705 let params = BinanceDepthParamsBuilder::default()
706 .symbol("BTCUSDT")
707 .limit(100u32)
708 .build()
709 .unwrap();
710
711 assert_eq!(params.symbol, "BTCUSDT");
712 assert_eq!(params.limit, Some(100));
713 }
714
715 #[rstest]
716 fn test_ticker_params_serialization() {
717 let params = BinanceTicker24hrParams {
718 symbol: Some("BTCUSDT".to_string()),
719 };
720
721 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
722 assert_eq!(serialized, "symbol=BTCUSDT");
723 }
724
725 #[rstest]
726 fn test_order_query_params_builder() {
727 let params = BinanceOrderQueryParamsBuilder::default()
728 .symbol("BTCUSDT")
729 .order_id(12345_i64)
730 .recv_window(5_000_u64)
731 .build()
732 .unwrap();
733
734 assert_eq!(params.symbol, "BTCUSDT");
735 assert_eq!(params.order_id, Some(12345));
736 assert_eq!(params.recv_window, Some(5_000));
737 }
738
739 #[rstest]
740 fn test_income_history_params_serialization() {
741 let params = BinanceIncomeHistoryParamsBuilder::default()
742 .symbol("ETHUSDT")
743 .income_type(BinanceIncomeType::FundingFee)
744 .limit(50_u32)
745 .build()
746 .unwrap();
747
748 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
749 assert_eq!(serialized, "symbol=ETHUSDT&incomeType=FUNDING_FEE&limit=50");
750 }
751
752 #[rstest]
753 fn test_open_orders_params_builder() {
754 let params = BinanceOpenOrdersParamsBuilder::default()
755 .symbol("BNBUSDT")
756 .build()
757 .unwrap();
758
759 assert_eq!(params.symbol.as_deref(), Some("BNBUSDT"));
760 assert!(params.recv_window.is_none());
761 }
762
763 #[rstest]
764 fn test_new_algo_order_params_serialization_uses_activate_price() {
765 let params = BinanceNewAlgoOrderParamsBuilder::default()
766 .symbol("ETHUSDT")
767 .side(BinanceSide::Sell)
768 .order_type(BinanceFuturesOrderType::TrailingStopMarket)
769 .algo_type(BinanceAlgoType::Conditional)
770 .quantity("0.1")
771 .activation_price("10000.00")
772 .callback_rate("0.25")
773 .build()
774 .unwrap();
775
776 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
777 let query: std::collections::HashMap<String, String> =
778 serde_urlencoded::from_str(&serialized).unwrap();
779
780 assert_eq!(query.get("activatePrice"), Some(&"10000.00".to_string()));
781 assert_eq!(query.get("callbackRate"), Some(&"0.25".to_string()));
782 assert!(!query.contains_key("activationPrice"));
783 }
784
785 #[rstest]
786 fn test_new_order_params_with_price_match_serializes_correctly() {
787 let params = BinanceNewOrderParams {
788 symbol: "BTCUSDT".to_string(),
789 side: BinanceSide::Buy,
790 order_type: BinanceFuturesOrderType::Limit,
791 time_in_force: Some(BinanceTimeInForce::Gtc),
792 quantity: Some("0.001".to_string()),
793 price: None,
794 new_client_order_id: Some("test-order-001".to_string()),
795 stop_price: None,
796 reduce_only: None,
797 position_side: None,
798 close_position: None,
799 activation_price: None,
800 callback_rate: None,
801 working_type: None,
802 price_protect: None,
803 new_order_resp_type: None,
804 good_till_date: None,
805 recv_window: None,
806 price_match: Some(BinancePriceMatch::Opponent5),
807 self_trade_prevention_mode: None,
808 };
809
810 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
811 let query: std::collections::HashMap<String, String> =
812 serde_urlencoded::from_str(&serialized).unwrap();
813
814 assert_eq!(query.get("priceMatch"), Some(&"OPPONENT_5".to_string()));
815 assert!(!query.contains_key("price"));
816 assert_eq!(query.get("symbol"), Some(&"BTCUSDT".to_string()));
817 assert_eq!(query.get("side"), Some(&"BUY".to_string()));
818 assert_eq!(query.get("type"), Some(&"LIMIT".to_string()));
819 }
820
821 #[rstest]
822 fn test_new_order_params_without_price_match_omits_field() {
823 let params = BinanceNewOrderParams {
824 symbol: "BTCUSDT".to_string(),
825 side: BinanceSide::Buy,
826 order_type: BinanceFuturesOrderType::Limit,
827 time_in_force: Some(BinanceTimeInForce::Gtc),
828 quantity: Some("0.001".to_string()),
829 price: Some("50000.00".to_string()),
830 new_client_order_id: Some("test-order-002".to_string()),
831 stop_price: None,
832 reduce_only: None,
833 position_side: None,
834 close_position: None,
835 activation_price: None,
836 callback_rate: None,
837 working_type: None,
838 price_protect: None,
839 new_order_resp_type: None,
840 good_till_date: None,
841 recv_window: None,
842 price_match: None,
843 self_trade_prevention_mode: None,
844 };
845
846 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
847 let query: std::collections::HashMap<String, String> =
848 serde_urlencoded::from_str(&serialized).unwrap();
849
850 assert!(!query.contains_key("priceMatch"));
851 assert_eq!(query.get("price"), Some(&"50000.00".to_string()));
852 }
853}