1use std::borrow::Cow;
19
20use nautilus_model::enums::{
21 ContingencyType, LiquiditySide, OrderSide, OrderStatus, OrderType, PositionSide, TimeInForce,
22};
23use serde::{Deserialize, Deserializer, Serialize};
24use strum::{AsRefStr, Display, EnumIter, EnumString};
25
26use crate::error::{BitmexError, BitmexNonRetryableError};
27
28#[derive(
30 Copy,
31 Clone,
32 Debug,
33 Display,
34 PartialEq,
35 Eq,
36 AsRefStr,
37 EnumIter,
38 EnumString,
39 Serialize,
40 Deserialize,
41)]
42#[serde(rename_all = "PascalCase")]
43#[cfg_attr(
44 feature = "python",
45 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bitmex", eq, eq_int)
46)]
47pub enum BitmexSymbolStatus {
48 Open,
50 Closed,
52 Unlisted,
54}
55
56#[derive(
58 Copy,
59 Clone,
60 Debug,
61 Display,
62 PartialEq,
63 Eq,
64 AsRefStr,
65 EnumIter,
66 EnumString,
67 Serialize,
68 Deserialize,
69)]
70pub enum BitmexSide {
71 #[serde(rename = "Buy", alias = "BUY", alias = "buy")]
73 Buy,
74 #[serde(rename = "Sell", alias = "SELL", alias = "sell")]
76 Sell,
77}
78
79impl TryFrom<OrderSide> for BitmexSide {
80 type Error = BitmexError;
81
82 fn try_from(value: OrderSide) -> Result<Self, Self::Error> {
83 match value {
84 OrderSide::Buy => Ok(Self::Buy),
85 OrderSide::Sell => Ok(Self::Sell),
86 _ => Err(BitmexError::NonRetryable {
87 source: BitmexNonRetryableError::Validation {
88 field: "order_side".to_string(),
89 message: format!("Invalid order side: {value:?}"),
90 },
91 }),
92 }
93 }
94}
95
96impl BitmexSide {
97 pub fn try_from_order_side(value: OrderSide) -> anyhow::Result<Self> {
103 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
104 }
105}
106
107impl From<BitmexSide> for OrderSide {
108 fn from(side: BitmexSide) -> Self {
109 match side {
110 BitmexSide::Buy => Self::Buy,
111 BitmexSide::Sell => Self::Sell,
112 }
113 }
114}
115
116#[derive(
118 Copy,
119 Clone,
120 Debug,
121 Display,
122 PartialEq,
123 Eq,
124 AsRefStr,
125 EnumIter,
126 EnumString,
127 Serialize,
128 Deserialize,
129)]
130#[cfg_attr(
131 feature = "python",
132 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bitmex", eq, eq_int)
133)]
134pub enum BitmexPositionSide {
135 #[serde(rename = "LONG", alias = "Long", alias = "long")]
137 Long,
138 #[serde(rename = "SHORT", alias = "Short", alias = "short")]
140 Short,
141 #[serde(rename = "FLAT", alias = "Flat", alias = "flat")]
143 Flat,
144}
145
146impl From<BitmexPositionSide> for PositionSide {
147 fn from(side: BitmexPositionSide) -> Self {
148 match side {
149 BitmexPositionSide::Long => Self::Long,
150 BitmexPositionSide::Short => Self::Short,
151 BitmexPositionSide::Flat => Self::Flat,
152 }
153 }
154}
155
156impl From<PositionSide> for BitmexPositionSide {
157 fn from(side: PositionSide) -> Self {
158 match side {
159 PositionSide::Long => Self::Long,
160 PositionSide::Short => Self::Short,
161 PositionSide::Flat | PositionSide::NoPositionSide => Self::Flat,
162 }
163 }
164}
165
166#[derive(
168 Copy,
169 Clone,
170 Debug,
171 Display,
172 PartialEq,
173 Eq,
174 AsRefStr,
175 EnumIter,
176 EnumString,
177 Serialize,
178 Deserialize,
179)]
180pub enum BitmexOrderType {
181 Market,
183 Limit,
185 Stop,
187 StopLimit,
189 MarketIfTouched,
191 LimitIfTouched,
193 Pegged,
195}
196
197impl TryFrom<OrderType> for BitmexOrderType {
198 type Error = BitmexError;
199
200 fn try_from(value: OrderType) -> Result<Self, Self::Error> {
201 match value {
202 OrderType::Market => Ok(Self::Market),
203 OrderType::Limit => Ok(Self::Limit),
204 OrderType::StopMarket => Ok(Self::Stop),
205 OrderType::StopLimit => Ok(Self::StopLimit),
206 OrderType::MarketIfTouched => Ok(Self::MarketIfTouched),
207 OrderType::LimitIfTouched => Ok(Self::LimitIfTouched),
208 OrderType::TrailingStopMarket => Ok(Self::Pegged),
209 OrderType::TrailingStopLimit => Ok(Self::Pegged),
210 OrderType::MarketToLimit => Err(BitmexError::NonRetryable {
211 source: BitmexNonRetryableError::Validation {
212 field: "order_type".to_string(),
213 message: "MarketToLimit order type is not supported by BitMEX".to_string(),
214 },
215 }),
216 }
217 }
218}
219
220impl BitmexOrderType {
221 pub fn try_from_order_type(value: OrderType) -> anyhow::Result<Self> {
227 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
228 }
229}
230
231impl From<BitmexOrderType> for OrderType {
232 fn from(value: BitmexOrderType) -> Self {
233 match value {
234 BitmexOrderType::Market => Self::Market,
235 BitmexOrderType::Limit => Self::Limit,
236 BitmexOrderType::Stop => Self::StopMarket,
237 BitmexOrderType::StopLimit => Self::StopLimit,
238 BitmexOrderType::MarketIfTouched => Self::MarketIfTouched,
239 BitmexOrderType::LimitIfTouched => Self::LimitIfTouched,
240 BitmexOrderType::Pegged => Self::Limit,
241 }
242 }
243}
244
245#[derive(
247 Copy,
248 Clone,
249 Debug,
250 Display,
251 PartialEq,
252 Eq,
253 AsRefStr,
254 EnumIter,
255 EnumString,
256 Serialize,
257 Deserialize,
258)]
259pub enum BitmexOrderStatus {
260 New,
262 PartiallyFilled,
264 Filled,
266 PendingCancel,
268 Canceled,
270 Rejected,
272 Expired,
274}
275
276impl From<BitmexOrderStatus> for OrderStatus {
277 fn from(value: BitmexOrderStatus) -> Self {
278 match value {
279 BitmexOrderStatus::New => Self::Accepted,
280 BitmexOrderStatus::PartiallyFilled => Self::PartiallyFilled,
281 BitmexOrderStatus::Filled => Self::Filled,
282 BitmexOrderStatus::PendingCancel => Self::PendingCancel,
283 BitmexOrderStatus::Canceled => Self::Canceled,
284 BitmexOrderStatus::Rejected => Self::Rejected,
285 BitmexOrderStatus::Expired => Self::Expired,
286 }
287 }
288}
289
290#[derive(
292 Copy,
293 Clone,
294 Debug,
295 Display,
296 PartialEq,
297 Eq,
298 AsRefStr,
299 EnumIter,
300 EnumString,
301 Serialize,
302 Deserialize,
303)]
304pub enum BitmexTimeInForce {
305 Day,
306 GoodTillCancel,
307 AtTheOpening,
308 ImmediateOrCancel,
309 FillOrKill,
310 GoodTillCrossing,
311 GoodTillDate,
312 AtTheClose,
313 GoodThroughCrossing,
314 AtCrossing,
315}
316
317impl TryFrom<BitmexTimeInForce> for TimeInForce {
318 type Error = BitmexError;
319
320 fn try_from(value: BitmexTimeInForce) -> Result<Self, Self::Error> {
321 match value {
322 BitmexTimeInForce::Day => Ok(Self::Day),
323 BitmexTimeInForce::GoodTillCancel => Ok(Self::Gtc),
324 BitmexTimeInForce::GoodTillDate => Ok(Self::Gtd),
325 BitmexTimeInForce::ImmediateOrCancel => Ok(Self::Ioc),
326 BitmexTimeInForce::FillOrKill => Ok(Self::Fok),
327 BitmexTimeInForce::AtTheOpening => Ok(Self::AtTheOpen),
328 BitmexTimeInForce::AtTheClose => Ok(Self::AtTheClose),
329 _ => Err(BitmexError::NonRetryable {
330 source: BitmexNonRetryableError::Validation {
331 field: "time_in_force".to_string(),
332 message: format!("Unsupported BitmexTimeInForce: {value}"),
333 },
334 }),
335 }
336 }
337}
338
339impl TryFrom<TimeInForce> for BitmexTimeInForce {
340 type Error = crate::error::BitmexError;
341
342 fn try_from(value: TimeInForce) -> Result<Self, Self::Error> {
343 match value {
344 TimeInForce::Day => Ok(Self::Day),
345 TimeInForce::Gtc => Ok(Self::GoodTillCancel),
346 TimeInForce::Gtd => Ok(Self::GoodTillDate),
347 TimeInForce::Ioc => Ok(Self::ImmediateOrCancel),
348 TimeInForce::Fok => Ok(Self::FillOrKill),
349 TimeInForce::AtTheOpen => Ok(Self::AtTheOpening),
350 TimeInForce::AtTheClose => Ok(Self::AtTheClose),
351 }
352 }
353}
354
355impl BitmexTimeInForce {
356 pub fn try_from_time_in_force(value: TimeInForce) -> anyhow::Result<Self> {
362 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
363 }
364}
365
366#[derive(
368 Copy,
369 Clone,
370 Debug,
371 Display,
372 PartialEq,
373 Eq,
374 AsRefStr,
375 EnumIter,
376 EnumString,
377 Serialize,
378 Deserialize,
379)]
380pub enum BitmexContingencyType {
381 OneCancelsTheOther,
382 OneTriggersTheOther,
383 OneUpdatesTheOtherAbsolute,
384 OneUpdatesTheOtherProportional,
385 #[serde(rename = "")]
386 Unknown, }
388
389impl From<BitmexContingencyType> for ContingencyType {
390 fn from(value: BitmexContingencyType) -> Self {
391 match value {
392 BitmexContingencyType::OneCancelsTheOther => Self::Oco,
393 BitmexContingencyType::OneTriggersTheOther => Self::Oto,
394 BitmexContingencyType::OneUpdatesTheOtherProportional => Self::Ouo,
395 BitmexContingencyType::OneUpdatesTheOtherAbsolute => Self::Ouo,
396 BitmexContingencyType::Unknown => Self::NoContingency,
397 }
398 }
399}
400
401impl TryFrom<ContingencyType> for BitmexContingencyType {
402 type Error = BitmexError;
403
404 fn try_from(value: ContingencyType) -> Result<Self, Self::Error> {
405 match value {
406 ContingencyType::NoContingency => Ok(Self::Unknown),
407 ContingencyType::Oco => Ok(Self::OneCancelsTheOther),
408 ContingencyType::Oto => Ok(Self::OneTriggersTheOther),
409 ContingencyType::Ouo => Err(BitmexError::NonRetryable {
410 source: BitmexNonRetryableError::Validation {
411 field: "contingency_type".to_string(),
412 message: "OUO contingency type not supported by BitMEX".to_string(),
413 },
414 }),
415 }
416 }
417}
418
419#[derive(
421 Copy,
422 Clone,
423 Debug,
424 Display,
425 PartialEq,
426 Eq,
427 AsRefStr,
428 EnumIter,
429 EnumString,
430 Serialize,
431 Deserialize,
432)]
433pub enum BitmexPegPriceType {
434 LastPeg,
435 OpeningPeg,
436 MidPricePeg,
437 MarketPeg,
438 PrimaryPeg,
439 PegToVWAP,
440 TrailingStopPeg,
441 PegToLimitPrice,
442 ShortSaleMinPricePeg,
443 #[serde(rename = "")]
444 Unknown, }
446
447#[derive(
449 Copy,
450 Clone,
451 Debug,
452 Display,
453 PartialEq,
454 Eq,
455 AsRefStr,
456 EnumIter,
457 EnumString,
458 Serialize,
459 Deserialize,
460)]
461pub enum BitmexExecInstruction {
462 ParticipateDoNotInitiate,
463 AllOrNone,
464 MarkPrice,
465 IndexPrice,
466 LastPrice,
467 Close,
468 ReduceOnly,
469 Fixed,
470 #[serde(rename = "")]
471 Unknown, }
473
474impl BitmexExecInstruction {
475 pub fn join(instructions: &[Self]) -> String {
477 instructions
478 .iter()
479 .map(std::string::ToString::to_string)
480 .collect::<Vec<_>>()
481 .join(",")
482 }
483}
484
485#[derive(
487 Copy,
488 Clone,
489 Debug,
490 Display,
491 PartialEq,
492 Eq,
493 AsRefStr,
494 EnumIter,
495 EnumString,
496 Serialize,
497 Deserialize,
498)]
499pub enum BitmexExecType {
500 New,
502 Trade,
504 Canceled,
506 CancelReject,
508 Replaced,
510 Rejected,
512 AmendReject,
514 Funding,
516 Settlement,
518 Suspended,
520 Released,
522 Insurance,
524 Rebalance,
526 Liquidation,
528 Bankruptcy,
530 TrialFill,
532 TriggeredOrActivatedBySystem,
534}
535
536#[derive(
538 Copy,
539 Clone,
540 Debug,
541 Display,
542 PartialEq,
543 Eq,
544 AsRefStr,
545 EnumIter,
546 EnumString,
547 Serialize,
548 Deserialize,
549)]
550pub enum BitmexLiquidityIndicator {
551 #[serde(rename = "Added")]
554 #[serde(alias = "AddedLiquidity")]
555 Maker,
556 #[serde(rename = "Removed")]
559 #[serde(alias = "RemovedLiquidity")]
560 Taker,
561}
562
563impl From<BitmexLiquidityIndicator> for LiquiditySide {
564 fn from(value: BitmexLiquidityIndicator) -> Self {
565 match value {
566 BitmexLiquidityIndicator::Maker => Self::Maker,
567 BitmexLiquidityIndicator::Taker => Self::Taker,
568 }
569 }
570}
571
572#[derive(
579 Copy,
580 Clone,
581 Debug,
582 Display,
583 PartialEq,
584 Eq,
585 AsRefStr,
586 EnumIter,
587 EnumString,
588 Serialize,
589 Deserialize,
590)]
591#[serde(rename_all = "UPPERCASE")]
592pub enum BitmexInstrumentType {
593 #[serde(rename = "FXXXS")]
595 LegacyFutures,
596
597 #[serde(rename = "FXXXN")]
599 LegacyFuturesN,
600
601 #[serde(rename = "FMXXS")]
603 FuturesSpreads,
604
605 #[serde(rename = "FFICSX")]
608 PredictionMarket,
609
610 #[serde(rename = "FFSCSX")]
613 StockPerpetual,
614
615 #[serde(rename = "FFWCSX")]
617 PerpetualContract,
618
619 #[serde(rename = "FFWCSF")]
621 PerpetualContractFx,
622
623 #[serde(rename = "FFCCSX")]
625 Futures,
626
627 #[serde(rename = "IFXXXP")]
629 Spot,
630
631 #[serde(rename = "OCECCS")]
633 CallOption,
634
635 #[serde(rename = "OPECCS")]
637 PutOption,
638
639 #[serde(rename = "SRMCSX")]
641 SwapRate,
642
643 #[serde(rename = "RCSXXX")]
645 ReferenceBasket,
646
647 #[serde(rename = "MRBXXX")]
649 BasketIndex,
650
651 #[serde(rename = "MRCXXX")]
653 CryptoIndex,
654
655 #[serde(rename = "MRFXXX")]
657 FxIndex,
658
659 #[serde(rename = "MRRXXX")]
661 LendingIndex,
662
663 #[serde(rename = "MRIXXX")]
665 VolatilityIndex,
666
667 #[serde(rename = "MRSXXX")]
669 StockIndex,
670
671 #[serde(rename = "MRVDXX")]
673 YieldIndex,
674}
675
676#[derive(Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize)]
678pub enum BitmexProductType {
679 #[serde(rename = "instrument")]
681 All,
682
683 #[serde(rename = "CONTRACTS")]
685 Contracts,
686
687 #[serde(rename = "INDICES")]
689 Indices,
690
691 #[serde(rename = "DERIVATIVES")]
693 Derivatives,
694
695 #[serde(rename = "SPOT")]
697 Spot,
698
699 #[serde(rename = "instrument")]
701 #[serde(untagged)]
702 Specific(String),
703}
704
705impl BitmexProductType {
706 #[must_use]
708 pub fn to_subscription(&self) -> Cow<'static, str> {
709 match self {
710 Self::All => Cow::Borrowed("instrument"),
711 Self::Specific(symbol) => Cow::Owned(format!("instrument:{symbol}")),
712 Self::Contracts => Cow::Borrowed("CONTRACTS"),
713 Self::Indices => Cow::Borrowed("INDICES"),
714 Self::Derivatives => Cow::Borrowed("DERIVATIVES"),
715 Self::Spot => Cow::Borrowed("SPOT"),
716 }
717 }
718}
719
720impl<'de> Deserialize<'de> for BitmexProductType {
721 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
722 where
723 D: Deserializer<'de>,
724 {
725 let s = String::deserialize(deserializer)?;
726
727 match s.as_str() {
728 "instrument" => Ok(Self::All),
729 "CONTRACTS" => Ok(Self::Contracts),
730 "INDICES" => Ok(Self::Indices),
731 "DERIVATIVES" => Ok(Self::Derivatives),
732 "SPOT" => Ok(Self::Spot),
733 s if s.starts_with("instrument:") => {
734 let symbol = s.strip_prefix("instrument:").unwrap();
735 Ok(Self::Specific(symbol.to_string()))
736 }
737 _ => Err(serde::de::Error::custom(format!(
738 "Invalid product type: {s}"
739 ))),
740 }
741 }
742}
743
744#[derive(
746 Copy,
747 Clone,
748 Debug,
749 Display,
750 PartialEq,
751 Eq,
752 AsRefStr,
753 EnumIter,
754 EnumString,
755 Serialize,
756 Deserialize,
757)]
758pub enum BitmexTickDirection {
759 PlusTick,
761 MinusTick,
763 ZeroPlusTick,
765 ZeroMinusTick,
767}
768
769#[derive(
771 Clone,
772 Copy,
773 Debug,
774 Display,
775 PartialEq,
776 Eq,
777 AsRefStr,
778 EnumIter,
779 EnumString,
780 Serialize,
781 Deserialize,
782)]
783pub enum BitmexInstrumentState {
784 Open,
786 Closed,
788 Unlisted,
790 Settled,
792 Delisted,
794}
795
796#[derive(
798 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
799)]
800pub enum BitmexFairMethod {
801 FundingRate,
803 ImpactMidPrice,
805 LastPrice,
807}
808
809#[derive(
811 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
812)]
813pub enum BitmexMarkMethod {
814 FairPrice,
816 FairPriceStox,
818 LastPrice,
820 CompositeIndex,
822}
823
824#[cfg(test)]
825mod tests {
826 use rstest::rstest;
827
828 use super::*;
829
830 #[rstest]
831 fn test_bitmex_side_deserialization() {
832 assert_eq!(
834 serde_json::from_str::<BitmexSide>(r#""Buy""#).unwrap(),
835 BitmexSide::Buy
836 );
837 assert_eq!(
838 serde_json::from_str::<BitmexSide>(r#""BUY""#).unwrap(),
839 BitmexSide::Buy
840 );
841 assert_eq!(
842 serde_json::from_str::<BitmexSide>(r#""buy""#).unwrap(),
843 BitmexSide::Buy
844 );
845 assert_eq!(
846 serde_json::from_str::<BitmexSide>(r#""Sell""#).unwrap(),
847 BitmexSide::Sell
848 );
849 assert_eq!(
850 serde_json::from_str::<BitmexSide>(r#""SELL""#).unwrap(),
851 BitmexSide::Sell
852 );
853 assert_eq!(
854 serde_json::from_str::<BitmexSide>(r#""sell""#).unwrap(),
855 BitmexSide::Sell
856 );
857 }
858
859 #[rstest]
860 fn test_bitmex_order_type_deserialization() {
861 assert_eq!(
862 serde_json::from_str::<BitmexOrderType>(r#""Market""#).unwrap(),
863 BitmexOrderType::Market
864 );
865 assert_eq!(
866 serde_json::from_str::<BitmexOrderType>(r#""Limit""#).unwrap(),
867 BitmexOrderType::Limit
868 );
869 assert_eq!(
870 serde_json::from_str::<BitmexOrderType>(r#""Stop""#).unwrap(),
871 BitmexOrderType::Stop
872 );
873 assert_eq!(
874 serde_json::from_str::<BitmexOrderType>(r#""StopLimit""#).unwrap(),
875 BitmexOrderType::StopLimit
876 );
877 assert_eq!(
878 serde_json::from_str::<BitmexOrderType>(r#""MarketIfTouched""#).unwrap(),
879 BitmexOrderType::MarketIfTouched
880 );
881 assert_eq!(
882 serde_json::from_str::<BitmexOrderType>(r#""LimitIfTouched""#).unwrap(),
883 BitmexOrderType::LimitIfTouched
884 );
885 assert_eq!(
886 serde_json::from_str::<BitmexOrderType>(r#""Pegged""#).unwrap(),
887 BitmexOrderType::Pegged
888 );
889 }
890
891 #[rstest]
892 fn test_instrument_type_serialization() {
893 assert_eq!(
895 serde_json::to_string(&BitmexInstrumentType::PerpetualContract).unwrap(),
896 r#""FFWCSX""#
897 );
898 assert_eq!(
899 serde_json::to_string(&BitmexInstrumentType::PerpetualContractFx).unwrap(),
900 r#""FFWCSF""#
901 );
902 assert_eq!(
903 serde_json::to_string(&BitmexInstrumentType::StockPerpetual).unwrap(),
904 r#""FFSCSX""#
905 );
906 assert_eq!(
907 serde_json::to_string(&BitmexInstrumentType::Spot).unwrap(),
908 r#""IFXXXP""#
909 );
910 assert_eq!(
911 serde_json::to_string(&BitmexInstrumentType::Futures).unwrap(),
912 r#""FFCCSX""#
913 );
914 assert_eq!(
915 serde_json::to_string(&BitmexInstrumentType::PredictionMarket).unwrap(),
916 r#""FFICSX""#
917 );
918 assert_eq!(
919 serde_json::to_string(&BitmexInstrumentType::CallOption).unwrap(),
920 r#""OCECCS""#
921 );
922 assert_eq!(
923 serde_json::to_string(&BitmexInstrumentType::PutOption).unwrap(),
924 r#""OPECCS""#
925 );
926 assert_eq!(
927 serde_json::to_string(&BitmexInstrumentType::SwapRate).unwrap(),
928 r#""SRMCSX""#
929 );
930
931 assert_eq!(
933 serde_json::to_string(&BitmexInstrumentType::LegacyFutures).unwrap(),
934 r#""FXXXS""#
935 );
936 assert_eq!(
937 serde_json::to_string(&BitmexInstrumentType::LegacyFuturesN).unwrap(),
938 r#""FXXXN""#
939 );
940 assert_eq!(
941 serde_json::to_string(&BitmexInstrumentType::FuturesSpreads).unwrap(),
942 r#""FMXXS""#
943 );
944 assert_eq!(
945 serde_json::to_string(&BitmexInstrumentType::ReferenceBasket).unwrap(),
946 r#""RCSXXX""#
947 );
948
949 assert_eq!(
951 serde_json::to_string(&BitmexInstrumentType::BasketIndex).unwrap(),
952 r#""MRBXXX""#
953 );
954 assert_eq!(
955 serde_json::to_string(&BitmexInstrumentType::CryptoIndex).unwrap(),
956 r#""MRCXXX""#
957 );
958 assert_eq!(
959 serde_json::to_string(&BitmexInstrumentType::FxIndex).unwrap(),
960 r#""MRFXXX""#
961 );
962 assert_eq!(
963 serde_json::to_string(&BitmexInstrumentType::LendingIndex).unwrap(),
964 r#""MRRXXX""#
965 );
966 assert_eq!(
967 serde_json::to_string(&BitmexInstrumentType::VolatilityIndex).unwrap(),
968 r#""MRIXXX""#
969 );
970 assert_eq!(
971 serde_json::to_string(&BitmexInstrumentType::StockIndex).unwrap(),
972 r#""MRSXXX""#
973 );
974 assert_eq!(
975 serde_json::to_string(&BitmexInstrumentType::YieldIndex).unwrap(),
976 r#""MRVDXX""#
977 );
978 }
979
980 #[rstest]
981 fn test_instrument_type_deserialization() {
982 assert_eq!(
984 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSX""#).unwrap(),
985 BitmexInstrumentType::PerpetualContract
986 );
987 assert_eq!(
988 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSF""#).unwrap(),
989 BitmexInstrumentType::PerpetualContractFx
990 );
991 assert_eq!(
992 serde_json::from_str::<BitmexInstrumentType>(r#""FFSCSX""#).unwrap(),
993 BitmexInstrumentType::StockPerpetual
994 );
995 assert_eq!(
996 serde_json::from_str::<BitmexInstrumentType>(r#""IFXXXP""#).unwrap(),
997 BitmexInstrumentType::Spot
998 );
999 assert_eq!(
1000 serde_json::from_str::<BitmexInstrumentType>(r#""FFCCSX""#).unwrap(),
1001 BitmexInstrumentType::Futures
1002 );
1003 assert_eq!(
1004 serde_json::from_str::<BitmexInstrumentType>(r#""FFICSX""#).unwrap(),
1005 BitmexInstrumentType::PredictionMarket
1006 );
1007 assert_eq!(
1008 serde_json::from_str::<BitmexInstrumentType>(r#""OCECCS""#).unwrap(),
1009 BitmexInstrumentType::CallOption
1010 );
1011 assert_eq!(
1012 serde_json::from_str::<BitmexInstrumentType>(r#""OPECCS""#).unwrap(),
1013 BitmexInstrumentType::PutOption
1014 );
1015 assert_eq!(
1016 serde_json::from_str::<BitmexInstrumentType>(r#""SRMCSX""#).unwrap(),
1017 BitmexInstrumentType::SwapRate
1018 );
1019
1020 assert_eq!(
1022 serde_json::from_str::<BitmexInstrumentType>(r#""FXXXS""#).unwrap(),
1023 BitmexInstrumentType::LegacyFutures
1024 );
1025 assert_eq!(
1026 serde_json::from_str::<BitmexInstrumentType>(r#""FXXXN""#).unwrap(),
1027 BitmexInstrumentType::LegacyFuturesN
1028 );
1029 assert_eq!(
1030 serde_json::from_str::<BitmexInstrumentType>(r#""FMXXS""#).unwrap(),
1031 BitmexInstrumentType::FuturesSpreads
1032 );
1033 assert_eq!(
1034 serde_json::from_str::<BitmexInstrumentType>(r#""RCSXXX""#).unwrap(),
1035 BitmexInstrumentType::ReferenceBasket
1036 );
1037
1038 assert_eq!(
1040 serde_json::from_str::<BitmexInstrumentType>(r#""MRBXXX""#).unwrap(),
1041 BitmexInstrumentType::BasketIndex
1042 );
1043 assert_eq!(
1044 serde_json::from_str::<BitmexInstrumentType>(r#""MRCXXX""#).unwrap(),
1045 BitmexInstrumentType::CryptoIndex
1046 );
1047 assert_eq!(
1048 serde_json::from_str::<BitmexInstrumentType>(r#""MRFXXX""#).unwrap(),
1049 BitmexInstrumentType::FxIndex
1050 );
1051 assert_eq!(
1052 serde_json::from_str::<BitmexInstrumentType>(r#""MRRXXX""#).unwrap(),
1053 BitmexInstrumentType::LendingIndex
1054 );
1055 assert_eq!(
1056 serde_json::from_str::<BitmexInstrumentType>(r#""MRIXXX""#).unwrap(),
1057 BitmexInstrumentType::VolatilityIndex
1058 );
1059 assert_eq!(
1060 serde_json::from_str::<BitmexInstrumentType>(r#""MRSXXX""#).unwrap(),
1061 BitmexInstrumentType::StockIndex
1062 );
1063 assert_eq!(
1064 serde_json::from_str::<BitmexInstrumentType>(r#""MRVDXX""#).unwrap(),
1065 BitmexInstrumentType::YieldIndex
1066 );
1067
1068 assert!(serde_json::from_str::<BitmexInstrumentType>(r#""INVALID""#).is_err());
1070 }
1071
1072 #[rstest]
1073 fn test_subscription_strings() {
1074 assert_eq!(BitmexProductType::All.to_subscription(), "instrument");
1075 assert_eq!(
1076 BitmexProductType::Specific("XBTUSD".to_string()).to_subscription(),
1077 "instrument:XBTUSD"
1078 );
1079 assert_eq!(BitmexProductType::Contracts.to_subscription(), "CONTRACTS");
1080 assert_eq!(BitmexProductType::Indices.to_subscription(), "INDICES");
1081 assert_eq!(
1082 BitmexProductType::Derivatives.to_subscription(),
1083 "DERIVATIVES"
1084 );
1085 assert_eq!(BitmexProductType::Spot.to_subscription(), "SPOT");
1086 }
1087
1088 #[rstest]
1089 fn test_serialization() {
1090 assert_eq!(
1092 serde_json::to_string(&BitmexProductType::All).unwrap(),
1093 r#""instrument""#
1094 );
1095 assert_eq!(
1096 serde_json::to_string(&BitmexProductType::Specific("XBTUSD".to_string())).unwrap(),
1097 r#""XBTUSD""#
1098 );
1099 assert_eq!(
1100 serde_json::to_string(&BitmexProductType::Contracts).unwrap(),
1101 r#""CONTRACTS""#
1102 );
1103 }
1104
1105 #[rstest]
1106 fn test_deserialization() {
1107 assert_eq!(
1108 serde_json::from_str::<BitmexProductType>(r#""instrument""#).unwrap(),
1109 BitmexProductType::All
1110 );
1111 assert_eq!(
1112 serde_json::from_str::<BitmexProductType>(r#""instrument:XBTUSD""#).unwrap(),
1113 BitmexProductType::Specific("XBTUSD".to_string())
1114 );
1115 assert_eq!(
1116 serde_json::from_str::<BitmexProductType>(r#""CONTRACTS""#).unwrap(),
1117 BitmexProductType::Contracts
1118 );
1119 }
1120
1121 #[rstest]
1122 fn test_error_cases() {
1123 assert!(serde_json::from_str::<BitmexProductType>(r#""invalid_type""#).is_err());
1124 assert!(serde_json::from_str::<BitmexProductType>(r"123").is_err());
1125 assert!(serde_json::from_str::<BitmexProductType>(r"{}").is_err());
1126 }
1127
1128 #[rstest]
1129 fn test_order_side_try_from() {
1130 assert_eq!(
1132 BitmexSide::try_from(OrderSide::Buy).unwrap(),
1133 BitmexSide::Buy
1134 );
1135 assert_eq!(
1136 BitmexSide::try_from(OrderSide::Sell).unwrap(),
1137 BitmexSide::Sell
1138 );
1139
1140 let result = BitmexSide::try_from(OrderSide::NoOrderSide);
1142 assert!(result.is_err());
1143 match result {
1144 Err(BitmexError::NonRetryable {
1145 source: BitmexNonRetryableError::Validation { field, .. },
1146 ..
1147 }) => {
1148 assert_eq!(field, "order_side");
1149 }
1150 _ => panic!("Expected validation error"),
1151 }
1152 }
1153
1154 #[rstest]
1155 fn test_order_type_try_from() {
1156 assert_eq!(
1158 BitmexOrderType::try_from(OrderType::Market).unwrap(),
1159 BitmexOrderType::Market
1160 );
1161 assert_eq!(
1162 BitmexOrderType::try_from(OrderType::Limit).unwrap(),
1163 BitmexOrderType::Limit
1164 );
1165
1166 let result = BitmexOrderType::try_from(OrderType::MarketToLimit);
1168 assert!(result.is_err());
1169 match result {
1170 Err(BitmexError::NonRetryable {
1171 source: BitmexNonRetryableError::Validation { message, .. },
1172 ..
1173 }) => {
1174 assert!(message.contains("not supported"));
1175 }
1176 _ => panic!("Expected validation error"),
1177 }
1178 }
1179
1180 #[rstest]
1181 fn test_time_in_force_conversions() {
1182 assert_eq!(
1184 TimeInForce::try_from(BitmexTimeInForce::Day).unwrap(),
1185 TimeInForce::Day
1186 );
1187 assert_eq!(
1188 TimeInForce::try_from(BitmexTimeInForce::GoodTillCancel).unwrap(),
1189 TimeInForce::Gtc
1190 );
1191 assert_eq!(
1192 TimeInForce::try_from(BitmexTimeInForce::ImmediateOrCancel).unwrap(),
1193 TimeInForce::Ioc
1194 );
1195
1196 let result = TimeInForce::try_from(BitmexTimeInForce::GoodTillCrossing);
1198 assert!(result.is_err());
1199 match result {
1200 Err(BitmexError::NonRetryable {
1201 source: BitmexNonRetryableError::Validation { field, message },
1202 ..
1203 }) => {
1204 assert_eq!(field, "time_in_force");
1205 assert!(message.contains("Unsupported"));
1206 }
1207 _ => panic!("Expected validation error"),
1208 }
1209
1210 assert_eq!(
1212 BitmexTimeInForce::try_from(TimeInForce::Day).unwrap(),
1213 BitmexTimeInForce::Day
1214 );
1215 assert_eq!(
1216 BitmexTimeInForce::try_from(TimeInForce::Gtc).unwrap(),
1217 BitmexTimeInForce::GoodTillCancel
1218 );
1219 assert_eq!(
1220 BitmexTimeInForce::try_from(TimeInForce::Fok).unwrap(),
1221 BitmexTimeInForce::FillOrKill
1222 );
1223 }
1224
1225 #[rstest]
1226 fn test_helper_methods() {
1227 let result = BitmexSide::try_from_order_side(OrderSide::Buy);
1229 assert!(result.is_ok());
1230 assert_eq!(result.unwrap(), BitmexSide::Buy);
1231
1232 let result = BitmexSide::try_from_order_side(OrderSide::NoOrderSide);
1233 assert!(result.is_err());
1234
1235 let result = BitmexOrderType::try_from_order_type(OrderType::Limit);
1237 assert!(result.is_ok());
1238 assert_eq!(result.unwrap(), BitmexOrderType::Limit);
1239
1240 let result = BitmexOrderType::try_from_order_type(OrderType::MarketToLimit);
1241 assert!(result.is_err());
1242
1243 let result = BitmexTimeInForce::try_from_time_in_force(TimeInForce::Ioc);
1245 assert!(result.is_ok());
1246 assert_eq!(result.unwrap(), BitmexTimeInForce::ImmediateOrCancel);
1247 }
1248}