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.adapters.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.adapters.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.adapters.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.adapters.binance")
132)]
133pub enum BinanceEnvironment {
134 #[default]
136 Live,
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 }
175 }
176}
177
178impl From<BinanceSide> for OrderSide {
179 fn from(value: BinanceSide) -> Self {
180 match value {
181 BinanceSide::Buy => Self::Buy,
182 BinanceSide::Sell => Self::Sell,
183 }
184 }
185}
186
187#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
189#[serde(rename_all = "UPPERCASE")]
190#[cfg_attr(
191 feature = "python",
192 pyo3::pyclass(module = "nautilus_trader.adapters.binance", eq, from_py_object)
193)]
194#[cfg_attr(
195 feature = "python",
196 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.binance")
197)]
198pub enum BinancePositionSide {
199 Both,
201 Long,
203 Short,
205 #[serde(other)]
207 Unknown,
208}
209
210#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
216#[cfg_attr(
217 feature = "python",
218 pyo3::pyclass(
219 module = "nautilus_trader.adapters.binance",
220 eq,
221 from_py_object,
222 rename_all = "SCREAMING_SNAKE_CASE"
223 )
224)]
225#[cfg_attr(
226 feature = "python",
227 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.binance")
228)]
229pub enum BinanceMarginType {
230 #[serde(rename = "CROSSED", alias = "cross")]
232 Cross,
233 #[serde(rename = "ISOLATED", alias = "isolated")]
235 Isolated,
236 #[default]
238 #[serde(other)]
239 Unknown,
240}
241
242#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
244#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
245pub enum BinanceWorkingType {
246 ContractPrice,
248 MarkPrice,
250 #[serde(other)]
252 Unknown,
253}
254
255#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
257#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
258pub enum BinanceOrderStatus {
259 New,
261 PendingNew,
263 PartiallyFilled,
265 Filled,
267 Canceled,
269 PendingCancel,
271 Rejected,
273 Expired,
275 ExpiredInMatch,
277 NewInsurance,
279 NewAdl,
281 #[serde(other)]
283 Unknown,
284}
285
286#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
292#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
293pub enum BinanceAlgoStatus {
294 New,
296 Triggering,
298 Triggered,
300 Finished,
302 Canceled,
304 Expired,
306 Rejected,
308 #[serde(other)]
310 Unknown,
311}
312
313#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
317#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
318pub enum BinanceAlgoType {
319 #[default]
321 Conditional,
322 #[serde(other)]
324 Unknown,
325}
326
327#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
329#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
330pub enum BinanceFuturesOrderType {
331 Limit,
333 Market,
335 Stop,
337 StopMarket,
339 TakeProfit,
341 TakeProfitMarket,
343 TrailingStopMarket,
345 Liquidation,
347 Adl,
349 #[serde(other)]
351 Unknown,
352}
353
354impl TryFrom<BinanceFuturesOrderType> for OrderType {
355 type Error = anyhow::Error;
356
357 fn try_from(value: BinanceFuturesOrderType) -> Result<Self, Self::Error> {
358 Ok(match value {
359 BinanceFuturesOrderType::Limit => Self::Limit,
360 BinanceFuturesOrderType::Market => Self::Market,
361 BinanceFuturesOrderType::Stop => Self::StopLimit,
362 BinanceFuturesOrderType::StopMarket => Self::StopMarket,
363 BinanceFuturesOrderType::TakeProfit => Self::LimitIfTouched,
364 BinanceFuturesOrderType::TakeProfitMarket => Self::MarketIfTouched,
365 BinanceFuturesOrderType::TrailingStopMarket => Self::TrailingStopMarket,
366 BinanceFuturesOrderType::Liquidation | BinanceFuturesOrderType::Adl => Self::Market,
367 BinanceFuturesOrderType::Unknown => anyhow::bail!("unknown Binance Futures order type"),
368 })
369 }
370}
371
372#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
374#[serde(rename_all = "UPPERCASE")]
375pub enum BinanceTimeInForce {
376 Gtc,
378 Ioc,
380 Fok,
382 Gtx,
384 Gtd,
386 Rpi,
388 #[serde(other)]
390 Unknown,
391}
392
393impl TryFrom<TimeInForce> for BinanceTimeInForce {
394 type Error = anyhow::Error;
395
396 fn try_from(value: TimeInForce) -> Result<Self, Self::Error> {
397 match value {
398 TimeInForce::Gtc => Ok(Self::Gtc),
399 TimeInForce::Ioc => Ok(Self::Ioc),
400 TimeInForce::Fok => Ok(Self::Fok),
401 TimeInForce::Gtd => Ok(Self::Gtd),
402 _ => anyhow::bail!("Unsupported `TimeInForce` for Binance: {value:?}"),
403 }
404 }
405}
406
407#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
409#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
410pub enum BinanceIncomeType {
411 Transfer,
413 WelcomeBonus,
415 RealizedPnl,
417 FundingFee,
419 Commission,
421 CommissionRebate,
423 ApiRebate,
425 InsuranceClear,
427 ReferralKickback,
429 ContestReward,
431 CrossCollateralTransfer,
433 OptionsPremiumFee,
435 OptionsSettleProfit,
437 InternalTransfer,
439 AutoExchange,
441 #[serde(rename = "DELIVERED_SETTELMENT")]
443 DeliveredSettlement,
444 CoinSwapDeposit,
446 CoinSwapWithdraw,
448 PositionLimitIncreaseFee,
450 StrategyUmfuturesTransfer,
452 FeeReturn,
454 BfusdReward,
456 #[serde(other)]
458 Unknown,
459}
460
461#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
463#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
464pub enum BinancePriceMatch {
465 None,
467 Opponent,
469 #[serde(rename = "OPPONENT_5")]
471 Opponent5,
472 #[serde(rename = "OPPONENT_10")]
474 Opponent10,
475 #[serde(rename = "OPPONENT_20")]
477 Opponent20,
478 Queue,
480 #[serde(rename = "QUEUE_5")]
482 Queue5,
483 #[serde(rename = "QUEUE_10")]
485 Queue10,
486 #[serde(rename = "QUEUE_20")]
488 Queue20,
489 #[serde(other)]
491 Unknown,
492}
493
494impl BinancePriceMatch {
495 pub fn from_param(s: &str) -> anyhow::Result<Self> {
503 let value = s.to_uppercase();
504 serde_json::from_value(serde_json::Value::String(value))
505 .map_err(|_| anyhow::anyhow!("Invalid price_match value: {s:?}"))
506 .and_then(|pm: Self| {
507 if pm == Self::None || pm == Self::Unknown {
508 anyhow::bail!("Invalid price_match value: {s:?}")
509 }
510 Ok(pm)
511 })
512 }
513}
514
515#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
517#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
518pub enum BinanceSelfTradePreventionMode {
519 None,
521 ExpireMaker,
523 ExpireTaker,
525 ExpireBoth,
527 Decrement,
529 Transfer,
531 #[serde(other)]
533 Unknown,
534}
535
536#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
538#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
539pub enum BinanceTradingStatus {
540 Trading,
542 PendingTrading,
544 PreTrading,
546 PostTrading,
548 EndOfDay,
550 Halt,
552 AuctionMatch,
554 Break,
556 PreDelivering,
558 Delivering,
560 Delivered,
562 PreSettle,
564 Settling,
566 Close,
568 TradingHalt,
570 TradingCancelOnly,
572 #[serde(other)]
574 Unknown,
575}
576
577impl From<BinanceTradingStatus> for MarketStatusAction {
578 fn from(status: BinanceTradingStatus) -> Self {
579 match status {
580 BinanceTradingStatus::Trading => Self::Trading,
581 BinanceTradingStatus::PendingTrading | BinanceTradingStatus::PreTrading => {
582 Self::PreOpen
583 }
584 BinanceTradingStatus::PostTrading => Self::PostClose,
585 BinanceTradingStatus::EndOfDay => Self::Close,
586 BinanceTradingStatus::Halt => Self::Halt,
587 BinanceTradingStatus::AuctionMatch => Self::Cross,
588 BinanceTradingStatus::Break => Self::Pause,
589 BinanceTradingStatus::PreDelivering | BinanceTradingStatus::PreSettle => Self::PreClose,
590 BinanceTradingStatus::Delivering
591 | BinanceTradingStatus::Delivered
592 | BinanceTradingStatus::Settling
593 | BinanceTradingStatus::Close => Self::Close,
594 BinanceTradingStatus::TradingHalt | BinanceTradingStatus::TradingCancelOnly => {
595 Self::Halt
596 }
597 BinanceTradingStatus::Unknown => Self::NotAvailableForTrading,
598 }
599 }
600}
601
602#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
604#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
605pub enum BinanceContractStatus {
606 Trading,
608 TradingHalt,
610 PendingTrading,
612 PreDelivering,
614 Delivering,
616 Delivered,
618 PreSettle,
620 Settling,
622 Close,
624 PreDelisting,
626 Delisting,
628 Down,
630 TradingCancelOnly,
632 #[serde(other)]
634 Unknown,
635}
636
637impl From<BinanceContractStatus> for MarketStatusAction {
638 fn from(status: BinanceContractStatus) -> Self {
639 match status {
640 BinanceContractStatus::Trading => Self::Trading,
641 BinanceContractStatus::TradingHalt | BinanceContractStatus::TradingCancelOnly => {
642 Self::Halt
643 }
644 BinanceContractStatus::PendingTrading => Self::PreOpen,
645 BinanceContractStatus::PreDelivering
646 | BinanceContractStatus::PreDelisting
647 | BinanceContractStatus::PreSettle => Self::PreClose,
648 BinanceContractStatus::Delivering
649 | BinanceContractStatus::Delivered
650 | BinanceContractStatus::Settling
651 | BinanceContractStatus::Close => Self::Close,
652 BinanceContractStatus::Delisting => Self::Suspend,
653 BinanceContractStatus::Down | BinanceContractStatus::Unknown => {
654 Self::NotAvailableForTrading
655 }
656 }
657 }
658}
659
660#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
664#[serde(rename_all = "camelCase")]
665pub enum BinanceWsEventType {
666 AggTrade,
668 Trade,
670 BookTicker,
672 DepthUpdate,
674 MarkPriceUpdate,
676 Kline,
678 ForceOrder,
680 #[serde(rename = "24hrTicker")]
682 Ticker24Hr,
683 #[serde(rename = "24hrMiniTicker")]
685 MiniTicker24Hr,
686
687 #[serde(rename = "ACCOUNT_UPDATE")]
690 AccountUpdate,
691 #[serde(rename = "ORDER_TRADE_UPDATE")]
693 OrderTradeUpdate,
694 #[serde(rename = "TRADE_LITE")]
696 TradeLite,
697 #[serde(rename = "ALGO_UPDATE")]
699 AlgoUpdate,
700 #[serde(rename = "MARGIN_CALL")]
702 MarginCall,
703 #[serde(rename = "ACCOUNT_CONFIG_UPDATE")]
705 AccountConfigUpdate,
706 #[serde(rename = "listenKeyExpired")]
708 ListenKeyExpired,
709
710 #[serde(other)]
712 Unknown,
713}
714
715impl BinanceWsEventType {
716 #[must_use]
718 pub const fn as_str(self) -> &'static str {
719 match self {
720 Self::AggTrade => "aggTrade",
721 Self::Trade => "trade",
722 Self::BookTicker => "bookTicker",
723 Self::DepthUpdate => "depthUpdate",
724 Self::MarkPriceUpdate => "markPriceUpdate",
725 Self::Kline => "kline",
726 Self::ForceOrder => "forceOrder",
727 Self::Ticker24Hr => "24hrTicker",
728 Self::MiniTicker24Hr => "24hrMiniTicker",
729 Self::AccountUpdate => "ACCOUNT_UPDATE",
730 Self::OrderTradeUpdate => "ORDER_TRADE_UPDATE",
731 Self::TradeLite => "TRADE_LITE",
732 Self::AlgoUpdate => "ALGO_UPDATE",
733 Self::MarginCall => "MARGIN_CALL",
734 Self::AccountConfigUpdate => "ACCOUNT_CONFIG_UPDATE",
735 Self::ListenKeyExpired => "listenKeyExpired",
736 Self::Unknown => "unknown",
737 }
738 }
739}
740
741impl Display for BinanceWsEventType {
742 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
743 write!(f, "{}", self.as_str())
744 }
745}
746
747#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
751#[serde(rename_all = "UPPERCASE")]
752pub enum BinanceWsMethod {
753 Subscribe,
755 Unsubscribe,
757}
758
759#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
761#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
762pub enum BinanceFilterType {
763 PriceFilter,
765 PercentPrice,
767 PercentPriceBySide,
769 LotSize,
771 MarketLotSize,
773 Notional,
775 MinNotional,
777 IcebergParts,
779 MaxNumOrders,
781 MaxNumAlgoOrders,
783 MaxNumIcebergOrders,
785 MaxPosition,
787 TrailingDelta,
789 MaxNumOrderAmends,
791 MaxNumOrderLists,
793 MaxAsset,
795 ExchangeMaxNumOrders,
797 ExchangeMaxNumAlgoOrders,
799 ExchangeMaxNumIcebergOrders,
801 ExchangeMaxNumOrderLists,
803 TPlusSell,
805 #[serde(other)]
807 Unknown,
808}
809
810impl Display for BinanceEnvironment {
811 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
812 match self {
813 Self::Live => write!(f, "Live"),
814 Self::Testnet => write!(f, "Testnet"),
815 Self::Demo => write!(f, "Demo"),
816 }
817 }
818}
819
820#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
822#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
823pub enum BinanceRateLimitType {
824 RequestWeight,
826 Orders,
828 RawRequests,
830 #[serde(other)]
832 Unknown,
833}
834
835#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
837#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
838pub enum BinanceRateLimitInterval {
839 Second,
841 Minute,
843 Day,
845 #[serde(other)]
847 Unknown,
848}
849
850#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
855pub enum BinanceKlineInterval {
856 #[serde(rename = "1s")]
858 Second1,
859 #[default]
861 #[serde(rename = "1m")]
862 Minute1,
863 #[serde(rename = "3m")]
865 Minute3,
866 #[serde(rename = "5m")]
868 Minute5,
869 #[serde(rename = "15m")]
871 Minute15,
872 #[serde(rename = "30m")]
874 Minute30,
875 #[serde(rename = "1h")]
877 Hour1,
878 #[serde(rename = "2h")]
880 Hour2,
881 #[serde(rename = "4h")]
883 Hour4,
884 #[serde(rename = "6h")]
886 Hour6,
887 #[serde(rename = "8h")]
889 Hour8,
890 #[serde(rename = "12h")]
892 Hour12,
893 #[serde(rename = "1d")]
895 Day1,
896 #[serde(rename = "3d")]
898 Day3,
899 #[serde(rename = "1w")]
901 Week1,
902 #[serde(rename = "1M")]
904 Month1,
905}
906
907impl BinanceKlineInterval {
908 #[must_use]
910 pub const fn as_str(&self) -> &'static str {
911 match self {
912 Self::Second1 => "1s",
913 Self::Minute1 => "1m",
914 Self::Minute3 => "3m",
915 Self::Minute5 => "5m",
916 Self::Minute15 => "15m",
917 Self::Minute30 => "30m",
918 Self::Hour1 => "1h",
919 Self::Hour2 => "2h",
920 Self::Hour4 => "4h",
921 Self::Hour6 => "6h",
922 Self::Hour8 => "8h",
923 Self::Hour12 => "12h",
924 Self::Day1 => "1d",
925 Self::Day3 => "3d",
926 Self::Week1 => "1w",
927 Self::Month1 => "1M",
928 }
929 }
930}
931
932#[cfg(test)]
933mod tests {
934 use rstest::rstest;
935 use serde_json::json;
936
937 use super::*;
938
939 #[rstest]
940 fn test_product_type_as_str() {
941 assert_eq!(BinanceProductType::Spot.as_str(), "SPOT");
942 assert_eq!(BinanceProductType::Margin.as_str(), "MARGIN");
943 assert_eq!(BinanceProductType::UsdM.as_str(), "USD_M");
944 assert_eq!(BinanceProductType::CoinM.as_str(), "COIN_M");
945 assert_eq!(BinanceProductType::Options.as_str(), "OPTIONS");
946 }
947
948 #[rstest]
949 fn test_product_type_suffix() {
950 assert_eq!(BinanceProductType::Spot.suffix(), "-SPOT");
951 assert_eq!(BinanceProductType::Margin.suffix(), "-MARGIN");
952 assert_eq!(BinanceProductType::UsdM.suffix(), "-LINEAR");
953 assert_eq!(BinanceProductType::CoinM.suffix(), "-INVERSE");
954 assert_eq!(BinanceProductType::Options.suffix(), "-OPTION");
955 }
956
957 #[rstest]
958 fn test_product_type_predicates() {
959 assert!(BinanceProductType::Spot.is_spot());
960 assert!(BinanceProductType::Margin.is_spot());
961 assert!(!BinanceProductType::UsdM.is_spot());
962
963 assert!(BinanceProductType::UsdM.is_futures());
964 assert!(BinanceProductType::CoinM.is_futures());
965 assert!(!BinanceProductType::Spot.is_futures());
966
967 assert!(BinanceProductType::CoinM.is_inverse());
968 assert!(!BinanceProductType::UsdM.is_inverse());
969
970 assert!(BinanceProductType::Options.is_options());
971 assert!(!BinanceProductType::Spot.is_options());
972 }
973
974 #[rstest]
975 #[case("\"REQUEST_WEIGHT\"", BinanceRateLimitType::RequestWeight)]
976 #[case("\"ORDERS\"", BinanceRateLimitType::Orders)]
977 #[case("\"RAW_REQUESTS\"", BinanceRateLimitType::RawRequests)]
978 #[case("\"UNDOCUMENTED\"", BinanceRateLimitType::Unknown)]
979 fn test_rate_limit_type_deserializes(
980 #[case] raw: &str,
981 #[case] expected: BinanceRateLimitType,
982 ) {
983 let value: BinanceRateLimitType = serde_json::from_str(raw).unwrap();
984 assert_eq!(value, expected);
985 }
986
987 #[rstest]
988 #[case("\"SECOND\"", BinanceRateLimitInterval::Second)]
989 #[case("\"MINUTE\"", BinanceRateLimitInterval::Minute)]
990 #[case("\"DAY\"", BinanceRateLimitInterval::Day)]
991 #[case("\"WEEK\"", BinanceRateLimitInterval::Unknown)]
992 fn test_rate_limit_interval_deserializes(
993 #[case] raw: &str,
994 #[case] expected: BinanceRateLimitInterval,
995 ) {
996 let value: BinanceRateLimitInterval = serde_json::from_str(raw).unwrap();
997 assert_eq!(value, expected);
998 }
999
1000 #[rstest]
1001 #[case(BinanceMarginType::Cross, "CROSSED", "cross")]
1002 #[case(BinanceMarginType::Isolated, "ISOLATED", "isolated")]
1003 fn test_margin_type_serde_roundtrip(
1004 #[case] variant: BinanceMarginType,
1005 #[case] post_format: &str,
1006 #[case] get_format: &str,
1007 ) {
1008 let serialized = serde_json::to_value(variant).unwrap();
1009 assert_eq!(serialized, json!(post_format));
1010
1011 let from_post: BinanceMarginType =
1012 serde_json::from_str(&format!("\"{post_format}\"")).unwrap();
1013 assert_eq!(from_post, variant);
1014
1015 let from_get: BinanceMarginType =
1016 serde_json::from_str(&format!("\"{get_format}\"")).unwrap();
1017 assert_eq!(from_get, variant);
1018 }
1019
1020 #[rstest]
1021 fn test_margin_type_unknown_fallback() {
1022 let value: BinanceMarginType = serde_json::from_str("\"SOMETHING_NEW\"").unwrap();
1023 assert_eq!(value, BinanceMarginType::Unknown);
1024 }
1025
1026 #[rstest]
1027 fn test_contract_status_trading_halt_deserializes_and_maps() {
1028 let status: BinanceContractStatus = serde_json::from_str("\"TRADING_HALT\"").unwrap();
1032 assert_eq!(status, BinanceContractStatus::TradingHalt);
1033 assert_eq!(MarketStatusAction::from(status), MarketStatusAction::Halt);
1034 }
1035
1036 #[rstest]
1037 fn test_rate_limit_enums_serialize_to_binance_strings() {
1038 assert_eq!(
1039 serde_json::to_value(BinanceRateLimitType::RequestWeight).unwrap(),
1040 json!("REQUEST_WEIGHT")
1041 );
1042 assert_eq!(
1043 serde_json::to_value(BinanceRateLimitInterval::Minute).unwrap(),
1044 json!("MINUTE")
1045 );
1046 }
1047
1048 #[rstest]
1049 #[case("\"NONE\"", BinancePriceMatch::None)]
1050 #[case("\"OPPONENT\"", BinancePriceMatch::Opponent)]
1051 #[case("\"OPPONENT_5\"", BinancePriceMatch::Opponent5)]
1052 #[case("\"OPPONENT_10\"", BinancePriceMatch::Opponent10)]
1053 #[case("\"OPPONENT_20\"", BinancePriceMatch::Opponent20)]
1054 #[case("\"QUEUE\"", BinancePriceMatch::Queue)]
1055 #[case("\"QUEUE_5\"", BinancePriceMatch::Queue5)]
1056 #[case("\"QUEUE_10\"", BinancePriceMatch::Queue10)]
1057 #[case("\"QUEUE_20\"", BinancePriceMatch::Queue20)]
1058 #[case("\"SOMETHING_NEW\"", BinancePriceMatch::Unknown)]
1059 fn test_price_match_deserializes(#[case] raw: &str, #[case] expected: BinancePriceMatch) {
1060 let value: BinancePriceMatch = serde_json::from_str(raw).unwrap();
1061 assert_eq!(value, expected);
1062 }
1063
1064 #[rstest]
1065 #[case(BinancePriceMatch::None, "NONE")]
1066 #[case(BinancePriceMatch::Opponent, "OPPONENT")]
1067 #[case(BinancePriceMatch::Opponent5, "OPPONENT_5")]
1068 #[case(BinancePriceMatch::Opponent10, "OPPONENT_10")]
1069 #[case(BinancePriceMatch::Opponent20, "OPPONENT_20")]
1070 #[case(BinancePriceMatch::Queue, "QUEUE")]
1071 #[case(BinancePriceMatch::Queue5, "QUEUE_5")]
1072 #[case(BinancePriceMatch::Queue10, "QUEUE_10")]
1073 #[case(BinancePriceMatch::Queue20, "QUEUE_20")]
1074 fn test_price_match_serializes(#[case] variant: BinancePriceMatch, #[case] expected: &str) {
1075 let serialized = serde_json::to_value(variant).unwrap();
1076 assert_eq!(serialized, json!(expected));
1077 }
1078
1079 #[rstest]
1080 #[case("OPPONENT", BinancePriceMatch::Opponent)]
1081 #[case("opponent", BinancePriceMatch::Opponent)]
1082 #[case("OPPONENT_5", BinancePriceMatch::Opponent5)]
1083 #[case("opponent_5", BinancePriceMatch::Opponent5)]
1084 #[case("QUEUE_20", BinancePriceMatch::Queue20)]
1085 #[case("queue_20", BinancePriceMatch::Queue20)]
1086 fn test_price_match_from_param_valid(#[case] input: &str, #[case] expected: BinancePriceMatch) {
1087 let result = BinancePriceMatch::from_param(input).unwrap();
1088 assert_eq!(result, expected);
1089 }
1090
1091 #[rstest]
1092 #[case("NONE")]
1093 #[case("invalid")]
1094 #[case("")]
1095 fn test_price_match_from_param_invalid(#[case] input: &str) {
1096 BinancePriceMatch::from_param(input).unwrap_err();
1097 }
1098}