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.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.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.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 _ => 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.adapters.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.adapters.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 TradingHalt,
590 PendingTrading,
592 PreDelivering,
594 Delivering,
596 Delivered,
598 PreSettle,
600 Settling,
602 Close,
604 PreDelisting,
606 Delisting,
608 Down,
610 #[serde(other)]
612 Unknown,
613}
614
615impl From<BinanceContractStatus> for MarketStatusAction {
616 fn from(status: BinanceContractStatus) -> Self {
617 match status {
618 BinanceContractStatus::Trading => Self::Trading,
619 BinanceContractStatus::TradingHalt => Self::Halt,
620 BinanceContractStatus::PendingTrading => Self::PreOpen,
621 BinanceContractStatus::PreDelivering
622 | BinanceContractStatus::PreDelisting
623 | BinanceContractStatus::PreSettle => Self::PreClose,
624 BinanceContractStatus::Delivering
625 | BinanceContractStatus::Delivered
626 | BinanceContractStatus::Settling
627 | BinanceContractStatus::Close => Self::Close,
628 BinanceContractStatus::Delisting => Self::Suspend,
629 BinanceContractStatus::Down | BinanceContractStatus::Unknown => {
630 Self::NotAvailableForTrading
631 }
632 }
633 }
634}
635
636#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
640#[serde(rename_all = "camelCase")]
641pub enum BinanceWsEventType {
642 AggTrade,
644 Trade,
646 BookTicker,
648 DepthUpdate,
650 MarkPriceUpdate,
652 Kline,
654 ForceOrder,
656 #[serde(rename = "24hrTicker")]
658 Ticker24Hr,
659 #[serde(rename = "24hrMiniTicker")]
661 MiniTicker24Hr,
662
663 #[serde(rename = "ACCOUNT_UPDATE")]
666 AccountUpdate,
667 #[serde(rename = "ORDER_TRADE_UPDATE")]
669 OrderTradeUpdate,
670 #[serde(rename = "TRADE_LITE")]
672 TradeLite,
673 #[serde(rename = "ALGO_UPDATE")]
675 AlgoUpdate,
676 #[serde(rename = "MARGIN_CALL")]
678 MarginCall,
679 #[serde(rename = "ACCOUNT_CONFIG_UPDATE")]
681 AccountConfigUpdate,
682 #[serde(rename = "listenKeyExpired")]
684 ListenKeyExpired,
685
686 #[serde(other)]
688 Unknown,
689}
690
691impl BinanceWsEventType {
692 #[must_use]
694 pub const fn as_str(self) -> &'static str {
695 match self {
696 Self::AggTrade => "aggTrade",
697 Self::Trade => "trade",
698 Self::BookTicker => "bookTicker",
699 Self::DepthUpdate => "depthUpdate",
700 Self::MarkPriceUpdate => "markPriceUpdate",
701 Self::Kline => "kline",
702 Self::ForceOrder => "forceOrder",
703 Self::Ticker24Hr => "24hrTicker",
704 Self::MiniTicker24Hr => "24hrMiniTicker",
705 Self::AccountUpdate => "ACCOUNT_UPDATE",
706 Self::OrderTradeUpdate => "ORDER_TRADE_UPDATE",
707 Self::TradeLite => "TRADE_LITE",
708 Self::AlgoUpdate => "ALGO_UPDATE",
709 Self::MarginCall => "MARGIN_CALL",
710 Self::AccountConfigUpdate => "ACCOUNT_CONFIG_UPDATE",
711 Self::ListenKeyExpired => "listenKeyExpired",
712 Self::Unknown => "unknown",
713 }
714 }
715}
716
717impl Display for BinanceWsEventType {
718 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
719 write!(f, "{}", self.as_str())
720 }
721}
722
723#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
727#[serde(rename_all = "UPPERCASE")]
728pub enum BinanceWsMethod {
729 Subscribe,
731 Unsubscribe,
733}
734
735#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
737#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
738pub enum BinanceFilterType {
739 PriceFilter,
741 PercentPrice,
743 PercentPriceBySide,
745 LotSize,
747 MarketLotSize,
749 Notional,
751 MinNotional,
753 IcebergParts,
755 MaxNumOrders,
757 MaxNumAlgoOrders,
759 MaxNumIcebergOrders,
761 MaxPosition,
763 TrailingDelta,
765 MaxNumOrderAmends,
767 MaxNumOrderLists,
769 MaxAsset,
771 ExchangeMaxNumOrders,
773 ExchangeMaxNumAlgoOrders,
775 ExchangeMaxNumIcebergOrders,
777 ExchangeMaxNumOrderLists,
779 TPlusSell,
781 #[serde(other)]
783 Unknown,
784}
785
786impl Display for BinanceEnvironment {
787 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
788 match self {
789 Self::Live => write!(f, "Live"),
790 Self::Testnet => write!(f, "Testnet"),
791 Self::Demo => write!(f, "Demo"),
792 }
793 }
794}
795
796#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
798#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
799pub enum BinanceRateLimitType {
800 RequestWeight,
802 Orders,
804 RawRequests,
806 #[serde(other)]
808 Unknown,
809}
810
811#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
813#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
814pub enum BinanceRateLimitInterval {
815 Second,
817 Minute,
819 Day,
821 #[serde(other)]
823 Unknown,
824}
825
826#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
831pub enum BinanceKlineInterval {
832 #[serde(rename = "1s")]
834 Second1,
835 #[default]
837 #[serde(rename = "1m")]
838 Minute1,
839 #[serde(rename = "3m")]
841 Minute3,
842 #[serde(rename = "5m")]
844 Minute5,
845 #[serde(rename = "15m")]
847 Minute15,
848 #[serde(rename = "30m")]
850 Minute30,
851 #[serde(rename = "1h")]
853 Hour1,
854 #[serde(rename = "2h")]
856 Hour2,
857 #[serde(rename = "4h")]
859 Hour4,
860 #[serde(rename = "6h")]
862 Hour6,
863 #[serde(rename = "8h")]
865 Hour8,
866 #[serde(rename = "12h")]
868 Hour12,
869 #[serde(rename = "1d")]
871 Day1,
872 #[serde(rename = "3d")]
874 Day3,
875 #[serde(rename = "1w")]
877 Week1,
878 #[serde(rename = "1M")]
880 Month1,
881}
882
883impl BinanceKlineInterval {
884 #[must_use]
886 pub const fn as_str(&self) -> &'static str {
887 match self {
888 Self::Second1 => "1s",
889 Self::Minute1 => "1m",
890 Self::Minute3 => "3m",
891 Self::Minute5 => "5m",
892 Self::Minute15 => "15m",
893 Self::Minute30 => "30m",
894 Self::Hour1 => "1h",
895 Self::Hour2 => "2h",
896 Self::Hour4 => "4h",
897 Self::Hour6 => "6h",
898 Self::Hour8 => "8h",
899 Self::Hour12 => "12h",
900 Self::Day1 => "1d",
901 Self::Day3 => "3d",
902 Self::Week1 => "1w",
903 Self::Month1 => "1M",
904 }
905 }
906}
907
908#[cfg(test)]
909mod tests {
910 use rstest::rstest;
911 use serde_json::json;
912
913 use super::*;
914
915 #[rstest]
916 fn test_product_type_as_str() {
917 assert_eq!(BinanceProductType::Spot.as_str(), "SPOT");
918 assert_eq!(BinanceProductType::Margin.as_str(), "MARGIN");
919 assert_eq!(BinanceProductType::UsdM.as_str(), "USD_M");
920 assert_eq!(BinanceProductType::CoinM.as_str(), "COIN_M");
921 assert_eq!(BinanceProductType::Options.as_str(), "OPTIONS");
922 }
923
924 #[rstest]
925 fn test_product_type_suffix() {
926 assert_eq!(BinanceProductType::Spot.suffix(), "-SPOT");
927 assert_eq!(BinanceProductType::Margin.suffix(), "-MARGIN");
928 assert_eq!(BinanceProductType::UsdM.suffix(), "-LINEAR");
929 assert_eq!(BinanceProductType::CoinM.suffix(), "-INVERSE");
930 assert_eq!(BinanceProductType::Options.suffix(), "-OPTION");
931 }
932
933 #[rstest]
934 fn test_product_type_predicates() {
935 assert!(BinanceProductType::Spot.is_spot());
936 assert!(BinanceProductType::Margin.is_spot());
937 assert!(!BinanceProductType::UsdM.is_spot());
938
939 assert!(BinanceProductType::UsdM.is_futures());
940 assert!(BinanceProductType::CoinM.is_futures());
941 assert!(!BinanceProductType::Spot.is_futures());
942
943 assert!(BinanceProductType::CoinM.is_inverse());
944 assert!(!BinanceProductType::UsdM.is_inverse());
945
946 assert!(BinanceProductType::Options.is_options());
947 assert!(!BinanceProductType::Spot.is_options());
948 }
949
950 #[rstest]
951 #[case("\"REQUEST_WEIGHT\"", BinanceRateLimitType::RequestWeight)]
952 #[case("\"ORDERS\"", BinanceRateLimitType::Orders)]
953 #[case("\"RAW_REQUESTS\"", BinanceRateLimitType::RawRequests)]
954 #[case("\"UNDOCUMENTED\"", BinanceRateLimitType::Unknown)]
955 fn test_rate_limit_type_deserializes(
956 #[case] raw: &str,
957 #[case] expected: BinanceRateLimitType,
958 ) {
959 let value: BinanceRateLimitType = serde_json::from_str(raw).unwrap();
960 assert_eq!(value, expected);
961 }
962
963 #[rstest]
964 #[case("\"SECOND\"", BinanceRateLimitInterval::Second)]
965 #[case("\"MINUTE\"", BinanceRateLimitInterval::Minute)]
966 #[case("\"DAY\"", BinanceRateLimitInterval::Day)]
967 #[case("\"WEEK\"", BinanceRateLimitInterval::Unknown)]
968 fn test_rate_limit_interval_deserializes(
969 #[case] raw: &str,
970 #[case] expected: BinanceRateLimitInterval,
971 ) {
972 let value: BinanceRateLimitInterval = serde_json::from_str(raw).unwrap();
973 assert_eq!(value, expected);
974 }
975
976 #[rstest]
977 #[case(BinanceMarginType::Cross, "CROSSED", "cross")]
978 #[case(BinanceMarginType::Isolated, "ISOLATED", "isolated")]
979 fn test_margin_type_serde_roundtrip(
980 #[case] variant: BinanceMarginType,
981 #[case] post_format: &str,
982 #[case] get_format: &str,
983 ) {
984 let serialized = serde_json::to_value(variant).unwrap();
985 assert_eq!(serialized, json!(post_format));
986
987 let from_post: BinanceMarginType =
988 serde_json::from_str(&format!("\"{post_format}\"")).unwrap();
989 assert_eq!(from_post, variant);
990
991 let from_get: BinanceMarginType =
992 serde_json::from_str(&format!("\"{get_format}\"")).unwrap();
993 assert_eq!(from_get, variant);
994 }
995
996 #[rstest]
997 fn test_margin_type_unknown_fallback() {
998 let value: BinanceMarginType = serde_json::from_str("\"SOMETHING_NEW\"").unwrap();
999 assert_eq!(value, BinanceMarginType::Unknown);
1000 }
1001
1002 #[rstest]
1003 fn test_contract_status_trading_halt_deserializes_and_maps() {
1004 let status: BinanceContractStatus = serde_json::from_str("\"TRADING_HALT\"").unwrap();
1008 assert_eq!(status, BinanceContractStatus::TradingHalt);
1009 assert_eq!(MarketStatusAction::from(status), MarketStatusAction::Halt);
1010 }
1011
1012 #[rstest]
1013 fn test_rate_limit_enums_serialize_to_binance_strings() {
1014 assert_eq!(
1015 serde_json::to_value(BinanceRateLimitType::RequestWeight).unwrap(),
1016 json!("REQUEST_WEIGHT")
1017 );
1018 assert_eq!(
1019 serde_json::to_value(BinanceRateLimitInterval::Minute).unwrap(),
1020 json!("MINUTE")
1021 );
1022 }
1023
1024 #[rstest]
1025 #[case("\"NONE\"", BinancePriceMatch::None)]
1026 #[case("\"OPPONENT\"", BinancePriceMatch::Opponent)]
1027 #[case("\"OPPONENT_5\"", BinancePriceMatch::Opponent5)]
1028 #[case("\"OPPONENT_10\"", BinancePriceMatch::Opponent10)]
1029 #[case("\"OPPONENT_20\"", BinancePriceMatch::Opponent20)]
1030 #[case("\"QUEUE\"", BinancePriceMatch::Queue)]
1031 #[case("\"QUEUE_5\"", BinancePriceMatch::Queue5)]
1032 #[case("\"QUEUE_10\"", BinancePriceMatch::Queue10)]
1033 #[case("\"QUEUE_20\"", BinancePriceMatch::Queue20)]
1034 #[case("\"SOMETHING_NEW\"", BinancePriceMatch::Unknown)]
1035 fn test_price_match_deserializes(#[case] raw: &str, #[case] expected: BinancePriceMatch) {
1036 let value: BinancePriceMatch = serde_json::from_str(raw).unwrap();
1037 assert_eq!(value, expected);
1038 }
1039
1040 #[rstest]
1041 #[case(BinancePriceMatch::None, "NONE")]
1042 #[case(BinancePriceMatch::Opponent, "OPPONENT")]
1043 #[case(BinancePriceMatch::Opponent5, "OPPONENT_5")]
1044 #[case(BinancePriceMatch::Opponent10, "OPPONENT_10")]
1045 #[case(BinancePriceMatch::Opponent20, "OPPONENT_20")]
1046 #[case(BinancePriceMatch::Queue, "QUEUE")]
1047 #[case(BinancePriceMatch::Queue5, "QUEUE_5")]
1048 #[case(BinancePriceMatch::Queue10, "QUEUE_10")]
1049 #[case(BinancePriceMatch::Queue20, "QUEUE_20")]
1050 fn test_price_match_serializes(#[case] variant: BinancePriceMatch, #[case] expected: &str) {
1051 let serialized = serde_json::to_value(variant).unwrap();
1052 assert_eq!(serialized, json!(expected));
1053 }
1054
1055 #[rstest]
1056 #[case("OPPONENT", BinancePriceMatch::Opponent)]
1057 #[case("opponent", BinancePriceMatch::Opponent)]
1058 #[case("OPPONENT_5", BinancePriceMatch::Opponent5)]
1059 #[case("opponent_5", BinancePriceMatch::Opponent5)]
1060 #[case("QUEUE_20", BinancePriceMatch::Queue20)]
1061 #[case("queue_20", BinancePriceMatch::Queue20)]
1062 fn test_price_match_from_param_valid(#[case] input: &str, #[case] expected: BinancePriceMatch) {
1063 let result = BinancePriceMatch::from_param(input).unwrap();
1064 assert_eq!(result, expected);
1065 }
1066
1067 #[rstest]
1068 #[case("NONE")]
1069 #[case("invalid")]
1070 #[case("")]
1071 fn test_price_match_from_param_invalid(#[case] input: &str) {
1072 BinancePriceMatch::from_param(input).unwrap_err();
1073 }
1074}