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, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
772)]
773pub enum BitmexInstrumentState {
774 Open,
776 Closed,
778 Unlisted,
780 Settled,
782}
783
784#[derive(
786 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
787)]
788pub enum BitmexFairMethod {
789 FundingRate,
791 ImpactMidPrice,
793 LastPrice,
795}
796
797#[derive(
799 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
800)]
801pub enum BitmexMarkMethod {
802 FairPrice,
804 FairPriceStox,
806 LastPrice,
808 CompositeIndex,
810}
811
812#[cfg(test)]
813mod tests {
814 use rstest::rstest;
815
816 use super::*;
817
818 #[rstest]
819 fn test_bitmex_side_deserialization() {
820 assert_eq!(
822 serde_json::from_str::<BitmexSide>(r#""Buy""#).unwrap(),
823 BitmexSide::Buy
824 );
825 assert_eq!(
826 serde_json::from_str::<BitmexSide>(r#""BUY""#).unwrap(),
827 BitmexSide::Buy
828 );
829 assert_eq!(
830 serde_json::from_str::<BitmexSide>(r#""buy""#).unwrap(),
831 BitmexSide::Buy
832 );
833 assert_eq!(
834 serde_json::from_str::<BitmexSide>(r#""Sell""#).unwrap(),
835 BitmexSide::Sell
836 );
837 assert_eq!(
838 serde_json::from_str::<BitmexSide>(r#""SELL""#).unwrap(),
839 BitmexSide::Sell
840 );
841 assert_eq!(
842 serde_json::from_str::<BitmexSide>(r#""sell""#).unwrap(),
843 BitmexSide::Sell
844 );
845 }
846
847 #[rstest]
848 fn test_bitmex_order_type_deserialization() {
849 assert_eq!(
850 serde_json::from_str::<BitmexOrderType>(r#""Market""#).unwrap(),
851 BitmexOrderType::Market
852 );
853 assert_eq!(
854 serde_json::from_str::<BitmexOrderType>(r#""Limit""#).unwrap(),
855 BitmexOrderType::Limit
856 );
857 assert_eq!(
858 serde_json::from_str::<BitmexOrderType>(r#""Stop""#).unwrap(),
859 BitmexOrderType::Stop
860 );
861 assert_eq!(
862 serde_json::from_str::<BitmexOrderType>(r#""StopLimit""#).unwrap(),
863 BitmexOrderType::StopLimit
864 );
865 assert_eq!(
866 serde_json::from_str::<BitmexOrderType>(r#""MarketIfTouched""#).unwrap(),
867 BitmexOrderType::MarketIfTouched
868 );
869 assert_eq!(
870 serde_json::from_str::<BitmexOrderType>(r#""LimitIfTouched""#).unwrap(),
871 BitmexOrderType::LimitIfTouched
872 );
873 assert_eq!(
874 serde_json::from_str::<BitmexOrderType>(r#""Pegged""#).unwrap(),
875 BitmexOrderType::Pegged
876 );
877 }
878
879 #[rstest]
880 fn test_instrument_type_serialization() {
881 assert_eq!(
883 serde_json::to_string(&BitmexInstrumentType::PerpetualContract).unwrap(),
884 r#""FFWCSX""#
885 );
886 assert_eq!(
887 serde_json::to_string(&BitmexInstrumentType::PerpetualContractFx).unwrap(),
888 r#""FFWCSF""#
889 );
890 assert_eq!(
891 serde_json::to_string(&BitmexInstrumentType::StockPerpetual).unwrap(),
892 r#""FFSCSX""#
893 );
894 assert_eq!(
895 serde_json::to_string(&BitmexInstrumentType::Spot).unwrap(),
896 r#""IFXXXP""#
897 );
898 assert_eq!(
899 serde_json::to_string(&BitmexInstrumentType::Futures).unwrap(),
900 r#""FFCCSX""#
901 );
902 assert_eq!(
903 serde_json::to_string(&BitmexInstrumentType::PredictionMarket).unwrap(),
904 r#""FFICSX""#
905 );
906 assert_eq!(
907 serde_json::to_string(&BitmexInstrumentType::CallOption).unwrap(),
908 r#""OCECCS""#
909 );
910 assert_eq!(
911 serde_json::to_string(&BitmexInstrumentType::PutOption).unwrap(),
912 r#""OPECCS""#
913 );
914 assert_eq!(
915 serde_json::to_string(&BitmexInstrumentType::SwapRate).unwrap(),
916 r#""SRMCSX""#
917 );
918
919 assert_eq!(
921 serde_json::to_string(&BitmexInstrumentType::LegacyFutures).unwrap(),
922 r#""FXXXS""#
923 );
924 assert_eq!(
925 serde_json::to_string(&BitmexInstrumentType::LegacyFuturesN).unwrap(),
926 r#""FXXXN""#
927 );
928 assert_eq!(
929 serde_json::to_string(&BitmexInstrumentType::FuturesSpreads).unwrap(),
930 r#""FMXXS""#
931 );
932 assert_eq!(
933 serde_json::to_string(&BitmexInstrumentType::ReferenceBasket).unwrap(),
934 r#""RCSXXX""#
935 );
936
937 assert_eq!(
939 serde_json::to_string(&BitmexInstrumentType::BasketIndex).unwrap(),
940 r#""MRBXXX""#
941 );
942 assert_eq!(
943 serde_json::to_string(&BitmexInstrumentType::CryptoIndex).unwrap(),
944 r#""MRCXXX""#
945 );
946 assert_eq!(
947 serde_json::to_string(&BitmexInstrumentType::FxIndex).unwrap(),
948 r#""MRFXXX""#
949 );
950 assert_eq!(
951 serde_json::to_string(&BitmexInstrumentType::LendingIndex).unwrap(),
952 r#""MRRXXX""#
953 );
954 assert_eq!(
955 serde_json::to_string(&BitmexInstrumentType::VolatilityIndex).unwrap(),
956 r#""MRIXXX""#
957 );
958 assert_eq!(
959 serde_json::to_string(&BitmexInstrumentType::StockIndex).unwrap(),
960 r#""MRSXXX""#
961 );
962 assert_eq!(
963 serde_json::to_string(&BitmexInstrumentType::YieldIndex).unwrap(),
964 r#""MRVDXX""#
965 );
966 }
967
968 #[rstest]
969 fn test_instrument_type_deserialization() {
970 assert_eq!(
972 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSX""#).unwrap(),
973 BitmexInstrumentType::PerpetualContract
974 );
975 assert_eq!(
976 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSF""#).unwrap(),
977 BitmexInstrumentType::PerpetualContractFx
978 );
979 assert_eq!(
980 serde_json::from_str::<BitmexInstrumentType>(r#""FFSCSX""#).unwrap(),
981 BitmexInstrumentType::StockPerpetual
982 );
983 assert_eq!(
984 serde_json::from_str::<BitmexInstrumentType>(r#""IFXXXP""#).unwrap(),
985 BitmexInstrumentType::Spot
986 );
987 assert_eq!(
988 serde_json::from_str::<BitmexInstrumentType>(r#""FFCCSX""#).unwrap(),
989 BitmexInstrumentType::Futures
990 );
991 assert_eq!(
992 serde_json::from_str::<BitmexInstrumentType>(r#""FFICSX""#).unwrap(),
993 BitmexInstrumentType::PredictionMarket
994 );
995 assert_eq!(
996 serde_json::from_str::<BitmexInstrumentType>(r#""OCECCS""#).unwrap(),
997 BitmexInstrumentType::CallOption
998 );
999 assert_eq!(
1000 serde_json::from_str::<BitmexInstrumentType>(r#""OPECCS""#).unwrap(),
1001 BitmexInstrumentType::PutOption
1002 );
1003 assert_eq!(
1004 serde_json::from_str::<BitmexInstrumentType>(r#""SRMCSX""#).unwrap(),
1005 BitmexInstrumentType::SwapRate
1006 );
1007
1008 assert_eq!(
1010 serde_json::from_str::<BitmexInstrumentType>(r#""FXXXS""#).unwrap(),
1011 BitmexInstrumentType::LegacyFutures
1012 );
1013 assert_eq!(
1014 serde_json::from_str::<BitmexInstrumentType>(r#""FXXXN""#).unwrap(),
1015 BitmexInstrumentType::LegacyFuturesN
1016 );
1017 assert_eq!(
1018 serde_json::from_str::<BitmexInstrumentType>(r#""FMXXS""#).unwrap(),
1019 BitmexInstrumentType::FuturesSpreads
1020 );
1021 assert_eq!(
1022 serde_json::from_str::<BitmexInstrumentType>(r#""RCSXXX""#).unwrap(),
1023 BitmexInstrumentType::ReferenceBasket
1024 );
1025
1026 assert_eq!(
1028 serde_json::from_str::<BitmexInstrumentType>(r#""MRBXXX""#).unwrap(),
1029 BitmexInstrumentType::BasketIndex
1030 );
1031 assert_eq!(
1032 serde_json::from_str::<BitmexInstrumentType>(r#""MRCXXX""#).unwrap(),
1033 BitmexInstrumentType::CryptoIndex
1034 );
1035 assert_eq!(
1036 serde_json::from_str::<BitmexInstrumentType>(r#""MRFXXX""#).unwrap(),
1037 BitmexInstrumentType::FxIndex
1038 );
1039 assert_eq!(
1040 serde_json::from_str::<BitmexInstrumentType>(r#""MRRXXX""#).unwrap(),
1041 BitmexInstrumentType::LendingIndex
1042 );
1043 assert_eq!(
1044 serde_json::from_str::<BitmexInstrumentType>(r#""MRIXXX""#).unwrap(),
1045 BitmexInstrumentType::VolatilityIndex
1046 );
1047 assert_eq!(
1048 serde_json::from_str::<BitmexInstrumentType>(r#""MRSXXX""#).unwrap(),
1049 BitmexInstrumentType::StockIndex
1050 );
1051 assert_eq!(
1052 serde_json::from_str::<BitmexInstrumentType>(r#""MRVDXX""#).unwrap(),
1053 BitmexInstrumentType::YieldIndex
1054 );
1055
1056 assert!(serde_json::from_str::<BitmexInstrumentType>(r#""INVALID""#).is_err());
1058 }
1059
1060 #[rstest]
1061 fn test_subscription_strings() {
1062 assert_eq!(BitmexProductType::All.to_subscription(), "instrument");
1063 assert_eq!(
1064 BitmexProductType::Specific("XBTUSD".to_string()).to_subscription(),
1065 "instrument:XBTUSD"
1066 );
1067 assert_eq!(BitmexProductType::Contracts.to_subscription(), "CONTRACTS");
1068 assert_eq!(BitmexProductType::Indices.to_subscription(), "INDICES");
1069 assert_eq!(
1070 BitmexProductType::Derivatives.to_subscription(),
1071 "DERIVATIVES"
1072 );
1073 assert_eq!(BitmexProductType::Spot.to_subscription(), "SPOT");
1074 }
1075
1076 #[rstest]
1077 fn test_serialization() {
1078 assert_eq!(
1080 serde_json::to_string(&BitmexProductType::All).unwrap(),
1081 r#""instrument""#
1082 );
1083 assert_eq!(
1084 serde_json::to_string(&BitmexProductType::Specific("XBTUSD".to_string())).unwrap(),
1085 r#""XBTUSD""#
1086 );
1087 assert_eq!(
1088 serde_json::to_string(&BitmexProductType::Contracts).unwrap(),
1089 r#""CONTRACTS""#
1090 );
1091 }
1092
1093 #[rstest]
1094 fn test_deserialization() {
1095 assert_eq!(
1096 serde_json::from_str::<BitmexProductType>(r#""instrument""#).unwrap(),
1097 BitmexProductType::All
1098 );
1099 assert_eq!(
1100 serde_json::from_str::<BitmexProductType>(r#""instrument:XBTUSD""#).unwrap(),
1101 BitmexProductType::Specific("XBTUSD".to_string())
1102 );
1103 assert_eq!(
1104 serde_json::from_str::<BitmexProductType>(r#""CONTRACTS""#).unwrap(),
1105 BitmexProductType::Contracts
1106 );
1107 }
1108
1109 #[rstest]
1110 fn test_error_cases() {
1111 assert!(serde_json::from_str::<BitmexProductType>(r#""invalid_type""#).is_err());
1112 assert!(serde_json::from_str::<BitmexProductType>(r"123").is_err());
1113 assert!(serde_json::from_str::<BitmexProductType>(r"{}").is_err());
1114 }
1115
1116 #[rstest]
1117 fn test_order_side_try_from() {
1118 assert_eq!(
1120 BitmexSide::try_from(OrderSide::Buy).unwrap(),
1121 BitmexSide::Buy
1122 );
1123 assert_eq!(
1124 BitmexSide::try_from(OrderSide::Sell).unwrap(),
1125 BitmexSide::Sell
1126 );
1127
1128 let result = BitmexSide::try_from(OrderSide::NoOrderSide);
1130 assert!(result.is_err());
1131 match result {
1132 Err(BitmexError::NonRetryable {
1133 source: BitmexNonRetryableError::Validation { field, .. },
1134 ..
1135 }) => {
1136 assert_eq!(field, "order_side");
1137 }
1138 _ => panic!("Expected validation error"),
1139 }
1140 }
1141
1142 #[rstest]
1143 fn test_order_type_try_from() {
1144 assert_eq!(
1146 BitmexOrderType::try_from(OrderType::Market).unwrap(),
1147 BitmexOrderType::Market
1148 );
1149 assert_eq!(
1150 BitmexOrderType::try_from(OrderType::Limit).unwrap(),
1151 BitmexOrderType::Limit
1152 );
1153
1154 let result = BitmexOrderType::try_from(OrderType::MarketToLimit);
1156 assert!(result.is_err());
1157 match result {
1158 Err(BitmexError::NonRetryable {
1159 source: BitmexNonRetryableError::Validation { message, .. },
1160 ..
1161 }) => {
1162 assert!(message.contains("not supported"));
1163 }
1164 _ => panic!("Expected validation error"),
1165 }
1166 }
1167
1168 #[rstest]
1169 fn test_time_in_force_conversions() {
1170 assert_eq!(
1172 TimeInForce::try_from(BitmexTimeInForce::Day).unwrap(),
1173 TimeInForce::Day
1174 );
1175 assert_eq!(
1176 TimeInForce::try_from(BitmexTimeInForce::GoodTillCancel).unwrap(),
1177 TimeInForce::Gtc
1178 );
1179 assert_eq!(
1180 TimeInForce::try_from(BitmexTimeInForce::ImmediateOrCancel).unwrap(),
1181 TimeInForce::Ioc
1182 );
1183
1184 let result = TimeInForce::try_from(BitmexTimeInForce::GoodTillCrossing);
1186 assert!(result.is_err());
1187 match result {
1188 Err(BitmexError::NonRetryable {
1189 source: BitmexNonRetryableError::Validation { field, message },
1190 ..
1191 }) => {
1192 assert_eq!(field, "time_in_force");
1193 assert!(message.contains("Unsupported"));
1194 }
1195 _ => panic!("Expected validation error"),
1196 }
1197
1198 assert_eq!(
1200 BitmexTimeInForce::try_from(TimeInForce::Day).unwrap(),
1201 BitmexTimeInForce::Day
1202 );
1203 assert_eq!(
1204 BitmexTimeInForce::try_from(TimeInForce::Gtc).unwrap(),
1205 BitmexTimeInForce::GoodTillCancel
1206 );
1207 assert_eq!(
1208 BitmexTimeInForce::try_from(TimeInForce::Fok).unwrap(),
1209 BitmexTimeInForce::FillOrKill
1210 );
1211 }
1212
1213 #[rstest]
1214 fn test_helper_methods() {
1215 let result = BitmexSide::try_from_order_side(OrderSide::Buy);
1217 assert!(result.is_ok());
1218 assert_eq!(result.unwrap(), BitmexSide::Buy);
1219
1220 let result = BitmexSide::try_from_order_side(OrderSide::NoOrderSide);
1221 assert!(result.is_err());
1222
1223 let result = BitmexOrderType::try_from_order_type(OrderType::Limit);
1225 assert!(result.is_ok());
1226 assert_eq!(result.unwrap(), BitmexOrderType::Limit);
1227
1228 let result = BitmexOrderType::try_from_order_type(OrderType::MarketToLimit);
1229 assert!(result.is_err());
1230
1231 let result = BitmexTimeInForce::try_from_time_in_force(TimeInForce::Ioc);
1233 assert!(result.is_ok());
1234 assert_eq!(result.unwrap(), BitmexTimeInForce::ImmediateOrCancel);
1235 }
1236}