1use std::borrow::Cow;
19
20use nautilus_model::enums::{
21 ContingencyType, LiquiditySide, OrderSide, OrderSideSpecified, 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.core.nautilus_pyo3.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<OrderSideSpecified> for BitmexSide {
85 fn from(value: OrderSideSpecified) -> Self {
86 match value {
87 OrderSideSpecified::Buy => Self::Buy,
88 OrderSideSpecified::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(
119 module = "nautilus_trader.core.nautilus_pyo3.bitmex",
120 eq,
121 eq_int,
122 from_py_object
123 )
124)]
125pub enum BitmexPositionSide {
126 #[serde(rename = "LONG", alias = "Long", alias = "long")]
128 Long,
129 #[serde(rename = "SHORT", alias = "Short", alias = "short")]
131 Short,
132 #[serde(rename = "FLAT", alias = "Flat", alias = "flat")]
134 Flat,
135}
136
137impl From<BitmexPositionSide> for PositionSide {
138 fn from(side: BitmexPositionSide) -> Self {
139 match side {
140 BitmexPositionSide::Long => Self::Long,
141 BitmexPositionSide::Short => Self::Short,
142 BitmexPositionSide::Flat => Self::Flat,
143 }
144 }
145}
146
147impl From<PositionSide> for BitmexPositionSide {
148 fn from(side: PositionSide) -> Self {
149 match side {
150 PositionSide::Long => Self::Long,
151 PositionSide::Short => Self::Short,
152 PositionSide::Flat | PositionSide::NoPositionSide => Self::Flat,
153 }
154 }
155}
156
157#[derive(
159 Copy,
160 Clone,
161 Debug,
162 Display,
163 PartialEq,
164 Eq,
165 AsRefStr,
166 EnumIter,
167 EnumString,
168 Serialize,
169 Deserialize,
170)]
171pub enum BitmexOrderType {
172 Market,
174 Limit,
176 Stop,
178 StopLimit,
180 MarketIfTouched,
182 LimitIfTouched,
184 Pegged,
186}
187
188impl TryFrom<OrderType> for BitmexOrderType {
189 type Error = anyhow::Error;
190
191 fn try_from(value: OrderType) -> Result<Self, Self::Error> {
192 match value {
193 OrderType::Market => Ok(Self::Market),
194 OrderType::Limit => Ok(Self::Limit),
195 OrderType::StopMarket => Ok(Self::Stop),
196 OrderType::StopLimit => Ok(Self::StopLimit),
197 OrderType::MarketIfTouched => Ok(Self::MarketIfTouched),
198 OrderType::LimitIfTouched => Ok(Self::LimitIfTouched),
199 OrderType::TrailingStopMarket => Ok(Self::Pegged),
200 OrderType::TrailingStopLimit => Ok(Self::Pegged),
201 OrderType::MarketToLimit => {
202 anyhow::bail!("MarketToLimit order type is not supported by BitMEX")
203 }
204 }
205 }
206}
207
208impl BitmexOrderType {
209 pub fn try_from_order_type(value: OrderType) -> anyhow::Result<Self> {
215 Self::try_from(value)
216 }
217}
218
219impl From<BitmexOrderType> for OrderType {
220 fn from(value: BitmexOrderType) -> Self {
221 match value {
222 BitmexOrderType::Market => Self::Market,
223 BitmexOrderType::Limit => Self::Limit,
224 BitmexOrderType::Stop => Self::StopMarket,
225 BitmexOrderType::StopLimit => Self::StopLimit,
226 BitmexOrderType::MarketIfTouched => Self::MarketIfTouched,
227 BitmexOrderType::LimitIfTouched => Self::LimitIfTouched,
228 BitmexOrderType::Pegged => Self::Limit,
229 }
230 }
231}
232
233#[derive(
235 Copy,
236 Clone,
237 Debug,
238 Display,
239 PartialEq,
240 Eq,
241 AsRefStr,
242 EnumIter,
243 EnumString,
244 Serialize,
245 Deserialize,
246)]
247pub enum BitmexOrderStatus {
248 New,
250 PendingNew,
252 PartiallyFilled,
254 Filled,
256 PendingReplace,
258 PendingCancel,
260 Canceled,
262 Rejected,
264 Expired,
266}
267
268impl From<BitmexOrderStatus> for OrderStatus {
269 fn from(value: BitmexOrderStatus) -> Self {
270 match value {
271 BitmexOrderStatus::New => Self::Accepted,
272 BitmexOrderStatus::PendingNew => Self::Submitted,
273 BitmexOrderStatus::PartiallyFilled => Self::PartiallyFilled,
274 BitmexOrderStatus::Filled => Self::Filled,
275 BitmexOrderStatus::PendingReplace => Self::PendingUpdate,
276 BitmexOrderStatus::PendingCancel => Self::PendingCancel,
277 BitmexOrderStatus::Canceled => Self::Canceled,
278 BitmexOrderStatus::Rejected => Self::Rejected,
279 BitmexOrderStatus::Expired => Self::Expired,
280 }
281 }
282}
283
284#[derive(
286 Copy,
287 Clone,
288 Debug,
289 Display,
290 PartialEq,
291 Eq,
292 AsRefStr,
293 EnumIter,
294 EnumString,
295 Serialize,
296 Deserialize,
297)]
298pub enum BitmexTimeInForce {
299 Day,
300 GoodTillCancel,
301 AtTheOpening,
302 ImmediateOrCancel,
303 FillOrKill,
304 GoodTillCrossing,
305 GoodTillDate,
306 AtTheClose,
307 GoodThroughCrossing,
308 AtCrossing,
309}
310
311impl TryFrom<BitmexTimeInForce> for TimeInForce {
312 type Error = anyhow::Error;
313
314 fn try_from(value: BitmexTimeInForce) -> Result<Self, Self::Error> {
315 match value {
316 BitmexTimeInForce::Day => Ok(Self::Day),
317 BitmexTimeInForce::GoodTillCancel => Ok(Self::Gtc),
318 BitmexTimeInForce::GoodTillDate => Ok(Self::Gtd),
319 BitmexTimeInForce::ImmediateOrCancel => Ok(Self::Ioc),
320 BitmexTimeInForce::FillOrKill => Ok(Self::Fok),
321 BitmexTimeInForce::AtTheOpening => Ok(Self::AtTheOpen),
322 BitmexTimeInForce::AtTheClose => Ok(Self::AtTheClose),
323 _ => anyhow::bail!("Unsupported BitmexTimeInForce: {value}"),
324 }
325 }
326}
327
328impl TryFrom<TimeInForce> for BitmexTimeInForce {
329 type Error = anyhow::Error;
330
331 fn try_from(value: TimeInForce) -> Result<Self, Self::Error> {
332 match value {
333 TimeInForce::Day => Ok(Self::Day),
334 TimeInForce::Gtc => Ok(Self::GoodTillCancel),
335 TimeInForce::Gtd => Ok(Self::GoodTillDate),
336 TimeInForce::Ioc => Ok(Self::ImmediateOrCancel),
337 TimeInForce::Fok => Ok(Self::FillOrKill),
338 TimeInForce::AtTheOpen => Ok(Self::AtTheOpening),
339 TimeInForce::AtTheClose => Ok(Self::AtTheClose),
340 }
341 }
342}
343
344impl BitmexTimeInForce {
345 pub fn try_from_time_in_force(value: TimeInForce) -> anyhow::Result<Self> {
351 Self::try_from(value)
352 }
353}
354
355#[derive(
357 Copy,
358 Clone,
359 Debug,
360 Display,
361 PartialEq,
362 Eq,
363 AsRefStr,
364 EnumIter,
365 EnumString,
366 Serialize,
367 Deserialize,
368)]
369pub enum BitmexContingencyType {
370 OneCancelsTheOther,
371 OneTriggersTheOther,
372 OneUpdatesTheOtherAbsolute,
373 OneUpdatesTheOtherProportional,
374 #[serde(rename = "")]
375 Unknown, }
377
378impl From<BitmexContingencyType> for ContingencyType {
379 fn from(value: BitmexContingencyType) -> Self {
380 match value {
381 BitmexContingencyType::OneCancelsTheOther => Self::Oco,
382 BitmexContingencyType::OneTriggersTheOther => Self::Oto,
383 BitmexContingencyType::OneUpdatesTheOtherProportional => Self::Ouo,
384 BitmexContingencyType::OneUpdatesTheOtherAbsolute => Self::Ouo,
385 BitmexContingencyType::Unknown => Self::NoContingency,
386 }
387 }
388}
389
390impl TryFrom<ContingencyType> for BitmexContingencyType {
391 type Error = anyhow::Error;
392
393 fn try_from(value: ContingencyType) -> Result<Self, Self::Error> {
394 match value {
395 ContingencyType::NoContingency => Ok(Self::Unknown),
396 ContingencyType::Oco => Ok(Self::OneCancelsTheOther),
397 ContingencyType::Oto => Ok(Self::OneTriggersTheOther),
398 ContingencyType::Ouo => anyhow::bail!("OUO contingency type not supported by BitMEX"),
399 }
400 }
401}
402
403#[derive(
405 Copy,
406 Clone,
407 Debug,
408 Display,
409 PartialEq,
410 Eq,
411 AsRefStr,
412 EnumIter,
413 EnumString,
414 Serialize,
415 Deserialize,
416)]
417pub enum BitmexPegPriceType {
418 LastPeg,
419 OpeningPeg,
420 MidPricePeg,
421 MarketPeg,
422 PrimaryPeg,
423 PegToVWAP,
424 TrailingStopPeg,
425 PegToLimitPrice,
426 ShortSaleMinPricePeg,
427 #[serde(rename = "")]
428 Unknown, }
430
431#[derive(
433 Copy,
434 Clone,
435 Debug,
436 Display,
437 PartialEq,
438 Eq,
439 AsRefStr,
440 EnumIter,
441 EnumString,
442 Serialize,
443 Deserialize,
444)]
445pub enum BitmexExecInstruction {
446 ParticipateDoNotInitiate,
447 AllOrNone,
448 MarkPrice,
449 IndexPrice,
450 LastPrice,
451 Close,
452 ReduceOnly,
453 Fixed,
454 #[serde(rename = "")]
455 Unknown, }
457
458impl BitmexExecInstruction {
459 pub fn join(instructions: &[Self]) -> String {
461 instructions
462 .iter()
463 .map(ToString::to_string)
464 .collect::<Vec<_>>()
465 .join(",")
466 }
467}
468
469#[derive(Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize)]
471pub enum BitmexExecType {
472 New,
474 Trade,
476 Canceled,
478 CancelReject,
480 Replaced,
482 Rejected,
484 AmendReject,
486 Funding,
488 Settlement,
490 Suspended,
492 Released,
494 Insurance,
496 Rebalance,
498 Liquidation,
500 Bankruptcy,
502 TrialFill,
504 TriggeredOrActivatedBySystem,
506 #[strum(disabled)]
508 Unknown(String),
509}
510
511impl<'de> Deserialize<'de> for BitmexExecType {
512 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
513 where
514 D: Deserializer<'de>,
515 {
516 let s = String::deserialize(deserializer)?;
517
518 match s.as_str() {
519 "New" => Ok(Self::New),
520 "Trade" => Ok(Self::Trade),
521 "Canceled" => Ok(Self::Canceled),
522 "CancelReject" => Ok(Self::CancelReject),
523 "Replaced" => Ok(Self::Replaced),
524 "Rejected" => Ok(Self::Rejected),
525 "AmendReject" => Ok(Self::AmendReject),
526 "Funding" => Ok(Self::Funding),
527 "Settlement" => Ok(Self::Settlement),
528 "Suspended" => Ok(Self::Suspended),
529 "Released" => Ok(Self::Released),
530 "Insurance" => Ok(Self::Insurance),
531 "Rebalance" => Ok(Self::Rebalance),
532 "Liquidation" => Ok(Self::Liquidation),
533 "Bankruptcy" => Ok(Self::Bankruptcy),
534 "TrialFill" => Ok(Self::TrialFill),
535 "TriggeredOrActivatedBySystem" => Ok(Self::TriggeredOrActivatedBySystem),
536 other => Ok(Self::Unknown(other.to_string())),
537 }
538 }
539}
540
541#[derive(
543 Copy,
544 Clone,
545 Debug,
546 Display,
547 PartialEq,
548 Eq,
549 AsRefStr,
550 EnumIter,
551 EnumString,
552 Serialize,
553 Deserialize,
554)]
555pub enum BitmexLiquidityIndicator {
556 #[serde(rename = "Added")]
559 #[serde(alias = "AddedLiquidity")]
560 Maker,
561 #[serde(rename = "Removed")]
564 #[serde(alias = "RemovedLiquidity")]
565 Taker,
566}
567
568impl From<BitmexLiquidityIndicator> for LiquiditySide {
569 fn from(value: BitmexLiquidityIndicator) -> Self {
570 match value {
571 BitmexLiquidityIndicator::Maker => Self::Maker,
572 BitmexLiquidityIndicator::Taker => Self::Taker,
573 }
574 }
575}
576
577#[derive(
584 Copy,
585 Clone,
586 Debug,
587 Display,
588 PartialEq,
589 Eq,
590 AsRefStr,
591 EnumIter,
592 EnumString,
593 Serialize,
594 Deserialize,
595)]
596#[serde(rename_all = "UPPERCASE")]
597pub enum BitmexInstrumentType {
598 #[serde(rename = "FXXXS")]
600 LegacyFutures,
601
602 #[serde(rename = "FXXXN")]
604 LegacyFuturesN,
605
606 #[serde(rename = "FMXXS")]
608 FuturesSpreads,
609
610 #[serde(rename = "FFICSX")]
613 PredictionMarket,
614
615 #[serde(rename = "FFSCSX")]
618 StockPerpetual,
619
620 #[serde(rename = "FFWCSX")]
622 PerpetualContract,
623
624 #[serde(rename = "FFWCSF")]
626 PerpetualContractFx,
627
628 #[serde(rename = "FFCCSX")]
630 Futures,
631
632 #[serde(rename = "IFXXXP")]
634 Spot,
635
636 #[serde(rename = "OCECCS")]
638 CallOption,
639
640 #[serde(rename = "OPECCS")]
642 PutOption,
643
644 #[serde(rename = "SRMCSX")]
646 SwapRate,
647
648 #[serde(rename = "RCSXXX")]
650 ReferenceBasket,
651
652 #[serde(rename = "MRBXXX")]
654 BasketIndex,
655
656 #[serde(rename = "MRCXXX")]
658 CryptoIndex,
659
660 #[serde(rename = "MRFXXX")]
662 FxIndex,
663
664 #[serde(rename = "MRRXXX")]
666 LendingIndex,
667
668 #[serde(rename = "MRIXXX")]
670 VolatilityIndex,
671
672 #[serde(rename = "MRSXXX")]
674 StockIndex,
675
676 #[serde(rename = "MRVDXX")]
678 YieldIndex,
679}
680
681#[derive(Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize)]
683pub enum BitmexProductType {
684 #[serde(rename = "instrument")]
686 All,
687
688 #[serde(rename = "CONTRACTS")]
690 Contracts,
691
692 #[serde(rename = "INDICES")]
694 Indices,
695
696 #[serde(rename = "DERIVATIVES")]
698 Derivatives,
699
700 #[serde(rename = "SPOT")]
702 Spot,
703
704 #[serde(rename = "instrument")]
706 #[serde(untagged)]
707 Specific(String),
708}
709
710impl BitmexProductType {
711 #[must_use]
713 pub fn to_subscription(&self) -> Cow<'static, str> {
714 match self {
715 Self::All => Cow::Borrowed("instrument"),
716 Self::Specific(symbol) => Cow::Owned(format!("instrument:{symbol}")),
717 Self::Contracts => Cow::Borrowed("CONTRACTS"),
718 Self::Indices => Cow::Borrowed("INDICES"),
719 Self::Derivatives => Cow::Borrowed("DERIVATIVES"),
720 Self::Spot => Cow::Borrowed("SPOT"),
721 }
722 }
723}
724
725impl<'de> Deserialize<'de> for BitmexProductType {
726 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
727 where
728 D: Deserializer<'de>,
729 {
730 let s = String::deserialize(deserializer)?;
731
732 match s.as_str() {
733 "instrument" => Ok(Self::All),
734 "CONTRACTS" => Ok(Self::Contracts),
735 "INDICES" => Ok(Self::Indices),
736 "DERIVATIVES" => Ok(Self::Derivatives),
737 "SPOT" => Ok(Self::Spot),
738 s if s.starts_with("instrument:") => {
739 let symbol = s.strip_prefix("instrument:").unwrap();
740 Ok(Self::Specific(symbol.to_string()))
741 }
742 _ => Err(serde::de::Error::custom(format!(
743 "Invalid product type: {s}"
744 ))),
745 }
746 }
747}
748
749#[derive(
751 Copy,
752 Clone,
753 Debug,
754 Display,
755 PartialEq,
756 Eq,
757 AsRefStr,
758 EnumIter,
759 EnumString,
760 Serialize,
761 Deserialize,
762)]
763pub enum BitmexTickDirection {
764 PlusTick,
766 MinusTick,
768 ZeroPlusTick,
770 ZeroMinusTick,
772}
773
774#[derive(
776 Clone,
777 Copy,
778 Debug,
779 Display,
780 PartialEq,
781 Eq,
782 AsRefStr,
783 EnumIter,
784 EnumString,
785 Serialize,
786 Deserialize,
787)]
788pub enum BitmexInstrumentState {
789 Open,
791 Closed,
793 Unlisted,
795 Settled,
797 Delisted,
799}
800
801#[derive(
803 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
804)]
805pub enum BitmexFairMethod {
806 FundingRate,
808 ImpactMidPrice,
810 LastPrice,
812}
813
814#[derive(
816 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
817)]
818pub enum BitmexMarkMethod {
819 FairPrice,
821 FairPriceStox,
823 LastPrice,
825 LastPricePreLaunch,
827 CompositeIndex,
829}
830
831#[cfg(test)]
832mod tests {
833 use rstest::rstest;
834
835 use super::*;
836
837 #[rstest]
838 fn test_bitmex_side_deserialization() {
839 assert_eq!(
841 serde_json::from_str::<BitmexSide>(r#""Buy""#).unwrap(),
842 BitmexSide::Buy
843 );
844 assert_eq!(
845 serde_json::from_str::<BitmexSide>(r#""BUY""#).unwrap(),
846 BitmexSide::Buy
847 );
848 assert_eq!(
849 serde_json::from_str::<BitmexSide>(r#""buy""#).unwrap(),
850 BitmexSide::Buy
851 );
852 assert_eq!(
853 serde_json::from_str::<BitmexSide>(r#""Sell""#).unwrap(),
854 BitmexSide::Sell
855 );
856 assert_eq!(
857 serde_json::from_str::<BitmexSide>(r#""SELL""#).unwrap(),
858 BitmexSide::Sell
859 );
860 assert_eq!(
861 serde_json::from_str::<BitmexSide>(r#""sell""#).unwrap(),
862 BitmexSide::Sell
863 );
864 }
865
866 #[rstest]
867 fn test_bitmex_order_type_deserialization() {
868 assert_eq!(
869 serde_json::from_str::<BitmexOrderType>(r#""Market""#).unwrap(),
870 BitmexOrderType::Market
871 );
872 assert_eq!(
873 serde_json::from_str::<BitmexOrderType>(r#""Limit""#).unwrap(),
874 BitmexOrderType::Limit
875 );
876 assert_eq!(
877 serde_json::from_str::<BitmexOrderType>(r#""Stop""#).unwrap(),
878 BitmexOrderType::Stop
879 );
880 assert_eq!(
881 serde_json::from_str::<BitmexOrderType>(r#""StopLimit""#).unwrap(),
882 BitmexOrderType::StopLimit
883 );
884 assert_eq!(
885 serde_json::from_str::<BitmexOrderType>(r#""MarketIfTouched""#).unwrap(),
886 BitmexOrderType::MarketIfTouched
887 );
888 assert_eq!(
889 serde_json::from_str::<BitmexOrderType>(r#""LimitIfTouched""#).unwrap(),
890 BitmexOrderType::LimitIfTouched
891 );
892 assert_eq!(
893 serde_json::from_str::<BitmexOrderType>(r#""Pegged""#).unwrap(),
894 BitmexOrderType::Pegged
895 );
896 }
897
898 #[rstest]
899 fn test_instrument_type_serialization() {
900 assert_eq!(
902 serde_json::to_string(&BitmexInstrumentType::PerpetualContract).unwrap(),
903 r#""FFWCSX""#
904 );
905 assert_eq!(
906 serde_json::to_string(&BitmexInstrumentType::PerpetualContractFx).unwrap(),
907 r#""FFWCSF""#
908 );
909 assert_eq!(
910 serde_json::to_string(&BitmexInstrumentType::StockPerpetual).unwrap(),
911 r#""FFSCSX""#
912 );
913 assert_eq!(
914 serde_json::to_string(&BitmexInstrumentType::Spot).unwrap(),
915 r#""IFXXXP""#
916 );
917 assert_eq!(
918 serde_json::to_string(&BitmexInstrumentType::Futures).unwrap(),
919 r#""FFCCSX""#
920 );
921 assert_eq!(
922 serde_json::to_string(&BitmexInstrumentType::PredictionMarket).unwrap(),
923 r#""FFICSX""#
924 );
925 assert_eq!(
926 serde_json::to_string(&BitmexInstrumentType::CallOption).unwrap(),
927 r#""OCECCS""#
928 );
929 assert_eq!(
930 serde_json::to_string(&BitmexInstrumentType::PutOption).unwrap(),
931 r#""OPECCS""#
932 );
933 assert_eq!(
934 serde_json::to_string(&BitmexInstrumentType::SwapRate).unwrap(),
935 r#""SRMCSX""#
936 );
937
938 assert_eq!(
940 serde_json::to_string(&BitmexInstrumentType::LegacyFutures).unwrap(),
941 r#""FXXXS""#
942 );
943 assert_eq!(
944 serde_json::to_string(&BitmexInstrumentType::LegacyFuturesN).unwrap(),
945 r#""FXXXN""#
946 );
947 assert_eq!(
948 serde_json::to_string(&BitmexInstrumentType::FuturesSpreads).unwrap(),
949 r#""FMXXS""#
950 );
951 assert_eq!(
952 serde_json::to_string(&BitmexInstrumentType::ReferenceBasket).unwrap(),
953 r#""RCSXXX""#
954 );
955
956 assert_eq!(
958 serde_json::to_string(&BitmexInstrumentType::BasketIndex).unwrap(),
959 r#""MRBXXX""#
960 );
961 assert_eq!(
962 serde_json::to_string(&BitmexInstrumentType::CryptoIndex).unwrap(),
963 r#""MRCXXX""#
964 );
965 assert_eq!(
966 serde_json::to_string(&BitmexInstrumentType::FxIndex).unwrap(),
967 r#""MRFXXX""#
968 );
969 assert_eq!(
970 serde_json::to_string(&BitmexInstrumentType::LendingIndex).unwrap(),
971 r#""MRRXXX""#
972 );
973 assert_eq!(
974 serde_json::to_string(&BitmexInstrumentType::VolatilityIndex).unwrap(),
975 r#""MRIXXX""#
976 );
977 assert_eq!(
978 serde_json::to_string(&BitmexInstrumentType::StockIndex).unwrap(),
979 r#""MRSXXX""#
980 );
981 assert_eq!(
982 serde_json::to_string(&BitmexInstrumentType::YieldIndex).unwrap(),
983 r#""MRVDXX""#
984 );
985 }
986
987 #[rstest]
988 fn test_instrument_type_deserialization() {
989 assert_eq!(
991 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSX""#).unwrap(),
992 BitmexInstrumentType::PerpetualContract
993 );
994 assert_eq!(
995 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSF""#).unwrap(),
996 BitmexInstrumentType::PerpetualContractFx
997 );
998 assert_eq!(
999 serde_json::from_str::<BitmexInstrumentType>(r#""FFSCSX""#).unwrap(),
1000 BitmexInstrumentType::StockPerpetual
1001 );
1002 assert_eq!(
1003 serde_json::from_str::<BitmexInstrumentType>(r#""IFXXXP""#).unwrap(),
1004 BitmexInstrumentType::Spot
1005 );
1006 assert_eq!(
1007 serde_json::from_str::<BitmexInstrumentType>(r#""FFCCSX""#).unwrap(),
1008 BitmexInstrumentType::Futures
1009 );
1010 assert_eq!(
1011 serde_json::from_str::<BitmexInstrumentType>(r#""FFICSX""#).unwrap(),
1012 BitmexInstrumentType::PredictionMarket
1013 );
1014 assert_eq!(
1015 serde_json::from_str::<BitmexInstrumentType>(r#""OCECCS""#).unwrap(),
1016 BitmexInstrumentType::CallOption
1017 );
1018 assert_eq!(
1019 serde_json::from_str::<BitmexInstrumentType>(r#""OPECCS""#).unwrap(),
1020 BitmexInstrumentType::PutOption
1021 );
1022 assert_eq!(
1023 serde_json::from_str::<BitmexInstrumentType>(r#""SRMCSX""#).unwrap(),
1024 BitmexInstrumentType::SwapRate
1025 );
1026
1027 assert_eq!(
1029 serde_json::from_str::<BitmexInstrumentType>(r#""FXXXS""#).unwrap(),
1030 BitmexInstrumentType::LegacyFutures
1031 );
1032 assert_eq!(
1033 serde_json::from_str::<BitmexInstrumentType>(r#""FXXXN""#).unwrap(),
1034 BitmexInstrumentType::LegacyFuturesN
1035 );
1036 assert_eq!(
1037 serde_json::from_str::<BitmexInstrumentType>(r#""FMXXS""#).unwrap(),
1038 BitmexInstrumentType::FuturesSpreads
1039 );
1040 assert_eq!(
1041 serde_json::from_str::<BitmexInstrumentType>(r#""RCSXXX""#).unwrap(),
1042 BitmexInstrumentType::ReferenceBasket
1043 );
1044
1045 assert_eq!(
1047 serde_json::from_str::<BitmexInstrumentType>(r#""MRBXXX""#).unwrap(),
1048 BitmexInstrumentType::BasketIndex
1049 );
1050 assert_eq!(
1051 serde_json::from_str::<BitmexInstrumentType>(r#""MRCXXX""#).unwrap(),
1052 BitmexInstrumentType::CryptoIndex
1053 );
1054 assert_eq!(
1055 serde_json::from_str::<BitmexInstrumentType>(r#""MRFXXX""#).unwrap(),
1056 BitmexInstrumentType::FxIndex
1057 );
1058 assert_eq!(
1059 serde_json::from_str::<BitmexInstrumentType>(r#""MRRXXX""#).unwrap(),
1060 BitmexInstrumentType::LendingIndex
1061 );
1062 assert_eq!(
1063 serde_json::from_str::<BitmexInstrumentType>(r#""MRIXXX""#).unwrap(),
1064 BitmexInstrumentType::VolatilityIndex
1065 );
1066 assert_eq!(
1067 serde_json::from_str::<BitmexInstrumentType>(r#""MRSXXX""#).unwrap(),
1068 BitmexInstrumentType::StockIndex
1069 );
1070 assert_eq!(
1071 serde_json::from_str::<BitmexInstrumentType>(r#""MRVDXX""#).unwrap(),
1072 BitmexInstrumentType::YieldIndex
1073 );
1074
1075 assert!(serde_json::from_str::<BitmexInstrumentType>(r#""INVALID""#).is_err());
1077 }
1078
1079 #[rstest]
1080 fn test_subscription_strings() {
1081 assert_eq!(BitmexProductType::All.to_subscription(), "instrument");
1082 assert_eq!(
1083 BitmexProductType::Specific("XBTUSD".to_string()).to_subscription(),
1084 "instrument:XBTUSD"
1085 );
1086 assert_eq!(BitmexProductType::Contracts.to_subscription(), "CONTRACTS");
1087 assert_eq!(BitmexProductType::Indices.to_subscription(), "INDICES");
1088 assert_eq!(
1089 BitmexProductType::Derivatives.to_subscription(),
1090 "DERIVATIVES"
1091 );
1092 assert_eq!(BitmexProductType::Spot.to_subscription(), "SPOT");
1093 }
1094
1095 #[rstest]
1096 fn test_serialization() {
1097 assert_eq!(
1099 serde_json::to_string(&BitmexProductType::All).unwrap(),
1100 r#""instrument""#
1101 );
1102 assert_eq!(
1103 serde_json::to_string(&BitmexProductType::Specific("XBTUSD".to_string())).unwrap(),
1104 r#""XBTUSD""#
1105 );
1106 assert_eq!(
1107 serde_json::to_string(&BitmexProductType::Contracts).unwrap(),
1108 r#""CONTRACTS""#
1109 );
1110 }
1111
1112 #[rstest]
1113 fn test_deserialization() {
1114 assert_eq!(
1115 serde_json::from_str::<BitmexProductType>(r#""instrument""#).unwrap(),
1116 BitmexProductType::All
1117 );
1118 assert_eq!(
1119 serde_json::from_str::<BitmexProductType>(r#""instrument:XBTUSD""#).unwrap(),
1120 BitmexProductType::Specific("XBTUSD".to_string())
1121 );
1122 assert_eq!(
1123 serde_json::from_str::<BitmexProductType>(r#""CONTRACTS""#).unwrap(),
1124 BitmexProductType::Contracts
1125 );
1126 }
1127
1128 #[rstest]
1129 fn test_error_cases() {
1130 assert!(serde_json::from_str::<BitmexProductType>(r#""invalid_type""#).is_err());
1131 assert!(serde_json::from_str::<BitmexProductType>(r"123").is_err());
1132 assert!(serde_json::from_str::<BitmexProductType>(r"{}").is_err());
1133 }
1134
1135 #[rstest]
1136 fn test_order_side_from_specified() {
1137 assert_eq!(BitmexSide::from(OrderSideSpecified::Buy), BitmexSide::Buy);
1138 assert_eq!(BitmexSide::from(OrderSideSpecified::Sell), BitmexSide::Sell);
1139 }
1140
1141 #[rstest]
1142 fn test_order_type_try_from() {
1143 assert_eq!(
1145 BitmexOrderType::try_from(OrderType::Market).unwrap(),
1146 BitmexOrderType::Market
1147 );
1148 assert_eq!(
1149 BitmexOrderType::try_from(OrderType::Limit).unwrap(),
1150 BitmexOrderType::Limit
1151 );
1152
1153 let result = BitmexOrderType::try_from(OrderType::MarketToLimit);
1155 assert!(result.is_err());
1156 assert!(result.unwrap_err().to_string().contains("not supported"));
1157 }
1158
1159 #[rstest]
1160 fn test_time_in_force_conversions() {
1161 assert_eq!(
1163 TimeInForce::try_from(BitmexTimeInForce::Day).unwrap(),
1164 TimeInForce::Day
1165 );
1166 assert_eq!(
1167 TimeInForce::try_from(BitmexTimeInForce::GoodTillCancel).unwrap(),
1168 TimeInForce::Gtc
1169 );
1170 assert_eq!(
1171 TimeInForce::try_from(BitmexTimeInForce::ImmediateOrCancel).unwrap(),
1172 TimeInForce::Ioc
1173 );
1174
1175 let result = TimeInForce::try_from(BitmexTimeInForce::GoodTillCrossing);
1177 assert!(result.is_err());
1178 assert!(result.unwrap_err().to_string().contains("Unsupported"));
1179
1180 assert_eq!(
1182 BitmexTimeInForce::try_from(TimeInForce::Day).unwrap(),
1183 BitmexTimeInForce::Day
1184 );
1185 assert_eq!(
1186 BitmexTimeInForce::try_from(TimeInForce::Gtc).unwrap(),
1187 BitmexTimeInForce::GoodTillCancel
1188 );
1189 assert_eq!(
1190 BitmexTimeInForce::try_from(TimeInForce::Fok).unwrap(),
1191 BitmexTimeInForce::FillOrKill
1192 );
1193 }
1194
1195 #[rstest]
1196 fn test_helper_methods() {
1197 let result = BitmexOrderType::try_from_order_type(OrderType::Limit);
1199 assert!(result.is_ok());
1200 assert_eq!(result.unwrap(), BitmexOrderType::Limit);
1201
1202 let result = BitmexOrderType::try_from_order_type(OrderType::MarketToLimit);
1203 assert!(result.is_err());
1204
1205 let result = BitmexTimeInForce::try_from_time_in_force(TimeInForce::Ioc);
1207 assert!(result.is_ok());
1208 assert_eq!(result.unwrap(), BitmexTimeInForce::ImmediateOrCancel);
1209 }
1210}