nautilus_bitmex/common/
enums.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! BitMEX-specific enumerations shared by HTTP and WebSocket components.
17
18use std::borrow::Cow;
19
20use nautilus_model::enums::{
21    ContingencyType, LiquiditySide, OrderSide, OrderStatus, OrderType, PositionSide, TimeInForce,
22};
23use serde::{Deserialize, Deserializer, Serialize};
24use strum::{AsRefStr, Display, EnumIter, EnumString};
25
26use crate::error::{BitmexError, BitmexNonRetryableError};
27
28/// Represents the status of a BitMEX symbol.
29#[derive(
30    Copy,
31    Clone,
32    Debug,
33    Display,
34    PartialEq,
35    Eq,
36    AsRefStr,
37    EnumIter,
38    EnumString,
39    Serialize,
40    Deserialize,
41)]
42#[serde(rename_all = "PascalCase")]
43#[cfg_attr(
44    feature = "python",
45    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bitmex", eq, eq_int)
46)]
47pub enum BitmexSymbolStatus {
48    /// Symbol is open for trading.
49    Open,
50    /// Symbol is closed for trading.
51    Closed,
52    /// Symbol is unlisted.
53    Unlisted,
54}
55
56/// Represents the side of an order or trade (Buy/Sell).
57#[derive(
58    Copy,
59    Clone,
60    Debug,
61    Display,
62    PartialEq,
63    Eq,
64    AsRefStr,
65    EnumIter,
66    EnumString,
67    Serialize,
68    Deserialize,
69)]
70pub enum BitmexSide {
71    /// Buy side of a trade or order.
72    #[serde(rename = "Buy", alias = "BUY", alias = "buy")]
73    Buy,
74    /// Sell side of a trade or order.
75    #[serde(rename = "Sell", alias = "SELL", alias = "sell")]
76    Sell,
77}
78
79impl TryFrom<OrderSide> for BitmexSide {
80    type Error = BitmexError;
81
82    fn try_from(value: OrderSide) -> Result<Self, Self::Error> {
83        match value {
84            OrderSide::Buy => Ok(Self::Buy),
85            OrderSide::Sell => Ok(Self::Sell),
86            _ => Err(BitmexError::NonRetryable {
87                source: BitmexNonRetryableError::Validation {
88                    field: "order_side".to_string(),
89                    message: format!("Invalid order side: {value:?}"),
90                },
91            }),
92        }
93    }
94}
95
96impl BitmexSide {
97    /// Try to convert from Nautilus OrderSide.
98    ///
99    /// # Errors
100    ///
101    /// Returns an error if the order side is not Buy or Sell.
102    pub fn try_from_order_side(value: OrderSide) -> anyhow::Result<Self> {
103        Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
104    }
105}
106
107impl From<BitmexSide> for OrderSide {
108    fn from(side: BitmexSide) -> Self {
109        match side {
110            BitmexSide::Buy => Self::Buy,
111            BitmexSide::Sell => Self::Sell,
112        }
113    }
114}
115
116/// Represents the position side for BitMEX positions.
117#[derive(
118    Copy,
119    Clone,
120    Debug,
121    Display,
122    PartialEq,
123    Eq,
124    AsRefStr,
125    EnumIter,
126    EnumString,
127    Serialize,
128    Deserialize,
129)]
130#[cfg_attr(
131    feature = "python",
132    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bitmex", eq, eq_int)
133)]
134pub enum BitmexPositionSide {
135    /// Long position.
136    #[serde(rename = "LONG", alias = "Long", alias = "long")]
137    Long,
138    /// Short position.
139    #[serde(rename = "SHORT", alias = "Short", alias = "short")]
140    Short,
141    /// No position.
142    #[serde(rename = "FLAT", alias = "Flat", alias = "flat")]
143    Flat,
144}
145
146impl From<BitmexPositionSide> for PositionSide {
147    fn from(side: BitmexPositionSide) -> Self {
148        match side {
149            BitmexPositionSide::Long => Self::Long,
150            BitmexPositionSide::Short => Self::Short,
151            BitmexPositionSide::Flat => Self::Flat,
152        }
153    }
154}
155
156impl From<PositionSide> for BitmexPositionSide {
157    fn from(side: PositionSide) -> Self {
158        match side {
159            PositionSide::Long => Self::Long,
160            PositionSide::Short => Self::Short,
161            PositionSide::Flat | PositionSide::NoPositionSide => Self::Flat,
162        }
163    }
164}
165
166/// Represents the available order types on BitMEX.
167#[derive(
168    Copy,
169    Clone,
170    Debug,
171    Display,
172    PartialEq,
173    Eq,
174    AsRefStr,
175    EnumIter,
176    EnumString,
177    Serialize,
178    Deserialize,
179)]
180pub enum BitmexOrderType {
181    /// Market order, executed immediately at current market price.
182    Market,
183    /// Limit order, executed only at specified price or better.
184    Limit,
185    /// Stop Market order, triggers a market order when price reaches stop price.
186    Stop,
187    /// Stop Limit order, triggers a limit order when price reaches stop price.
188    StopLimit,
189    /// Market if touched order, triggers a market order when price reaches touch price.
190    MarketIfTouched,
191    /// Limit if touched order, triggers a limit order when price reaches touch price.
192    LimitIfTouched,
193    /// Pegged order, price automatically tracks market.
194    Pegged,
195}
196
197impl TryFrom<OrderType> for BitmexOrderType {
198    type Error = BitmexError;
199
200    fn try_from(value: OrderType) -> Result<Self, Self::Error> {
201        match value {
202            OrderType::Market => Ok(Self::Market),
203            OrderType::Limit => Ok(Self::Limit),
204            OrderType::StopMarket => Ok(Self::Stop),
205            OrderType::StopLimit => Ok(Self::StopLimit),
206            OrderType::MarketIfTouched => Ok(Self::MarketIfTouched),
207            OrderType::LimitIfTouched => Ok(Self::LimitIfTouched),
208            OrderType::TrailingStopMarket => Ok(Self::Pegged),
209            OrderType::TrailingStopLimit => Ok(Self::Pegged),
210            OrderType::MarketToLimit => Err(BitmexError::NonRetryable {
211                source: BitmexNonRetryableError::Validation {
212                    field: "order_type".to_string(),
213                    message: "MarketToLimit order type is not supported by BitMEX".to_string(),
214                },
215            }),
216        }
217    }
218}
219
220impl BitmexOrderType {
221    /// Try to convert from Nautilus OrderType with anyhow::Result.
222    ///
223    /// # Errors
224    ///
225    /// Returns an error if the order type is MarketToLimit (not supported by BitMEX).
226    pub fn try_from_order_type(value: OrderType) -> anyhow::Result<Self> {
227        Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
228    }
229}
230
231impl From<BitmexOrderType> for OrderType {
232    fn from(value: BitmexOrderType) -> Self {
233        match value {
234            BitmexOrderType::Market => Self::Market,
235            BitmexOrderType::Limit => Self::Limit,
236            BitmexOrderType::Stop => Self::StopMarket,
237            BitmexOrderType::StopLimit => Self::StopLimit,
238            BitmexOrderType::MarketIfTouched => Self::MarketIfTouched,
239            BitmexOrderType::LimitIfTouched => Self::LimitIfTouched,
240            BitmexOrderType::Pegged => Self::Limit,
241        }
242    }
243}
244
245/// Represents the possible states of an order throughout its lifecycle.
246#[derive(
247    Copy,
248    Clone,
249    Debug,
250    Display,
251    PartialEq,
252    Eq,
253    AsRefStr,
254    EnumIter,
255    EnumString,
256    Serialize,
257    Deserialize,
258)]
259pub enum BitmexOrderStatus {
260    /// Order has been placed but not yet processed.
261    New,
262    /// Order has been partially filled.
263    PartiallyFilled,
264    /// Order has been completely filled.
265    Filled,
266    /// Order cancellation is pending.
267    PendingCancel,
268    /// Order has been canceled by user or system.
269    Canceled,
270    /// Order was rejected by the system.
271    Rejected,
272    /// Order has expired according to its time in force.
273    Expired,
274}
275
276impl From<BitmexOrderStatus> for OrderStatus {
277    fn from(value: BitmexOrderStatus) -> Self {
278        match value {
279            BitmexOrderStatus::New => Self::Accepted,
280            BitmexOrderStatus::PartiallyFilled => Self::PartiallyFilled,
281            BitmexOrderStatus::Filled => Self::Filled,
282            BitmexOrderStatus::PendingCancel => Self::PendingCancel,
283            BitmexOrderStatus::Canceled => Self::Canceled,
284            BitmexOrderStatus::Rejected => Self::Rejected,
285            BitmexOrderStatus::Expired => Self::Expired,
286        }
287    }
288}
289
290/// Specifies how long an order should remain active.
291#[derive(
292    Copy,
293    Clone,
294    Debug,
295    Display,
296    PartialEq,
297    Eq,
298    AsRefStr,
299    EnumIter,
300    EnumString,
301    Serialize,
302    Deserialize,
303)]
304pub enum BitmexTimeInForce {
305    Day,
306    GoodTillCancel,
307    AtTheOpening,
308    ImmediateOrCancel,
309    FillOrKill,
310    GoodTillCrossing,
311    GoodTillDate,
312    AtTheClose,
313    GoodThroughCrossing,
314    AtCrossing,
315}
316
317impl TryFrom<BitmexTimeInForce> for TimeInForce {
318    type Error = BitmexError;
319
320    fn try_from(value: BitmexTimeInForce) -> Result<Self, Self::Error> {
321        match value {
322            BitmexTimeInForce::Day => Ok(Self::Day),
323            BitmexTimeInForce::GoodTillCancel => Ok(Self::Gtc),
324            BitmexTimeInForce::GoodTillDate => Ok(Self::Gtd),
325            BitmexTimeInForce::ImmediateOrCancel => Ok(Self::Ioc),
326            BitmexTimeInForce::FillOrKill => Ok(Self::Fok),
327            BitmexTimeInForce::AtTheOpening => Ok(Self::AtTheOpen),
328            BitmexTimeInForce::AtTheClose => Ok(Self::AtTheClose),
329            _ => Err(BitmexError::NonRetryable {
330                source: BitmexNonRetryableError::Validation {
331                    field: "time_in_force".to_string(),
332                    message: format!("Unsupported BitmexTimeInForce: {value}"),
333                },
334            }),
335        }
336    }
337}
338
339impl TryFrom<TimeInForce> for BitmexTimeInForce {
340    type Error = crate::error::BitmexError;
341
342    fn try_from(value: TimeInForce) -> Result<Self, Self::Error> {
343        match value {
344            TimeInForce::Day => Ok(Self::Day),
345            TimeInForce::Gtc => Ok(Self::GoodTillCancel),
346            TimeInForce::Gtd => Ok(Self::GoodTillDate),
347            TimeInForce::Ioc => Ok(Self::ImmediateOrCancel),
348            TimeInForce::Fok => Ok(Self::FillOrKill),
349            TimeInForce::AtTheOpen => Ok(Self::AtTheOpening),
350            TimeInForce::AtTheClose => Ok(Self::AtTheClose),
351        }
352    }
353}
354
355impl BitmexTimeInForce {
356    /// Try to convert from Nautilus TimeInForce with anyhow::Result.
357    ///
358    /// # Errors
359    ///
360    /// Returns an error if the time in force is not supported by BitMEX.
361    pub fn try_from_time_in_force(value: TimeInForce) -> anyhow::Result<Self> {
362        Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
363    }
364}
365
366/// Represents the available contingency types on BitMEX.
367#[derive(
368    Copy,
369    Clone,
370    Debug,
371    Display,
372    PartialEq,
373    Eq,
374    AsRefStr,
375    EnumIter,
376    EnumString,
377    Serialize,
378    Deserialize,
379)]
380pub enum BitmexContingencyType {
381    OneCancelsTheOther,
382    OneTriggersTheOther,
383    OneUpdatesTheOtherAbsolute,
384    OneUpdatesTheOtherProportional,
385    #[serde(rename = "")]
386    Unknown, // Can be empty
387}
388
389impl From<BitmexContingencyType> for ContingencyType {
390    fn from(value: BitmexContingencyType) -> Self {
391        match value {
392            BitmexContingencyType::OneCancelsTheOther => Self::Oco,
393            BitmexContingencyType::OneTriggersTheOther => Self::Oto,
394            BitmexContingencyType::OneUpdatesTheOtherProportional => Self::Ouo,
395            BitmexContingencyType::OneUpdatesTheOtherAbsolute => Self::Ouo,
396            BitmexContingencyType::Unknown => Self::NoContingency,
397        }
398    }
399}
400
401impl TryFrom<ContingencyType> for BitmexContingencyType {
402    type Error = BitmexError;
403
404    fn try_from(value: ContingencyType) -> Result<Self, Self::Error> {
405        match value {
406            ContingencyType::NoContingency => Ok(Self::Unknown),
407            ContingencyType::Oco => Ok(Self::OneCancelsTheOther),
408            ContingencyType::Oto => Ok(Self::OneTriggersTheOther),
409            ContingencyType::Ouo => Err(BitmexError::NonRetryable {
410                source: BitmexNonRetryableError::Validation {
411                    field: "contingency_type".to_string(),
412                    message: "OUO contingency type not supported by BitMEX".to_string(),
413                },
414            }),
415        }
416    }
417}
418
419/// Represents the available peg price types on BitMEX.
420#[derive(
421    Copy,
422    Clone,
423    Debug,
424    Display,
425    PartialEq,
426    Eq,
427    AsRefStr,
428    EnumIter,
429    EnumString,
430    Serialize,
431    Deserialize,
432)]
433pub enum BitmexPegPriceType {
434    LastPeg,
435    OpeningPeg,
436    MidPricePeg,
437    MarketPeg,
438    PrimaryPeg,
439    PegToVWAP,
440    TrailingStopPeg,
441    PegToLimitPrice,
442    ShortSaleMinPricePeg,
443    #[serde(rename = "")]
444    Unknown, // Can be empty
445}
446
447/// Represents the available execution instruments on BitMEX.
448#[derive(
449    Copy,
450    Clone,
451    Debug,
452    Display,
453    PartialEq,
454    Eq,
455    AsRefStr,
456    EnumIter,
457    EnumString,
458    Serialize,
459    Deserialize,
460)]
461pub enum BitmexExecInstruction {
462    ParticipateDoNotInitiate,
463    AllOrNone,
464    MarkPrice,
465    IndexPrice,
466    LastPrice,
467    Close,
468    ReduceOnly,
469    Fixed,
470    #[serde(rename = "")]
471    Unknown, // Can be empty
472}
473
474impl BitmexExecInstruction {
475    /// Joins execution instructions into the comma-separated string expected by BitMEX.
476    pub fn join(instructions: &[Self]) -> String {
477        instructions
478            .iter()
479            .map(std::string::ToString::to_string)
480            .collect::<Vec<_>>()
481            .join(",")
482    }
483}
484
485/// Represents the type of execution that generated a trade.
486#[derive(
487    Copy,
488    Clone,
489    Debug,
490    Display,
491    PartialEq,
492    Eq,
493    AsRefStr,
494    EnumIter,
495    EnumString,
496    Serialize,
497    Deserialize,
498)]
499pub enum BitmexExecType {
500    /// New order placed.
501    New,
502    /// Normal trade execution.
503    Trade,
504    /// Order canceled.
505    Canceled,
506    /// Cancel request rejected.
507    CancelReject,
508    /// Order replaced.
509    Replaced,
510    /// Order rejected.
511    Rejected,
512    /// Order amendment rejected.
513    AmendReject,
514    /// Funding rate execution.
515    Funding,
516    /// Settlement execution.
517    Settlement,
518    /// Order suspended.
519    Suspended,
520    /// Order released.
521    Released,
522    /// Insurance payment.
523    Insurance,
524    /// Rebalance.
525    Rebalance,
526    /// Liquidation execution.
527    Liquidation,
528    /// Bankruptcy execution.
529    Bankruptcy,
530    /// Trial fill (testnet only).
531    TrialFill,
532    /// Stop/trigger order activated by system.
533    TriggeredOrActivatedBySystem,
534}
535
536/// Indicates whether the execution was maker or taker.
537#[derive(
538    Copy,
539    Clone,
540    Debug,
541    Display,
542    PartialEq,
543    Eq,
544    AsRefStr,
545    EnumIter,
546    EnumString,
547    Serialize,
548    Deserialize,
549)]
550pub enum BitmexLiquidityIndicator {
551    /// Provided liquidity to the order book (maker).
552    /// BitMEX returns "Added" in REST API responses and "AddedLiquidity" in WebSocket messages.
553    #[serde(rename = "Added")]
554    #[serde(alias = "AddedLiquidity")]
555    Maker,
556    /// Took liquidity from the order book (taker).
557    /// BitMEX returns "Removed" in REST API responses and "RemovedLiquidity" in WebSocket messages.
558    #[serde(rename = "Removed")]
559    #[serde(alias = "RemovedLiquidity")]
560    Taker,
561}
562
563impl From<BitmexLiquidityIndicator> for LiquiditySide {
564    fn from(value: BitmexLiquidityIndicator) -> Self {
565        match value {
566            BitmexLiquidityIndicator::Maker => Self::Maker,
567            BitmexLiquidityIndicator::Taker => Self::Taker,
568        }
569    }
570}
571
572/// Represents BitMEX instrument types (CFI codes).
573///
574/// The CFI (Classification of Financial Instruments) code is a 6-character code
575/// following ISO 10962 standard that classifies financial instruments.
576///
577/// See: <https://support.bitmex.com/hc/en-gb/articles/6299296145565-What-are-the-Typ-Values-for-Instrument-endpoint>
578#[derive(
579    Copy,
580    Clone,
581    Debug,
582    Display,
583    PartialEq,
584    Eq,
585    AsRefStr,
586    EnumIter,
587    EnumString,
588    Serialize,
589    Deserialize,
590)]
591#[serde(rename_all = "UPPERCASE")]
592pub enum BitmexInstrumentType {
593    /// Legacy futures (settled).
594    #[serde(rename = "FXXXS")]
595    LegacyFutures,
596
597    /// Legacy futures (settled, variant).
598    #[serde(rename = "FXXXN")]
599    LegacyFuturesN,
600
601    /// Futures spreads (settled).
602    #[serde(rename = "FMXXS")]
603    FuturesSpreads,
604
605    /// Prediction Markets (non-standardized financial future on index, cash settled).
606    /// CFI code FFICSX - traders predict outcomes of events.
607    #[serde(rename = "FFICSX")]
608    PredictionMarket,
609
610    /// Stock-based Perpetual Contracts (e.g., SPY, equity derivatives).
611    /// CFI code FFSCSX - financial future on stocks, cash settled.
612    #[serde(rename = "FFSCSX")]
613    StockPerpetual,
614
615    /// Perpetual Contracts (crypto).
616    #[serde(rename = "FFWCSX")]
617    PerpetualContract,
618
619    /// Perpetual Contracts (FX underliers).
620    #[serde(rename = "FFWCSF")]
621    PerpetualContractFx,
622
623    /// Futures (calendar futures, cash settled).
624    #[serde(rename = "FFCCSX")]
625    Futures,
626
627    /// Spot trading pairs.
628    #[serde(rename = "IFXXXP")]
629    Spot,
630
631    /// Call options (European, cash settled).
632    #[serde(rename = "OCECCS")]
633    CallOption,
634
635    /// Put options (European, cash settled).
636    #[serde(rename = "OPECCS")]
637    PutOption,
638
639    /// Swap rate contracts (yield products).
640    #[serde(rename = "SRMCSX")]
641    SwapRate,
642
643    /// Reference basket contracts.
644    #[serde(rename = "RCSXXX")]
645    ReferenceBasket,
646
647    /// BitMEX Basket Index.
648    #[serde(rename = "MRBXXX")]
649    BasketIndex,
650
651    /// BitMEX Crypto Index.
652    #[serde(rename = "MRCXXX")]
653    CryptoIndex,
654
655    /// BitMEX FX Index.
656    #[serde(rename = "MRFXXX")]
657    FxIndex,
658
659    /// BitMEX Lending/Premium Index.
660    #[serde(rename = "MRRXXX")]
661    LendingIndex,
662
663    /// BitMEX Volatility Index.
664    #[serde(rename = "MRIXXX")]
665    VolatilityIndex,
666
667    /// BitMEX Stock/Securities Index.
668    #[serde(rename = "MRSXXX")]
669    StockIndex,
670
671    /// BitMEX Yield/Dividend Index.
672    #[serde(rename = "MRVDXX")]
673    YieldIndex,
674}
675
676/// Represents the different types of instrument subscriptions available on BitMEX.
677#[derive(Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize)]
678pub enum BitmexProductType {
679    /// All instruments AND indices.
680    #[serde(rename = "instrument")]
681    All,
682
683    /// All instruments, but no indices.
684    #[serde(rename = "CONTRACTS")]
685    Contracts,
686
687    /// All indices, but no tradeable instruments.
688    #[serde(rename = "INDICES")]
689    Indices,
690
691    /// Only derivative instruments, and no indices.
692    #[serde(rename = "DERIVATIVES")]
693    Derivatives,
694
695    /// Only spot instruments, and no indices.
696    #[serde(rename = "SPOT")]
697    Spot,
698
699    /// Specific instrument subscription (e.g., "instrument:XBTUSD").
700    #[serde(rename = "instrument")]
701    #[serde(untagged)]
702    Specific(String),
703}
704
705impl BitmexProductType {
706    /// Converts the product type to its websocket subscription string.
707    #[must_use]
708    pub fn to_subscription(&self) -> Cow<'static, str> {
709        match self {
710            Self::All => Cow::Borrowed("instrument"),
711            Self::Specific(symbol) => Cow::Owned(format!("instrument:{symbol}")),
712            Self::Contracts => Cow::Borrowed("CONTRACTS"),
713            Self::Indices => Cow::Borrowed("INDICES"),
714            Self::Derivatives => Cow::Borrowed("DERIVATIVES"),
715            Self::Spot => Cow::Borrowed("SPOT"),
716        }
717    }
718}
719
720impl<'de> Deserialize<'de> for BitmexProductType {
721    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
722    where
723        D: Deserializer<'de>,
724    {
725        let s = String::deserialize(deserializer)?;
726
727        match s.as_str() {
728            "instrument" => Ok(Self::All),
729            "CONTRACTS" => Ok(Self::Contracts),
730            "INDICES" => Ok(Self::Indices),
731            "DERIVATIVES" => Ok(Self::Derivatives),
732            "SPOT" => Ok(Self::Spot),
733            s if s.starts_with("instrument:") => {
734                let symbol = s.strip_prefix("instrument:").unwrap();
735                Ok(Self::Specific(symbol.to_string()))
736            }
737            _ => Err(serde::de::Error::custom(format!(
738                "Invalid product type: {s}"
739            ))),
740        }
741    }
742}
743
744/// Represents the tick direction of the last trade.
745#[derive(
746    Copy,
747    Clone,
748    Debug,
749    Display,
750    PartialEq,
751    Eq,
752    AsRefStr,
753    EnumIter,
754    EnumString,
755    Serialize,
756    Deserialize,
757)]
758pub enum BitmexTickDirection {
759    /// Price increased on last trade.
760    PlusTick,
761    /// Price decreased on last trade.
762    MinusTick,
763    /// Price unchanged, but previous tick was plus.
764    ZeroPlusTick,
765    /// Price unchanged, but previous tick was minus.
766    ZeroMinusTick,
767}
768
769/// Represents the state of an instrument.
770#[derive(
771    Clone,
772    Copy,
773    Debug,
774    Display,
775    PartialEq,
776    Eq,
777    AsRefStr,
778    EnumIter,
779    EnumString,
780    Serialize,
781    Deserialize,
782)]
783pub enum BitmexInstrumentState {
784    /// Instrument is open for trading.
785    Open,
786    /// Instrument is closed for trading.
787    Closed,
788    /// Instrument is unlisted.
789    Unlisted,
790    /// Instrument is settled.
791    Settled,
792    /// Instrument is delisted.
793    Delisted,
794}
795
796/// Represents the fair price calculation method.
797#[derive(
798    Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
799)]
800pub enum BitmexFairMethod {
801    /// Funding rate based.
802    FundingRate,
803    /// Impact mid price.
804    ImpactMidPrice,
805    /// Last price.
806    LastPrice,
807}
808
809/// Represents the mark price calculation method.
810#[derive(
811    Clone, Debug, Display, PartialEq, Eq, AsRefStr, EnumIter, EnumString, Serialize, Deserialize,
812)]
813pub enum BitmexMarkMethod {
814    /// Fair price.
815    FairPrice,
816    /// Fair price for stock-based perpetuals.
817    FairPriceStox,
818    /// Last price.
819    LastPrice,
820    /// Composite index.
821    CompositeIndex,
822}
823
824#[cfg(test)]
825mod tests {
826    use rstest::rstest;
827
828    use super::*;
829
830    #[rstest]
831    fn test_bitmex_side_deserialization() {
832        // Test all case variations
833        assert_eq!(
834            serde_json::from_str::<BitmexSide>(r#""Buy""#).unwrap(),
835            BitmexSide::Buy
836        );
837        assert_eq!(
838            serde_json::from_str::<BitmexSide>(r#""BUY""#).unwrap(),
839            BitmexSide::Buy
840        );
841        assert_eq!(
842            serde_json::from_str::<BitmexSide>(r#""buy""#).unwrap(),
843            BitmexSide::Buy
844        );
845        assert_eq!(
846            serde_json::from_str::<BitmexSide>(r#""Sell""#).unwrap(),
847            BitmexSide::Sell
848        );
849        assert_eq!(
850            serde_json::from_str::<BitmexSide>(r#""SELL""#).unwrap(),
851            BitmexSide::Sell
852        );
853        assert_eq!(
854            serde_json::from_str::<BitmexSide>(r#""sell""#).unwrap(),
855            BitmexSide::Sell
856        );
857    }
858
859    #[rstest]
860    fn test_bitmex_order_type_deserialization() {
861        assert_eq!(
862            serde_json::from_str::<BitmexOrderType>(r#""Market""#).unwrap(),
863            BitmexOrderType::Market
864        );
865        assert_eq!(
866            serde_json::from_str::<BitmexOrderType>(r#""Limit""#).unwrap(),
867            BitmexOrderType::Limit
868        );
869        assert_eq!(
870            serde_json::from_str::<BitmexOrderType>(r#""Stop""#).unwrap(),
871            BitmexOrderType::Stop
872        );
873        assert_eq!(
874            serde_json::from_str::<BitmexOrderType>(r#""StopLimit""#).unwrap(),
875            BitmexOrderType::StopLimit
876        );
877        assert_eq!(
878            serde_json::from_str::<BitmexOrderType>(r#""MarketIfTouched""#).unwrap(),
879            BitmexOrderType::MarketIfTouched
880        );
881        assert_eq!(
882            serde_json::from_str::<BitmexOrderType>(r#""LimitIfTouched""#).unwrap(),
883            BitmexOrderType::LimitIfTouched
884        );
885        assert_eq!(
886            serde_json::from_str::<BitmexOrderType>(r#""Pegged""#).unwrap(),
887            BitmexOrderType::Pegged
888        );
889    }
890
891    #[rstest]
892    fn test_instrument_type_serialization() {
893        // Tradeable instruments
894        assert_eq!(
895            serde_json::to_string(&BitmexInstrumentType::PerpetualContract).unwrap(),
896            r#""FFWCSX""#
897        );
898        assert_eq!(
899            serde_json::to_string(&BitmexInstrumentType::PerpetualContractFx).unwrap(),
900            r#""FFWCSF""#
901        );
902        assert_eq!(
903            serde_json::to_string(&BitmexInstrumentType::StockPerpetual).unwrap(),
904            r#""FFSCSX""#
905        );
906        assert_eq!(
907            serde_json::to_string(&BitmexInstrumentType::Spot).unwrap(),
908            r#""IFXXXP""#
909        );
910        assert_eq!(
911            serde_json::to_string(&BitmexInstrumentType::Futures).unwrap(),
912            r#""FFCCSX""#
913        );
914        assert_eq!(
915            serde_json::to_string(&BitmexInstrumentType::PredictionMarket).unwrap(),
916            r#""FFICSX""#
917        );
918        assert_eq!(
919            serde_json::to_string(&BitmexInstrumentType::CallOption).unwrap(),
920            r#""OCECCS""#
921        );
922        assert_eq!(
923            serde_json::to_string(&BitmexInstrumentType::PutOption).unwrap(),
924            r#""OPECCS""#
925        );
926        assert_eq!(
927            serde_json::to_string(&BitmexInstrumentType::SwapRate).unwrap(),
928            r#""SRMCSX""#
929        );
930
931        // Legacy instruments
932        assert_eq!(
933            serde_json::to_string(&BitmexInstrumentType::LegacyFutures).unwrap(),
934            r#""FXXXS""#
935        );
936        assert_eq!(
937            serde_json::to_string(&BitmexInstrumentType::LegacyFuturesN).unwrap(),
938            r#""FXXXN""#
939        );
940        assert_eq!(
941            serde_json::to_string(&BitmexInstrumentType::FuturesSpreads).unwrap(),
942            r#""FMXXS""#
943        );
944        assert_eq!(
945            serde_json::to_string(&BitmexInstrumentType::ReferenceBasket).unwrap(),
946            r#""RCSXXX""#
947        );
948
949        // Index types
950        assert_eq!(
951            serde_json::to_string(&BitmexInstrumentType::BasketIndex).unwrap(),
952            r#""MRBXXX""#
953        );
954        assert_eq!(
955            serde_json::to_string(&BitmexInstrumentType::CryptoIndex).unwrap(),
956            r#""MRCXXX""#
957        );
958        assert_eq!(
959            serde_json::to_string(&BitmexInstrumentType::FxIndex).unwrap(),
960            r#""MRFXXX""#
961        );
962        assert_eq!(
963            serde_json::to_string(&BitmexInstrumentType::LendingIndex).unwrap(),
964            r#""MRRXXX""#
965        );
966        assert_eq!(
967            serde_json::to_string(&BitmexInstrumentType::VolatilityIndex).unwrap(),
968            r#""MRIXXX""#
969        );
970        assert_eq!(
971            serde_json::to_string(&BitmexInstrumentType::StockIndex).unwrap(),
972            r#""MRSXXX""#
973        );
974        assert_eq!(
975            serde_json::to_string(&BitmexInstrumentType::YieldIndex).unwrap(),
976            r#""MRVDXX""#
977        );
978    }
979
980    #[rstest]
981    fn test_instrument_type_deserialization() {
982        // Tradeable instruments
983        assert_eq!(
984            serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSX""#).unwrap(),
985            BitmexInstrumentType::PerpetualContract
986        );
987        assert_eq!(
988            serde_json::from_str::<BitmexInstrumentType>(r#""FFWCSF""#).unwrap(),
989            BitmexInstrumentType::PerpetualContractFx
990        );
991        assert_eq!(
992            serde_json::from_str::<BitmexInstrumentType>(r#""FFSCSX""#).unwrap(),
993            BitmexInstrumentType::StockPerpetual
994        );
995        assert_eq!(
996            serde_json::from_str::<BitmexInstrumentType>(r#""IFXXXP""#).unwrap(),
997            BitmexInstrumentType::Spot
998        );
999        assert_eq!(
1000            serde_json::from_str::<BitmexInstrumentType>(r#""FFCCSX""#).unwrap(),
1001            BitmexInstrumentType::Futures
1002        );
1003        assert_eq!(
1004            serde_json::from_str::<BitmexInstrumentType>(r#""FFICSX""#).unwrap(),
1005            BitmexInstrumentType::PredictionMarket
1006        );
1007        assert_eq!(
1008            serde_json::from_str::<BitmexInstrumentType>(r#""OCECCS""#).unwrap(),
1009            BitmexInstrumentType::CallOption
1010        );
1011        assert_eq!(
1012            serde_json::from_str::<BitmexInstrumentType>(r#""OPECCS""#).unwrap(),
1013            BitmexInstrumentType::PutOption
1014        );
1015        assert_eq!(
1016            serde_json::from_str::<BitmexInstrumentType>(r#""SRMCSX""#).unwrap(),
1017            BitmexInstrumentType::SwapRate
1018        );
1019
1020        // Legacy instruments
1021        assert_eq!(
1022            serde_json::from_str::<BitmexInstrumentType>(r#""FXXXS""#).unwrap(),
1023            BitmexInstrumentType::LegacyFutures
1024        );
1025        assert_eq!(
1026            serde_json::from_str::<BitmexInstrumentType>(r#""FXXXN""#).unwrap(),
1027            BitmexInstrumentType::LegacyFuturesN
1028        );
1029        assert_eq!(
1030            serde_json::from_str::<BitmexInstrumentType>(r#""FMXXS""#).unwrap(),
1031            BitmexInstrumentType::FuturesSpreads
1032        );
1033        assert_eq!(
1034            serde_json::from_str::<BitmexInstrumentType>(r#""RCSXXX""#).unwrap(),
1035            BitmexInstrumentType::ReferenceBasket
1036        );
1037
1038        // Index types
1039        assert_eq!(
1040            serde_json::from_str::<BitmexInstrumentType>(r#""MRBXXX""#).unwrap(),
1041            BitmexInstrumentType::BasketIndex
1042        );
1043        assert_eq!(
1044            serde_json::from_str::<BitmexInstrumentType>(r#""MRCXXX""#).unwrap(),
1045            BitmexInstrumentType::CryptoIndex
1046        );
1047        assert_eq!(
1048            serde_json::from_str::<BitmexInstrumentType>(r#""MRFXXX""#).unwrap(),
1049            BitmexInstrumentType::FxIndex
1050        );
1051        assert_eq!(
1052            serde_json::from_str::<BitmexInstrumentType>(r#""MRRXXX""#).unwrap(),
1053            BitmexInstrumentType::LendingIndex
1054        );
1055        assert_eq!(
1056            serde_json::from_str::<BitmexInstrumentType>(r#""MRIXXX""#).unwrap(),
1057            BitmexInstrumentType::VolatilityIndex
1058        );
1059        assert_eq!(
1060            serde_json::from_str::<BitmexInstrumentType>(r#""MRSXXX""#).unwrap(),
1061            BitmexInstrumentType::StockIndex
1062        );
1063        assert_eq!(
1064            serde_json::from_str::<BitmexInstrumentType>(r#""MRVDXX""#).unwrap(),
1065            BitmexInstrumentType::YieldIndex
1066        );
1067
1068        // Error case
1069        assert!(serde_json::from_str::<BitmexInstrumentType>(r#""INVALID""#).is_err());
1070    }
1071
1072    #[rstest]
1073    fn test_subscription_strings() {
1074        assert_eq!(BitmexProductType::All.to_subscription(), "instrument");
1075        assert_eq!(
1076            BitmexProductType::Specific("XBTUSD".to_string()).to_subscription(),
1077            "instrument:XBTUSD"
1078        );
1079        assert_eq!(BitmexProductType::Contracts.to_subscription(), "CONTRACTS");
1080        assert_eq!(BitmexProductType::Indices.to_subscription(), "INDICES");
1081        assert_eq!(
1082            BitmexProductType::Derivatives.to_subscription(),
1083            "DERIVATIVES"
1084        );
1085        assert_eq!(BitmexProductType::Spot.to_subscription(), "SPOT");
1086    }
1087
1088    #[rstest]
1089    fn test_serialization() {
1090        // Test serialization
1091        assert_eq!(
1092            serde_json::to_string(&BitmexProductType::All).unwrap(),
1093            r#""instrument""#
1094        );
1095        assert_eq!(
1096            serde_json::to_string(&BitmexProductType::Specific("XBTUSD".to_string())).unwrap(),
1097            r#""XBTUSD""#
1098        );
1099        assert_eq!(
1100            serde_json::to_string(&BitmexProductType::Contracts).unwrap(),
1101            r#""CONTRACTS""#
1102        );
1103    }
1104
1105    #[rstest]
1106    fn test_deserialization() {
1107        assert_eq!(
1108            serde_json::from_str::<BitmexProductType>(r#""instrument""#).unwrap(),
1109            BitmexProductType::All
1110        );
1111        assert_eq!(
1112            serde_json::from_str::<BitmexProductType>(r#""instrument:XBTUSD""#).unwrap(),
1113            BitmexProductType::Specific("XBTUSD".to_string())
1114        );
1115        assert_eq!(
1116            serde_json::from_str::<BitmexProductType>(r#""CONTRACTS""#).unwrap(),
1117            BitmexProductType::Contracts
1118        );
1119    }
1120
1121    #[rstest]
1122    fn test_error_cases() {
1123        assert!(serde_json::from_str::<BitmexProductType>(r#""invalid_type""#).is_err());
1124        assert!(serde_json::from_str::<BitmexProductType>(r"123").is_err());
1125        assert!(serde_json::from_str::<BitmexProductType>(r"{}").is_err());
1126    }
1127
1128    #[rstest]
1129    fn test_order_side_try_from() {
1130        // Valid conversions
1131        assert_eq!(
1132            BitmexSide::try_from(OrderSide::Buy).unwrap(),
1133            BitmexSide::Buy
1134        );
1135        assert_eq!(
1136            BitmexSide::try_from(OrderSide::Sell).unwrap(),
1137            BitmexSide::Sell
1138        );
1139
1140        // Invalid conversions
1141        let result = BitmexSide::try_from(OrderSide::NoOrderSide);
1142        assert!(result.is_err());
1143        match result {
1144            Err(BitmexError::NonRetryable {
1145                source: BitmexNonRetryableError::Validation { field, .. },
1146                ..
1147            }) => {
1148                assert_eq!(field, "order_side");
1149            }
1150            _ => panic!("Expected validation error"),
1151        }
1152    }
1153
1154    #[rstest]
1155    fn test_order_type_try_from() {
1156        // Valid conversions
1157        assert_eq!(
1158            BitmexOrderType::try_from(OrderType::Market).unwrap(),
1159            BitmexOrderType::Market
1160        );
1161        assert_eq!(
1162            BitmexOrderType::try_from(OrderType::Limit).unwrap(),
1163            BitmexOrderType::Limit
1164        );
1165
1166        // MarketToLimit should fail
1167        let result = BitmexOrderType::try_from(OrderType::MarketToLimit);
1168        assert!(result.is_err());
1169        match result {
1170            Err(BitmexError::NonRetryable {
1171                source: BitmexNonRetryableError::Validation { message, .. },
1172                ..
1173            }) => {
1174                assert!(message.contains("not supported"));
1175            }
1176            _ => panic!("Expected validation error"),
1177        }
1178    }
1179
1180    #[rstest]
1181    fn test_time_in_force_conversions() {
1182        // BitMEX to Nautilus (all supported variants)
1183        assert_eq!(
1184            TimeInForce::try_from(BitmexTimeInForce::Day).unwrap(),
1185            TimeInForce::Day
1186        );
1187        assert_eq!(
1188            TimeInForce::try_from(BitmexTimeInForce::GoodTillCancel).unwrap(),
1189            TimeInForce::Gtc
1190        );
1191        assert_eq!(
1192            TimeInForce::try_from(BitmexTimeInForce::ImmediateOrCancel).unwrap(),
1193            TimeInForce::Ioc
1194        );
1195
1196        // Unsupported BitMEX variants should fail
1197        let result = TimeInForce::try_from(BitmexTimeInForce::GoodTillCrossing);
1198        assert!(result.is_err());
1199        match result {
1200            Err(BitmexError::NonRetryable {
1201                source: BitmexNonRetryableError::Validation { field, message },
1202                ..
1203            }) => {
1204                assert_eq!(field, "time_in_force");
1205                assert!(message.contains("Unsupported"));
1206            }
1207            _ => panic!("Expected validation error"),
1208        }
1209
1210        // Nautilus to BitMEX (all supported variants)
1211        assert_eq!(
1212            BitmexTimeInForce::try_from(TimeInForce::Day).unwrap(),
1213            BitmexTimeInForce::Day
1214        );
1215        assert_eq!(
1216            BitmexTimeInForce::try_from(TimeInForce::Gtc).unwrap(),
1217            BitmexTimeInForce::GoodTillCancel
1218        );
1219        assert_eq!(
1220            BitmexTimeInForce::try_from(TimeInForce::Fok).unwrap(),
1221            BitmexTimeInForce::FillOrKill
1222        );
1223    }
1224
1225    #[rstest]
1226    fn test_helper_methods() {
1227        // Test try_from_order_side helper
1228        let result = BitmexSide::try_from_order_side(OrderSide::Buy);
1229        assert!(result.is_ok());
1230        assert_eq!(result.unwrap(), BitmexSide::Buy);
1231
1232        let result = BitmexSide::try_from_order_side(OrderSide::NoOrderSide);
1233        assert!(result.is_err());
1234
1235        // Test try_from_order_type helper
1236        let result = BitmexOrderType::try_from_order_type(OrderType::Limit);
1237        assert!(result.is_ok());
1238        assert_eq!(result.unwrap(), BitmexOrderType::Limit);
1239
1240        let result = BitmexOrderType::try_from_order_type(OrderType::MarketToLimit);
1241        assert!(result.is_err());
1242
1243        // Test try_from_time_in_force helper
1244        let result = BitmexTimeInForce::try_from_time_in_force(TimeInForce::Ioc);
1245        assert!(result.is_ok());
1246        assert_eq!(result.unwrap(), BitmexTimeInForce::ImmediateOrCancel);
1247    }
1248}