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 From<BinanceFuturesOrderType> for OrderType {
355 fn from(value: BinanceFuturesOrderType) -> Self {
356 match value {
357 BinanceFuturesOrderType::Limit => Self::Limit,
358 BinanceFuturesOrderType::Market => Self::Market,
359 BinanceFuturesOrderType::Stop => Self::StopLimit,
360 BinanceFuturesOrderType::StopMarket => Self::StopMarket,
361 BinanceFuturesOrderType::TakeProfit => Self::LimitIfTouched,
362 BinanceFuturesOrderType::TakeProfitMarket => Self::MarketIfTouched,
363 BinanceFuturesOrderType::TrailingStopMarket => Self::TrailingStopMarket,
364 BinanceFuturesOrderType::Liquidation
365 | BinanceFuturesOrderType::Adl
366 | BinanceFuturesOrderType::Unknown => Self::Market, }
368 }
369}
370
371#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
373#[serde(rename_all = "UPPERCASE")]
374pub enum BinanceTimeInForce {
375 Gtc,
377 Ioc,
379 Fok,
381 Gtx,
383 Gtd,
385 Rpi,
387 #[serde(other)]
389 Unknown,
390}
391
392impl TryFrom<TimeInForce> for BinanceTimeInForce {
393 type Error = anyhow::Error;
394
395 fn try_from(value: TimeInForce) -> Result<Self, Self::Error> {
396 match value {
397 TimeInForce::Gtc => Ok(Self::Gtc),
398 TimeInForce::Ioc => Ok(Self::Ioc),
399 TimeInForce::Fok => Ok(Self::Fok),
400 TimeInForce::Gtd => Ok(Self::Gtd),
401 _ => anyhow::bail!("Unsupported `TimeInForce` for Binance: {value:?}"),
402 }
403 }
404}
405
406#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
408#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
409pub enum BinanceIncomeType {
410 Transfer,
412 WelcomeBonus,
414 RealizedPnl,
416 FundingFee,
418 Commission,
420 CommissionRebate,
422 ApiRebate,
424 InsuranceClear,
426 ReferralKickback,
428 ContestReward,
430 CrossCollateralTransfer,
432 OptionsPremiumFee,
434 OptionsSettleProfit,
436 InternalTransfer,
438 AutoExchange,
440 #[serde(rename = "DELIVERED_SETTELMENT")]
442 DeliveredSettlement,
443 CoinSwapDeposit,
445 CoinSwapWithdraw,
447 PositionLimitIncreaseFee,
449 StrategyUmfuturesTransfer,
451 FeeReturn,
453 BfusdReward,
455 #[serde(other)]
457 Unknown,
458}
459
460#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
462#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
463pub enum BinancePriceMatch {
464 None,
466 Opponent,
468 #[serde(rename = "OPPONENT_5")]
470 Opponent5,
471 #[serde(rename = "OPPONENT_10")]
473 Opponent10,
474 #[serde(rename = "OPPONENT_20")]
476 Opponent20,
477 Queue,
479 #[serde(rename = "QUEUE_5")]
481 Queue5,
482 #[serde(rename = "QUEUE_10")]
484 Queue10,
485 #[serde(rename = "QUEUE_20")]
487 Queue20,
488 #[serde(other)]
490 Unknown,
491}
492
493impl BinancePriceMatch {
494 pub fn from_param(s: &str) -> anyhow::Result<Self> {
502 let value = s.to_uppercase();
503 serde_json::from_value(serde_json::Value::String(value))
504 .map_err(|_| anyhow::anyhow!("Invalid price_match value: {s:?}"))
505 .and_then(|pm: Self| {
506 if pm == Self::None || pm == Self::Unknown {
507 anyhow::bail!("Invalid price_match value: {s:?}")
508 }
509 Ok(pm)
510 })
511 }
512}
513
514#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
516#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
517pub enum BinanceSelfTradePreventionMode {
518 None,
520 ExpireMaker,
522 ExpireTaker,
524 ExpireBoth,
526 Decrement,
528 Transfer,
530 #[serde(other)]
532 Unknown,
533}
534
535#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
537#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
538pub enum BinanceTradingStatus {
539 Trading,
541 PendingTrading,
543 PreTrading,
545 PostTrading,
547 EndOfDay,
549 Halt,
551 AuctionMatch,
553 Break,
555 PreDelivering,
557 Delivering,
559 Delivered,
561 PreSettle,
563 Settling,
565 Close,
567 TradingHalt,
569 TradingCancelOnly,
571 #[serde(other)]
573 Unknown,
574}
575
576impl From<BinanceTradingStatus> for MarketStatusAction {
577 fn from(status: BinanceTradingStatus) -> Self {
578 match status {
579 BinanceTradingStatus::Trading => Self::Trading,
580 BinanceTradingStatus::PendingTrading | BinanceTradingStatus::PreTrading => {
581 Self::PreOpen
582 }
583 BinanceTradingStatus::PostTrading => Self::PostClose,
584 BinanceTradingStatus::EndOfDay => Self::Close,
585 BinanceTradingStatus::Halt => Self::Halt,
586 BinanceTradingStatus::AuctionMatch => Self::Cross,
587 BinanceTradingStatus::Break => Self::Pause,
588 BinanceTradingStatus::PreDelivering | BinanceTradingStatus::PreSettle => Self::PreClose,
589 BinanceTradingStatus::Delivering
590 | BinanceTradingStatus::Delivered
591 | BinanceTradingStatus::Settling
592 | BinanceTradingStatus::Close => Self::Close,
593 BinanceTradingStatus::TradingHalt | BinanceTradingStatus::TradingCancelOnly => {
594 Self::Halt
595 }
596 BinanceTradingStatus::Unknown => Self::NotAvailableForTrading,
597 }
598 }
599}
600
601#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
603#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
604pub enum BinanceContractStatus {
605 Trading,
607 TradingHalt,
609 PendingTrading,
611 PreDelivering,
613 Delivering,
615 Delivered,
617 PreSettle,
619 Settling,
621 Close,
623 PreDelisting,
625 Delisting,
627 Down,
629 TradingCancelOnly,
631 #[serde(other)]
633 Unknown,
634}
635
636impl From<BinanceContractStatus> for MarketStatusAction {
637 fn from(status: BinanceContractStatus) -> Self {
638 match status {
639 BinanceContractStatus::Trading => Self::Trading,
640 BinanceContractStatus::TradingHalt | BinanceContractStatus::TradingCancelOnly => {
641 Self::Halt
642 }
643 BinanceContractStatus::PendingTrading => Self::PreOpen,
644 BinanceContractStatus::PreDelivering
645 | BinanceContractStatus::PreDelisting
646 | BinanceContractStatus::PreSettle => Self::PreClose,
647 BinanceContractStatus::Delivering
648 | BinanceContractStatus::Delivered
649 | BinanceContractStatus::Settling
650 | BinanceContractStatus::Close => Self::Close,
651 BinanceContractStatus::Delisting => Self::Suspend,
652 BinanceContractStatus::Down | BinanceContractStatus::Unknown => {
653 Self::NotAvailableForTrading
654 }
655 }
656 }
657}
658
659#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
663#[serde(rename_all = "camelCase")]
664pub enum BinanceWsEventType {
665 AggTrade,
667 Trade,
669 BookTicker,
671 DepthUpdate,
673 MarkPriceUpdate,
675 Kline,
677 ForceOrder,
679 #[serde(rename = "24hrTicker")]
681 Ticker24Hr,
682 #[serde(rename = "24hrMiniTicker")]
684 MiniTicker24Hr,
685
686 #[serde(rename = "ACCOUNT_UPDATE")]
689 AccountUpdate,
690 #[serde(rename = "ORDER_TRADE_UPDATE")]
692 OrderTradeUpdate,
693 #[serde(rename = "TRADE_LITE")]
695 TradeLite,
696 #[serde(rename = "ALGO_UPDATE")]
698 AlgoUpdate,
699 #[serde(rename = "MARGIN_CALL")]
701 MarginCall,
702 #[serde(rename = "ACCOUNT_CONFIG_UPDATE")]
704 AccountConfigUpdate,
705 #[serde(rename = "listenKeyExpired")]
707 ListenKeyExpired,
708
709 #[serde(other)]
711 Unknown,
712}
713
714impl BinanceWsEventType {
715 #[must_use]
717 pub const fn as_str(self) -> &'static str {
718 match self {
719 Self::AggTrade => "aggTrade",
720 Self::Trade => "trade",
721 Self::BookTicker => "bookTicker",
722 Self::DepthUpdate => "depthUpdate",
723 Self::MarkPriceUpdate => "markPriceUpdate",
724 Self::Kline => "kline",
725 Self::ForceOrder => "forceOrder",
726 Self::Ticker24Hr => "24hrTicker",
727 Self::MiniTicker24Hr => "24hrMiniTicker",
728 Self::AccountUpdate => "ACCOUNT_UPDATE",
729 Self::OrderTradeUpdate => "ORDER_TRADE_UPDATE",
730 Self::TradeLite => "TRADE_LITE",
731 Self::AlgoUpdate => "ALGO_UPDATE",
732 Self::MarginCall => "MARGIN_CALL",
733 Self::AccountConfigUpdate => "ACCOUNT_CONFIG_UPDATE",
734 Self::ListenKeyExpired => "listenKeyExpired",
735 Self::Unknown => "unknown",
736 }
737 }
738}
739
740impl Display for BinanceWsEventType {
741 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
742 write!(f, "{}", self.as_str())
743 }
744}
745
746#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
750#[serde(rename_all = "UPPERCASE")]
751pub enum BinanceWsMethod {
752 Subscribe,
754 Unsubscribe,
756}
757
758#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
760#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
761pub enum BinanceFilterType {
762 PriceFilter,
764 PercentPrice,
766 PercentPriceBySide,
768 LotSize,
770 MarketLotSize,
772 Notional,
774 MinNotional,
776 IcebergParts,
778 MaxNumOrders,
780 MaxNumAlgoOrders,
782 MaxNumIcebergOrders,
784 MaxPosition,
786 TrailingDelta,
788 MaxNumOrderAmends,
790 MaxNumOrderLists,
792 MaxAsset,
794 ExchangeMaxNumOrders,
796 ExchangeMaxNumAlgoOrders,
798 ExchangeMaxNumIcebergOrders,
800 ExchangeMaxNumOrderLists,
802 TPlusSell,
804 #[serde(other)]
806 Unknown,
807}
808
809impl Display for BinanceEnvironment {
810 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
811 match self {
812 Self::Live => write!(f, "Live"),
813 Self::Testnet => write!(f, "Testnet"),
814 Self::Demo => write!(f, "Demo"),
815 }
816 }
817}
818
819#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
821#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
822pub enum BinanceRateLimitType {
823 RequestWeight,
825 Orders,
827 RawRequests,
829 #[serde(other)]
831 Unknown,
832}
833
834#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
836#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
837pub enum BinanceRateLimitInterval {
838 Second,
840 Minute,
842 Day,
844 #[serde(other)]
846 Unknown,
847}
848
849#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
854pub enum BinanceKlineInterval {
855 #[serde(rename = "1s")]
857 Second1,
858 #[default]
860 #[serde(rename = "1m")]
861 Minute1,
862 #[serde(rename = "3m")]
864 Minute3,
865 #[serde(rename = "5m")]
867 Minute5,
868 #[serde(rename = "15m")]
870 Minute15,
871 #[serde(rename = "30m")]
873 Minute30,
874 #[serde(rename = "1h")]
876 Hour1,
877 #[serde(rename = "2h")]
879 Hour2,
880 #[serde(rename = "4h")]
882 Hour4,
883 #[serde(rename = "6h")]
885 Hour6,
886 #[serde(rename = "8h")]
888 Hour8,
889 #[serde(rename = "12h")]
891 Hour12,
892 #[serde(rename = "1d")]
894 Day1,
895 #[serde(rename = "3d")]
897 Day3,
898 #[serde(rename = "1w")]
900 Week1,
901 #[serde(rename = "1M")]
903 Month1,
904}
905
906impl BinanceKlineInterval {
907 #[must_use]
909 pub const fn as_str(&self) -> &'static str {
910 match self {
911 Self::Second1 => "1s",
912 Self::Minute1 => "1m",
913 Self::Minute3 => "3m",
914 Self::Minute5 => "5m",
915 Self::Minute15 => "15m",
916 Self::Minute30 => "30m",
917 Self::Hour1 => "1h",
918 Self::Hour2 => "2h",
919 Self::Hour4 => "4h",
920 Self::Hour6 => "6h",
921 Self::Hour8 => "8h",
922 Self::Hour12 => "12h",
923 Self::Day1 => "1d",
924 Self::Day3 => "3d",
925 Self::Week1 => "1w",
926 Self::Month1 => "1M",
927 }
928 }
929}
930
931#[cfg(test)]
932mod tests {
933 use rstest::rstest;
934 use serde_json::json;
935
936 use super::*;
937
938 #[rstest]
939 fn test_product_type_as_str() {
940 assert_eq!(BinanceProductType::Spot.as_str(), "SPOT");
941 assert_eq!(BinanceProductType::Margin.as_str(), "MARGIN");
942 assert_eq!(BinanceProductType::UsdM.as_str(), "USD_M");
943 assert_eq!(BinanceProductType::CoinM.as_str(), "COIN_M");
944 assert_eq!(BinanceProductType::Options.as_str(), "OPTIONS");
945 }
946
947 #[rstest]
948 fn test_product_type_suffix() {
949 assert_eq!(BinanceProductType::Spot.suffix(), "-SPOT");
950 assert_eq!(BinanceProductType::Margin.suffix(), "-MARGIN");
951 assert_eq!(BinanceProductType::UsdM.suffix(), "-LINEAR");
952 assert_eq!(BinanceProductType::CoinM.suffix(), "-INVERSE");
953 assert_eq!(BinanceProductType::Options.suffix(), "-OPTION");
954 }
955
956 #[rstest]
957 fn test_product_type_predicates() {
958 assert!(BinanceProductType::Spot.is_spot());
959 assert!(BinanceProductType::Margin.is_spot());
960 assert!(!BinanceProductType::UsdM.is_spot());
961
962 assert!(BinanceProductType::UsdM.is_futures());
963 assert!(BinanceProductType::CoinM.is_futures());
964 assert!(!BinanceProductType::Spot.is_futures());
965
966 assert!(BinanceProductType::CoinM.is_inverse());
967 assert!(!BinanceProductType::UsdM.is_inverse());
968
969 assert!(BinanceProductType::Options.is_options());
970 assert!(!BinanceProductType::Spot.is_options());
971 }
972
973 #[rstest]
974 #[case("\"REQUEST_WEIGHT\"", BinanceRateLimitType::RequestWeight)]
975 #[case("\"ORDERS\"", BinanceRateLimitType::Orders)]
976 #[case("\"RAW_REQUESTS\"", BinanceRateLimitType::RawRequests)]
977 #[case("\"UNDOCUMENTED\"", BinanceRateLimitType::Unknown)]
978 fn test_rate_limit_type_deserializes(
979 #[case] raw: &str,
980 #[case] expected: BinanceRateLimitType,
981 ) {
982 let value: BinanceRateLimitType = serde_json::from_str(raw).unwrap();
983 assert_eq!(value, expected);
984 }
985
986 #[rstest]
987 #[case("\"SECOND\"", BinanceRateLimitInterval::Second)]
988 #[case("\"MINUTE\"", BinanceRateLimitInterval::Minute)]
989 #[case("\"DAY\"", BinanceRateLimitInterval::Day)]
990 #[case("\"WEEK\"", BinanceRateLimitInterval::Unknown)]
991 fn test_rate_limit_interval_deserializes(
992 #[case] raw: &str,
993 #[case] expected: BinanceRateLimitInterval,
994 ) {
995 let value: BinanceRateLimitInterval = serde_json::from_str(raw).unwrap();
996 assert_eq!(value, expected);
997 }
998
999 #[rstest]
1000 #[case(BinanceMarginType::Cross, "CROSSED", "cross")]
1001 #[case(BinanceMarginType::Isolated, "ISOLATED", "isolated")]
1002 fn test_margin_type_serde_roundtrip(
1003 #[case] variant: BinanceMarginType,
1004 #[case] post_format: &str,
1005 #[case] get_format: &str,
1006 ) {
1007 let serialized = serde_json::to_value(variant).unwrap();
1008 assert_eq!(serialized, json!(post_format));
1009
1010 let from_post: BinanceMarginType =
1011 serde_json::from_str(&format!("\"{post_format}\"")).unwrap();
1012 assert_eq!(from_post, variant);
1013
1014 let from_get: BinanceMarginType =
1015 serde_json::from_str(&format!("\"{get_format}\"")).unwrap();
1016 assert_eq!(from_get, variant);
1017 }
1018
1019 #[rstest]
1020 fn test_margin_type_unknown_fallback() {
1021 let value: BinanceMarginType = serde_json::from_str("\"SOMETHING_NEW\"").unwrap();
1022 assert_eq!(value, BinanceMarginType::Unknown);
1023 }
1024
1025 #[rstest]
1026 fn test_contract_status_trading_halt_deserializes_and_maps() {
1027 let status: BinanceContractStatus = serde_json::from_str("\"TRADING_HALT\"").unwrap();
1031 assert_eq!(status, BinanceContractStatus::TradingHalt);
1032 assert_eq!(MarketStatusAction::from(status), MarketStatusAction::Halt);
1033 }
1034
1035 #[rstest]
1036 fn test_rate_limit_enums_serialize_to_binance_strings() {
1037 assert_eq!(
1038 serde_json::to_value(BinanceRateLimitType::RequestWeight).unwrap(),
1039 json!("REQUEST_WEIGHT")
1040 );
1041 assert_eq!(
1042 serde_json::to_value(BinanceRateLimitInterval::Minute).unwrap(),
1043 json!("MINUTE")
1044 );
1045 }
1046
1047 #[rstest]
1048 #[case("\"NONE\"", BinancePriceMatch::None)]
1049 #[case("\"OPPONENT\"", BinancePriceMatch::Opponent)]
1050 #[case("\"OPPONENT_5\"", BinancePriceMatch::Opponent5)]
1051 #[case("\"OPPONENT_10\"", BinancePriceMatch::Opponent10)]
1052 #[case("\"OPPONENT_20\"", BinancePriceMatch::Opponent20)]
1053 #[case("\"QUEUE\"", BinancePriceMatch::Queue)]
1054 #[case("\"QUEUE_5\"", BinancePriceMatch::Queue5)]
1055 #[case("\"QUEUE_10\"", BinancePriceMatch::Queue10)]
1056 #[case("\"QUEUE_20\"", BinancePriceMatch::Queue20)]
1057 #[case("\"SOMETHING_NEW\"", BinancePriceMatch::Unknown)]
1058 fn test_price_match_deserializes(#[case] raw: &str, #[case] expected: BinancePriceMatch) {
1059 let value: BinancePriceMatch = serde_json::from_str(raw).unwrap();
1060 assert_eq!(value, expected);
1061 }
1062
1063 #[rstest]
1064 #[case(BinancePriceMatch::None, "NONE")]
1065 #[case(BinancePriceMatch::Opponent, "OPPONENT")]
1066 #[case(BinancePriceMatch::Opponent5, "OPPONENT_5")]
1067 #[case(BinancePriceMatch::Opponent10, "OPPONENT_10")]
1068 #[case(BinancePriceMatch::Opponent20, "OPPONENT_20")]
1069 #[case(BinancePriceMatch::Queue, "QUEUE")]
1070 #[case(BinancePriceMatch::Queue5, "QUEUE_5")]
1071 #[case(BinancePriceMatch::Queue10, "QUEUE_10")]
1072 #[case(BinancePriceMatch::Queue20, "QUEUE_20")]
1073 fn test_price_match_serializes(#[case] variant: BinancePriceMatch, #[case] expected: &str) {
1074 let serialized = serde_json::to_value(variant).unwrap();
1075 assert_eq!(serialized, json!(expected));
1076 }
1077
1078 #[rstest]
1079 #[case("OPPONENT", BinancePriceMatch::Opponent)]
1080 #[case("opponent", BinancePriceMatch::Opponent)]
1081 #[case("OPPONENT_5", BinancePriceMatch::Opponent5)]
1082 #[case("opponent_5", BinancePriceMatch::Opponent5)]
1083 #[case("QUEUE_20", BinancePriceMatch::Queue20)]
1084 #[case("queue_20", BinancePriceMatch::Queue20)]
1085 fn test_price_match_from_param_valid(#[case] input: &str, #[case] expected: BinancePriceMatch) {
1086 let result = BinancePriceMatch::from_param(input).unwrap();
1087 assert_eq!(result, expected);
1088 }
1089
1090 #[rstest]
1091 #[case("NONE")]
1092 #[case("invalid")]
1093 #[case("")]
1094 fn test_price_match_from_param_invalid(#[case] input: &str) {
1095 BinancePriceMatch::from_param(input).unwrap_err();
1096 }
1097}