1use std::borrow::Cow;
19
20use nautilus_model::enums::{
21 ContingencyType, LiquiditySide, MarketStatusAction, OrderSide, OrderStatus, OrderType,
22 PositionSide, TimeInForce,
23};
24use serde::{Deserialize, Deserializer, Serialize};
25use strum::{AsRefStr, Display, EnumIter, EnumString};
26
27#[derive(
29 Copy,
30 Clone,
31 Debug,
32 Display,
33 PartialEq,
34 Eq,
35 AsRefStr,
36 EnumIter,
37 EnumString,
38 Serialize,
39 Deserialize,
40)]
41#[serde(rename_all = "PascalCase")]
42#[cfg_attr(
43 feature = "python",
44 pyo3::pyclass(
45 module = "nautilus_trader.adapters.bitmex",
46 eq,
47 eq_int,
48 from_py_object,
49 rename_all = "SCREAMING_SNAKE_CASE",
50 )
51)]
52pub enum BitmexSymbolStatus {
53 Open,
55 Closed,
57 Unlisted,
59}
60
61#[derive(
63 Copy,
64 Clone,
65 Debug,
66 Display,
67 PartialEq,
68 Eq,
69 AsRefStr,
70 EnumIter,
71 EnumString,
72 Serialize,
73 Deserialize,
74)]
75pub enum BitmexSide {
76 #[serde(rename = "Buy", alias = "BUY", alias = "buy")]
78 Buy,
79 #[serde(rename = "Sell", alias = "SELL", alias = "sell")]
81 Sell,
82}
83
84impl From<OrderSide> for BitmexSide {
85 fn from(value: OrderSide) -> Self {
86 match value {
87 OrderSide::Buy => Self::Buy,
88 OrderSide::Sell => Self::Sell,
89 }
90 }
91}
92
93impl From<BitmexSide> for OrderSide {
94 fn from(side: BitmexSide) -> Self {
95 match side {
96 BitmexSide::Buy => Self::Buy,
97 BitmexSide::Sell => Self::Sell,
98 }
99 }
100}
101
102#[derive(
104 Copy,
105 Clone,
106 Debug,
107 Display,
108 PartialEq,
109 Eq,
110 AsRefStr,
111 EnumIter,
112 EnumString,
113 Serialize,
114 Deserialize,
115)]
116#[cfg_attr(
117 feature = "python",
118 pyo3::pyclass(module = "nautilus_trader.adapters.bitmex", eq, eq_int, from_py_object)
119)]
120pub enum BitmexPositionSide {
121 #[serde(rename = "LONG", alias = "Long", alias = "long")]
123 Long,
124 #[serde(rename = "SHORT", alias = "Short", alias = "short")]
126 Short,
127 #[serde(rename = "FLAT", alias = "Flat", alias = "flat")]
129 Flat,
130}
131
132impl From<BitmexPositionSide> for PositionSide {
133 fn from(side: BitmexPositionSide) -> Self {
134 match side {
135 BitmexPositionSide::Long => Self::Long,
136 BitmexPositionSide::Short => Self::Short,
137 BitmexPositionSide::Flat => Self::Flat,
138 }
139 }
140}
141
142impl From<PositionSide> for BitmexPositionSide {
143 fn from(side: PositionSide) -> Self {
144 match side {
145 PositionSide::Long => Self::Long,
146 PositionSide::Short => Self::Short,
147 PositionSide::Flat => Self::Flat,
148 }
149 }
150}
151
152#[derive(
154 Copy,
155 Clone,
156 Debug,
157 Display,
158 PartialEq,
159 Eq,
160 AsRefStr,
161 EnumIter,
162 EnumString,
163 Serialize,
164 Deserialize,
165)]
166pub enum BitmexOrderType {
167 Market,
169 Limit,
171 Stop,
173 StopLimit,
175 MarketIfTouched,
177 LimitIfTouched,
179 Pegged,
181}
182
183impl TryFrom<OrderType> for BitmexOrderType {
184 type Error = anyhow::Error;
185
186 fn try_from(value: OrderType) -> Result<Self, Self::Error> {
187 match value {
188 OrderType::Market => Ok(Self::Market),
189 OrderType::Limit => Ok(Self::Limit),
190 OrderType::StopMarket => Ok(Self::Stop),
191 OrderType::StopLimit => Ok(Self::StopLimit),
192 OrderType::MarketIfTouched => Ok(Self::MarketIfTouched),
193 OrderType::LimitIfTouched => Ok(Self::LimitIfTouched),
194 OrderType::TrailingStopMarket => Ok(Self::Pegged),
195 OrderType::TrailingStopLimit => Ok(Self::Pegged),
196 OrderType::MarketToLimit => {
197 anyhow::bail!("MarketToLimit order type is not supported by BitMEX")
198 }
199 }
200 }
201}
202
203impl BitmexOrderType {
204 pub fn try_from_order_type(value: OrderType) -> anyhow::Result<Self> {
210 Self::try_from(value)
211 }
212}
213
214impl From<BitmexOrderType> for OrderType {
215 fn from(value: BitmexOrderType) -> Self {
216 match value {
217 BitmexOrderType::Market => Self::Market,
218 BitmexOrderType::Limit => Self::Limit,
219 BitmexOrderType::Stop => Self::StopMarket,
220 BitmexOrderType::StopLimit => Self::StopLimit,
221 BitmexOrderType::MarketIfTouched => Self::MarketIfTouched,
222 BitmexOrderType::LimitIfTouched => Self::LimitIfTouched,
223 BitmexOrderType::Pegged => Self::Limit,
224 }
225 }
226}
227
228#[derive(
230 Copy,
231 Clone,
232 Debug,
233 Display,
234 PartialEq,
235 Eq,
236 AsRefStr,
237 EnumIter,
238 EnumString,
239 Serialize,
240 Deserialize,
241)]
242pub enum BitmexOrderStatus {
243 New,
245 PendingNew,
247 PartiallyFilled,
249 Filled,
251 PendingReplace,
253 PendingCancel,
255 Canceled,
257 Rejected,
259 Expired,
261}
262
263impl BitmexOrderStatus {
264 pub fn is_terminal(self) -> bool {
266 matches!(
267 self,
268 Self::Filled | Self::Canceled | Self::Rejected | Self::Expired
269 )
270 }
271}
272
273impl From<BitmexOrderStatus> for OrderStatus {
274 fn from(value: BitmexOrderStatus) -> Self {
275 match value {
276 BitmexOrderStatus::New => Self::Accepted,
277 BitmexOrderStatus::PendingNew => Self::Submitted,
278 BitmexOrderStatus::PartiallyFilled => Self::PartiallyFilled,
279 BitmexOrderStatus::Filled => Self::Filled,
280 BitmexOrderStatus::PendingReplace => Self::PendingUpdate,
281 BitmexOrderStatus::PendingCancel => Self::PendingCancel,
282 BitmexOrderStatus::Canceled => Self::Canceled,
283 BitmexOrderStatus::Rejected => Self::Rejected,
284 BitmexOrderStatus::Expired => Self::Expired,
285 }
286 }
287}
288
289#[derive(
291 Copy,
292 Clone,
293 Debug,
294 Display,
295 PartialEq,
296 Eq,
297 AsRefStr,
298 EnumIter,
299 EnumString,
300 Serialize,
301 Deserialize,
302)]
303pub enum BitmexTimeInForce {
304 Day,
305 GoodTillCancel,
306 AtTheOpening,
307 ImmediateOrCancel,
308 FillOrKill,
309 GoodTillCrossing,
310 GoodTillDate,
311 AtTheClose,
312 GoodThroughCrossing,
313 AtCrossing,
314}
315
316impl TryFrom<BitmexTimeInForce> for TimeInForce {
317 type Error = anyhow::Error;
318
319 fn try_from(value: BitmexTimeInForce) -> Result<Self, Self::Error> {
320 match value {
321 BitmexTimeInForce::Day => Ok(Self::Day),
322 BitmexTimeInForce::GoodTillCancel => Ok(Self::Gtc),
323 BitmexTimeInForce::GoodTillDate => Ok(Self::Gtd),
324 BitmexTimeInForce::ImmediateOrCancel => Ok(Self::Ioc),
325 BitmexTimeInForce::FillOrKill => Ok(Self::Fok),
326 BitmexTimeInForce::AtTheOpening => Ok(Self::AtTheOpen),
327 BitmexTimeInForce::AtTheClose => Ok(Self::AtTheClose),
328 _ => anyhow::bail!("Unsupported BitmexTimeInForce: {value}"),
329 }
330 }
331}
332
333impl TryFrom<TimeInForce> for BitmexTimeInForce {
334 type Error = anyhow::Error;
335
336 fn try_from(value: TimeInForce) -> Result<Self, Self::Error> {
337 match value {
338 TimeInForce::Day => Ok(Self::Day),
339 TimeInForce::Gtc => Ok(Self::GoodTillCancel),
340 TimeInForce::Gtd => Ok(Self::GoodTillDate),
341 TimeInForce::Ioc => Ok(Self::ImmediateOrCancel),
342 TimeInForce::Fok => Ok(Self::FillOrKill),
343 TimeInForce::AtTheOpen => Ok(Self::AtTheOpening),
344 TimeInForce::AtTheClose => Ok(Self::AtTheClose),
345 }
346 }
347}
348
349impl BitmexTimeInForce {
350 pub fn try_from_time_in_force(value: TimeInForce) -> anyhow::Result<Self> {
356 if value == TimeInForce::Gtd {
357 anyhow::bail!(
358 "GTD time in force is not supported for BitMEX order submit; use GTC, Day, IOC, or FOK"
359 );
360 }
361
362 Self::try_from(value)
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 Option<ContingencyType> {
390 fn from(value: BitmexContingencyType) -> Self {
391 match value {
392 BitmexContingencyType::OneCancelsTheOther => Some(ContingencyType::Oco),
393 BitmexContingencyType::OneTriggersTheOther => Some(ContingencyType::Oto),
394 BitmexContingencyType::OneUpdatesTheOtherProportional
395 | BitmexContingencyType::OneUpdatesTheOtherAbsolute => Some(ContingencyType::Ouo),
396 BitmexContingencyType::Unknown => None,
397 }
398 }
399}
400
401impl TryFrom<ContingencyType> for BitmexContingencyType {
402 type Error = anyhow::Error;
403
404 fn try_from(value: ContingencyType) -> Result<Self, Self::Error> {
405 match value {
406 ContingencyType::Oco => Ok(Self::OneCancelsTheOther),
407 ContingencyType::Oto => Ok(Self::OneTriggersTheOther),
408 ContingencyType::Ouo => anyhow::bail!("OUO contingency type not supported by BitMEX"),
409 }
410 }
411}
412
413#[derive(
415 Copy,
416 Clone,
417 Debug,
418 Display,
419 PartialEq,
420 Eq,
421 AsRefStr,
422 EnumIter,
423 EnumString,
424 Serialize,
425 Deserialize,
426)]
427pub enum BitmexPegPriceType {
428 LastPeg,
429 OpeningPeg,
430 MidPricePeg,
431 MarketPeg,
432 PrimaryPeg,
433 PegToVWAP,
434 TrailingStopPeg,
435 PegToLimitPrice,
436 ShortSaleMinPricePeg,
437 #[serde(rename = "")]
438 Unknown, }
440
441#[derive(
443 Copy,
444 Clone,
445 Debug,
446 Display,
447 PartialEq,
448 Eq,
449 AsRefStr,
450 EnumIter,
451 EnumString,
452 Serialize,
453 Deserialize,
454)]
455pub enum BitmexExecInstruction {
456 ParticipateDoNotInitiate,
457 AllOrNone,
458 MarkPrice,
459 IndexPrice,
460 LastPrice,
461 Close,
462 ReduceOnly,
463 Fixed,
464 #[serde(rename = "")]
465 Unknown, }
467
468impl BitmexExecInstruction {
469 pub fn join(instructions: &[Self]) -> String {
471 instructions
472 .iter()
473 .map(ToString::to_string)
474 .collect::<Vec<_>>()
475 .join(",")
476 }
477}
478
479#[derive(Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize)]
481pub enum BitmexExecType {
482 New,
484 Trade,
486 Canceled,
488 CancelReject,
490 Replaced,
492 Rejected,
494 AmendReject,
496 Funding,
498 Settlement,
500 Suspended,
502 Released,
504 Insurance,
506 Rebalance,
508 Liquidation,
510 Bankruptcy,
512 TrialFill,
514 TriggeredOrActivatedBySystem,
516 #[strum(disabled)]
518 Unknown(String),
519}
520
521impl<'de> Deserialize<'de> for BitmexExecType {
522 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
523 where
524 D: Deserializer<'de>,
525 {
526 let s = String::deserialize(deserializer)?;
527
528 match s.as_str() {
529 "New" => Ok(Self::New),
530 "Trade" => Ok(Self::Trade),
531 "Canceled" => Ok(Self::Canceled),
532 "CancelReject" => Ok(Self::CancelReject),
533 "Replaced" => Ok(Self::Replaced),
534 "Rejected" => Ok(Self::Rejected),
535 "AmendReject" => Ok(Self::AmendReject),
536 "Funding" => Ok(Self::Funding),
537 "Settlement" => Ok(Self::Settlement),
538 "Suspended" => Ok(Self::Suspended),
539 "Released" => Ok(Self::Released),
540 "Insurance" => Ok(Self::Insurance),
541 "Rebalance" => Ok(Self::Rebalance),
542 "Liquidation" => Ok(Self::Liquidation),
543 "Bankruptcy" => Ok(Self::Bankruptcy),
544 "TrialFill" => Ok(Self::TrialFill),
545 "TriggeredOrActivatedBySystem" => Ok(Self::TriggeredOrActivatedBySystem),
546 other => Ok(Self::Unknown(other.to_string())),
547 }
548 }
549}
550
551#[derive(
553 Copy,
554 Clone,
555 Debug,
556 Display,
557 PartialEq,
558 Eq,
559 AsRefStr,
560 EnumIter,
561 EnumString,
562 Serialize,
563 Deserialize,
564)]
565pub enum BitmexLiquidityIndicator {
566 #[serde(rename = "Added")]
569 #[serde(alias = "AddedLiquidity")]
570 Maker,
571 #[serde(rename = "Removed")]
574 #[serde(alias = "RemovedLiquidity")]
575 Taker,
576}
577
578impl From<BitmexLiquidityIndicator> for LiquiditySide {
579 fn from(value: BitmexLiquidityIndicator) -> Self {
580 match value {
581 BitmexLiquidityIndicator::Maker => Self::Maker,
582 BitmexLiquidityIndicator::Taker => Self::Taker,
583 }
584 }
585}
586
587#[derive(
594 Copy,
595 Clone,
596 Debug,
597 Display,
598 PartialEq,
599 Eq,
600 AsRefStr,
601 EnumIter,
602 EnumString,
603 Serialize,
604 Deserialize,
605)]
606#[serde(rename_all = "UPPERCASE")]
607pub enum BitmexInstrumentType {
608 #[serde(rename = "FXXXS")]
610 LegacyFutures,
611
612 #[serde(rename = "FXXXN")]
614 LegacyFuturesN,
615
616 #[serde(rename = "FMXXS")]
618 FuturesSpreads,
619
620 #[serde(rename = "FFMCSX")]
622 FuturesSpread,
623
624 #[serde(rename = "FFICSX")]
627 PredictionMarket,
628
629 #[serde(rename = "FFSCSX")]
632 TradFiPerpetual,
633
634 #[serde(rename = "FFWCSX")]
636 PerpetualContract,
637
638 #[serde(rename = "FFWCSF")]
640 PerpetualContractFx,
641
642 #[serde(rename = "FFCCSX")]
644 Futures,
645
646 #[serde(rename = "IFXXXP")]
648 Spot,
649
650 #[serde(rename = "OCECCS")]
652 CallOption,
653
654 #[serde(rename = "OPECCS")]
656 PutOption,
657
658 #[serde(rename = "SRMCSX")]
660 SwapRate,
661
662 #[serde(rename = "RCSXXX")]
664 ReferenceBasket,
665
666 #[serde(rename = "MRBXXX")]
668 BasketIndex,
669
670 #[serde(rename = "MRCXXX")]
672 CryptoIndex,
673
674 #[serde(rename = "MRFXXX")]
676 FxIndex,
677
678 #[serde(rename = "MRRXXX")]
680 LendingIndex,
681
682 #[serde(rename = "MRIXXX")]
684 VolatilityIndex,
685
686 #[serde(rename = "MRSXXX")]
688 StockIndex,
689
690 #[serde(rename = "MRVDXX")]
692 YieldIndex,
693
694 #[serde(other)]
696 Other,
697}
698
699#[derive(Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize)]
701pub enum BitmexProductType {
702 #[serde(rename = "instrument")]
704 All,
705
706 #[serde(rename = "CONTRACTS")]
708 Contracts,
709
710 #[serde(rename = "INDICES")]
712 Indices,
713
714 #[serde(rename = "DERIVATIVES")]
716 Derivatives,
717
718 #[serde(rename = "SPOT")]
720 Spot,
721
722 #[serde(rename = "instrument")]
724 #[serde(untagged)]
725 Specific(String),
726}
727
728impl BitmexProductType {
729 #[must_use]
731 pub fn to_subscription(&self) -> Cow<'static, str> {
732 match self {
733 Self::All => Cow::Borrowed("instrument"),
734 Self::Specific(symbol) => Cow::Owned(format!("instrument:{symbol}")),
735 Self::Contracts => Cow::Borrowed("CONTRACTS"),
736 Self::Indices => Cow::Borrowed("INDICES"),
737 Self::Derivatives => Cow::Borrowed("DERIVATIVES"),
738 Self::Spot => Cow::Borrowed("SPOT"),
739 }
740 }
741}
742
743impl<'de> Deserialize<'de> for BitmexProductType {
744 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
745 where
746 D: Deserializer<'de>,
747 {
748 let s = String::deserialize(deserializer)?;
749
750 match s.as_str() {
751 "instrument" => Ok(Self::All),
752 "CONTRACTS" => Ok(Self::Contracts),
753 "INDICES" => Ok(Self::Indices),
754 "DERIVATIVES" => Ok(Self::Derivatives),
755 "SPOT" => Ok(Self::Spot),
756 s if s.starts_with("instrument:") => {
757 let symbol = s.strip_prefix("instrument:").unwrap();
758 Ok(Self::Specific(symbol.to_string()))
759 }
760 _ => Err(serde::de::Error::custom(format!(
761 "Invalid product type: {s}"
762 ))),
763 }
764 }
765}
766
767#[derive(
769 Copy,
770 Clone,
771 Debug,
772 Display,
773 PartialEq,
774 Eq,
775 AsRefStr,
776 EnumIter,
777 EnumString,
778 Serialize,
779 Deserialize,
780)]
781pub enum BitmexTickDirection {
782 PlusTick,
784 MinusTick,
786 ZeroPlusTick,
788 ZeroMinusTick,
790}
791
792#[derive(
794 Clone,
795 Copy,
796 Debug,
797 Display,
798 PartialEq,
799 Eq,
800 AsRefStr,
801 EnumIter,
802 EnumString,
803 Serialize,
804 Deserialize,
805)]
806pub enum BitmexInstrumentState {
807 Open,
809 Closed,
811 Unlisted,
813 Settled,
815 Delisted,
817 #[serde(other)]
819 Unknown,
820}
821
822impl From<&BitmexInstrumentState> for MarketStatusAction {
823 fn from(state: &BitmexInstrumentState) -> Self {
824 match state {
825 BitmexInstrumentState::Open => Self::Trading,
826 BitmexInstrumentState::Closed => Self::Close,
827 BitmexInstrumentState::Settled => Self::Close,
828 BitmexInstrumentState::Unlisted => Self::NotAvailableForTrading,
829 BitmexInstrumentState::Delisted => Self::NotAvailableForTrading,
830 BitmexInstrumentState::Unknown => Self::NotAvailableForTrading,
831 }
832 }
833}
834
835#[derive(
837 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
838)]
839pub enum BitmexFairMethod {
840 FundingRate,
842 ImpactMidPrice,
844 LastPrice,
846}
847
848#[derive(
850 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
851)]
852pub enum BitmexMarkMethod {
853 FairPrice,
855 FairPriceStox,
857 LastPrice,
859 LastPricePreLaunch,
861 CompositeIndex,
863}
864
865#[derive(
867 Copy,
868 Clone,
869 Debug,
870 Default,
871 Display,
872 PartialEq,
873 Eq,
874 Hash,
875 AsRefStr,
876 EnumIter,
877 EnumString,
878 Serialize,
879 Deserialize,
880)]
881#[serde(rename_all = "lowercase")]
882#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
883#[cfg_attr(
884 feature = "python",
885 pyo3::pyclass(
886 eq,
887 eq_int,
888 module = "nautilus_trader.adapters.bitmex",
889 from_py_object,
890 rename_all = "SCREAMING_SNAKE_CASE",
891 )
892)]
893#[cfg_attr(
894 feature = "python",
895 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bitmex")
896)]
897pub enum BitmexEnvironment {
898 #[default]
900 Mainnet,
901 Testnet,
903}
904
905#[cfg(test)]
906mod tests {
907 use rstest::rstest;
908
909 use super::*;
910
911 #[rstest]
912 fn test_bitmex_side_deserialization() {
913 assert_eq!(
915 serde_json::from_str::<BitmexSide>(r#""Buy""#).unwrap(),
916 BitmexSide::Buy
917 );
918 assert_eq!(
919 serde_json::from_str::<BitmexSide>(r#""BUY""#).unwrap(),
920 BitmexSide::Buy
921 );
922 assert_eq!(
923 serde_json::from_str::<BitmexSide>(r#""buy""#).unwrap(),
924 BitmexSide::Buy
925 );
926 assert_eq!(
927 serde_json::from_str::<BitmexSide>(r#""Sell""#).unwrap(),
928 BitmexSide::Sell
929 );
930 assert_eq!(
931 serde_json::from_str::<BitmexSide>(r#""SELL""#).unwrap(),
932 BitmexSide::Sell
933 );
934 assert_eq!(
935 serde_json::from_str::<BitmexSide>(r#""sell""#).unwrap(),
936 BitmexSide::Sell
937 );
938 }
939
940 #[rstest]
941 fn test_bitmex_order_type_deserialization() {
942 assert_eq!(
943 serde_json::from_str::<BitmexOrderType>(r#""Market""#).unwrap(),
944 BitmexOrderType::Market
945 );
946 assert_eq!(
947 serde_json::from_str::<BitmexOrderType>(r#""Limit""#).unwrap(),
948 BitmexOrderType::Limit
949 );
950 assert_eq!(
951 serde_json::from_str::<BitmexOrderType>(r#""Stop""#).unwrap(),
952 BitmexOrderType::Stop
953 );
954 assert_eq!(
955 serde_json::from_str::<BitmexOrderType>(r#""StopLimit""#).unwrap(),
956 BitmexOrderType::StopLimit
957 );
958 assert_eq!(
959 serde_json::from_str::<BitmexOrderType>(r#""MarketIfTouched""#).unwrap(),
960 BitmexOrderType::MarketIfTouched
961 );
962 assert_eq!(
963 serde_json::from_str::<BitmexOrderType>(r#""LimitIfTouched""#).unwrap(),
964 BitmexOrderType::LimitIfTouched
965 );
966 assert_eq!(
967 serde_json::from_str::<BitmexOrderType>(r#""Pegged""#).unwrap(),
968 BitmexOrderType::Pegged
969 );
970 }
971
972 #[rstest]
973 fn test_instrument_type_serialization() {
974 assert_eq!(
976 serde_json::to_string(&BitmexInstrumentType::PerpetualContract).unwrap(),
977 r#""FFWCSX""#
978 );
979 assert_eq!(
980 serde_json::to_string(&BitmexInstrumentType::PerpetualContractFx).unwrap(),
981 r#""FFWCSF""#
982 );
983 assert_eq!(
984 serde_json::to_string(&BitmexInstrumentType::TradFiPerpetual).unwrap(),
985 r#""FFSCSX""#
986 );
987 assert_eq!(
988 serde_json::to_string(&BitmexInstrumentType::Spot).unwrap(),
989 r#""IFXXXP""#
990 );
991 assert_eq!(
992 serde_json::to_string(&BitmexInstrumentType::Futures).unwrap(),
993 r#""FFCCSX""#
994 );
995 assert_eq!(
996 serde_json::to_string(&BitmexInstrumentType::PredictionMarket).unwrap(),
997 r#""FFICSX""#
998 );
999 assert_eq!(
1000 serde_json::to_string(&BitmexInstrumentType::CallOption).unwrap(),
1001 r#""OCECCS""#
1002 );
1003 assert_eq!(
1004 serde_json::to_string(&BitmexInstrumentType::PutOption).unwrap(),
1005 r#""OPECCS""#
1006 );
1007 assert_eq!(
1008 serde_json::to_string(&BitmexInstrumentType::SwapRate).unwrap(),
1009 r#""SRMCSX""#
1010 );
1011
1012 assert_eq!(
1014 serde_json::to_string(&BitmexInstrumentType::LegacyFutures).unwrap(),
1015 r#""FXXXS""#
1016 );
1017 assert_eq!(
1018 serde_json::to_string(&BitmexInstrumentType::LegacyFuturesN).unwrap(),
1019 r#""FXXXN""#
1020 );
1021 assert_eq!(
1022 serde_json::to_string(&BitmexInstrumentType::FuturesSpreads).unwrap(),
1023 r#""FMXXS""#
1024 );
1025 assert_eq!(
1026 serde_json::to_string(&BitmexInstrumentType::FuturesSpread).unwrap(),
1027 r#""FFMCSX""#
1028 );
1029 assert_eq!(
1030 serde_json::to_string(&BitmexInstrumentType::ReferenceBasket).unwrap(),
1031 r#""RCSXXX""#
1032 );
1033
1034 assert_eq!(
1036 serde_json::to_string(&BitmexInstrumentType::BasketIndex).unwrap(),
1037 r#""MRBXXX""#
1038 );
1039 assert_eq!(
1040 serde_json::to_string(&BitmexInstrumentType::CryptoIndex).unwrap(),
1041 r#""MRCXXX""#
1042 );
1043 assert_eq!(
1044 serde_json::to_string(&BitmexInstrumentType::FxIndex).unwrap(),
1045 r#""MRFXXX""#
1046 );
1047 assert_eq!(
1048 serde_json::to_string(&BitmexInstrumentType::LendingIndex).unwrap(),
1049 r#""MRRXXX""#
1050 );
1051 assert_eq!(
1052 serde_json::to_string(&BitmexInstrumentType::VolatilityIndex).unwrap(),
1053 r#""MRIXXX""#
1054 );
1055 assert_eq!(
1056 serde_json::to_string(&BitmexInstrumentType::StockIndex).unwrap(),
1057 r#""MRSXXX""#
1058 );
1059 assert_eq!(
1060 serde_json::to_string(&BitmexInstrumentType::YieldIndex).unwrap(),
1061 r#""MRVDXX""#
1062 );
1063 }
1064
1065 #[rstest]
1066 fn test_instrument_type_deserialization() {
1067 assert_eq!(
1069 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSX""#).unwrap(),
1070 BitmexInstrumentType::PerpetualContract
1071 );
1072 assert_eq!(
1073 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSF""#).unwrap(),
1074 BitmexInstrumentType::PerpetualContractFx
1075 );
1076 assert_eq!(
1077 serde_json::from_str::<BitmexInstrumentType>(r#""FFSCSX""#).unwrap(),
1078 BitmexInstrumentType::TradFiPerpetual
1079 );
1080 assert_eq!(
1081 serde_json::from_str::<BitmexInstrumentType>(r#""IFXXXP""#).unwrap(),
1082 BitmexInstrumentType::Spot
1083 );
1084 assert_eq!(
1085 serde_json::from_str::<BitmexInstrumentType>(r#""FFCCSX""#).unwrap(),
1086 BitmexInstrumentType::Futures
1087 );
1088 assert_eq!(
1089 serde_json::from_str::<BitmexInstrumentType>(r#""FFICSX""#).unwrap(),
1090 BitmexInstrumentType::PredictionMarket
1091 );
1092 assert_eq!(
1093 serde_json::from_str::<BitmexInstrumentType>(r#""OCECCS""#).unwrap(),
1094 BitmexInstrumentType::CallOption
1095 );
1096 assert_eq!(
1097 serde_json::from_str::<BitmexInstrumentType>(r#""OPECCS""#).unwrap(),
1098 BitmexInstrumentType::PutOption
1099 );
1100 assert_eq!(
1101 serde_json::from_str::<BitmexInstrumentType>(r#""SRMCSX""#).unwrap(),
1102 BitmexInstrumentType::SwapRate
1103 );
1104
1105 assert_eq!(
1107 serde_json::from_str::<BitmexInstrumentType>(r#""FXXXS""#).unwrap(),
1108 BitmexInstrumentType::LegacyFutures
1109 );
1110 assert_eq!(
1111 serde_json::from_str::<BitmexInstrumentType>(r#""FXXXN""#).unwrap(),
1112 BitmexInstrumentType::LegacyFuturesN
1113 );
1114 assert_eq!(
1115 serde_json::from_str::<BitmexInstrumentType>(r#""FMXXS""#).unwrap(),
1116 BitmexInstrumentType::FuturesSpreads
1117 );
1118 assert_eq!(
1119 serde_json::from_str::<BitmexInstrumentType>(r#""FFMCSX""#).unwrap(),
1120 BitmexInstrumentType::FuturesSpread
1121 );
1122 assert_eq!(
1123 serde_json::from_str::<BitmexInstrumentType>(r#""RCSXXX""#).unwrap(),
1124 BitmexInstrumentType::ReferenceBasket
1125 );
1126
1127 assert_eq!(
1129 serde_json::from_str::<BitmexInstrumentType>(r#""MRBXXX""#).unwrap(),
1130 BitmexInstrumentType::BasketIndex
1131 );
1132 assert_eq!(
1133 serde_json::from_str::<BitmexInstrumentType>(r#""MRCXXX""#).unwrap(),
1134 BitmexInstrumentType::CryptoIndex
1135 );
1136 assert_eq!(
1137 serde_json::from_str::<BitmexInstrumentType>(r#""MRFXXX""#).unwrap(),
1138 BitmexInstrumentType::FxIndex
1139 );
1140 assert_eq!(
1141 serde_json::from_str::<BitmexInstrumentType>(r#""MRRXXX""#).unwrap(),
1142 BitmexInstrumentType::LendingIndex
1143 );
1144 assert_eq!(
1145 serde_json::from_str::<BitmexInstrumentType>(r#""MRIXXX""#).unwrap(),
1146 BitmexInstrumentType::VolatilityIndex
1147 );
1148 assert_eq!(
1149 serde_json::from_str::<BitmexInstrumentType>(r#""MRSXXX""#).unwrap(),
1150 BitmexInstrumentType::StockIndex
1151 );
1152 assert_eq!(
1153 serde_json::from_str::<BitmexInstrumentType>(r#""MRVDXX""#).unwrap(),
1154 BitmexInstrumentType::YieldIndex
1155 );
1156
1157 assert_eq!(
1158 serde_json::from_str::<BitmexInstrumentType>(r#""INVALID""#).unwrap(),
1159 BitmexInstrumentType::Other
1160 );
1161 }
1162
1163 #[rstest]
1164 fn test_subscription_strings() {
1165 assert_eq!(BitmexProductType::All.to_subscription(), "instrument");
1166 assert_eq!(
1167 BitmexProductType::Specific("XBTUSD".to_string()).to_subscription(),
1168 "instrument:XBTUSD"
1169 );
1170 assert_eq!(BitmexProductType::Contracts.to_subscription(), "CONTRACTS");
1171 assert_eq!(BitmexProductType::Indices.to_subscription(), "INDICES");
1172 assert_eq!(
1173 BitmexProductType::Derivatives.to_subscription(),
1174 "DERIVATIVES"
1175 );
1176 assert_eq!(BitmexProductType::Spot.to_subscription(), "SPOT");
1177 }
1178
1179 #[rstest]
1180 fn test_serialization() {
1181 assert_eq!(
1183 serde_json::to_string(&BitmexProductType::All).unwrap(),
1184 r#""instrument""#
1185 );
1186 assert_eq!(
1187 serde_json::to_string(&BitmexProductType::Specific("XBTUSD".to_string())).unwrap(),
1188 r#""XBTUSD""#
1189 );
1190 assert_eq!(
1191 serde_json::to_string(&BitmexProductType::Contracts).unwrap(),
1192 r#""CONTRACTS""#
1193 );
1194 }
1195
1196 #[rstest]
1197 fn test_deserialization() {
1198 assert_eq!(
1199 serde_json::from_str::<BitmexProductType>(r#""instrument""#).unwrap(),
1200 BitmexProductType::All
1201 );
1202 assert_eq!(
1203 serde_json::from_str::<BitmexProductType>(r#""instrument:XBTUSD""#).unwrap(),
1204 BitmexProductType::Specific("XBTUSD".to_string())
1205 );
1206 assert_eq!(
1207 serde_json::from_str::<BitmexProductType>(r#""CONTRACTS""#).unwrap(),
1208 BitmexProductType::Contracts
1209 );
1210 }
1211
1212 #[rstest]
1213 fn test_error_cases() {
1214 assert!(serde_json::from_str::<BitmexProductType>(r#""invalid_type""#).is_err());
1215 assert!(serde_json::from_str::<BitmexProductType>("123").is_err());
1216 assert!(serde_json::from_str::<BitmexProductType>("{}").is_err());
1217 }
1218
1219 #[rstest]
1220 fn test_order_side_from_specified() {
1221 assert_eq!(BitmexSide::from(OrderSide::Buy), BitmexSide::Buy);
1222 assert_eq!(BitmexSide::from(OrderSide::Sell), BitmexSide::Sell);
1223 }
1224
1225 #[rstest]
1226 fn test_order_type_try_from() {
1227 assert_eq!(
1229 BitmexOrderType::try_from(OrderType::Market).unwrap(),
1230 BitmexOrderType::Market
1231 );
1232 assert_eq!(
1233 BitmexOrderType::try_from(OrderType::Limit).unwrap(),
1234 BitmexOrderType::Limit
1235 );
1236
1237 let result = BitmexOrderType::try_from(OrderType::MarketToLimit);
1239 assert!(result.is_err());
1240 assert!(result.unwrap_err().to_string().contains("not supported"));
1241 }
1242
1243 #[rstest]
1244 fn test_time_in_force_conversions() {
1245 assert_eq!(
1247 TimeInForce::try_from(BitmexTimeInForce::Day).unwrap(),
1248 TimeInForce::Day
1249 );
1250 assert_eq!(
1251 TimeInForce::try_from(BitmexTimeInForce::GoodTillCancel).unwrap(),
1252 TimeInForce::Gtc
1253 );
1254 assert_eq!(
1255 TimeInForce::try_from(BitmexTimeInForce::ImmediateOrCancel).unwrap(),
1256 TimeInForce::Ioc
1257 );
1258
1259 let result = TimeInForce::try_from(BitmexTimeInForce::GoodTillCrossing);
1261 assert!(result.is_err());
1262 assert!(result.unwrap_err().to_string().contains("Unsupported"));
1263
1264 assert_eq!(
1266 BitmexTimeInForce::try_from(TimeInForce::Day).unwrap(),
1267 BitmexTimeInForce::Day
1268 );
1269 assert_eq!(
1270 BitmexTimeInForce::try_from(TimeInForce::Gtc).unwrap(),
1271 BitmexTimeInForce::GoodTillCancel
1272 );
1273 assert_eq!(
1274 BitmexTimeInForce::try_from(TimeInForce::Fok).unwrap(),
1275 BitmexTimeInForce::FillOrKill
1276 );
1277 }
1278
1279 #[rstest]
1280 fn test_helper_methods() {
1281 let result = BitmexOrderType::try_from_order_type(OrderType::Limit);
1283 assert!(result.is_ok());
1284 assert_eq!(result.unwrap(), BitmexOrderType::Limit);
1285
1286 let result = BitmexOrderType::try_from_order_type(OrderType::MarketToLimit);
1287 assert!(result.is_err());
1288
1289 let result = BitmexTimeInForce::try_from_time_in_force(TimeInForce::Ioc);
1291 assert!(result.is_ok());
1292 assert_eq!(result.unwrap(), BitmexTimeInForce::ImmediateOrCancel);
1293
1294 let result = BitmexTimeInForce::try_from_time_in_force(TimeInForce::Gtd);
1295 assert!(result.is_err());
1296 assert!(
1297 result
1298 .unwrap_err()
1299 .to_string()
1300 .contains("GTD time in force is not supported")
1301 );
1302 }
1303
1304 #[rstest]
1305 #[case(BitmexInstrumentState::Open, MarketStatusAction::Trading)]
1306 #[case(BitmexInstrumentState::Closed, MarketStatusAction::Close)]
1307 #[case(BitmexInstrumentState::Settled, MarketStatusAction::Close)]
1308 #[case(
1309 BitmexInstrumentState::Unlisted,
1310 MarketStatusAction::NotAvailableForTrading
1311 )]
1312 #[case(
1313 BitmexInstrumentState::Delisted,
1314 MarketStatusAction::NotAvailableForTrading
1315 )]
1316 #[case(
1317 BitmexInstrumentState::Unknown,
1318 MarketStatusAction::NotAvailableForTrading
1319 )]
1320 fn test_bitmex_instrument_state_to_market_status_action(
1321 #[case] state: BitmexInstrumentState,
1322 #[case] expected: MarketStatusAction,
1323 ) {
1324 assert_eq!(MarketStatusAction::from(&state), expected);
1325 }
1326
1327 #[rstest]
1328 fn test_bitmex_instrument_state_unknown_deserializes_from_unrecognized_string() {
1329 let state: BitmexInstrumentState = serde_json::from_str(r#""SomeFutureState""#).unwrap();
1330 assert_eq!(state, BitmexInstrumentState::Unknown);
1331 }
1332}