1use std::fmt::Display;
19
20use nautilus_model::enums::{MarketStatusAction, OrderSide, OrderType, TimeInForce};
21use serde::{Deserialize, Serialize};
22
23#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
28#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
29#[cfg_attr(
30 feature = "python",
31 pyo3::pyclass(
32 module = "nautilus_trader.core.nautilus_pyo3.binance",
33 eq,
34 from_py_object,
35 rename_all = "SCREAMING_SNAKE_CASE"
36 )
37)]
38#[cfg_attr(
39 feature = "python",
40 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.binance")
41)]
42pub enum BinanceProductType {
43 #[default]
45 Spot,
46 Margin,
48 UsdM,
50 CoinM,
52 Options,
54}
55
56impl BinanceProductType {
57 #[must_use]
59 pub const fn as_str(self) -> &'static str {
60 match self {
61 Self::Spot => "SPOT",
62 Self::Margin => "MARGIN",
63 Self::UsdM => "USD_M",
64 Self::CoinM => "COIN_M",
65 Self::Options => "OPTIONS",
66 }
67 }
68
69 #[must_use]
71 pub const fn suffix(self) -> &'static str {
72 match self {
73 Self::Spot => "-SPOT",
74 Self::Margin => "-MARGIN",
75 Self::UsdM => "-LINEAR",
76 Self::CoinM => "-INVERSE",
77 Self::Options => "-OPTION",
78 }
79 }
80
81 #[must_use]
83 pub const fn is_spot(self) -> bool {
84 matches!(self, Self::Spot | Self::Margin)
85 }
86
87 #[must_use]
89 pub const fn is_futures(self) -> bool {
90 matches!(self, Self::UsdM | Self::CoinM)
91 }
92
93 #[must_use]
95 pub const fn is_linear(self) -> bool {
96 matches!(self, Self::Spot | Self::Margin | Self::UsdM)
97 }
98
99 #[must_use]
101 pub const fn is_inverse(self) -> bool {
102 matches!(self, Self::CoinM)
103 }
104
105 #[must_use]
107 pub const fn is_options(self) -> bool {
108 matches!(self, Self::Options)
109 }
110}
111
112impl Display for BinanceProductType {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 write!(f, "{}", self.as_str())
115 }
116}
117
118#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
120#[cfg_attr(
121 feature = "python",
122 pyo3::pyclass(
123 module = "nautilus_trader.core.nautilus_pyo3.binance",
124 eq,
125 from_py_object,
126 rename_all = "SCREAMING_SNAKE_CASE"
127 )
128)]
129#[cfg_attr(
130 feature = "python",
131 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.binance")
132)]
133pub enum BinanceEnvironment {
134 #[default]
136 Mainnet,
137 Testnet,
139 Demo,
141}
142
143impl BinanceEnvironment {
144 #[must_use]
146 pub const fn is_testnet(self) -> bool {
147 matches!(self, Self::Testnet)
148 }
149
150 #[must_use]
152 pub const fn is_sandbox(self) -> bool {
153 matches!(self, Self::Testnet | Self::Demo)
154 }
155}
156
157#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
159#[serde(rename_all = "UPPERCASE")]
160pub enum BinanceSide {
161 Buy,
163 Sell,
165}
166
167impl TryFrom<OrderSide> for BinanceSide {
168 type Error = anyhow::Error;
169
170 fn try_from(value: OrderSide) -> Result<Self, Self::Error> {
171 match value {
172 OrderSide::Buy => Ok(Self::Buy),
173 OrderSide::Sell => Ok(Self::Sell),
174 _ => anyhow::bail!("Unsupported `OrderSide` for Binance: {value:?}"),
175 }
176 }
177}
178
179impl From<BinanceSide> for OrderSide {
180 fn from(value: BinanceSide) -> Self {
181 match value {
182 BinanceSide::Buy => Self::Buy,
183 BinanceSide::Sell => Self::Sell,
184 }
185 }
186}
187
188#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
190#[serde(rename_all = "UPPERCASE")]
191#[cfg_attr(
192 feature = "python",
193 pyo3::pyclass(
194 module = "nautilus_trader.core.nautilus_pyo3.binance",
195 eq,
196 from_py_object
197 )
198)]
199#[cfg_attr(
200 feature = "python",
201 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.binance")
202)]
203pub enum BinancePositionSide {
204 Both,
206 Long,
208 Short,
210 #[serde(other)]
212 Unknown,
213}
214
215#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
221#[cfg_attr(
222 feature = "python",
223 pyo3::pyclass(
224 module = "nautilus_trader.core.nautilus_pyo3.binance",
225 eq,
226 from_py_object,
227 rename_all = "SCREAMING_SNAKE_CASE"
228 )
229)]
230#[cfg_attr(
231 feature = "python",
232 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.binance")
233)]
234pub enum BinanceMarginType {
235 #[serde(rename = "CROSSED", alias = "cross")]
237 Cross,
238 #[serde(rename = "ISOLATED", alias = "isolated")]
240 Isolated,
241 #[default]
243 #[serde(other)]
244 Unknown,
245}
246
247#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
249#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
250pub enum BinanceWorkingType {
251 ContractPrice,
253 MarkPrice,
255 #[serde(other)]
257 Unknown,
258}
259
260#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
262#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
263pub enum BinanceOrderStatus {
264 New,
266 PendingNew,
268 PartiallyFilled,
270 Filled,
272 Canceled,
274 PendingCancel,
276 Rejected,
278 Expired,
280 ExpiredInMatch,
282 NewInsurance,
284 NewAdl,
286 #[serde(other)]
288 Unknown,
289}
290
291#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
297#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
298pub enum BinanceAlgoStatus {
299 New,
301 Triggering,
303 Triggered,
305 Finished,
307 Canceled,
309 Expired,
311 Rejected,
313 #[serde(other)]
315 Unknown,
316}
317
318#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
322#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
323pub enum BinanceAlgoType {
324 #[default]
326 Conditional,
327 #[serde(other)]
329 Unknown,
330}
331
332#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
334#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
335pub enum BinanceFuturesOrderType {
336 Limit,
338 Market,
340 Stop,
342 StopMarket,
344 TakeProfit,
346 TakeProfitMarket,
348 TrailingStopMarket,
350 Liquidation,
352 Adl,
354 #[serde(other)]
356 Unknown,
357}
358
359impl From<BinanceFuturesOrderType> for OrderType {
360 fn from(value: BinanceFuturesOrderType) -> Self {
361 match value {
362 BinanceFuturesOrderType::Limit => Self::Limit,
363 BinanceFuturesOrderType::Market => Self::Market,
364 BinanceFuturesOrderType::Stop => Self::StopLimit,
365 BinanceFuturesOrderType::StopMarket => Self::StopMarket,
366 BinanceFuturesOrderType::TakeProfit => Self::LimitIfTouched,
367 BinanceFuturesOrderType::TakeProfitMarket => Self::MarketIfTouched,
368 BinanceFuturesOrderType::TrailingStopMarket => Self::TrailingStopMarket,
369 BinanceFuturesOrderType::Liquidation
370 | BinanceFuturesOrderType::Adl
371 | BinanceFuturesOrderType::Unknown => Self::Market, }
373 }
374}
375
376#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
378#[serde(rename_all = "UPPERCASE")]
379pub enum BinanceTimeInForce {
380 Gtc,
382 Ioc,
384 Fok,
386 Gtx,
388 Gtd,
390 Rpi,
392 #[serde(other)]
394 Unknown,
395}
396
397impl TryFrom<TimeInForce> for BinanceTimeInForce {
398 type Error = anyhow::Error;
399
400 fn try_from(value: TimeInForce) -> Result<Self, Self::Error> {
401 match value {
402 TimeInForce::Gtc => Ok(Self::Gtc),
403 TimeInForce::Ioc => Ok(Self::Ioc),
404 TimeInForce::Fok => Ok(Self::Fok),
405 TimeInForce::Gtd => Ok(Self::Gtd),
406 _ => anyhow::bail!("Unsupported `TimeInForce` for Binance: {value:?}"),
407 }
408 }
409}
410
411#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
413#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
414pub enum BinanceIncomeType {
415 Transfer,
417 WelcomeBonus,
419 RealizedPnl,
421 FundingFee,
423 Commission,
425 CommissionRebate,
427 ApiRebate,
429 InsuranceClear,
431 ReferralKickback,
433 ContestReward,
435 CrossCollateralTransfer,
437 OptionsPremiumFee,
439 OptionsSettleProfit,
441 InternalTransfer,
443 AutoExchange,
445 #[serde(rename = "DELIVERED_SETTELMENT")]
447 DeliveredSettlement,
448 CoinSwapDeposit,
450 CoinSwapWithdraw,
452 PositionLimitIncreaseFee,
454 StrategyUmfuturesTransfer,
456 FeeReturn,
458 BfusdReward,
460 #[serde(other)]
462 Unknown,
463}
464
465#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
467#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
468pub enum BinancePriceMatch {
469 None,
471 Opponent,
473 #[serde(rename = "OPPONENT_5")]
475 Opponent5,
476 #[serde(rename = "OPPONENT_10")]
478 Opponent10,
479 #[serde(rename = "OPPONENT_20")]
481 Opponent20,
482 Queue,
484 #[serde(rename = "QUEUE_5")]
486 Queue5,
487 #[serde(rename = "QUEUE_10")]
489 Queue10,
490 #[serde(rename = "QUEUE_20")]
492 Queue20,
493 #[serde(other)]
495 Unknown,
496}
497
498impl BinancePriceMatch {
499 pub fn from_param(s: &str) -> anyhow::Result<Self> {
507 let value = s.to_uppercase();
508 serde_json::from_value(serde_json::Value::String(value))
509 .map_err(|_| anyhow::anyhow!("Invalid price_match value: {s:?}"))
510 .and_then(|pm: Self| {
511 if pm == Self::None || pm == Self::Unknown {
512 anyhow::bail!("Invalid price_match value: {s:?}")
513 }
514 Ok(pm)
515 })
516 }
517}
518
519#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
521#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
522pub enum BinanceSelfTradePreventionMode {
523 None,
525 ExpireMaker,
527 ExpireTaker,
529 ExpireBoth,
531 Decrement,
533 Transfer,
535 #[serde(other)]
537 Unknown,
538}
539
540#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
542#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
543pub enum BinanceTradingStatus {
544 Trading,
546 PendingTrading,
548 PreTrading,
550 PostTrading,
552 EndOfDay,
554 Halt,
556 AuctionMatch,
558 Break,
560 #[serde(other)]
562 Unknown,
563}
564
565impl From<BinanceTradingStatus> for MarketStatusAction {
566 fn from(status: BinanceTradingStatus) -> Self {
567 match status {
568 BinanceTradingStatus::Trading => Self::Trading,
569 BinanceTradingStatus::PendingTrading | BinanceTradingStatus::PreTrading => {
570 Self::PreOpen
571 }
572 BinanceTradingStatus::PostTrading => Self::PostClose,
573 BinanceTradingStatus::EndOfDay => Self::Close,
574 BinanceTradingStatus::Halt => Self::Halt,
575 BinanceTradingStatus::AuctionMatch => Self::Cross,
576 BinanceTradingStatus::Break => Self::Pause,
577 BinanceTradingStatus::Unknown => Self::NotAvailableForTrading,
578 }
579 }
580}
581
582#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
584#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
585pub enum BinanceContractStatus {
586 Trading,
588 PendingTrading,
590 PreDelivering,
592 Delivering,
594 Delivered,
596 PreSettle,
598 Settling,
600 Close,
602 PreDelisting,
604 Delisting,
606 Down,
608 #[serde(other)]
610 Unknown,
611}
612
613impl From<BinanceContractStatus> for MarketStatusAction {
614 fn from(status: BinanceContractStatus) -> Self {
615 match status {
616 BinanceContractStatus::Trading => Self::Trading,
617 BinanceContractStatus::PendingTrading => Self::PreOpen,
618 BinanceContractStatus::PreDelivering
619 | BinanceContractStatus::PreDelisting
620 | BinanceContractStatus::PreSettle => Self::PreClose,
621 BinanceContractStatus::Delivering
622 | BinanceContractStatus::Delivered
623 | BinanceContractStatus::Settling
624 | BinanceContractStatus::Close => Self::Close,
625 BinanceContractStatus::Delisting => Self::Suspend,
626 BinanceContractStatus::Down | BinanceContractStatus::Unknown => {
627 Self::NotAvailableForTrading
628 }
629 }
630 }
631}
632
633#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
637#[serde(rename_all = "camelCase")]
638pub enum BinanceWsEventType {
639 AggTrade,
641 Trade,
643 BookTicker,
645 DepthUpdate,
647 MarkPriceUpdate,
649 Kline,
651 ForceOrder,
653 #[serde(rename = "24hrTicker")]
655 Ticker24Hr,
656 #[serde(rename = "24hrMiniTicker")]
658 MiniTicker24Hr,
659
660 #[serde(rename = "ACCOUNT_UPDATE")]
663 AccountUpdate,
664 #[serde(rename = "ORDER_TRADE_UPDATE")]
666 OrderTradeUpdate,
667 #[serde(rename = "TRADE_LITE")]
669 TradeLite,
670 #[serde(rename = "ALGO_UPDATE")]
672 AlgoUpdate,
673 #[serde(rename = "MARGIN_CALL")]
675 MarginCall,
676 #[serde(rename = "ACCOUNT_CONFIG_UPDATE")]
678 AccountConfigUpdate,
679 #[serde(rename = "listenKeyExpired")]
681 ListenKeyExpired,
682
683 #[serde(other)]
685 Unknown,
686}
687
688impl BinanceWsEventType {
689 #[must_use]
691 pub const fn as_str(self) -> &'static str {
692 match self {
693 Self::AggTrade => "aggTrade",
694 Self::Trade => "trade",
695 Self::BookTicker => "bookTicker",
696 Self::DepthUpdate => "depthUpdate",
697 Self::MarkPriceUpdate => "markPriceUpdate",
698 Self::Kline => "kline",
699 Self::ForceOrder => "forceOrder",
700 Self::Ticker24Hr => "24hrTicker",
701 Self::MiniTicker24Hr => "24hrMiniTicker",
702 Self::AccountUpdate => "ACCOUNT_UPDATE",
703 Self::OrderTradeUpdate => "ORDER_TRADE_UPDATE",
704 Self::TradeLite => "TRADE_LITE",
705 Self::AlgoUpdate => "ALGO_UPDATE",
706 Self::MarginCall => "MARGIN_CALL",
707 Self::AccountConfigUpdate => "ACCOUNT_CONFIG_UPDATE",
708 Self::ListenKeyExpired => "listenKeyExpired",
709 Self::Unknown => "unknown",
710 }
711 }
712}
713
714impl Display for BinanceWsEventType {
715 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
716 write!(f, "{}", self.as_str())
717 }
718}
719
720#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
724#[serde(rename_all = "UPPERCASE")]
725pub enum BinanceWsMethod {
726 Subscribe,
728 Unsubscribe,
730}
731
732#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
734#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
735pub enum BinanceFilterType {
736 PriceFilter,
738 PercentPrice,
740 PercentPriceBySide,
742 LotSize,
744 MarketLotSize,
746 Notional,
748 MinNotional,
750 IcebergParts,
752 MaxNumOrders,
754 MaxNumAlgoOrders,
756 MaxNumIcebergOrders,
758 MaxPosition,
760 TrailingDelta,
762 MaxNumOrderAmends,
764 MaxNumOrderLists,
766 MaxAsset,
768 ExchangeMaxNumOrders,
770 ExchangeMaxNumAlgoOrders,
772 ExchangeMaxNumIcebergOrders,
774 ExchangeMaxNumOrderLists,
776 TPlusSell,
778 #[serde(other)]
780 Unknown,
781}
782
783impl Display for BinanceEnvironment {
784 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
785 match self {
786 Self::Mainnet => write!(f, "Mainnet"),
787 Self::Testnet => write!(f, "Testnet"),
788 Self::Demo => write!(f, "Demo"),
789 }
790 }
791}
792
793#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
795#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
796pub enum BinanceRateLimitType {
797 RequestWeight,
799 Orders,
801 RawRequests,
803 #[serde(other)]
805 Unknown,
806}
807
808#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
810#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
811pub enum BinanceRateLimitInterval {
812 Second,
814 Minute,
816 Day,
818 #[serde(other)]
820 Unknown,
821}
822
823#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
828pub enum BinanceKlineInterval {
829 #[serde(rename = "1s")]
831 Second1,
832 #[default]
834 #[serde(rename = "1m")]
835 Minute1,
836 #[serde(rename = "3m")]
838 Minute3,
839 #[serde(rename = "5m")]
841 Minute5,
842 #[serde(rename = "15m")]
844 Minute15,
845 #[serde(rename = "30m")]
847 Minute30,
848 #[serde(rename = "1h")]
850 Hour1,
851 #[serde(rename = "2h")]
853 Hour2,
854 #[serde(rename = "4h")]
856 Hour4,
857 #[serde(rename = "6h")]
859 Hour6,
860 #[serde(rename = "8h")]
862 Hour8,
863 #[serde(rename = "12h")]
865 Hour12,
866 #[serde(rename = "1d")]
868 Day1,
869 #[serde(rename = "3d")]
871 Day3,
872 #[serde(rename = "1w")]
874 Week1,
875 #[serde(rename = "1M")]
877 Month1,
878}
879
880impl BinanceKlineInterval {
881 #[must_use]
883 pub const fn as_str(&self) -> &'static str {
884 match self {
885 Self::Second1 => "1s",
886 Self::Minute1 => "1m",
887 Self::Minute3 => "3m",
888 Self::Minute5 => "5m",
889 Self::Minute15 => "15m",
890 Self::Minute30 => "30m",
891 Self::Hour1 => "1h",
892 Self::Hour2 => "2h",
893 Self::Hour4 => "4h",
894 Self::Hour6 => "6h",
895 Self::Hour8 => "8h",
896 Self::Hour12 => "12h",
897 Self::Day1 => "1d",
898 Self::Day3 => "3d",
899 Self::Week1 => "1w",
900 Self::Month1 => "1M",
901 }
902 }
903}
904
905#[cfg(test)]
906mod tests {
907 use rstest::rstest;
908 use serde_json::json;
909
910 use super::*;
911
912 #[rstest]
913 fn test_product_type_as_str() {
914 assert_eq!(BinanceProductType::Spot.as_str(), "SPOT");
915 assert_eq!(BinanceProductType::Margin.as_str(), "MARGIN");
916 assert_eq!(BinanceProductType::UsdM.as_str(), "USD_M");
917 assert_eq!(BinanceProductType::CoinM.as_str(), "COIN_M");
918 assert_eq!(BinanceProductType::Options.as_str(), "OPTIONS");
919 }
920
921 #[rstest]
922 fn test_product_type_suffix() {
923 assert_eq!(BinanceProductType::Spot.suffix(), "-SPOT");
924 assert_eq!(BinanceProductType::Margin.suffix(), "-MARGIN");
925 assert_eq!(BinanceProductType::UsdM.suffix(), "-LINEAR");
926 assert_eq!(BinanceProductType::CoinM.suffix(), "-INVERSE");
927 assert_eq!(BinanceProductType::Options.suffix(), "-OPTION");
928 }
929
930 #[rstest]
931 fn test_product_type_predicates() {
932 assert!(BinanceProductType::Spot.is_spot());
933 assert!(BinanceProductType::Margin.is_spot());
934 assert!(!BinanceProductType::UsdM.is_spot());
935
936 assert!(BinanceProductType::UsdM.is_futures());
937 assert!(BinanceProductType::CoinM.is_futures());
938 assert!(!BinanceProductType::Spot.is_futures());
939
940 assert!(BinanceProductType::CoinM.is_inverse());
941 assert!(!BinanceProductType::UsdM.is_inverse());
942
943 assert!(BinanceProductType::Options.is_options());
944 assert!(!BinanceProductType::Spot.is_options());
945 }
946
947 #[rstest]
948 #[case("\"REQUEST_WEIGHT\"", BinanceRateLimitType::RequestWeight)]
949 #[case("\"ORDERS\"", BinanceRateLimitType::Orders)]
950 #[case("\"RAW_REQUESTS\"", BinanceRateLimitType::RawRequests)]
951 #[case("\"UNDOCUMENTED\"", BinanceRateLimitType::Unknown)]
952 fn test_rate_limit_type_deserializes(
953 #[case] raw: &str,
954 #[case] expected: BinanceRateLimitType,
955 ) {
956 let value: BinanceRateLimitType = serde_json::from_str(raw).unwrap();
957 assert_eq!(value, expected);
958 }
959
960 #[rstest]
961 #[case("\"SECOND\"", BinanceRateLimitInterval::Second)]
962 #[case("\"MINUTE\"", BinanceRateLimitInterval::Minute)]
963 #[case("\"DAY\"", BinanceRateLimitInterval::Day)]
964 #[case("\"WEEK\"", BinanceRateLimitInterval::Unknown)]
965 fn test_rate_limit_interval_deserializes(
966 #[case] raw: &str,
967 #[case] expected: BinanceRateLimitInterval,
968 ) {
969 let value: BinanceRateLimitInterval = serde_json::from_str(raw).unwrap();
970 assert_eq!(value, expected);
971 }
972
973 #[rstest]
974 #[case(BinanceMarginType::Cross, "CROSSED", "cross")]
975 #[case(BinanceMarginType::Isolated, "ISOLATED", "isolated")]
976 fn test_margin_type_serde_roundtrip(
977 #[case] variant: BinanceMarginType,
978 #[case] post_format: &str,
979 #[case] get_format: &str,
980 ) {
981 let serialized = serde_json::to_value(variant).unwrap();
982 assert_eq!(serialized, json!(post_format));
983
984 let from_post: BinanceMarginType =
985 serde_json::from_str(&format!("\"{post_format}\"")).unwrap();
986 assert_eq!(from_post, variant);
987
988 let from_get: BinanceMarginType =
989 serde_json::from_str(&format!("\"{get_format}\"")).unwrap();
990 assert_eq!(from_get, variant);
991 }
992
993 #[rstest]
994 fn test_margin_type_unknown_fallback() {
995 let value: BinanceMarginType = serde_json::from_str("\"SOMETHING_NEW\"").unwrap();
996 assert_eq!(value, BinanceMarginType::Unknown);
997 }
998
999 #[rstest]
1000 fn test_rate_limit_enums_serialize_to_binance_strings() {
1001 assert_eq!(
1002 serde_json::to_value(BinanceRateLimitType::RequestWeight).unwrap(),
1003 json!("REQUEST_WEIGHT")
1004 );
1005 assert_eq!(
1006 serde_json::to_value(BinanceRateLimitInterval::Minute).unwrap(),
1007 json!("MINUTE")
1008 );
1009 }
1010
1011 #[rstest]
1012 #[case("\"NONE\"", BinancePriceMatch::None)]
1013 #[case("\"OPPONENT\"", BinancePriceMatch::Opponent)]
1014 #[case("\"OPPONENT_5\"", BinancePriceMatch::Opponent5)]
1015 #[case("\"OPPONENT_10\"", BinancePriceMatch::Opponent10)]
1016 #[case("\"OPPONENT_20\"", BinancePriceMatch::Opponent20)]
1017 #[case("\"QUEUE\"", BinancePriceMatch::Queue)]
1018 #[case("\"QUEUE_5\"", BinancePriceMatch::Queue5)]
1019 #[case("\"QUEUE_10\"", BinancePriceMatch::Queue10)]
1020 #[case("\"QUEUE_20\"", BinancePriceMatch::Queue20)]
1021 #[case("\"SOMETHING_NEW\"", BinancePriceMatch::Unknown)]
1022 fn test_price_match_deserializes(#[case] raw: &str, #[case] expected: BinancePriceMatch) {
1023 let value: BinancePriceMatch = serde_json::from_str(raw).unwrap();
1024 assert_eq!(value, expected);
1025 }
1026
1027 #[rstest]
1028 #[case(BinancePriceMatch::None, "NONE")]
1029 #[case(BinancePriceMatch::Opponent, "OPPONENT")]
1030 #[case(BinancePriceMatch::Opponent5, "OPPONENT_5")]
1031 #[case(BinancePriceMatch::Opponent10, "OPPONENT_10")]
1032 #[case(BinancePriceMatch::Opponent20, "OPPONENT_20")]
1033 #[case(BinancePriceMatch::Queue, "QUEUE")]
1034 #[case(BinancePriceMatch::Queue5, "QUEUE_5")]
1035 #[case(BinancePriceMatch::Queue10, "QUEUE_10")]
1036 #[case(BinancePriceMatch::Queue20, "QUEUE_20")]
1037 fn test_price_match_serializes(#[case] variant: BinancePriceMatch, #[case] expected: &str) {
1038 let serialized = serde_json::to_value(variant).unwrap();
1039 assert_eq!(serialized, json!(expected));
1040 }
1041
1042 #[rstest]
1043 #[case("OPPONENT", BinancePriceMatch::Opponent)]
1044 #[case("opponent", BinancePriceMatch::Opponent)]
1045 #[case("OPPONENT_5", BinancePriceMatch::Opponent5)]
1046 #[case("opponent_5", BinancePriceMatch::Opponent5)]
1047 #[case("QUEUE_20", BinancePriceMatch::Queue20)]
1048 #[case("queue_20", BinancePriceMatch::Queue20)]
1049 fn test_price_match_from_param_valid(#[case] input: &str, #[case] expected: BinancePriceMatch) {
1050 let result = BinancePriceMatch::from_param(input).unwrap();
1051 assert_eq!(result, expected);
1052 }
1053
1054 #[rstest]
1055 #[case("NONE")]
1056 #[case("invalid")]
1057 #[case("")]
1058 fn test_price_match_from_param_invalid(#[case] input: &str) {
1059 assert!(BinancePriceMatch::from_param(input).is_err());
1060 }
1061}