1use nautilus_model::enums::{
17 ContingencyType, LiquiditySide, OrderSide, OrderStatus, OrderType, PositionSide, TimeInForce,
18};
19use serde::{Deserialize, Deserializer, Serialize};
20use strum::{AsRefStr, Display, EnumIter, EnumString};
21
22use crate::error::{BitmexError, BitmexNonRetryableError};
23
24#[derive(
26 Copy,
27 Clone,
28 Debug,
29 Display,
30 PartialEq,
31 Eq,
32 AsRefStr,
33 EnumIter,
34 EnumString,
35 Serialize,
36 Deserialize,
37)]
38#[serde(rename_all = "PascalCase")]
39#[cfg_attr(
40 feature = "python",
41 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bitmex", eq, eq_int)
42)]
43pub enum BitmexSymbolStatus {
44 Open,
46 Closed,
48 Unlisted,
50}
51
52#[derive(
54 Copy,
55 Clone,
56 Debug,
57 Display,
58 PartialEq,
59 Eq,
60 AsRefStr,
61 EnumIter,
62 EnumString,
63 Serialize,
64 Deserialize,
65)]
66pub enum BitmexSide {
67 #[serde(rename = "Buy", alias = "BUY", alias = "buy")]
69 Buy,
70 #[serde(rename = "Sell", alias = "SELL", alias = "sell")]
72 Sell,
73}
74
75impl TryFrom<OrderSide> for BitmexSide {
76 type Error = BitmexError;
77
78 fn try_from(value: OrderSide) -> Result<Self, Self::Error> {
79 match value {
80 OrderSide::Buy => Ok(Self::Buy),
81 OrderSide::Sell => Ok(Self::Sell),
82 _ => Err(BitmexError::NonRetryable {
83 source: BitmexNonRetryableError::Validation {
84 field: "order_side".to_string(),
85 message: format!("Invalid order side: {value:?}"),
86 },
87 }),
88 }
89 }
90}
91
92impl BitmexSide {
93 pub fn try_from_order_side(value: OrderSide) -> anyhow::Result<Self> {
99 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
100 }
101}
102
103impl From<BitmexSide> for OrderSide {
104 fn from(side: BitmexSide) -> Self {
105 match side {
106 BitmexSide::Buy => Self::Buy,
107 BitmexSide::Sell => Self::Sell,
108 }
109 }
110}
111
112#[derive(
114 Copy,
115 Clone,
116 Debug,
117 Display,
118 PartialEq,
119 Eq,
120 AsRefStr,
121 EnumIter,
122 EnumString,
123 Serialize,
124 Deserialize,
125)]
126#[cfg_attr(
127 feature = "python",
128 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bitmex", eq, eq_int)
129)]
130pub enum BitmexPositionSide {
131 #[serde(rename = "LONG", alias = "Long", alias = "long")]
133 Long,
134 #[serde(rename = "SHORT", alias = "Short", alias = "short")]
136 Short,
137 #[serde(rename = "FLAT", alias = "Flat", alias = "flat")]
139 Flat,
140}
141
142impl From<BitmexPositionSide> for PositionSide {
143 fn from(side: BitmexPositionSide) -> Self {
144 match side {
145 BitmexPositionSide::Long => Self::Long,
146 BitmexPositionSide::Short => Self::Short,
147 BitmexPositionSide::Flat => Self::Flat,
148 }
149 }
150}
151
152impl From<PositionSide> for BitmexPositionSide {
153 fn from(side: PositionSide) -> Self {
154 match side {
155 PositionSide::Long => Self::Long,
156 PositionSide::Short => Self::Short,
157 PositionSide::Flat | PositionSide::NoPositionSide => Self::Flat,
158 }
159 }
160}
161
162#[derive(
164 Copy,
165 Clone,
166 Debug,
167 Display,
168 PartialEq,
169 Eq,
170 AsRefStr,
171 EnumIter,
172 EnumString,
173 Serialize,
174 Deserialize,
175)]
176pub enum BitmexOrderType {
177 Market,
179 Limit,
181 Stop,
183 StopLimit,
185 MarketIfTouched,
187 LimitIfTouched,
189 Pegged,
191}
192
193impl TryFrom<OrderType> for BitmexOrderType {
194 type Error = BitmexError;
195
196 fn try_from(value: OrderType) -> Result<Self, Self::Error> {
197 match value {
198 OrderType::Market => Ok(Self::Market),
199 OrderType::Limit => Ok(Self::Limit),
200 OrderType::StopMarket => Ok(Self::Stop),
201 OrderType::StopLimit => Ok(Self::StopLimit),
202 OrderType::MarketIfTouched => Ok(Self::MarketIfTouched),
203 OrderType::LimitIfTouched => Ok(Self::LimitIfTouched),
204 OrderType::TrailingStopMarket => Ok(Self::Pegged),
205 OrderType::TrailingStopLimit => Ok(Self::Pegged),
206 OrderType::MarketToLimit => Err(BitmexError::NonRetryable {
207 source: BitmexNonRetryableError::Validation {
208 field: "order_type".to_string(),
209 message: "MarketToLimit order type is not supported by BitMEX".to_string(),
210 },
211 }),
212 }
213 }
214}
215
216impl BitmexOrderType {
217 pub fn try_from_order_type(value: OrderType) -> anyhow::Result<Self> {
223 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
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 PartiallyFilled,
260 Filled,
262 PendingCancel,
264 Canceled,
266 Rejected,
268 Expired,
270}
271
272impl From<BitmexOrderStatus> for OrderStatus {
273 fn from(value: BitmexOrderStatus) -> Self {
274 match value {
275 BitmexOrderStatus::New => Self::Accepted,
276 BitmexOrderStatus::PartiallyFilled => Self::PartiallyFilled,
277 BitmexOrderStatus::Filled => Self::Filled,
278 BitmexOrderStatus::PendingCancel => Self::PendingCancel,
279 BitmexOrderStatus::Canceled => Self::Canceled,
280 BitmexOrderStatus::Rejected => Self::Rejected,
281 BitmexOrderStatus::Expired => Self::Expired,
282 }
283 }
284}
285
286#[derive(
288 Copy,
289 Clone,
290 Debug,
291 Display,
292 PartialEq,
293 Eq,
294 AsRefStr,
295 EnumIter,
296 EnumString,
297 Serialize,
298 Deserialize,
299)]
300pub enum BitmexTimeInForce {
301 Day,
302 GoodTillCancel,
303 AtTheOpening,
304 ImmediateOrCancel,
305 FillOrKill,
306 GoodTillCrossing,
307 GoodTillDate,
308 AtTheClose,
309 GoodThroughCrossing,
310 AtCrossing,
311}
312
313impl TryFrom<BitmexTimeInForce> for TimeInForce {
314 type Error = BitmexError;
315
316 fn try_from(value: BitmexTimeInForce) -> Result<Self, Self::Error> {
317 match value {
318 BitmexTimeInForce::Day => Ok(Self::Day),
319 BitmexTimeInForce::GoodTillCancel => Ok(Self::Gtc),
320 BitmexTimeInForce::GoodTillDate => Ok(Self::Gtd),
321 BitmexTimeInForce::ImmediateOrCancel => Ok(Self::Ioc),
322 BitmexTimeInForce::FillOrKill => Ok(Self::Fok),
323 BitmexTimeInForce::AtTheOpening => Ok(Self::AtTheOpen),
324 BitmexTimeInForce::AtTheClose => Ok(Self::AtTheClose),
325 _ => Err(BitmexError::NonRetryable {
326 source: BitmexNonRetryableError::Validation {
327 field: "time_in_force".to_string(),
328 message: format!("Unsupported BitmexTimeInForce: {value}"),
329 },
330 }),
331 }
332 }
333}
334
335impl TryFrom<TimeInForce> for BitmexTimeInForce {
336 type Error = crate::error::BitmexError;
337
338 fn try_from(value: TimeInForce) -> Result<Self, Self::Error> {
339 match value {
340 TimeInForce::Day => Ok(Self::Day),
341 TimeInForce::Gtc => Ok(Self::GoodTillCancel),
342 TimeInForce::Gtd => Ok(Self::GoodTillDate),
343 TimeInForce::Ioc => Ok(Self::ImmediateOrCancel),
344 TimeInForce::Fok => Ok(Self::FillOrKill),
345 TimeInForce::AtTheOpen => Ok(Self::AtTheOpening),
346 TimeInForce::AtTheClose => Ok(Self::AtTheClose),
347 }
348 }
349}
350
351impl BitmexTimeInForce {
352 pub fn try_from_time_in_force(value: TimeInForce) -> anyhow::Result<Self> {
358 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
359 }
360}
361
362#[derive(
364 Copy,
365 Clone,
366 Debug,
367 Display,
368 PartialEq,
369 Eq,
370 AsRefStr,
371 EnumIter,
372 EnumString,
373 Serialize,
374 Deserialize,
375)]
376pub enum BitmexContingencyType {
377 OneCancelsTheOther,
378 OneTriggersTheOther,
379 OneUpdatesTheOtherAbsolute,
380 OneUpdatesTheOtherProportional,
381 #[serde(rename = "")]
382 Unknown, }
384
385impl From<BitmexContingencyType> for ContingencyType {
386 fn from(value: BitmexContingencyType) -> Self {
387 match value {
388 BitmexContingencyType::OneCancelsTheOther => Self::Oco,
389 BitmexContingencyType::OneTriggersTheOther => Self::Oto,
390 BitmexContingencyType::OneUpdatesTheOtherProportional => Self::Ouo,
391 BitmexContingencyType::OneUpdatesTheOtherAbsolute => Self::Ouo,
392 BitmexContingencyType::Unknown => Self::NoContingency,
393 }
394 }
395}
396
397#[derive(
399 Copy,
400 Clone,
401 Debug,
402 Display,
403 PartialEq,
404 Eq,
405 AsRefStr,
406 EnumIter,
407 EnumString,
408 Serialize,
409 Deserialize,
410)]
411pub enum BitmexPegPriceType {
412 LastPeg,
413 OpeningPeg,
414 MidPricePeg,
415 MarketPeg,
416 PrimaryPeg,
417 PegToVWAP,
418 TrailingStopPeg,
419 PegToLimitPrice,
420 ShortSaleMinPricePeg,
421 #[serde(rename = "")]
422 Unknown, }
424
425#[derive(
427 Copy,
428 Clone,
429 Debug,
430 Display,
431 PartialEq,
432 Eq,
433 AsRefStr,
434 EnumIter,
435 EnumString,
436 Serialize,
437 Deserialize,
438)]
439pub enum BitmexExecInstruction {
440 ParticipateDoNotInitiate,
441 AllOrNone,
442 MarkPrice,
443 IndexPrice,
444 LastPrice,
445 Close,
446 ReduceOnly,
447 Fixed,
448 #[serde(rename = "")]
449 Unknown, }
451
452impl BitmexExecInstruction {
453 pub fn join(instructions: &[Self]) -> String {
454 instructions
455 .iter()
456 .map(std::string::ToString::to_string)
457 .collect::<Vec<_>>()
458 .join(",")
459 }
460}
461
462#[derive(
464 Copy,
465 Clone,
466 Debug,
467 Display,
468 PartialEq,
469 Eq,
470 AsRefStr,
471 EnumIter,
472 EnumString,
473 Serialize,
474 Deserialize,
475)]
476pub enum BitmexExecType {
477 New,
479 Trade,
481 Canceled,
483 CancelReject,
485 Replaced,
487 Rejected,
489 AmendReject,
491 Funding,
493 Settlement,
495 Suspended,
497 Released,
499 Insurance,
501 Rebalance,
503 Liquidation,
505 Bankruptcy,
507 TrialFill,
509}
510
511#[derive(
513 Copy,
514 Clone,
515 Debug,
516 Display,
517 PartialEq,
518 Eq,
519 AsRefStr,
520 EnumIter,
521 EnumString,
522 Serialize,
523 Deserialize,
524)]
525pub enum BitmexLiquidityIndicator {
526 #[serde(rename = "Added")]
529 #[serde(alias = "AddedLiquidity")]
530 Maker,
531 #[serde(rename = "Removed")]
534 #[serde(alias = "RemovedLiquidity")]
535 Taker,
536}
537
538impl From<BitmexLiquidityIndicator> for LiquiditySide {
539 fn from(value: BitmexLiquidityIndicator) -> Self {
540 match value {
541 BitmexLiquidityIndicator::Maker => Self::Maker,
542 BitmexLiquidityIndicator::Taker => Self::Taker,
543 }
544 }
545}
546
547#[derive(
549 Copy,
550 Clone,
551 Debug,
552 Display,
553 PartialEq,
554 Eq,
555 AsRefStr,
556 EnumIter,
557 EnumString,
558 Serialize,
559 Deserialize,
560)]
561#[serde(rename_all = "UPPERCASE")]
562pub enum BitmexInstrumentType {
563 #[serde(rename = "FXXXS")]
564 Unknown1, #[serde(rename = "FMXXS")]
567 Unknown2, #[serde(rename = "FFICSX")]
570 Unknown3, #[serde(rename = "FFWCSX")]
574 PerpetualContract,
575
576 #[serde(rename = "FFWCSF")]
578 PerpetualContractFx,
579
580 #[serde(rename = "IFXXXP")]
582 Spot,
583
584 #[serde(rename = "FFCCSX")]
586 Futures,
587
588 #[serde(rename = "MRBXXX")]
590 BasketIndex,
591
592 #[serde(rename = "MRCXXX")]
594 CryptoIndex,
595
596 #[serde(rename = "MRFXXX")]
598 FxIndex,
599
600 #[serde(rename = "MRRXXX")]
602 LendingIndex,
603
604 #[serde(rename = "MRIXXX")]
606 VolatilityIndex,
607}
608
609#[derive(Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize)]
611pub enum BitmexProductType {
612 #[serde(rename = "instrument")]
614 All,
615
616 #[serde(rename = "CONTRACTS")]
618 Contracts,
619
620 #[serde(rename = "INDICES")]
622 Indices,
623
624 #[serde(rename = "DERIVATIVES")]
626 Derivatives,
627
628 #[serde(rename = "SPOT")]
630 Spot,
631
632 #[serde(rename = "instrument")]
634 #[serde(untagged)]
635 Specific(String),
636}
637
638impl BitmexProductType {
639 #[must_use]
641 pub fn to_subscription(&self) -> String {
642 match self {
643 Self::All => "instrument".to_string(),
644 Self::Specific(symbol) => format!("instrument:{symbol}"),
645 Self::Contracts => "CONTRACTS".to_string(),
646 Self::Indices => "INDICES".to_string(),
647 Self::Derivatives => "DERIVATIVES".to_string(),
648 Self::Spot => "SPOT".to_string(),
649 }
650 }
651}
652
653impl<'de> Deserialize<'de> for BitmexProductType {
654 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
655 where
656 D: Deserializer<'de>,
657 {
658 let s = String::deserialize(deserializer)?;
659
660 match s.as_str() {
661 "instrument" => Ok(Self::All),
662 "CONTRACTS" => Ok(Self::Contracts),
663 "INDICES" => Ok(Self::Indices),
664 "DERIVATIVES" => Ok(Self::Derivatives),
665 "SPOT" => Ok(Self::Spot),
666 s if s.starts_with("instrument:") => {
667 let symbol = s.strip_prefix("instrument:").unwrap();
668 Ok(Self::Specific(symbol.to_string()))
669 }
670 _ => Err(serde::de::Error::custom(format!(
671 "Invalid product type: {s}"
672 ))),
673 }
674 }
675}
676
677#[derive(
679 Copy,
680 Clone,
681 Debug,
682 Display,
683 PartialEq,
684 Eq,
685 AsRefStr,
686 EnumIter,
687 EnumString,
688 Serialize,
689 Deserialize,
690)]
691pub enum BitmexTickDirection {
692 PlusTick,
694 MinusTick,
696 ZeroPlusTick,
698 ZeroMinusTick,
700}
701
702#[derive(
704 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
705)]
706pub enum BitmexInstrumentState {
707 Open,
709 Closed,
711 Unlisted,
713 Settled,
715}
716
717#[derive(
719 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
720)]
721pub enum BitmexFairMethod {
722 FundingRate,
724 ImpactMidPrice,
726 LastPrice,
728}
729
730#[derive(
732 Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
733)]
734pub enum BitmexMarkMethod {
735 FairPrice,
737 LastPrice,
739 CompositeIndex,
741}
742
743#[cfg(test)]
748mod tests {
749 use rstest::rstest;
750
751 use super::*;
752
753 #[rstest]
754 fn test_bitmex_side_deserialization() {
755 assert_eq!(
757 serde_json::from_str::<BitmexSide>(r#""Buy""#).unwrap(),
758 BitmexSide::Buy
759 );
760 assert_eq!(
761 serde_json::from_str::<BitmexSide>(r#""BUY""#).unwrap(),
762 BitmexSide::Buy
763 );
764 assert_eq!(
765 serde_json::from_str::<BitmexSide>(r#""buy""#).unwrap(),
766 BitmexSide::Buy
767 );
768 assert_eq!(
769 serde_json::from_str::<BitmexSide>(r#""Sell""#).unwrap(),
770 BitmexSide::Sell
771 );
772 assert_eq!(
773 serde_json::from_str::<BitmexSide>(r#""SELL""#).unwrap(),
774 BitmexSide::Sell
775 );
776 assert_eq!(
777 serde_json::from_str::<BitmexSide>(r#""sell""#).unwrap(),
778 BitmexSide::Sell
779 );
780 }
781
782 #[rstest]
783 fn test_instrument_type_serialization() {
784 assert_eq!(
785 serde_json::to_string(&BitmexInstrumentType::PerpetualContract).unwrap(),
786 r#""FFWCSX""#
787 );
788 assert_eq!(
789 serde_json::to_string(&BitmexInstrumentType::PerpetualContractFx).unwrap(),
790 r#""FFWCSF""#
791 );
792 assert_eq!(
793 serde_json::to_string(&BitmexInstrumentType::Spot).unwrap(),
794 r#""IFXXXP""#
795 );
796 assert_eq!(
797 serde_json::to_string(&BitmexInstrumentType::Futures).unwrap(),
798 r#""FFCCSX""#
799 );
800 assert_eq!(
801 serde_json::to_string(&BitmexInstrumentType::BasketIndex).unwrap(),
802 r#""MRBXXX""#
803 );
804 assert_eq!(
805 serde_json::to_string(&BitmexInstrumentType::CryptoIndex).unwrap(),
806 r#""MRCXXX""#
807 );
808 assert_eq!(
809 serde_json::to_string(&BitmexInstrumentType::FxIndex).unwrap(),
810 r#""MRFXXX""#
811 );
812 assert_eq!(
813 serde_json::to_string(&BitmexInstrumentType::LendingIndex).unwrap(),
814 r#""MRRXXX""#
815 );
816 assert_eq!(
817 serde_json::to_string(&BitmexInstrumentType::VolatilityIndex).unwrap(),
818 r#""MRIXXX""#
819 );
820 }
821
822 #[rstest]
823 fn test_instrument_type_deserialization() {
824 assert_eq!(
825 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSX""#).unwrap(),
826 BitmexInstrumentType::PerpetualContract
827 );
828 assert_eq!(
829 serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSF""#).unwrap(),
830 BitmexInstrumentType::PerpetualContractFx
831 );
832 assert_eq!(
833 serde_json::from_str::<BitmexInstrumentType>(r#""IFXXXP""#).unwrap(),
834 BitmexInstrumentType::Spot
835 );
836 assert_eq!(
837 serde_json::from_str::<BitmexInstrumentType>(r#""FFCCSX""#).unwrap(),
838 BitmexInstrumentType::Futures
839 );
840 assert_eq!(
841 serde_json::from_str::<BitmexInstrumentType>(r#""MRBXXX""#).unwrap(),
842 BitmexInstrumentType::BasketIndex
843 );
844 assert_eq!(
845 serde_json::from_str::<BitmexInstrumentType>(r#""MRCXXX""#).unwrap(),
846 BitmexInstrumentType::CryptoIndex
847 );
848 assert_eq!(
849 serde_json::from_str::<BitmexInstrumentType>(r#""MRFXXX""#).unwrap(),
850 BitmexInstrumentType::FxIndex
851 );
852 assert_eq!(
853 serde_json::from_str::<BitmexInstrumentType>(r#""MRRXXX""#).unwrap(),
854 BitmexInstrumentType::LendingIndex
855 );
856 assert_eq!(
857 serde_json::from_str::<BitmexInstrumentType>(r#""MRIXXX""#).unwrap(),
858 BitmexInstrumentType::VolatilityIndex
859 );
860
861 assert!(serde_json::from_str::<BitmexInstrumentType>(r#""INVALID""#).is_err());
863 }
864
865 #[rstest]
866 fn test_subscription_strings() {
867 assert_eq!(BitmexProductType::All.to_subscription(), "instrument");
868 assert_eq!(
869 BitmexProductType::Specific("XBTUSD".to_string()).to_subscription(),
870 "instrument:XBTUSD"
871 );
872 assert_eq!(BitmexProductType::Contracts.to_subscription(), "CONTRACTS");
873 assert_eq!(BitmexProductType::Indices.to_subscription(), "INDICES");
874 assert_eq!(
875 BitmexProductType::Derivatives.to_subscription(),
876 "DERIVATIVES"
877 );
878 assert_eq!(BitmexProductType::Spot.to_subscription(), "SPOT");
879 }
880
881 #[rstest]
882 fn test_serialization() {
883 assert_eq!(
885 serde_json::to_string(&BitmexProductType::All).unwrap(),
886 r#""instrument""#
887 );
888 assert_eq!(
889 serde_json::to_string(&BitmexProductType::Specific("XBTUSD".to_string())).unwrap(),
890 r#""XBTUSD""#
891 );
892 assert_eq!(
893 serde_json::to_string(&BitmexProductType::Contracts).unwrap(),
894 r#""CONTRACTS""#
895 );
896 }
897
898 #[rstest]
899 fn test_deserialization() {
900 assert_eq!(
901 serde_json::from_str::<BitmexProductType>(r#""instrument""#).unwrap(),
902 BitmexProductType::All
903 );
904 assert_eq!(
905 serde_json::from_str::<BitmexProductType>(r#""instrument:XBTUSD""#).unwrap(),
906 BitmexProductType::Specific("XBTUSD".to_string())
907 );
908 assert_eq!(
909 serde_json::from_str::<BitmexProductType>(r#""CONTRACTS""#).unwrap(),
910 BitmexProductType::Contracts
911 );
912 }
913
914 #[rstest]
915 fn test_error_cases() {
916 assert!(serde_json::from_str::<BitmexProductType>(r#""invalid_type""#).is_err());
917 assert!(serde_json::from_str::<BitmexProductType>(r"123").is_err());
918 assert!(serde_json::from_str::<BitmexProductType>(r"{}").is_err());
919 }
920
921 #[rstest]
922 fn test_order_side_try_from() {
923 assert_eq!(
925 BitmexSide::try_from(OrderSide::Buy).unwrap(),
926 BitmexSide::Buy
927 );
928 assert_eq!(
929 BitmexSide::try_from(OrderSide::Sell).unwrap(),
930 BitmexSide::Sell
931 );
932
933 let result = BitmexSide::try_from(OrderSide::NoOrderSide);
935 assert!(result.is_err());
936 match result {
937 Err(BitmexError::NonRetryable {
938 source: BitmexNonRetryableError::Validation { field, .. },
939 ..
940 }) => {
941 assert_eq!(field, "order_side");
942 }
943 _ => panic!("Expected validation error"),
944 }
945 }
946
947 #[rstest]
948 fn test_order_type_try_from() {
949 assert_eq!(
951 BitmexOrderType::try_from(OrderType::Market).unwrap(),
952 BitmexOrderType::Market
953 );
954 assert_eq!(
955 BitmexOrderType::try_from(OrderType::Limit).unwrap(),
956 BitmexOrderType::Limit
957 );
958
959 let result = BitmexOrderType::try_from(OrderType::MarketToLimit);
961 assert!(result.is_err());
962 match result {
963 Err(BitmexError::NonRetryable {
964 source: BitmexNonRetryableError::Validation { message, .. },
965 ..
966 }) => {
967 assert!(message.contains("not supported"));
968 }
969 _ => panic!("Expected validation error"),
970 }
971 }
972
973 #[rstest]
974 fn test_time_in_force_conversions() {
975 use nautilus_model::enums::TimeInForce;
976
977 assert_eq!(
979 TimeInForce::try_from(BitmexTimeInForce::Day).unwrap(),
980 TimeInForce::Day
981 );
982 assert_eq!(
983 TimeInForce::try_from(BitmexTimeInForce::GoodTillCancel).unwrap(),
984 TimeInForce::Gtc
985 );
986 assert_eq!(
987 TimeInForce::try_from(BitmexTimeInForce::ImmediateOrCancel).unwrap(),
988 TimeInForce::Ioc
989 );
990
991 let result = TimeInForce::try_from(BitmexTimeInForce::GoodTillCrossing);
993 assert!(result.is_err());
994 match result {
995 Err(BitmexError::NonRetryable {
996 source: BitmexNonRetryableError::Validation { field, message },
997 ..
998 }) => {
999 assert_eq!(field, "time_in_force");
1000 assert!(message.contains("Unsupported"));
1001 }
1002 _ => panic!("Expected validation error"),
1003 }
1004
1005 assert_eq!(
1007 BitmexTimeInForce::try_from(TimeInForce::Day).unwrap(),
1008 BitmexTimeInForce::Day
1009 );
1010 assert_eq!(
1011 BitmexTimeInForce::try_from(TimeInForce::Gtc).unwrap(),
1012 BitmexTimeInForce::GoodTillCancel
1013 );
1014 assert_eq!(
1015 BitmexTimeInForce::try_from(TimeInForce::Fok).unwrap(),
1016 BitmexTimeInForce::FillOrKill
1017 );
1018 }
1019
1020 #[rstest]
1021 fn test_helper_methods() {
1022 let result = BitmexSide::try_from_order_side(OrderSide::Buy);
1024 assert!(result.is_ok());
1025 assert_eq!(result.unwrap(), BitmexSide::Buy);
1026
1027 let result = BitmexSide::try_from_order_side(OrderSide::NoOrderSide);
1028 assert!(result.is_err());
1029
1030 let result = BitmexOrderType::try_from_order_type(OrderType::Limit);
1032 assert!(result.is_ok());
1033 assert_eq!(result.unwrap(), BitmexOrderType::Limit);
1034
1035 let result = BitmexOrderType::try_from_order_type(OrderType::MarketToLimit);
1036 assert!(result.is_err());
1037
1038 let result = BitmexTimeInForce::try_from_time_in_force(TimeInForce::Ioc);
1040 assert!(result.is_ok());
1041 assert_eq!(result.unwrap(), BitmexTimeInForce::ImmediateOrCancel);
1042 }
1043}