1use std::borrow::Cow;
19
20use nautilus_model::enums::{
21 ContingencyType, LiquiditySide, MarketStatusAction, OrderSide, OrderSideSpecified, OrderStatus,
22 OrderType, 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.core.nautilus_pyo3.bitmex",
46 eq,
47 eq_int,
48 from_py_object,
49 rename_all = "SCREAMING_SNAKE_CASE",
50 )
51)]
52#[cfg_attr(
53 feature = "python",
54 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.bitmex")
55)]
56pub enum BitmexSymbolStatus {
57 Open,
59 Closed,
61 Unlisted,
63}
64
65#[derive(
67 Copy,
68 Clone,
69 Debug,
70 Display,
71 PartialEq,
72 Eq,
73 AsRefStr,
74 EnumIter,
75 EnumString,
76 Serialize,
77 Deserialize,
78)]
79pub enum BitmexSide {
80 #[serde(rename = "Buy", alias = "BUY", alias = "buy")]
82 Buy,
83 #[serde(rename = "Sell", alias = "SELL", alias = "sell")]
85 Sell,
86}
87
88impl From<OrderSideSpecified> for BitmexSide {
89 fn from(value: OrderSideSpecified) -> Self {
90 match value {
91 OrderSideSpecified::Buy => Self::Buy,
92 OrderSideSpecified::Sell => Self::Sell,
93 }
94 }
95}
96
97impl From<BitmexSide> for OrderSide {
98 fn from(side: BitmexSide) -> Self {
99 match side {
100 BitmexSide::Buy => Self::Buy,
101 BitmexSide::Sell => Self::Sell,
102 }
103 }
104}
105
106#[derive(
108 Copy,
109 Clone,
110 Debug,
111 Display,
112 PartialEq,
113 Eq,
114 AsRefStr,
115 EnumIter,
116 EnumString,
117 Serialize,
118 Deserialize,
119)]
120#[cfg_attr(
121 feature = "python",
122 pyo3::pyclass(
123 module = "nautilus_trader.core.nautilus_pyo3.bitmex",
124 eq,
125 eq_int,
126 from_py_object
127 )
128)]
129#[cfg_attr(
130 feature = "python",
131 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.bitmex")
132)]
133pub enum BitmexPositionSide {
134 #[serde(rename = "LONG", alias = "Long", alias = "long")]
136 Long,
137 #[serde(rename = "SHORT", alias = "Short", alias = "short")]
139 Short,
140 #[serde(rename = "FLAT", alias = "Flat", alias = "flat")]
142 Flat,
143}
144
145impl From<BitmexPositionSide> for PositionSide {
146 fn from(side: BitmexPositionSide) -> Self {
147 match side {
148 BitmexPositionSide::Long => Self::Long,
149 BitmexPositionSide::Short => Self::Short,
150 BitmexPositionSide::Flat => Self::Flat,
151 }
152 }
153}
154
155impl From<PositionSide> for BitmexPositionSide {
156 fn from(side: PositionSide) -> Self {
157 match side {
158 PositionSide::Long => Self::Long,
159 PositionSide::Short => Self::Short,
160 PositionSide::Flat | PositionSide::NoPositionSide => Self::Flat,
161 }
162 }
163}
164
165#[derive(
167 Copy,
168 Clone,
169 Debug,
170 Display,
171 PartialEq,
172 Eq,
173 AsRefStr,
174 EnumIter,
175 EnumString,
176 Serialize,
177 Deserialize,
178)]
179pub enum BitmexOrderType {
180 Market,
182 Limit,
184 Stop,
186 StopLimit,
188 MarketIfTouched,
190 LimitIfTouched,
192 Pegged,
194}
195
196impl TryFrom<OrderType> for BitmexOrderType {
197 type Error = anyhow::Error;
198
199 fn try_from(value: OrderType) -> Result<Self, Self::Error> {
200 match value {
201 OrderType::Market => Ok(Self::Market),
202 OrderType::Limit => Ok(Self::Limit),
203 OrderType::StopMarket => Ok(Self::Stop),
204 OrderType::StopLimit => Ok(Self::StopLimit),
205 OrderType::MarketIfTouched => Ok(Self::MarketIfTouched),
206 OrderType::LimitIfTouched => Ok(Self::LimitIfTouched),
207 OrderType::TrailingStopMarket => Ok(Self::Pegged),
208 OrderType::TrailingStopLimit => Ok(Self::Pegged),
209 OrderType::MarketToLimit => {
210 anyhow::bail!("MarketToLimit order type is not supported by BitMEX")
211 }
212 }
213 }
214}
215
216impl BitmexOrderType {
217 pub fn try_from_order_type(value: OrderType) -> anyhow::Result<Self> {
223 Self::try_from(value)
224 }
225}
226
227impl From<BitmexOrderType> for OrderType {
228 fn from(value: BitmexOrderType) -> Self {
229 match value {
230 BitmexOrderType::Market => Self::Market,
231 BitmexOrderType::Limit => Self::Limit,
232 BitmexOrderType::Stop => Self::StopMarket,
233 BitmexOrderType::StopLimit => Self::StopLimit,
234 BitmexOrderType::MarketIfTouched => Self::MarketIfTouched,
235 BitmexOrderType::LimitIfTouched => Self::LimitIfTouched,
236 BitmexOrderType::Pegged => Self::Limit,
237 }
238 }
239}
240
241#[derive(
243 Copy,
244 Clone,
245 Debug,
246 Display,
247 PartialEq,
248 Eq,
249 AsRefStr,
250 EnumIter,
251 EnumString,
252 Serialize,
253 Deserialize,
254)]
255pub enum BitmexOrderStatus {
256 New,
258 PendingNew,
260 PartiallyFilled,
262 Filled,
264 PendingReplace,
266 PendingCancel,
268 Canceled,
270 Rejected,
272 Expired,
274}
275
276impl BitmexOrderStatus {
277 pub fn is_terminal(self) -> bool {
279 matches!(
280 self,
281 Self::Filled | Self::Canceled | Self::Rejected | Self::Expired
282 )
283 }
284}
285
286impl From<BitmexOrderStatus> for OrderStatus {
287 fn from(value: BitmexOrderStatus) -> Self {
288 match value {
289 BitmexOrderStatus::New => Self::Accepted,
290 BitmexOrderStatus::PendingNew => Self::Submitted,
291 BitmexOrderStatus::PartiallyFilled => Self::PartiallyFilled,
292 BitmexOrderStatus::Filled => Self::Filled,
293 BitmexOrderStatus::PendingReplace => Self::PendingUpdate,
294 BitmexOrderStatus::PendingCancel => Self::PendingCancel,
295 BitmexOrderStatus::Canceled => Self::Canceled,
296 BitmexOrderStatus::Rejected => Self::Rejected,
297 BitmexOrderStatus::Expired => Self::Expired,
298 }
299 }
300}
301
302#[derive(
304 Copy,
305 Clone,
306 Debug,
307 Display,
308 PartialEq,
309 Eq,
310 AsRefStr,
311 EnumIter,
312 EnumString,
313 Serialize,
314 Deserialize,
315)]
316pub enum BitmexTimeInForce {
317 Day,
318 GoodTillCancel,
319 AtTheOpening,
320 ImmediateOrCancel,
321 FillOrKill,
322 GoodTillCrossing,
323 GoodTillDate,
324 AtTheClose,
325 GoodThroughCrossing,
326 AtCrossing,
327}
328
329impl TryFrom<BitmexTimeInForce> for TimeInForce {
330 type Error = anyhow::Error;
331
332 fn try_from(value: BitmexTimeInForce) -> Result<Self, Self::Error> {
333 match value {
334 BitmexTimeInForce::Day => Ok(Self::Day),
335 BitmexTimeInForce::GoodTillCancel => Ok(Self::Gtc),
336 BitmexTimeInForce::GoodTillDate => Ok(Self::Gtd),
337 BitmexTimeInForce::ImmediateOrCancel => Ok(Self::Ioc),
338 BitmexTimeInForce::FillOrKill => Ok(Self::Fok),
339 BitmexTimeInForce::AtTheOpening => Ok(Self::AtTheOpen),
340 BitmexTimeInForce::AtTheClose => Ok(Self::AtTheClose),
341 _ => anyhow::bail!("Unsupported BitmexTimeInForce: {value}"),
342 }
343 }
344}
345
346impl TryFrom<TimeInForce> for BitmexTimeInForce {
347 type Error = anyhow::Error;
348
349 fn try_from(value: TimeInForce) -> Result<Self, Self::Error> {
350 match value {
351 TimeInForce::Day => Ok(Self::Day),
352 TimeInForce::Gtc => Ok(Self::GoodTillCancel),
353 TimeInForce::Gtd => Ok(Self::GoodTillDate),
354 TimeInForce::Ioc => Ok(Self::ImmediateOrCancel),
355 TimeInForce::Fok => Ok(Self::FillOrKill),
356 TimeInForce::AtTheOpen => Ok(Self::AtTheOpening),
357 TimeInForce::AtTheClose => Ok(Self::AtTheClose),
358 }
359 }
360}
361
362impl BitmexTimeInForce {
363 pub fn try_from_time_in_force(value: TimeInForce) -> anyhow::Result<Self> {
369 Self::try_from(value)
370 }
371}
372
373#[derive(
375 Copy,
376 Clone,
377 Debug,
378 Display,
379 PartialEq,
380 Eq,
381 AsRefStr,
382 EnumIter,
383 EnumString,
384 Serialize,
385 Deserialize,
386)]
387pub enum BitmexContingencyType {
388 OneCancelsTheOther,
389 OneTriggersTheOther,
390 OneUpdatesTheOtherAbsolute,
391 OneUpdatesTheOtherProportional,
392 #[serde(rename = "")]
393 Unknown, }
395
396impl From<BitmexContingencyType> for ContingencyType {
397 fn from(value: BitmexContingencyType) -> Self {
398 match value {
399 BitmexContingencyType::OneCancelsTheOther => Self::Oco,
400 BitmexContingencyType::OneTriggersTheOther => Self::Oto,
401 BitmexContingencyType::OneUpdatesTheOtherProportional => Self::Ouo,
402 BitmexContingencyType::OneUpdatesTheOtherAbsolute => Self::Ouo,
403 BitmexContingencyType::Unknown => Self::NoContingency,
404 }
405 }
406}
407
408impl TryFrom<ContingencyType> for BitmexContingencyType {
409 type Error = anyhow::Error;
410
411 fn try_from(value: ContingencyType) -> Result<Self, Self::Error> {
412 match value {
413 ContingencyType::NoContingency => Ok(Self::Unknown),
414 ContingencyType::Oco => Ok(Self::OneCancelsTheOther),
415 ContingencyType::Oto => Ok(Self::OneTriggersTheOther),
416 ContingencyType::Ouo => anyhow::bail!("OUO contingency type not supported by BitMEX"),
417 }
418 }
419}
420
421#[derive(
423 Copy,
424 Clone,
425 Debug,
426 Display,
427 PartialEq,
428 Eq,
429 AsRefStr,
430 EnumIter,
431 EnumString,
432 Serialize,
433 Deserialize,
434)]
435pub enum BitmexPegPriceType {
436 LastPeg,
437 OpeningPeg,
438 MidPricePeg,
439 MarketPeg,
440 PrimaryPeg,
441 PegToVWAP,
442 TrailingStopPeg,
443 PegToLimitPrice,
444 ShortSaleMinPricePeg,
445 #[serde(rename = "")]
446 Unknown, }
448
449#[derive(
451 Copy,
452 Clone,
453 Debug,
454 Display,
455 PartialEq,
456 Eq,
457 AsRefStr,
458 EnumIter,
459 EnumString,
460 Serialize,
461 Deserialize,
462)]
463pub enum BitmexExecInstruction {
464 ParticipateDoNotInitiate,
465 AllOrNone,
466 MarkPrice,
467 IndexPrice,
468 LastPrice,
469 Close,
470 ReduceOnly,
471 Fixed,
472 #[serde(rename = "")]
473 Unknown, }
475
476impl BitmexExecInstruction {
477 pub fn join(instructions: &[Self]) -> String {
479 instructions
480 .iter()
481 .map(ToString::to_string)
482 .collect::<Vec<_>>()
483 .join(",")
484 }
485}
486
487#[derive(Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize)]
489pub enum BitmexExecType {
490 New,
492 Trade,
494 Canceled,
496 CancelReject,
498 Replaced,
500 Rejected,
502 AmendReject,
504 Funding,
506 Settlement,
508 Suspended,
510 Released,
512 Insurance,
514 Rebalance,
516 Liquidation,
518 Bankruptcy,
520 TrialFill,
522 TriggeredOrActivatedBySystem,
524 #[strum(disabled)]
526 Unknown(String),
527}
528
529impl<'de> Deserialize<'de> for BitmexExecType {
530 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
531 where
532 D: Deserializer<'de>,
533 {
534 let s = String::deserialize(deserializer)?;
535
536 match s.as_str() {
537 "New" => Ok(Self::New),
538 "Trade" => Ok(Self::Trade),
539 "Canceled" => Ok(Self::Canceled),
540 "CancelReject" => Ok(Self::CancelReject),
541 "Replaced" => Ok(Self::Replaced),
542 "Rejected" => Ok(Self::Rejected),
543 "AmendReject" => Ok(Self::AmendReject),
544 "Funding" => Ok(Self::Funding),
545 "Settlement" => Ok(Self::Settlement),
546 "Suspended" => Ok(Self::Suspended),
547 "Released" => Ok(Self::Released),
548 "Insurance" => Ok(Self::Insurance),
549 "Rebalance" => Ok(Self::Rebalance),
550 "Liquidation" => Ok(Self::Liquidation),
551 "Bankruptcy" => Ok(Self::Bankruptcy),
552 "TrialFill" => Ok(Self::TrialFill),
553 "TriggeredOrActivatedBySystem" => Ok(Self::TriggeredOrActivatedBySystem),
554 other => Ok(Self::Unknown(other.to_string())),
555 }
556 }
557}
558
559#[derive(
561 Copy,
562 Clone,
563 Debug,
564 Display,
565 PartialEq,
566 Eq,
567 AsRefStr,
568 EnumIter,
569 EnumString,
570 Serialize,
571 Deserialize,
572)]
573pub enum BitmexLiquidityIndicator {
574 #[serde(rename = "Added")]
577 #[serde(alias = "AddedLiquidity")]
578 Maker,
579 #[serde(rename = "Removed")]
582 #[serde(alias = "RemovedLiquidity")]
583 Taker,
584}
585
586impl From<BitmexLiquidityIndicator> for LiquiditySide {
587 fn from(value: BitmexLiquidityIndicator) -> Self {
588 match value {
589 BitmexLiquidityIndicator::Maker => Self::Maker,
590 BitmexLiquidityIndicator::Taker => Self::Taker,
591 }
592 }
593}
594
595#[derive(
602 Copy,
603 Clone,
604 Debug,
605 Display,
606 PartialEq,
607 Eq,
608 AsRefStr,
609 EnumIter,
610 EnumString,
611 Serialize,
612 Deserialize,
613)]
614#[serde(rename_all = "UPPERCASE")]
615pub enum BitmexInstrumentType {
616 #[serde(rename = "FXXXS")]
618 LegacyFutures,
619
620 #[serde(rename = "FXXXN")]
622 LegacyFuturesN,
623
624 #[serde(rename = "FMXXS")]
626 FuturesSpreads,
627
628 #[serde(rename = "FFICSX")]
631 PredictionMarket,
632
633 #[serde(rename = "FFSCSX")]
636 StockPerpetual,
637
638 #[serde(rename = "FFWCSX")]
640 PerpetualContract,
641
642 #[serde(rename = "FFWCSF")]
644 PerpetualContractFx,
645
646 #[serde(rename = "FFCCSX")]
648 Futures,
649
650 #[serde(rename = "IFXXXP")]
652 Spot,
653
654 #[serde(rename = "OCECCS")]
656 CallOption,
657
658 #[serde(rename = "OPECCS")]
660 PutOption,
661
662 #[serde(rename = "SRMCSX")]
664 SwapRate,
665
666 #[serde(rename = "RCSXXX")]
668 ReferenceBasket,
669
670 #[serde(rename = "MRBXXX")]
672 BasketIndex,
673
674 #[serde(rename = "MRCXXX")]
676 CryptoIndex,
677
678 #[serde(rename = "MRFXXX")]
680 FxIndex,
681
682 #[serde(rename = "MRRXXX")]
684 LendingIndex,
685
686 #[serde(rename = "MRIXXX")]
688 VolatilityIndex,
689
690 #[serde(rename = "MRSXXX")]
692 StockIndex,
693
694 #[serde(rename = "MRVDXX")]
696 YieldIndex,
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}
818
819impl From<&BitmexInstrumentState> for MarketStatusAction {
820 fn from(state: &BitmexInstrumentState) -> Self {
821 match state {
822 BitmexInstrumentState::Open => Self::Trading,
823 BitmexInstrumentState::Closed => Self::Close,
824 BitmexInstrumentState::Settled => Self::Close,
825 BitmexInstrumentState::Unlisted => Self::NotAvailableForTrading,
826 BitmexInstrumentState::Delisted => Self::NotAvailableForTrading,
827 }
828 }
829}
830
831#[derive(
833 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
834)]
835pub enum BitmexFairMethod {
836 FundingRate,
838 ImpactMidPrice,
840 LastPrice,
842}
843
844#[derive(
846 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
847)]
848pub enum BitmexMarkMethod {
849 FairPrice,
851 FairPriceStox,
853 LastPrice,
855 LastPricePreLaunch,
857 CompositeIndex,
859}
860
861#[derive(
863 Copy,
864 Clone,
865 Debug,
866 Default,
867 Display,
868 PartialEq,
869 Eq,
870 Hash,
871 AsRefStr,
872 EnumIter,
873 EnumString,
874 Serialize,
875 Deserialize,
876)]
877#[serde(rename_all = "lowercase")]
878#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
879#[cfg_attr(
880 feature = "python",
881 pyo3::pyclass(
882 eq,
883 eq_int,
884 module = "nautilus_trader.core.nautilus_pyo3.bitmex",
885 from_py_object,
886 rename_all = "SCREAMING_SNAKE_CASE",
887 )
888)]
889#[cfg_attr(
890 feature = "python",
891 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.bitmex")
892)]
893pub enum BitmexEnvironment {
894 #[default]
896 Mainnet,
897 Testnet,
899}
900
901#[cfg(test)]
902mod tests {
903 use rstest::rstest;
904
905 use super::*;
906
907 #[rstest]
908 fn test_bitmex_side_deserialization() {
909 assert_eq!(
911 serde_json::from_str::<BitmexSide>(r#""Buy""#).unwrap(),
912 BitmexSide::Buy
913 );
914 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#""Sell""#).unwrap(),
924 BitmexSide::Sell
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 }
935
936 #[rstest]
937 fn test_bitmex_order_type_deserialization() {
938 assert_eq!(
939 serde_json::from_str::<BitmexOrderType>(r#""Market""#).unwrap(),
940 BitmexOrderType::Market
941 );
942 assert_eq!(
943 serde_json::from_str::<BitmexOrderType>(r#""Limit""#).unwrap(),
944 BitmexOrderType::Limit
945 );
946 assert_eq!(
947 serde_json::from_str::<BitmexOrderType>(r#""Stop""#).unwrap(),
948 BitmexOrderType::Stop
949 );
950 assert_eq!(
951 serde_json::from_str::<BitmexOrderType>(r#""StopLimit""#).unwrap(),
952 BitmexOrderType::StopLimit
953 );
954 assert_eq!(
955 serde_json::from_str::<BitmexOrderType>(r#""MarketIfTouched""#).unwrap(),
956 BitmexOrderType::MarketIfTouched
957 );
958 assert_eq!(
959 serde_json::from_str::<BitmexOrderType>(r#""LimitIfTouched""#).unwrap(),
960 BitmexOrderType::LimitIfTouched
961 );
962 assert_eq!(
963 serde_json::from_str::<BitmexOrderType>(r#""Pegged""#).unwrap(),
964 BitmexOrderType::Pegged
965 );
966 }
967
968 #[rstest]
969 fn test_instrument_type_serialization() {
970 assert_eq!(
972 serde_json::to_string(&BitmexInstrumentType::PerpetualContract).unwrap(),
973 r#""FFWCSX""#
974 );
975 assert_eq!(
976 serde_json::to_string(&BitmexInstrumentType::PerpetualContractFx).unwrap(),
977 r#""FFWCSF""#
978 );
979 assert_eq!(
980 serde_json::to_string(&BitmexInstrumentType::StockPerpetual).unwrap(),
981 r#""FFSCSX""#
982 );
983 assert_eq!(
984 serde_json::to_string(&BitmexInstrumentType::Spot).unwrap(),
985 r#""IFXXXP""#
986 );
987 assert_eq!(
988 serde_json::to_string(&BitmexInstrumentType::Futures).unwrap(),
989 r#""FFCCSX""#
990 );
991 assert_eq!(
992 serde_json::to_string(&BitmexInstrumentType::PredictionMarket).unwrap(),
993 r#""FFICSX""#
994 );
995 assert_eq!(
996 serde_json::to_string(&BitmexInstrumentType::CallOption).unwrap(),
997 r#""OCECCS""#
998 );
999 assert_eq!(
1000 serde_json::to_string(&BitmexInstrumentType::PutOption).unwrap(),
1001 r#""OPECCS""#
1002 );
1003 assert_eq!(
1004 serde_json::to_string(&BitmexInstrumentType::SwapRate).unwrap(),
1005 r#""SRMCSX""#
1006 );
1007
1008 assert_eq!(
1010 serde_json::to_string(&BitmexInstrumentType::LegacyFutures).unwrap(),
1011 r#""FXXXS""#
1012 );
1013 assert_eq!(
1014 serde_json::to_string(&BitmexInstrumentType::LegacyFuturesN).unwrap(),
1015 r#""FXXXN""#
1016 );
1017 assert_eq!(
1018 serde_json::to_string(&BitmexInstrumentType::FuturesSpreads).unwrap(),
1019 r#""FMXXS""#
1020 );
1021 assert_eq!(
1022 serde_json::to_string(&BitmexInstrumentType::ReferenceBasket).unwrap(),
1023 r#""RCSXXX""#
1024 );
1025
1026 assert_eq!(
1028 serde_json::to_string(&BitmexInstrumentType::BasketIndex).unwrap(),
1029 r#""MRBXXX""#
1030 );
1031 assert_eq!(
1032 serde_json::to_string(&BitmexInstrumentType::CryptoIndex).unwrap(),
1033 r#""MRCXXX""#
1034 );
1035 assert_eq!(
1036 serde_json::to_string(&BitmexInstrumentType::FxIndex).unwrap(),
1037 r#""MRFXXX""#
1038 );
1039 assert_eq!(
1040 serde_json::to_string(&BitmexInstrumentType::LendingIndex).unwrap(),
1041 r#""MRRXXX""#
1042 );
1043 assert_eq!(
1044 serde_json::to_string(&BitmexInstrumentType::VolatilityIndex).unwrap(),
1045 r#""MRIXXX""#
1046 );
1047 assert_eq!(
1048 serde_json::to_string(&BitmexInstrumentType::StockIndex).unwrap(),
1049 r#""MRSXXX""#
1050 );
1051 assert_eq!(
1052 serde_json::to_string(&BitmexInstrumentType::YieldIndex).unwrap(),
1053 r#""MRVDXX""#
1054 );
1055 }
1056
1057 #[rstest]
1058 fn test_instrument_type_deserialization() {
1059 assert_eq!(
1061 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSX""#).unwrap(),
1062 BitmexInstrumentType::PerpetualContract
1063 );
1064 assert_eq!(
1065 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSF""#).unwrap(),
1066 BitmexInstrumentType::PerpetualContractFx
1067 );
1068 assert_eq!(
1069 serde_json::from_str::<BitmexInstrumentType>(r#""FFSCSX""#).unwrap(),
1070 BitmexInstrumentType::StockPerpetual
1071 );
1072 assert_eq!(
1073 serde_json::from_str::<BitmexInstrumentType>(r#""IFXXXP""#).unwrap(),
1074 BitmexInstrumentType::Spot
1075 );
1076 assert_eq!(
1077 serde_json::from_str::<BitmexInstrumentType>(r#""FFCCSX""#).unwrap(),
1078 BitmexInstrumentType::Futures
1079 );
1080 assert_eq!(
1081 serde_json::from_str::<BitmexInstrumentType>(r#""FFICSX""#).unwrap(),
1082 BitmexInstrumentType::PredictionMarket
1083 );
1084 assert_eq!(
1085 serde_json::from_str::<BitmexInstrumentType>(r#""OCECCS""#).unwrap(),
1086 BitmexInstrumentType::CallOption
1087 );
1088 assert_eq!(
1089 serde_json::from_str::<BitmexInstrumentType>(r#""OPECCS""#).unwrap(),
1090 BitmexInstrumentType::PutOption
1091 );
1092 assert_eq!(
1093 serde_json::from_str::<BitmexInstrumentType>(r#""SRMCSX""#).unwrap(),
1094 BitmexInstrumentType::SwapRate
1095 );
1096
1097 assert_eq!(
1099 serde_json::from_str::<BitmexInstrumentType>(r#""FXXXS""#).unwrap(),
1100 BitmexInstrumentType::LegacyFutures
1101 );
1102 assert_eq!(
1103 serde_json::from_str::<BitmexInstrumentType>(r#""FXXXN""#).unwrap(),
1104 BitmexInstrumentType::LegacyFuturesN
1105 );
1106 assert_eq!(
1107 serde_json::from_str::<BitmexInstrumentType>(r#""FMXXS""#).unwrap(),
1108 BitmexInstrumentType::FuturesSpreads
1109 );
1110 assert_eq!(
1111 serde_json::from_str::<BitmexInstrumentType>(r#""RCSXXX""#).unwrap(),
1112 BitmexInstrumentType::ReferenceBasket
1113 );
1114
1115 assert_eq!(
1117 serde_json::from_str::<BitmexInstrumentType>(r#""MRBXXX""#).unwrap(),
1118 BitmexInstrumentType::BasketIndex
1119 );
1120 assert_eq!(
1121 serde_json::from_str::<BitmexInstrumentType>(r#""MRCXXX""#).unwrap(),
1122 BitmexInstrumentType::CryptoIndex
1123 );
1124 assert_eq!(
1125 serde_json::from_str::<BitmexInstrumentType>(r#""MRFXXX""#).unwrap(),
1126 BitmexInstrumentType::FxIndex
1127 );
1128 assert_eq!(
1129 serde_json::from_str::<BitmexInstrumentType>(r#""MRRXXX""#).unwrap(),
1130 BitmexInstrumentType::LendingIndex
1131 );
1132 assert_eq!(
1133 serde_json::from_str::<BitmexInstrumentType>(r#""MRIXXX""#).unwrap(),
1134 BitmexInstrumentType::VolatilityIndex
1135 );
1136 assert_eq!(
1137 serde_json::from_str::<BitmexInstrumentType>(r#""MRSXXX""#).unwrap(),
1138 BitmexInstrumentType::StockIndex
1139 );
1140 assert_eq!(
1141 serde_json::from_str::<BitmexInstrumentType>(r#""MRVDXX""#).unwrap(),
1142 BitmexInstrumentType::YieldIndex
1143 );
1144
1145 assert!(serde_json::from_str::<BitmexInstrumentType>(r#""INVALID""#).is_err());
1147 }
1148
1149 #[rstest]
1150 fn test_subscription_strings() {
1151 assert_eq!(BitmexProductType::All.to_subscription(), "instrument");
1152 assert_eq!(
1153 BitmexProductType::Specific("XBTUSD".to_string()).to_subscription(),
1154 "instrument:XBTUSD"
1155 );
1156 assert_eq!(BitmexProductType::Contracts.to_subscription(), "CONTRACTS");
1157 assert_eq!(BitmexProductType::Indices.to_subscription(), "INDICES");
1158 assert_eq!(
1159 BitmexProductType::Derivatives.to_subscription(),
1160 "DERIVATIVES"
1161 );
1162 assert_eq!(BitmexProductType::Spot.to_subscription(), "SPOT");
1163 }
1164
1165 #[rstest]
1166 fn test_serialization() {
1167 assert_eq!(
1169 serde_json::to_string(&BitmexProductType::All).unwrap(),
1170 r#""instrument""#
1171 );
1172 assert_eq!(
1173 serde_json::to_string(&BitmexProductType::Specific("XBTUSD".to_string())).unwrap(),
1174 r#""XBTUSD""#
1175 );
1176 assert_eq!(
1177 serde_json::to_string(&BitmexProductType::Contracts).unwrap(),
1178 r#""CONTRACTS""#
1179 );
1180 }
1181
1182 #[rstest]
1183 fn test_deserialization() {
1184 assert_eq!(
1185 serde_json::from_str::<BitmexProductType>(r#""instrument""#).unwrap(),
1186 BitmexProductType::All
1187 );
1188 assert_eq!(
1189 serde_json::from_str::<BitmexProductType>(r#""instrument:XBTUSD""#).unwrap(),
1190 BitmexProductType::Specific("XBTUSD".to_string())
1191 );
1192 assert_eq!(
1193 serde_json::from_str::<BitmexProductType>(r#""CONTRACTS""#).unwrap(),
1194 BitmexProductType::Contracts
1195 );
1196 }
1197
1198 #[rstest]
1199 fn test_error_cases() {
1200 assert!(serde_json::from_str::<BitmexProductType>(r#""invalid_type""#).is_err());
1201 assert!(serde_json::from_str::<BitmexProductType>("123").is_err());
1202 assert!(serde_json::from_str::<BitmexProductType>("{}").is_err());
1203 }
1204
1205 #[rstest]
1206 fn test_order_side_from_specified() {
1207 assert_eq!(BitmexSide::from(OrderSideSpecified::Buy), BitmexSide::Buy);
1208 assert_eq!(BitmexSide::from(OrderSideSpecified::Sell), BitmexSide::Sell);
1209 }
1210
1211 #[rstest]
1212 fn test_order_type_try_from() {
1213 assert_eq!(
1215 BitmexOrderType::try_from(OrderType::Market).unwrap(),
1216 BitmexOrderType::Market
1217 );
1218 assert_eq!(
1219 BitmexOrderType::try_from(OrderType::Limit).unwrap(),
1220 BitmexOrderType::Limit
1221 );
1222
1223 let result = BitmexOrderType::try_from(OrderType::MarketToLimit);
1225 assert!(result.is_err());
1226 assert!(result.unwrap_err().to_string().contains("not supported"));
1227 }
1228
1229 #[rstest]
1230 fn test_time_in_force_conversions() {
1231 assert_eq!(
1233 TimeInForce::try_from(BitmexTimeInForce::Day).unwrap(),
1234 TimeInForce::Day
1235 );
1236 assert_eq!(
1237 TimeInForce::try_from(BitmexTimeInForce::GoodTillCancel).unwrap(),
1238 TimeInForce::Gtc
1239 );
1240 assert_eq!(
1241 TimeInForce::try_from(BitmexTimeInForce::ImmediateOrCancel).unwrap(),
1242 TimeInForce::Ioc
1243 );
1244
1245 let result = TimeInForce::try_from(BitmexTimeInForce::GoodTillCrossing);
1247 assert!(result.is_err());
1248 assert!(result.unwrap_err().to_string().contains("Unsupported"));
1249
1250 assert_eq!(
1252 BitmexTimeInForce::try_from(TimeInForce::Day).unwrap(),
1253 BitmexTimeInForce::Day
1254 );
1255 assert_eq!(
1256 BitmexTimeInForce::try_from(TimeInForce::Gtc).unwrap(),
1257 BitmexTimeInForce::GoodTillCancel
1258 );
1259 assert_eq!(
1260 BitmexTimeInForce::try_from(TimeInForce::Fok).unwrap(),
1261 BitmexTimeInForce::FillOrKill
1262 );
1263 }
1264
1265 #[rstest]
1266 fn test_helper_methods() {
1267 let result = BitmexOrderType::try_from_order_type(OrderType::Limit);
1269 assert!(result.is_ok());
1270 assert_eq!(result.unwrap(), BitmexOrderType::Limit);
1271
1272 let result = BitmexOrderType::try_from_order_type(OrderType::MarketToLimit);
1273 assert!(result.is_err());
1274
1275 let result = BitmexTimeInForce::try_from_time_in_force(TimeInForce::Ioc);
1277 assert!(result.is_ok());
1278 assert_eq!(result.unwrap(), BitmexTimeInForce::ImmediateOrCancel);
1279 }
1280
1281 #[rstest]
1282 #[case(BitmexInstrumentState::Open, MarketStatusAction::Trading)]
1283 #[case(BitmexInstrumentState::Closed, MarketStatusAction::Close)]
1284 #[case(BitmexInstrumentState::Settled, MarketStatusAction::Close)]
1285 #[case(
1286 BitmexInstrumentState::Unlisted,
1287 MarketStatusAction::NotAvailableForTrading
1288 )]
1289 #[case(
1290 BitmexInstrumentState::Delisted,
1291 MarketStatusAction::NotAvailableForTrading
1292 )]
1293 fn test_bitmex_instrument_state_to_market_status_action(
1294 #[case] state: BitmexInstrumentState,
1295 #[case] expected: MarketStatusAction,
1296 ) {
1297 assert_eq!(MarketStatusAction::from(&state), expected);
1298 }
1299}