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.adapters.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.adapters.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 if value == TimeInForce::Gtd {
370 anyhow::bail!(
371 "GTD time in force is not supported for BitMEX order submit; use GTC, Day, IOC, or FOK"
372 );
373 }
374
375 Self::try_from(value)
376 }
377}
378
379#[derive(
381 Copy,
382 Clone,
383 Debug,
384 Display,
385 PartialEq,
386 Eq,
387 AsRefStr,
388 EnumIter,
389 EnumString,
390 Serialize,
391 Deserialize,
392)]
393pub enum BitmexContingencyType {
394 OneCancelsTheOther,
395 OneTriggersTheOther,
396 OneUpdatesTheOtherAbsolute,
397 OneUpdatesTheOtherProportional,
398 #[serde(rename = "")]
399 Unknown, }
401
402impl From<BitmexContingencyType> for ContingencyType {
403 fn from(value: BitmexContingencyType) -> Self {
404 match value {
405 BitmexContingencyType::OneCancelsTheOther => Self::Oco,
406 BitmexContingencyType::OneTriggersTheOther => Self::Oto,
407 BitmexContingencyType::OneUpdatesTheOtherProportional => Self::Ouo,
408 BitmexContingencyType::OneUpdatesTheOtherAbsolute => Self::Ouo,
409 BitmexContingencyType::Unknown => Self::NoContingency,
410 }
411 }
412}
413
414impl TryFrom<ContingencyType> for BitmexContingencyType {
415 type Error = anyhow::Error;
416
417 fn try_from(value: ContingencyType) -> Result<Self, Self::Error> {
418 match value {
419 ContingencyType::NoContingency => Ok(Self::Unknown),
420 ContingencyType::Oco => Ok(Self::OneCancelsTheOther),
421 ContingencyType::Oto => Ok(Self::OneTriggersTheOther),
422 ContingencyType::Ouo => anyhow::bail!("OUO contingency type not supported by BitMEX"),
423 }
424 }
425}
426
427#[derive(
429 Copy,
430 Clone,
431 Debug,
432 Display,
433 PartialEq,
434 Eq,
435 AsRefStr,
436 EnumIter,
437 EnumString,
438 Serialize,
439 Deserialize,
440)]
441pub enum BitmexPegPriceType {
442 LastPeg,
443 OpeningPeg,
444 MidPricePeg,
445 MarketPeg,
446 PrimaryPeg,
447 PegToVWAP,
448 TrailingStopPeg,
449 PegToLimitPrice,
450 ShortSaleMinPricePeg,
451 #[serde(rename = "")]
452 Unknown, }
454
455#[derive(
457 Copy,
458 Clone,
459 Debug,
460 Display,
461 PartialEq,
462 Eq,
463 AsRefStr,
464 EnumIter,
465 EnumString,
466 Serialize,
467 Deserialize,
468)]
469pub enum BitmexExecInstruction {
470 ParticipateDoNotInitiate,
471 AllOrNone,
472 MarkPrice,
473 IndexPrice,
474 LastPrice,
475 Close,
476 ReduceOnly,
477 Fixed,
478 #[serde(rename = "")]
479 Unknown, }
481
482impl BitmexExecInstruction {
483 pub fn join(instructions: &[Self]) -> String {
485 instructions
486 .iter()
487 .map(ToString::to_string)
488 .collect::<Vec<_>>()
489 .join(",")
490 }
491}
492
493#[derive(Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize)]
495pub enum BitmexExecType {
496 New,
498 Trade,
500 Canceled,
502 CancelReject,
504 Replaced,
506 Rejected,
508 AmendReject,
510 Funding,
512 Settlement,
514 Suspended,
516 Released,
518 Insurance,
520 Rebalance,
522 Liquidation,
524 Bankruptcy,
526 TrialFill,
528 TriggeredOrActivatedBySystem,
530 #[strum(disabled)]
532 Unknown(String),
533}
534
535impl<'de> Deserialize<'de> for BitmexExecType {
536 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
537 where
538 D: Deserializer<'de>,
539 {
540 let s = String::deserialize(deserializer)?;
541
542 match s.as_str() {
543 "New" => Ok(Self::New),
544 "Trade" => Ok(Self::Trade),
545 "Canceled" => Ok(Self::Canceled),
546 "CancelReject" => Ok(Self::CancelReject),
547 "Replaced" => Ok(Self::Replaced),
548 "Rejected" => Ok(Self::Rejected),
549 "AmendReject" => Ok(Self::AmendReject),
550 "Funding" => Ok(Self::Funding),
551 "Settlement" => Ok(Self::Settlement),
552 "Suspended" => Ok(Self::Suspended),
553 "Released" => Ok(Self::Released),
554 "Insurance" => Ok(Self::Insurance),
555 "Rebalance" => Ok(Self::Rebalance),
556 "Liquidation" => Ok(Self::Liquidation),
557 "Bankruptcy" => Ok(Self::Bankruptcy),
558 "TrialFill" => Ok(Self::TrialFill),
559 "TriggeredOrActivatedBySystem" => Ok(Self::TriggeredOrActivatedBySystem),
560 other => Ok(Self::Unknown(other.to_string())),
561 }
562 }
563}
564
565#[derive(
567 Copy,
568 Clone,
569 Debug,
570 Display,
571 PartialEq,
572 Eq,
573 AsRefStr,
574 EnumIter,
575 EnumString,
576 Serialize,
577 Deserialize,
578)]
579pub enum BitmexLiquidityIndicator {
580 #[serde(rename = "Added")]
583 #[serde(alias = "AddedLiquidity")]
584 Maker,
585 #[serde(rename = "Removed")]
588 #[serde(alias = "RemovedLiquidity")]
589 Taker,
590}
591
592impl From<BitmexLiquidityIndicator> for LiquiditySide {
593 fn from(value: BitmexLiquidityIndicator) -> Self {
594 match value {
595 BitmexLiquidityIndicator::Maker => Self::Maker,
596 BitmexLiquidityIndicator::Taker => Self::Taker,
597 }
598 }
599}
600
601#[derive(
608 Copy,
609 Clone,
610 Debug,
611 Display,
612 PartialEq,
613 Eq,
614 AsRefStr,
615 EnumIter,
616 EnumString,
617 Serialize,
618 Deserialize,
619)]
620#[serde(rename_all = "UPPERCASE")]
621pub enum BitmexInstrumentType {
622 #[serde(rename = "FXXXS")]
624 LegacyFutures,
625
626 #[serde(rename = "FXXXN")]
628 LegacyFuturesN,
629
630 #[serde(rename = "FMXXS")]
632 FuturesSpreads,
633
634 #[serde(rename = "FFMCSX")]
636 FuturesSpread,
637
638 #[serde(rename = "FFICSX")]
641 PredictionMarket,
642
643 #[serde(rename = "FFSCSX")]
646 TradFiPerpetual,
647
648 #[serde(rename = "FFWCSX")]
650 PerpetualContract,
651
652 #[serde(rename = "FFWCSF")]
654 PerpetualContractFx,
655
656 #[serde(rename = "FFCCSX")]
658 Futures,
659
660 #[serde(rename = "IFXXXP")]
662 Spot,
663
664 #[serde(rename = "OCECCS")]
666 CallOption,
667
668 #[serde(rename = "OPECCS")]
670 PutOption,
671
672 #[serde(rename = "SRMCSX")]
674 SwapRate,
675
676 #[serde(rename = "RCSXXX")]
678 ReferenceBasket,
679
680 #[serde(rename = "MRBXXX")]
682 BasketIndex,
683
684 #[serde(rename = "MRCXXX")]
686 CryptoIndex,
687
688 #[serde(rename = "MRFXXX")]
690 FxIndex,
691
692 #[serde(rename = "MRRXXX")]
694 LendingIndex,
695
696 #[serde(rename = "MRIXXX")]
698 VolatilityIndex,
699
700 #[serde(rename = "MRSXXX")]
702 StockIndex,
703
704 #[serde(rename = "MRVDXX")]
706 YieldIndex,
707
708 #[serde(other)]
710 Other,
711}
712
713#[derive(Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize)]
715pub enum BitmexProductType {
716 #[serde(rename = "instrument")]
718 All,
719
720 #[serde(rename = "CONTRACTS")]
722 Contracts,
723
724 #[serde(rename = "INDICES")]
726 Indices,
727
728 #[serde(rename = "DERIVATIVES")]
730 Derivatives,
731
732 #[serde(rename = "SPOT")]
734 Spot,
735
736 #[serde(rename = "instrument")]
738 #[serde(untagged)]
739 Specific(String),
740}
741
742impl BitmexProductType {
743 #[must_use]
745 pub fn to_subscription(&self) -> Cow<'static, str> {
746 match self {
747 Self::All => Cow::Borrowed("instrument"),
748 Self::Specific(symbol) => Cow::Owned(format!("instrument:{symbol}")),
749 Self::Contracts => Cow::Borrowed("CONTRACTS"),
750 Self::Indices => Cow::Borrowed("INDICES"),
751 Self::Derivatives => Cow::Borrowed("DERIVATIVES"),
752 Self::Spot => Cow::Borrowed("SPOT"),
753 }
754 }
755}
756
757impl<'de> Deserialize<'de> for BitmexProductType {
758 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
759 where
760 D: Deserializer<'de>,
761 {
762 let s = String::deserialize(deserializer)?;
763
764 match s.as_str() {
765 "instrument" => Ok(Self::All),
766 "CONTRACTS" => Ok(Self::Contracts),
767 "INDICES" => Ok(Self::Indices),
768 "DERIVATIVES" => Ok(Self::Derivatives),
769 "SPOT" => Ok(Self::Spot),
770 s if s.starts_with("instrument:") => {
771 let symbol = s.strip_prefix("instrument:").unwrap();
772 Ok(Self::Specific(symbol.to_string()))
773 }
774 _ => Err(serde::de::Error::custom(format!(
775 "Invalid product type: {s}"
776 ))),
777 }
778 }
779}
780
781#[derive(
783 Copy,
784 Clone,
785 Debug,
786 Display,
787 PartialEq,
788 Eq,
789 AsRefStr,
790 EnumIter,
791 EnumString,
792 Serialize,
793 Deserialize,
794)]
795pub enum BitmexTickDirection {
796 PlusTick,
798 MinusTick,
800 ZeroPlusTick,
802 ZeroMinusTick,
804}
805
806#[derive(
808 Clone,
809 Copy,
810 Debug,
811 Display,
812 PartialEq,
813 Eq,
814 AsRefStr,
815 EnumIter,
816 EnumString,
817 Serialize,
818 Deserialize,
819)]
820pub enum BitmexInstrumentState {
821 Open,
823 Closed,
825 Unlisted,
827 Settled,
829 Delisted,
831 #[serde(other)]
833 Unknown,
834}
835
836impl From<&BitmexInstrumentState> for MarketStatusAction {
837 fn from(state: &BitmexInstrumentState) -> Self {
838 match state {
839 BitmexInstrumentState::Open => Self::Trading,
840 BitmexInstrumentState::Closed => Self::Close,
841 BitmexInstrumentState::Settled => Self::Close,
842 BitmexInstrumentState::Unlisted => Self::NotAvailableForTrading,
843 BitmexInstrumentState::Delisted => Self::NotAvailableForTrading,
844 BitmexInstrumentState::Unknown => Self::NotAvailableForTrading,
845 }
846 }
847}
848
849#[derive(
851 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
852)]
853pub enum BitmexFairMethod {
854 FundingRate,
856 ImpactMidPrice,
858 LastPrice,
860}
861
862#[derive(
864 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
865)]
866pub enum BitmexMarkMethod {
867 FairPrice,
869 FairPriceStox,
871 LastPrice,
873 LastPricePreLaunch,
875 CompositeIndex,
877}
878
879#[derive(
881 Copy,
882 Clone,
883 Debug,
884 Default,
885 Display,
886 PartialEq,
887 Eq,
888 Hash,
889 AsRefStr,
890 EnumIter,
891 EnumString,
892 Serialize,
893 Deserialize,
894)]
895#[serde(rename_all = "lowercase")]
896#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
897#[cfg_attr(
898 feature = "python",
899 pyo3::pyclass(
900 eq,
901 eq_int,
902 module = "nautilus_trader.core.nautilus_pyo3.bitmex",
903 from_py_object,
904 rename_all = "SCREAMING_SNAKE_CASE",
905 )
906)]
907#[cfg_attr(
908 feature = "python",
909 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bitmex")
910)]
911pub enum BitmexEnvironment {
912 #[default]
914 Mainnet,
915 Testnet,
917}
918
919#[cfg(test)]
920mod tests {
921 use rstest::rstest;
922
923 use super::*;
924
925 #[rstest]
926 fn test_bitmex_side_deserialization() {
927 assert_eq!(
929 serde_json::from_str::<BitmexSide>(r#""Buy""#).unwrap(),
930 BitmexSide::Buy
931 );
932 assert_eq!(
933 serde_json::from_str::<BitmexSide>(r#""BUY""#).unwrap(),
934 BitmexSide::Buy
935 );
936 assert_eq!(
937 serde_json::from_str::<BitmexSide>(r#""buy""#).unwrap(),
938 BitmexSide::Buy
939 );
940 assert_eq!(
941 serde_json::from_str::<BitmexSide>(r#""Sell""#).unwrap(),
942 BitmexSide::Sell
943 );
944 assert_eq!(
945 serde_json::from_str::<BitmexSide>(r#""SELL""#).unwrap(),
946 BitmexSide::Sell
947 );
948 assert_eq!(
949 serde_json::from_str::<BitmexSide>(r#""sell""#).unwrap(),
950 BitmexSide::Sell
951 );
952 }
953
954 #[rstest]
955 fn test_bitmex_order_type_deserialization() {
956 assert_eq!(
957 serde_json::from_str::<BitmexOrderType>(r#""Market""#).unwrap(),
958 BitmexOrderType::Market
959 );
960 assert_eq!(
961 serde_json::from_str::<BitmexOrderType>(r#""Limit""#).unwrap(),
962 BitmexOrderType::Limit
963 );
964 assert_eq!(
965 serde_json::from_str::<BitmexOrderType>(r#""Stop""#).unwrap(),
966 BitmexOrderType::Stop
967 );
968 assert_eq!(
969 serde_json::from_str::<BitmexOrderType>(r#""StopLimit""#).unwrap(),
970 BitmexOrderType::StopLimit
971 );
972 assert_eq!(
973 serde_json::from_str::<BitmexOrderType>(r#""MarketIfTouched""#).unwrap(),
974 BitmexOrderType::MarketIfTouched
975 );
976 assert_eq!(
977 serde_json::from_str::<BitmexOrderType>(r#""LimitIfTouched""#).unwrap(),
978 BitmexOrderType::LimitIfTouched
979 );
980 assert_eq!(
981 serde_json::from_str::<BitmexOrderType>(r#""Pegged""#).unwrap(),
982 BitmexOrderType::Pegged
983 );
984 }
985
986 #[rstest]
987 fn test_instrument_type_serialization() {
988 assert_eq!(
990 serde_json::to_string(&BitmexInstrumentType::PerpetualContract).unwrap(),
991 r#""FFWCSX""#
992 );
993 assert_eq!(
994 serde_json::to_string(&BitmexInstrumentType::PerpetualContractFx).unwrap(),
995 r#""FFWCSF""#
996 );
997 assert_eq!(
998 serde_json::to_string(&BitmexInstrumentType::TradFiPerpetual).unwrap(),
999 r#""FFSCSX""#
1000 );
1001 assert_eq!(
1002 serde_json::to_string(&BitmexInstrumentType::Spot).unwrap(),
1003 r#""IFXXXP""#
1004 );
1005 assert_eq!(
1006 serde_json::to_string(&BitmexInstrumentType::Futures).unwrap(),
1007 r#""FFCCSX""#
1008 );
1009 assert_eq!(
1010 serde_json::to_string(&BitmexInstrumentType::PredictionMarket).unwrap(),
1011 r#""FFICSX""#
1012 );
1013 assert_eq!(
1014 serde_json::to_string(&BitmexInstrumentType::CallOption).unwrap(),
1015 r#""OCECCS""#
1016 );
1017 assert_eq!(
1018 serde_json::to_string(&BitmexInstrumentType::PutOption).unwrap(),
1019 r#""OPECCS""#
1020 );
1021 assert_eq!(
1022 serde_json::to_string(&BitmexInstrumentType::SwapRate).unwrap(),
1023 r#""SRMCSX""#
1024 );
1025
1026 assert_eq!(
1028 serde_json::to_string(&BitmexInstrumentType::LegacyFutures).unwrap(),
1029 r#""FXXXS""#
1030 );
1031 assert_eq!(
1032 serde_json::to_string(&BitmexInstrumentType::LegacyFuturesN).unwrap(),
1033 r#""FXXXN""#
1034 );
1035 assert_eq!(
1036 serde_json::to_string(&BitmexInstrumentType::FuturesSpreads).unwrap(),
1037 r#""FMXXS""#
1038 );
1039 assert_eq!(
1040 serde_json::to_string(&BitmexInstrumentType::FuturesSpread).unwrap(),
1041 r#""FFMCSX""#
1042 );
1043 assert_eq!(
1044 serde_json::to_string(&BitmexInstrumentType::ReferenceBasket).unwrap(),
1045 r#""RCSXXX""#
1046 );
1047
1048 assert_eq!(
1050 serde_json::to_string(&BitmexInstrumentType::BasketIndex).unwrap(),
1051 r#""MRBXXX""#
1052 );
1053 assert_eq!(
1054 serde_json::to_string(&BitmexInstrumentType::CryptoIndex).unwrap(),
1055 r#""MRCXXX""#
1056 );
1057 assert_eq!(
1058 serde_json::to_string(&BitmexInstrumentType::FxIndex).unwrap(),
1059 r#""MRFXXX""#
1060 );
1061 assert_eq!(
1062 serde_json::to_string(&BitmexInstrumentType::LendingIndex).unwrap(),
1063 r#""MRRXXX""#
1064 );
1065 assert_eq!(
1066 serde_json::to_string(&BitmexInstrumentType::VolatilityIndex).unwrap(),
1067 r#""MRIXXX""#
1068 );
1069 assert_eq!(
1070 serde_json::to_string(&BitmexInstrumentType::StockIndex).unwrap(),
1071 r#""MRSXXX""#
1072 );
1073 assert_eq!(
1074 serde_json::to_string(&BitmexInstrumentType::YieldIndex).unwrap(),
1075 r#""MRVDXX""#
1076 );
1077 }
1078
1079 #[rstest]
1080 fn test_instrument_type_deserialization() {
1081 assert_eq!(
1083 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSX""#).unwrap(),
1084 BitmexInstrumentType::PerpetualContract
1085 );
1086 assert_eq!(
1087 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSF""#).unwrap(),
1088 BitmexInstrumentType::PerpetualContractFx
1089 );
1090 assert_eq!(
1091 serde_json::from_str::<BitmexInstrumentType>(r#""FFSCSX""#).unwrap(),
1092 BitmexInstrumentType::TradFiPerpetual
1093 );
1094 assert_eq!(
1095 serde_json::from_str::<BitmexInstrumentType>(r#""IFXXXP""#).unwrap(),
1096 BitmexInstrumentType::Spot
1097 );
1098 assert_eq!(
1099 serde_json::from_str::<BitmexInstrumentType>(r#""FFCCSX""#).unwrap(),
1100 BitmexInstrumentType::Futures
1101 );
1102 assert_eq!(
1103 serde_json::from_str::<BitmexInstrumentType>(r#""FFICSX""#).unwrap(),
1104 BitmexInstrumentType::PredictionMarket
1105 );
1106 assert_eq!(
1107 serde_json::from_str::<BitmexInstrumentType>(r#""OCECCS""#).unwrap(),
1108 BitmexInstrumentType::CallOption
1109 );
1110 assert_eq!(
1111 serde_json::from_str::<BitmexInstrumentType>(r#""OPECCS""#).unwrap(),
1112 BitmexInstrumentType::PutOption
1113 );
1114 assert_eq!(
1115 serde_json::from_str::<BitmexInstrumentType>(r#""SRMCSX""#).unwrap(),
1116 BitmexInstrumentType::SwapRate
1117 );
1118
1119 assert_eq!(
1121 serde_json::from_str::<BitmexInstrumentType>(r#""FXXXS""#).unwrap(),
1122 BitmexInstrumentType::LegacyFutures
1123 );
1124 assert_eq!(
1125 serde_json::from_str::<BitmexInstrumentType>(r#""FXXXN""#).unwrap(),
1126 BitmexInstrumentType::LegacyFuturesN
1127 );
1128 assert_eq!(
1129 serde_json::from_str::<BitmexInstrumentType>(r#""FMXXS""#).unwrap(),
1130 BitmexInstrumentType::FuturesSpreads
1131 );
1132 assert_eq!(
1133 serde_json::from_str::<BitmexInstrumentType>(r#""FFMCSX""#).unwrap(),
1134 BitmexInstrumentType::FuturesSpread
1135 );
1136 assert_eq!(
1137 serde_json::from_str::<BitmexInstrumentType>(r#""RCSXXX""#).unwrap(),
1138 BitmexInstrumentType::ReferenceBasket
1139 );
1140
1141 assert_eq!(
1143 serde_json::from_str::<BitmexInstrumentType>(r#""MRBXXX""#).unwrap(),
1144 BitmexInstrumentType::BasketIndex
1145 );
1146 assert_eq!(
1147 serde_json::from_str::<BitmexInstrumentType>(r#""MRCXXX""#).unwrap(),
1148 BitmexInstrumentType::CryptoIndex
1149 );
1150 assert_eq!(
1151 serde_json::from_str::<BitmexInstrumentType>(r#""MRFXXX""#).unwrap(),
1152 BitmexInstrumentType::FxIndex
1153 );
1154 assert_eq!(
1155 serde_json::from_str::<BitmexInstrumentType>(r#""MRRXXX""#).unwrap(),
1156 BitmexInstrumentType::LendingIndex
1157 );
1158 assert_eq!(
1159 serde_json::from_str::<BitmexInstrumentType>(r#""MRIXXX""#).unwrap(),
1160 BitmexInstrumentType::VolatilityIndex
1161 );
1162 assert_eq!(
1163 serde_json::from_str::<BitmexInstrumentType>(r#""MRSXXX""#).unwrap(),
1164 BitmexInstrumentType::StockIndex
1165 );
1166 assert_eq!(
1167 serde_json::from_str::<BitmexInstrumentType>(r#""MRVDXX""#).unwrap(),
1168 BitmexInstrumentType::YieldIndex
1169 );
1170
1171 assert_eq!(
1172 serde_json::from_str::<BitmexInstrumentType>(r#""INVALID""#).unwrap(),
1173 BitmexInstrumentType::Other
1174 );
1175 }
1176
1177 #[rstest]
1178 fn test_subscription_strings() {
1179 assert_eq!(BitmexProductType::All.to_subscription(), "instrument");
1180 assert_eq!(
1181 BitmexProductType::Specific("XBTUSD".to_string()).to_subscription(),
1182 "instrument:XBTUSD"
1183 );
1184 assert_eq!(BitmexProductType::Contracts.to_subscription(), "CONTRACTS");
1185 assert_eq!(BitmexProductType::Indices.to_subscription(), "INDICES");
1186 assert_eq!(
1187 BitmexProductType::Derivatives.to_subscription(),
1188 "DERIVATIVES"
1189 );
1190 assert_eq!(BitmexProductType::Spot.to_subscription(), "SPOT");
1191 }
1192
1193 #[rstest]
1194 fn test_serialization() {
1195 assert_eq!(
1197 serde_json::to_string(&BitmexProductType::All).unwrap(),
1198 r#""instrument""#
1199 );
1200 assert_eq!(
1201 serde_json::to_string(&BitmexProductType::Specific("XBTUSD".to_string())).unwrap(),
1202 r#""XBTUSD""#
1203 );
1204 assert_eq!(
1205 serde_json::to_string(&BitmexProductType::Contracts).unwrap(),
1206 r#""CONTRACTS""#
1207 );
1208 }
1209
1210 #[rstest]
1211 fn test_deserialization() {
1212 assert_eq!(
1213 serde_json::from_str::<BitmexProductType>(r#""instrument""#).unwrap(),
1214 BitmexProductType::All
1215 );
1216 assert_eq!(
1217 serde_json::from_str::<BitmexProductType>(r#""instrument:XBTUSD""#).unwrap(),
1218 BitmexProductType::Specific("XBTUSD".to_string())
1219 );
1220 assert_eq!(
1221 serde_json::from_str::<BitmexProductType>(r#""CONTRACTS""#).unwrap(),
1222 BitmexProductType::Contracts
1223 );
1224 }
1225
1226 #[rstest]
1227 fn test_error_cases() {
1228 assert!(serde_json::from_str::<BitmexProductType>(r#""invalid_type""#).is_err());
1229 assert!(serde_json::from_str::<BitmexProductType>("123").is_err());
1230 assert!(serde_json::from_str::<BitmexProductType>("{}").is_err());
1231 }
1232
1233 #[rstest]
1234 fn test_order_side_from_specified() {
1235 assert_eq!(BitmexSide::from(OrderSideSpecified::Buy), BitmexSide::Buy);
1236 assert_eq!(BitmexSide::from(OrderSideSpecified::Sell), BitmexSide::Sell);
1237 }
1238
1239 #[rstest]
1240 fn test_order_type_try_from() {
1241 assert_eq!(
1243 BitmexOrderType::try_from(OrderType::Market).unwrap(),
1244 BitmexOrderType::Market
1245 );
1246 assert_eq!(
1247 BitmexOrderType::try_from(OrderType::Limit).unwrap(),
1248 BitmexOrderType::Limit
1249 );
1250
1251 let result = BitmexOrderType::try_from(OrderType::MarketToLimit);
1253 assert!(result.is_err());
1254 assert!(result.unwrap_err().to_string().contains("not supported"));
1255 }
1256
1257 #[rstest]
1258 fn test_time_in_force_conversions() {
1259 assert_eq!(
1261 TimeInForce::try_from(BitmexTimeInForce::Day).unwrap(),
1262 TimeInForce::Day
1263 );
1264 assert_eq!(
1265 TimeInForce::try_from(BitmexTimeInForce::GoodTillCancel).unwrap(),
1266 TimeInForce::Gtc
1267 );
1268 assert_eq!(
1269 TimeInForce::try_from(BitmexTimeInForce::ImmediateOrCancel).unwrap(),
1270 TimeInForce::Ioc
1271 );
1272
1273 let result = TimeInForce::try_from(BitmexTimeInForce::GoodTillCrossing);
1275 assert!(result.is_err());
1276 assert!(result.unwrap_err().to_string().contains("Unsupported"));
1277
1278 assert_eq!(
1280 BitmexTimeInForce::try_from(TimeInForce::Day).unwrap(),
1281 BitmexTimeInForce::Day
1282 );
1283 assert_eq!(
1284 BitmexTimeInForce::try_from(TimeInForce::Gtc).unwrap(),
1285 BitmexTimeInForce::GoodTillCancel
1286 );
1287 assert_eq!(
1288 BitmexTimeInForce::try_from(TimeInForce::Fok).unwrap(),
1289 BitmexTimeInForce::FillOrKill
1290 );
1291 }
1292
1293 #[rstest]
1294 fn test_helper_methods() {
1295 let result = BitmexOrderType::try_from_order_type(OrderType::Limit);
1297 assert!(result.is_ok());
1298 assert_eq!(result.unwrap(), BitmexOrderType::Limit);
1299
1300 let result = BitmexOrderType::try_from_order_type(OrderType::MarketToLimit);
1301 assert!(result.is_err());
1302
1303 let result = BitmexTimeInForce::try_from_time_in_force(TimeInForce::Ioc);
1305 assert!(result.is_ok());
1306 assert_eq!(result.unwrap(), BitmexTimeInForce::ImmediateOrCancel);
1307
1308 let result = BitmexTimeInForce::try_from_time_in_force(TimeInForce::Gtd);
1309 assert!(result.is_err());
1310 assert!(
1311 result
1312 .unwrap_err()
1313 .to_string()
1314 .contains("GTD time in force is not supported")
1315 );
1316 }
1317
1318 #[rstest]
1319 #[case(BitmexInstrumentState::Open, MarketStatusAction::Trading)]
1320 #[case(BitmexInstrumentState::Closed, MarketStatusAction::Close)]
1321 #[case(BitmexInstrumentState::Settled, MarketStatusAction::Close)]
1322 #[case(
1323 BitmexInstrumentState::Unlisted,
1324 MarketStatusAction::NotAvailableForTrading
1325 )]
1326 #[case(
1327 BitmexInstrumentState::Delisted,
1328 MarketStatusAction::NotAvailableForTrading
1329 )]
1330 #[case(
1331 BitmexInstrumentState::Unknown,
1332 MarketStatusAction::NotAvailableForTrading
1333 )]
1334 fn test_bitmex_instrument_state_to_market_status_action(
1335 #[case] state: BitmexInstrumentState,
1336 #[case] expected: MarketStatusAction,
1337 ) {
1338 assert_eq!(MarketStatusAction::from(&state), expected);
1339 }
1340
1341 #[rstest]
1342 fn test_bitmex_instrument_state_unknown_deserializes_from_unrecognized_string() {
1343 let state: BitmexInstrumentState = serde_json::from_str(r#""SomeFutureState""#).unwrap();
1344 assert_eq!(state, BitmexInstrumentState::Unknown);
1345 }
1346}