Skip to main content

nautilus_bybit/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//! Enumerations that model Bybit string/int enums across HTTP and WebSocket payloads.
17
18use std::fmt::Display;
19
20use jiff::{Timestamp, civil::Date, tz::Offset};
21use nautilus_model::enums::{AggressorSide, OrderSide, TriggerType};
22use serde::{Deserialize, Serialize};
23use serde_repr::{Deserialize_repr, Serialize_repr};
24use strum::{AsRefStr, EnumIter, EnumString};
25
26/// Unified margin account status values.
27#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize_repr, Deserialize_repr)]
28#[repr(i32)]
29pub enum BybitUnifiedMarginStatus {
30    /// Classic account.
31    ClassicAccount = 1,
32    /// Unified trading account 1.0.
33    UnifiedTradingAccount10 = 3,
34    /// Unified trading account 1.0 pro.
35    UnifiedTradingAccount10Pro = 4,
36    /// Unified trading account 2.0.
37    UnifiedTradingAccount20 = 5,
38    /// Unified trading account 2.0 pro.
39    UnifiedTradingAccount20Pro = 6,
40}
41
42/// Margin mode used by Bybit when switching risk profiles.
43#[derive(
44    Clone,
45    Copy,
46    Debug,
47    strum::Display,
48    Eq,
49    PartialEq,
50    Hash,
51    AsRefStr,
52    EnumIter,
53    EnumString,
54    Serialize,
55    Deserialize,
56)]
57#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
58#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
59#[cfg_attr(
60    feature = "python",
61    pyo3::pyclass(
62        eq,
63        eq_int,
64        rename_all = "SCREAMING_SNAKE_CASE",
65        module = "nautilus_trader.adapters.bybit",
66        from_py_object
67    )
68)]
69#[cfg_attr(
70    feature = "python",
71    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
72)]
73pub enum BybitMarginMode {
74    IsolatedMargin,
75    RegularMargin,
76    PortfolioMargin,
77}
78
79/// Position mode as returned by the v5 API.
80#[derive(
81    Clone,
82    Copy,
83    Debug,
84    strum::Display,
85    Eq,
86    PartialEq,
87    Hash,
88    AsRefStr,
89    EnumIter,
90    EnumString,
91    Serialize_repr,
92    Deserialize_repr,
93)]
94#[repr(i32)]
95#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
96#[cfg_attr(
97    feature = "python",
98    pyo3::pyclass(
99        eq,
100        eq_int,
101        rename_all = "SCREAMING_SNAKE_CASE",
102        module = "nautilus_trader.adapters.bybit",
103        from_py_object
104    )
105)]
106#[cfg_attr(
107    feature = "python",
108    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
109)]
110pub enum BybitPositionMode {
111    /// Merged single position mode.
112    MergedSingle = 0,
113    /// Dual-side hedged position mode.
114    BothSides = 3,
115}
116
117/// Position index values used for hedge mode payloads.
118#[derive(
119    Clone,
120    Copy,
121    Debug,
122    strum::Display,
123    Eq,
124    PartialEq,
125    Hash,
126    AsRefStr,
127    EnumIter,
128    EnumString,
129    Serialize_repr,
130    Deserialize_repr,
131)]
132#[repr(i32)]
133#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
134#[cfg_attr(
135    feature = "python",
136    pyo3::pyclass(
137        eq,
138        eq_int,
139        rename_all = "SCREAMING_SNAKE_CASE",
140        module = "nautilus_trader.adapters.bybit",
141        from_py_object
142    )
143)]
144#[cfg_attr(
145    feature = "python",
146    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
147)]
148pub enum BybitPositionIdx {
149    /// One-way mode position identifier.
150    OneWay = 0,
151    /// Buy side of a hedge-mode position.
152    BuyHedge = 1,
153    /// Sell side of a hedge-mode position.
154    SellHedge = 2,
155}
156
157/// Account type enumeration.
158#[derive(
159    Copy,
160    Clone,
161    Debug,
162    strum::Display,
163    PartialEq,
164    Eq,
165    Hash,
166    AsRefStr,
167    EnumIter,
168    EnumString,
169    Serialize,
170    Deserialize,
171)]
172#[serde(rename_all = "UPPERCASE")]
173#[cfg_attr(
174    feature = "python",
175    pyo3::pyclass(
176        eq,
177        eq_int,
178        rename_all = "SCREAMING_SNAKE_CASE",
179        module = "nautilus_trader.adapters.bybit",
180        from_py_object
181    )
182)]
183#[cfg_attr(
184    feature = "python",
185    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
186)]
187pub enum BybitAccountType {
188    Unified,
189    Fund,
190}
191
192/// API key authentication type returned by `/v5/user/list-sub-apikeys`.
193#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize_repr, Deserialize_repr)]
194#[repr(u8)]
195pub enum BybitApiKeyType {
196    /// HMAC-SHA256 signed keys (the default).
197    Hmac = 1,
198    /// RSA-signed keys.
199    Rsa = 2,
200}
201
202/// Environments supported by the Bybit API stack.
203#[derive(
204    Copy,
205    Clone,
206    Debug,
207    strum::Display,
208    PartialEq,
209    Eq,
210    Hash,
211    AsRefStr,
212    EnumIter,
213    EnumString,
214    Serialize,
215    Deserialize,
216)]
217#[serde(rename_all = "lowercase")]
218#[cfg_attr(
219    feature = "python",
220    pyo3::pyclass(
221        eq,
222        eq_int,
223        rename_all = "SCREAMING_SNAKE_CASE",
224        module = "nautilus_trader.adapters.bybit",
225        from_py_object
226    )
227)]
228#[cfg_attr(
229    feature = "python",
230    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
231)]
232pub enum BybitEnvironment {
233    /// Live trading environment.
234    Mainnet,
235    /// Demo (paper trading) environment.
236    Demo,
237    /// Testnet environment for spot/derivatives.
238    Testnet,
239}
240
241/// Product categories supported by the v5 API.
242#[derive(
243    Copy,
244    Clone,
245    Debug,
246    strum::Display,
247    Default,
248    PartialEq,
249    Eq,
250    Hash,
251    AsRefStr,
252    EnumIter,
253    EnumString,
254    Serialize,
255    Deserialize,
256)]
257#[serde(rename_all = "lowercase")]
258#[cfg_attr(
259    feature = "python",
260    pyo3::pyclass(
261        eq,
262        eq_int,
263        rename_all = "SCREAMING_SNAKE_CASE",
264        module = "nautilus_trader.adapters.bybit",
265        from_py_object
266    )
267)]
268#[cfg_attr(
269    feature = "python",
270    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
271)]
272pub enum BybitProductType {
273    #[default]
274    Spot,
275    Linear,
276    Inverse,
277    Option,
278}
279
280/// Spot margin trading enablement states.
281#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
282pub enum BybitMarginTrading {
283    #[serde(rename = "none")]
284    None,
285    #[serde(rename = "utaOnly")]
286    UtaOnly,
287    #[serde(rename = "both")]
288    Both,
289    #[serde(rename = "normalSpotOnly")]
290    NormalSpotOnly,
291    #[serde(other)]
292    Other,
293}
294
295/// Innovation market flag for spot instruments.
296#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
297pub enum BybitInnovationFlag {
298    #[serde(rename = "0")]
299    Standard,
300    #[serde(rename = "1")]
301    Innovation,
302    #[serde(other)]
303    Other,
304}
305
306/// Instrument lifecycle status values.
307#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
308#[serde(rename_all = "PascalCase")]
309pub enum BybitInstrumentStatus {
310    PreLaunch,
311    PendingOpen,
312    Trading,
313    Delivering,
314    Closed,
315    #[serde(other)]
316    Other,
317}
318
319impl BybitProductType {
320    /// Returns the canonical lowercase identifier used for REST/WS routes.
321    #[must_use]
322    pub const fn as_str(self) -> &'static str {
323        match self {
324            Self::Spot => "spot",
325            Self::Linear => "linear",
326            Self::Inverse => "inverse",
327            Self::Option => "option",
328        }
329    }
330
331    /// Returns the uppercase suffix used in instrument identifiers (e.g. `-LINEAR`).
332    #[must_use]
333    pub const fn suffix(self) -> &'static str {
334        match self {
335            Self::Spot => "-SPOT",
336            Self::Linear => "-LINEAR",
337            Self::Inverse => "-INVERSE",
338            Self::Option => "-OPTION",
339        }
340    }
341
342    /// Returns the product type identified by the suffix in the symbol string.
343    #[must_use]
344    pub fn from_suffix(symbol: &str) -> Option<Self> {
345        if symbol.ends_with("-SPOT") {
346            Some(Self::Spot)
347        } else if symbol.ends_with("-LINEAR") {
348            Some(Self::Linear)
349        } else if symbol.ends_with("-INVERSE") {
350            Some(Self::Inverse)
351        } else if symbol.ends_with("-OPTION") {
352            Some(Self::Option)
353        } else {
354            None
355        }
356    }
357
358    /// Returns `true` if the product is a spot instrument.
359    #[must_use]
360    pub fn is_spot(self) -> bool {
361        matches!(self, Self::Spot)
362    }
363
364    /// Returns `true` if the product is a linear contract.
365    #[must_use]
366    pub fn is_linear(self) -> bool {
367        matches!(self, Self::Linear)
368    }
369
370    /// Returns `true` if the product is an inverse contract.
371    #[must_use]
372    pub fn is_inverse(self) -> bool {
373        matches!(self, Self::Inverse)
374    }
375
376    /// Returns `true` if the product is an option contract.
377    #[must_use]
378    pub fn is_option(self) -> bool {
379        matches!(self, Self::Option)
380    }
381}
382
383/// Contract type enumeration for linear and inverse derivatives.
384#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
385#[serde(rename_all = "PascalCase")]
386pub enum BybitContractType {
387    LinearPerpetual,
388    LinearFutures,
389    InversePerpetual,
390    InverseFutures,
391}
392
393/// Option flavor values.
394#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
395#[serde(rename_all = "PascalCase")]
396pub enum BybitOptionType {
397    Call,
398    Put,
399}
400
401/// Symbol type values for spot/linear/inverse instrument info responses.
402///
403/// New values may be added by the venue; unknown strings fall back to `Other` so deserialization
404/// remains forward-compatible.
405///
406/// # References
407/// - <https://bybit-exchange.github.io/docs/v5/enum#symboltype>
408#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
409#[serde(rename_all = "lowercase")]
410pub enum BybitSymbolType {
411    /// Innovation-zone derivatives.
412    Innovation,
413    /// Adventure-zone spot pairs.
414    Adventure,
415    /// Tokenized equities (spot xstocks).
416    Xstocks,
417    /// Commodity instruments.
418    Commodity,
419    /// Tokenized stock derivatives.
420    Stock,
421    /// Foreign exchange instruments.
422    Forex,
423    /// Exchange-traded fund derivatives.
424    #[serde(rename = "ETF")]
425    Etf,
426    /// Tokenized equities (spot mstocks).
427    Mstocks,
428    /// Forward-compatible fallback for any value the venue adds later.
429    #[serde(other)]
430    Other,
431}
432
433impl BybitSymbolType {
434    /// Returns the exact recognized value used by Bybit, or `None` for an unknown value.
435    #[must_use]
436    pub(crate) const fn as_str(self) -> Option<&'static str> {
437        match self {
438            Self::Innovation => Some("innovation"),
439            Self::Adventure => Some("adventure"),
440            Self::Xstocks => Some("xstocks"),
441            Self::Commodity => Some("commodity"),
442            Self::Stock => Some("stock"),
443            Self::Forex => Some("forex"),
444            Self::Etf => Some("ETF"),
445            Self::Mstocks => Some("mstocks"),
446            Self::Other => None,
447        }
448    }
449}
450
451/// Position side as represented in REST/WebSocket payloads.
452#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
453pub enum BybitPositionSide {
454    #[serde(rename = "")]
455    Flat,
456    #[serde(rename = "Buy")]
457    Buy,
458    #[serde(rename = "Sell")]
459    Sell,
460}
461
462/// WebSocket order request operations.
463#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
464pub enum BybitWsOrderRequestOp {
465    #[serde(rename = "order.create")]
466    Create,
467    #[serde(rename = "order.amend")]
468    Amend,
469    #[serde(rename = "order.cancel")]
470    Cancel,
471    #[serde(rename = "order.create-batch")]
472    CreateBatch,
473    #[serde(rename = "order.amend-batch")]
474    AmendBatch,
475    #[serde(rename = "order.cancel-batch")]
476    CancelBatch,
477}
478
479/// Available kline intervals.
480#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
481pub enum BybitKlineInterval {
482    #[serde(rename = "1")]
483    Minute1,
484    #[serde(rename = "3")]
485    Minute3,
486    #[serde(rename = "5")]
487    Minute5,
488    #[serde(rename = "15")]
489    Minute15,
490    #[serde(rename = "30")]
491    Minute30,
492    #[serde(rename = "60")]
493    Hour1,
494    #[serde(rename = "120")]
495    Hour2,
496    #[serde(rename = "240")]
497    Hour4,
498    #[serde(rename = "360")]
499    Hour6,
500    #[serde(rename = "720")]
501    Hour12,
502    #[serde(rename = "D")]
503    Day1,
504    #[serde(rename = "W")]
505    Week1,
506    #[serde(rename = "M")]
507    Month1,
508}
509
510impl BybitKlineInterval {
511    /// Returns the end time in milliseconds for a bar that starts at `start_ms`.
512    ///
513    /// For most intervals this is simply `start_ms + duration`. For monthly bars,
514    /// this calculates the actual first millisecond of the next month to handle
515    /// variable month lengths (28-31 days).
516    #[must_use]
517    pub fn bar_end_time_ms(&self, start_ms: i64) -> i64 {
518        match self {
519            Self::Month1 => {
520                let start_dt = Offset::UTC.to_datetime(
521                    Timestamp::from_millisecond(start_ms).unwrap_or(Timestamp::UNIX_EPOCH),
522                );
523                let (year, month) = if start_dt.month() == 12 {
524                    (start_dt.year() + 1, 1)
525                } else {
526                    (start_dt.year(), start_dt.month() + 1)
527                };
528                Date::new(year, month, 1)
529                    .and_then(|date| Offset::UTC.to_timestamp(date.at(0, 0, 0, 0)))
530                    .map_or(start_ms + 2_678_400_000, Timestamp::as_millisecond)
531            }
532            _ => start_ms + self.duration_ms(),
533        }
534    }
535
536    /// Returns the fixed duration of this interval in milliseconds.
537    ///
538    /// Note: For monthly bars, use [`Self::bar_end_time_ms`] instead as months have
539    /// variable lengths (28-31 days).
540    #[must_use]
541    pub const fn duration_ms(&self) -> i64 {
542        match self {
543            Self::Minute1 => 60_000,
544            Self::Minute3 => 180_000,
545            Self::Minute5 => 300_000,
546            Self::Minute15 => 900_000,
547            Self::Minute30 => 1_800_000,
548            Self::Hour1 => 3_600_000,
549            Self::Hour2 => 7_200_000,
550            Self::Hour4 => 14_400_000,
551            Self::Hour6 => 21_600_000,
552            Self::Hour12 => 43_200_000,
553            Self::Day1 => 86_400_000,
554            Self::Week1 => 604_800_000,
555            Self::Month1 => 2_678_400_000, // 31 days - use bar_end_time_ms() for accurate calculation
556        }
557    }
558}
559
560impl Display for BybitKlineInterval {
561    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
562        let s = match self {
563            Self::Minute1 => "1",
564            Self::Minute3 => "3",
565            Self::Minute5 => "5",
566            Self::Minute15 => "15",
567            Self::Minute30 => "30",
568            Self::Hour1 => "60",
569            Self::Hour2 => "120",
570            Self::Hour4 => "240",
571            Self::Hour6 => "360",
572            Self::Hour12 => "720",
573            Self::Day1 => "D",
574            Self::Week1 => "W",
575            Self::Month1 => "M",
576        };
577        write!(f, "{s}")
578    }
579}
580
581/// Order status values returned by Bybit.
582#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
583#[cfg_attr(
584    feature = "python",
585    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
586)]
587#[cfg_attr(
588    feature = "python",
589    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
590)]
591pub enum BybitOrderStatus {
592    #[serde(rename = "Created")]
593    Created,
594    #[serde(rename = "New")]
595    New,
596    #[serde(rename = "Rejected")]
597    Rejected,
598    #[serde(rename = "PartiallyFilled")]
599    PartiallyFilled,
600    #[serde(rename = "PartiallyFilledCanceled")]
601    PartiallyFilledCanceled,
602    #[serde(rename = "Filled")]
603    Filled,
604    #[serde(rename = "Cancelled")]
605    Canceled,
606    #[serde(rename = "Untriggered")]
607    Untriggered,
608    #[serde(rename = "Triggered")]
609    Triggered,
610    #[serde(rename = "Deactivated")]
611    Deactivated,
612}
613
614/// Order side enumeration.
615#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
616#[cfg_attr(
617    feature = "python",
618    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
619)]
620#[cfg_attr(
621    feature = "python",
622    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
623)]
624pub enum BybitOrderSide {
625    #[serde(rename = "")]
626    Unknown,
627    #[serde(rename = "Buy")]
628    Buy,
629    #[serde(rename = "Sell")]
630    Sell,
631}
632
633impl From<BybitOrderSide> for AggressorSide {
634    fn from(value: BybitOrderSide) -> Self {
635        match value {
636            BybitOrderSide::Buy => Self::Buy,
637            BybitOrderSide::Sell => Self::Sell,
638            BybitOrderSide::Unknown => Self::NoAggressor,
639        }
640    }
641}
642
643impl From<BybitOrderSide> for Option<OrderSide> {
644    fn from(value: BybitOrderSide) -> Self {
645        match value {
646            BybitOrderSide::Buy => Some(OrderSide::Buy),
647            BybitOrderSide::Sell => Some(OrderSide::Sell),
648            BybitOrderSide::Unknown => None,
649        }
650    }
651}
652
653impl TryFrom<BybitOrderSide> for OrderSide {
654    type Error = anyhow::Error;
655
656    fn try_from(value: BybitOrderSide) -> Result<Self, Self::Error> {
657        match value {
658            BybitOrderSide::Buy => Ok(Self::Buy),
659            BybitOrderSide::Sell => Ok(Self::Sell),
660            BybitOrderSide::Unknown => anyhow::bail!("Unspecified Bybit order side"),
661        }
662    }
663}
664
665impl From<OrderSide> for BybitOrderSide {
666    fn from(value: OrderSide) -> Self {
667        match value {
668            OrderSide::Buy => Self::Buy,
669            OrderSide::Sell => Self::Sell,
670        }
671    }
672}
673
674impl From<BybitTriggerType> for TriggerType {
675    fn from(value: BybitTriggerType) -> Self {
676        match value {
677            BybitTriggerType::None => Self::Default,
678            BybitTriggerType::LastPrice => Self::LastPrice,
679            BybitTriggerType::IndexPrice => Self::IndexPrice,
680            BybitTriggerType::MarkPrice => Self::MarkPrice,
681        }
682    }
683}
684
685impl From<TriggerType> for BybitTriggerType {
686    fn from(value: TriggerType) -> Self {
687        match value {
688            TriggerType::Default | TriggerType::LastPrice => Self::LastPrice,
689            TriggerType::IndexPrice => Self::IndexPrice,
690            TriggerType::MarkPrice => Self::MarkPrice,
691            _ => Self::LastPrice,
692        }
693    }
694}
695
696/// Resolves an optional Nautilus trigger type to a Bybit trigger type,
697/// defaulting to `LastPrice` when absent.
698pub fn resolve_trigger_type(trigger_type: Option<TriggerType>) -> BybitTriggerType {
699    trigger_type.map_or(BybitTriggerType::LastPrice, BybitTriggerType::from)
700}
701
702/// Order cancel reason values as returned by Bybit.
703#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
704#[serde(rename_all = "PascalCase")]
705#[cfg_attr(
706    feature = "python",
707    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
708)]
709#[cfg_attr(
710    feature = "python",
711    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
712)]
713pub enum BybitCancelType {
714    CancelByUser,
715    CancelByReduceOnly,
716    CancelByPrepareLiq,
717    CancelAllBeforeLiq,
718    CancelByPrepareAdl,
719    CancelAllBeforeAdl,
720    CancelByPrepareLackOfMargin,
721    CancelByPrepareOrderFilter,
722    CancelByPrepareOrderMarginCheckFailed,
723    CancelByPrepareOrderCommission,
724    CancelByPrepareOrderRms,
725    CancelByPrepareOrderOther,
726    CancelByRiskLimit,
727    CancelOnDisconnect,
728    CancelByStopOrdersExceeded,
729    CancelByPzMarketClose,
730    CancelByMarginCheckFailed,
731    CancelByPzTakeover,
732    CancelByAdmin,
733    CancelBySettle,
734    CancelByTpSlTsClear,
735    CancelByAmendNotModified,
736    CancelByPzCancel,
737    CancelBySmp,
738    #[serde(rename = "CancelByDCP")]
739    CancelByDcp,
740    CancelByRebalance,
741    #[serde(rename = "CancelByOCOTpCanceledBySlTriggered")]
742    CancelByOcoTpCanceledBySlTriggered,
743    #[serde(rename = "CancelByOCOSlCanceledByTpTriggered")]
744    CancelByOcoSlCanceledByTpTriggered,
745    CancelByCannotAffordOrderCost,
746    CancelByPmTrialMmOverEquity,
747    CancelByAccountBlocking,
748    CancelByDelivery,
749    CancelByMmpTriggered,
750    CancelByCrossSelfMuch,
751    CancelByCrossReachMaxTradeNum,
752    CancelByCrossSelfMatch,
753    CancelBySelfMatchPrevention,
754    #[serde(other)]
755    Other,
756}
757
758/// Order creation origin values.
759#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
760#[serde(rename_all = "PascalCase")]
761pub enum BybitCreateType {
762    CreateByUser,
763    CreateByFutureSpread,
764    CreateByAdminClosing,
765    CreateBySettle,
766    CreateByClosing,
767    CreateByTakeProfit,
768    CreateByStopLoss,
769    CreateByTrailingStop,
770    CreateByTrailingProfit,
771    CreateByStopOrder,
772    CreateByPartialTakeProfit,
773    CreateByPartialStopLoss,
774    CreateByAdl,
775    #[serde(rename = "CreateByAdl_PassThrough")]
776    CreateByAdlPassThrough,
777    CreateByLiq,
778    CreateByLiquidate,
779    CreateByTakeover,
780    #[serde(rename = "CreateByTakeOver_PassThrough")]
781    CreateByTakeOverPassThrough,
782    #[serde(rename = "CreateByBlock_PassThrough")]
783    CreateByBlockPassThrough,
784    #[serde(rename = "CreateByBlockTradeMovePosition_PassThrough")]
785    CreateByBlockTradeMovePositionPassThrough,
786    CreateByChaseOrder,
787    #[serde(rename = "CreateByFGridBot")]
788    CreateByFGridBot,
789    #[serde(rename = "CloseByFGridBot")]
790    CloseByFGridBot,
791    #[serde(rename = "CreateByTWAP")]
792    CreateByTwap,
793    CreateByMartingaleBot,
794    CloseByMartingaleBot,
795    #[serde(rename = "CreateByIceBerg")]
796    CreateByIceBerg,
797    CreateByBboOrder,
798    #[serde(rename = "CreateByTVSignal")]
799    CreateByTvSignal,
800    CreateByMmRateClose,
801    CreateByArbitrage,
802    CreateByDdh,
803    CreateByTpsl,
804    #[serde(other)]
805    Other,
806}
807
808/// BBO side type values for Bybit order placement.
809#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
810#[serde(rename_all = "PascalCase")]
811pub enum BybitBboSideType {
812    Queue,
813    Counterparty,
814}
815
816/// Venue order type enumeration.
817#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
818#[cfg_attr(
819    feature = "python",
820    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
821)]
822#[cfg_attr(
823    feature = "python",
824    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
825)]
826pub enum BybitOrderType {
827    #[serde(rename = "Market")]
828    Market,
829    #[serde(rename = "Limit")]
830    Limit,
831    #[serde(rename = "UNKNOWN")]
832    Unknown,
833}
834
835/// Stop order type classification.
836#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
837#[cfg_attr(
838    feature = "python",
839    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
840)]
841#[cfg_attr(
842    feature = "python",
843    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
844)]
845pub enum BybitStopOrderType {
846    #[serde(rename = "")]
847    None,
848    #[serde(rename = "UNKNOWN")]
849    Unknown,
850    #[serde(rename = "TakeProfit")]
851    TakeProfit,
852    #[serde(rename = "StopLoss")]
853    StopLoss,
854    #[serde(rename = "TrailingStop")]
855    TrailingStop,
856    #[serde(rename = "Stop")]
857    Stop,
858    #[serde(rename = "PartialTakeProfit")]
859    PartialTakeProfit,
860    #[serde(rename = "PartialStopLoss")]
861    PartialStopLoss,
862    #[serde(rename = "tpslOrder")]
863    TpslOrder,
864    #[serde(rename = "OcoOrder")]
865    OcoOrder,
866    #[serde(rename = "MmRateClose")]
867    MmRateClose,
868    #[serde(rename = "BidirectionalTpslOrder")]
869    BidirectionalTpslOrder,
870}
871
872/// Trigger type configuration.
873#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
874#[cfg_attr(
875    feature = "python",
876    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
877)]
878#[cfg_attr(
879    feature = "python",
880    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
881)]
882pub enum BybitTriggerType {
883    #[serde(rename = "")]
884    None,
885    #[serde(rename = "LastPrice")]
886    LastPrice,
887    #[serde(rename = "IndexPrice")]
888    IndexPrice,
889    #[serde(rename = "MarkPrice")]
890    MarkPrice,
891}
892
893/// Trigger direction integers used by the API.
894#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize_repr, Deserialize_repr)]
895#[repr(i32)]
896#[cfg_attr(
897    feature = "python",
898    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
899)]
900#[cfg_attr(
901    feature = "python",
902    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
903)]
904pub enum BybitTriggerDirection {
905    None = 0,
906    RisesTo = 1,
907    FallsTo = 2,
908}
909
910/// Take-profit/stop-loss mode for derivatives orders.
911#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
912#[serde(rename_all = "PascalCase")]
913#[cfg_attr(
914    feature = "python",
915    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
916)]
917#[cfg_attr(
918    feature = "python",
919    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
920)]
921pub enum BybitTpSlMode {
922    Full,
923    Partial,
924    #[serde(other)]
925    Unknown,
926}
927
928/// Time-in-force enumeration.
929#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
930#[cfg_attr(
931    feature = "python",
932    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
933)]
934#[cfg_attr(
935    feature = "python",
936    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
937)]
938pub enum BybitTimeInForce {
939    #[serde(rename = "GTC")]
940    Gtc,
941    #[serde(rename = "IOC")]
942    Ioc,
943    #[serde(rename = "FOK")]
944    Fok,
945    #[serde(rename = "PostOnly")]
946    PostOnly,
947    #[serde(rename = "RPI")]
948    Rpi,
949}
950
951/// Execution type values used in execution reports.
952///
953/// Reference: <https://bybit-exchange.github.io/docs/v5/enum#exectype>.
954#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
955pub enum BybitExecType {
956    #[serde(rename = "Trade")]
957    Trade,
958    #[serde(rename = "AdlTrade")]
959    AdlTrade,
960    #[serde(rename = "Funding")]
961    Funding,
962    #[serde(rename = "BustTrade")]
963    BustTrade,
964    #[serde(rename = "Delivery")]
965    Delivery,
966    #[serde(rename = "Settle")]
967    Settle,
968    #[serde(rename = "BlockTrade")]
969    BlockTrade,
970    #[serde(rename = "MovePosition")]
971    MovePosition,
972    #[serde(rename = "FutureSpread")]
973    FutureSpread,
974    /// Retained for execution history recorded while the venue issued this type
975    /// (2026-07-23 to 2026-08-20), before its replacement by the split settlement
976    /// and dividend types.
977    #[serde(rename = "CorporateAction")]
978    CorporateAction,
979    #[serde(rename = "ForwardSplitSettle")]
980    ForwardSplitSettle,
981    #[serde(rename = "ReverseSplitSettle")]
982    ReverseSplitSettle,
983    #[serde(rename = "Dividend")]
984    Dividend,
985    #[serde(other)]
986    #[serde(rename = "UNKNOWN")]
987    Unknown,
988}
989
990impl BybitExecType {
991    /// Returns `true` if this execution was generated by the venue rather than the user.
992    ///
993    /// This covers auto-deleveraging (`AdlTrade`), liquidation takeovers (`BustTrade`),
994    /// scheduled deliveries (`Delivery`), settlement (`Settle`), and corporate actions
995    /// (`CorporateAction`, `ForwardSplitSettle`, `ReverseSplitSettle`, `Dividend`).
996    #[must_use]
997    pub const fn is_exchange_generated(&self) -> bool {
998        matches!(
999            self,
1000            Self::AdlTrade
1001                | Self::BustTrade
1002                | Self::Delivery
1003                | Self::Settle
1004                | Self::CorporateAction
1005                | Self::ForwardSplitSettle
1006                | Self::ReverseSplitSettle
1007                | Self::Dividend
1008        )
1009    }
1010}
1011
1012/// Transaction types for wallet funding records.
1013#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1014pub enum BybitTransactionType {
1015    #[serde(rename = "TRANSFER_IN")]
1016    TransferIn,
1017    #[serde(rename = "TRANSFER_OUT")]
1018    TransferOut,
1019    #[serde(rename = "TRADE")]
1020    Trade,
1021    #[serde(rename = "SETTLEMENT")]
1022    Settlement,
1023    #[serde(rename = "DELIVERY")]
1024    Delivery,
1025    #[serde(rename = "LIQUIDATION")]
1026    Liquidation,
1027    #[serde(rename = "AIRDRP")]
1028    Airdrop,
1029    #[serde(rename = "ADL")]
1030    Adl,
1031    #[serde(rename = "DIVIDEND_SETTLEMENT")]
1032    DividendSettlement,
1033    #[serde(rename = "FORWARD_SPLIT_SETTLE")]
1034    ForwardSplitSettle,
1035    #[serde(rename = "REVERSE_SPLIT_SETTLE")]
1036    ReverseSplitSettle,
1037    #[serde(other)]
1038    Other,
1039}
1040
1041/// Endpoint classifications used by the Bybit API.
1042#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1043#[serde(rename_all = "UPPERCASE")]
1044pub enum BybitEndpointType {
1045    None,
1046    Asset,
1047    Market,
1048    Account,
1049    Trade,
1050    Position,
1051    User,
1052}
1053
1054/// Filter for open orders query.
1055///
1056/// Used with `GET /v5/order/realtime` to filter order status.
1057#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Serialize_repr, Deserialize_repr)]
1058#[repr(i32)]
1059#[cfg_attr(
1060    feature = "python",
1061    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
1062)]
1063#[cfg_attr(
1064    feature = "python",
1065    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
1066)]
1067pub enum BybitOpenOnly {
1068    /// Query open status orders only (New, PartiallyFilled).
1069    #[default]
1070    OpenOnly = 0,
1071    /// Query up to 500 recent closed orders (cancelled, rejected, filled).
1072    ClosedRecent = 1,
1073}
1074
1075/// Order filter for querying specific order types.
1076///
1077/// Used with `GET /v5/order/realtime` to filter by order category.
1078#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
1079#[cfg_attr(
1080    feature = "python",
1081    pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
1082)]
1083#[cfg_attr(
1084    feature = "python",
1085    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
1086)]
1087pub enum BybitOrderFilter {
1088    /// Active orders (default).
1089    #[default]
1090    Order,
1091    /// Conditional orders (futures and spot).
1092    StopOrder,
1093    /// Spot take-profit/stop-loss orders.
1094    #[serde(rename = "tpslOrder")]
1095    TpslOrder,
1096    /// Spot one-cancels-other orders.
1097    OcoOrder,
1098    /// Spot bidirectional TP/SL orders.
1099    BidirectionalTpslOrder,
1100}
1101
1102/// Margin actions for spot margin trading operations.
1103#[derive(
1104    Clone,
1105    Copy,
1106    Debug,
1107    strum::Display,
1108    Eq,
1109    PartialEq,
1110    Hash,
1111    AsRefStr,
1112    EnumIter,
1113    EnumString,
1114    Serialize,
1115    Deserialize,
1116)]
1117#[serde(rename_all = "snake_case")]
1118#[strum(serialize_all = "snake_case")]
1119#[cfg_attr(
1120    feature = "python",
1121    pyo3::pyclass(
1122        eq,
1123        eq_int,
1124        hash,
1125        frozen,
1126        rename_all = "SCREAMING_SNAKE_CASE",
1127        module = "nautilus_trader.adapters.bybit",
1128        from_py_object,
1129    )
1130)]
1131#[cfg_attr(
1132    feature = "python",
1133    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
1134)]
1135pub enum BybitMarginAction {
1136    /// Borrow funds for margin trading.
1137    Borrow,
1138    /// Repay borrowed funds.
1139    Repay,
1140    /// Query current borrowed amount.
1141    GetBorrowAmount,
1142}
1143
1144/// Result status returned by Bybit repayment endpoints.
1145#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, strum::Display, Serialize, Deserialize)]
1146pub enum BybitRepayStatus {
1147    /// The repayment is processing.
1148    #[serde(rename = "P")]
1149    #[strum(serialize = "P")]
1150    Processing,
1151    /// The repayment succeeded.
1152    #[serde(rename = "SU")]
1153    #[strum(serialize = "SU")]
1154    Success,
1155    /// The repayment failed.
1156    #[serde(rename = "FA")]
1157    #[strum(serialize = "FA")]
1158    Failed,
1159}
1160
1161/// Position status enumeration.
1162#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1163#[serde(rename_all = "PascalCase")]
1164pub enum BybitPositionStatus {
1165    Normal,
1166    Liq,
1167    Adl,
1168    Settle,
1169    Delivering,
1170    #[serde(other)]
1171    Other,
1172}
1173
1174/// Market unit for spot market orders.
1175#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1176pub enum BybitMarketUnit {
1177    #[serde(rename = "baseCoin")]
1178    BaseCoin,
1179    #[serde(rename = "quoteCoin")]
1180    QuoteCoin,
1181}
1182
1183/// Self-match prevention type reported on an order or execution.
1184///
1185/// `Other` is a catch-all for values Bybit adds after this enum was written. Requests use
1186/// [`BybitOrderSmpType`], which has no catch-all, so an unknown value cannot reach the venue.
1187#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1188pub enum BybitSmpType {
1189    None,
1190    CancelMaker,
1191    CancelTaker,
1192    CancelBoth,
1193    #[serde(other)]
1194    Other,
1195}
1196
1197/// Self-match prevention type accepted on an order request.
1198///
1199/// Mirrors [`BybitSmpType`] without its inbound catch-all, so every value this type can hold is one
1200/// Bybit accepts on an order.
1201#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, AsRefStr, Serialize, Deserialize)]
1202pub enum BybitOrderSmpType {
1203    None,
1204    CancelMaker,
1205    CancelTaker,
1206    CancelBoth,
1207}
1208
1209#[cfg(test)]
1210mod tests {
1211    use rstest::rstest;
1212
1213    use super::*;
1214
1215    #[rstest]
1216    #[case::minute1(BybitKlineInterval::Minute1, 60_000)]
1217    #[case::minute3(BybitKlineInterval::Minute3, 180_000)]
1218    #[case::minute5(BybitKlineInterval::Minute5, 300_000)]
1219    #[case::minute15(BybitKlineInterval::Minute15, 900_000)]
1220    #[case::minute30(BybitKlineInterval::Minute30, 1_800_000)]
1221    #[case::hour1(BybitKlineInterval::Hour1, 3_600_000)]
1222    #[case::hour2(BybitKlineInterval::Hour2, 7_200_000)]
1223    #[case::hour4(BybitKlineInterval::Hour4, 14_400_000)]
1224    #[case::hour6(BybitKlineInterval::Hour6, 21_600_000)]
1225    #[case::hour12(BybitKlineInterval::Hour12, 43_200_000)]
1226    #[case::day1(BybitKlineInterval::Day1, 86_400_000)]
1227    #[case::week1(BybitKlineInterval::Week1, 604_800_000)]
1228    #[case::month1(BybitKlineInterval::Month1, 2_678_400_000)]
1229    fn test_kline_interval_duration_ms(
1230        #[case] interval: BybitKlineInterval,
1231        #[case] expected_ms: i64,
1232    ) {
1233        assert_eq!(interval.duration_ms(), expected_ms);
1234    }
1235
1236    #[rstest]
1237    fn test_bar_end_time_ms_non_monthly_adds_duration() {
1238        let interval = BybitKlineInterval::Minute1;
1239        let start_ms = 1704067200000i64;
1240        assert_eq!(interval.bar_end_time_ms(start_ms), start_ms + 60_000);
1241    }
1242
1243    #[rstest]
1244    #[case::jan_31_days(1704067200000i64, 1706745600000i64)]
1245    #[case::feb_leap_year_29_days(1706745600000i64, 1709251200000i64)]
1246    #[case::apr_30_days(1711929600000i64, 1714521600000i64)]
1247    #[case::dec_to_next_year(1733011200000i64, 1735689600000i64)]
1248    fn test_bar_end_time_ms_monthly_variable_lengths(
1249        #[case] start_ms: i64,
1250        #[case] expected_end_ms: i64,
1251    ) {
1252        let interval = BybitKlineInterval::Month1;
1253        assert_eq!(interval.bar_end_time_ms(start_ms), expected_end_ms);
1254    }
1255
1256    #[rstest]
1257    #[case(BybitSymbolType::Innovation, "innovation")]
1258    #[case(BybitSymbolType::Adventure, "adventure")]
1259    #[case(BybitSymbolType::Xstocks, "xstocks")]
1260    #[case(BybitSymbolType::Commodity, "commodity")]
1261    #[case(BybitSymbolType::Stock, "stock")]
1262    #[case(BybitSymbolType::Forex, "forex")]
1263    #[case(BybitSymbolType::Etf, "ETF")]
1264    #[case(BybitSymbolType::Mstocks, "mstocks")]
1265    fn test_symbol_type_round_trip(#[case] symbol_type: BybitSymbolType, #[case] wire_value: &str) {
1266        let value = serde_json::Value::String(wire_value.to_string());
1267
1268        assert_eq!(
1269            serde_json::from_value::<BybitSymbolType>(value.clone()).unwrap(),
1270            symbol_type
1271        );
1272        assert_eq!(serde_json::to_value(symbol_type).unwrap(), value);
1273        assert_eq!(symbol_type.as_str(), Some(wire_value));
1274    }
1275
1276    #[rstest]
1277    fn test_unknown_symbol_type_has_no_wire_value() {
1278        assert_eq!(BybitSymbolType::Other.as_str(), None);
1279    }
1280
1281    #[rstest]
1282    #[case(BybitExecType::Trade, false)]
1283    #[case(BybitExecType::AdlTrade, true)]
1284    #[case(BybitExecType::BustTrade, true)]
1285    #[case(BybitExecType::Delivery, true)]
1286    #[case(BybitExecType::Settle, true)]
1287    #[case(BybitExecType::Funding, false)]
1288    #[case(BybitExecType::BlockTrade, false)]
1289    #[case(BybitExecType::MovePosition, false)]
1290    #[case(BybitExecType::FutureSpread, false)]
1291    #[case(BybitExecType::CorporateAction, true)]
1292    #[case(BybitExecType::ForwardSplitSettle, true)]
1293    #[case(BybitExecType::ReverseSplitSettle, true)]
1294    #[case(BybitExecType::Dividend, true)]
1295    #[case(BybitExecType::Unknown, false)]
1296    fn test_exec_type_is_exchange_generated(
1297        #[case] exec_type: BybitExecType,
1298        #[case] expected: bool,
1299    ) {
1300        assert_eq!(exec_type.is_exchange_generated(), expected);
1301    }
1302
1303    #[rstest]
1304    #[case(BybitExecType::Trade, "Trade")]
1305    #[case(BybitExecType::AdlTrade, "AdlTrade")]
1306    #[case(BybitExecType::Funding, "Funding")]
1307    #[case(BybitExecType::BustTrade, "BustTrade")]
1308    #[case(BybitExecType::Delivery, "Delivery")]
1309    #[case(BybitExecType::Settle, "Settle")]
1310    #[case(BybitExecType::BlockTrade, "BlockTrade")]
1311    #[case(BybitExecType::MovePosition, "MovePosition")]
1312    #[case(BybitExecType::FutureSpread, "FutureSpread")]
1313    #[case(BybitExecType::CorporateAction, "CorporateAction")]
1314    #[case(BybitExecType::ForwardSplitSettle, "ForwardSplitSettle")]
1315    #[case(BybitExecType::ReverseSplitSettle, "ReverseSplitSettle")]
1316    #[case(BybitExecType::Dividend, "Dividend")]
1317    #[case(BybitExecType::Unknown, "UNKNOWN")]
1318    fn test_exec_type_round_trip(#[case] exec_type: BybitExecType, #[case] wire_value: &str) {
1319        let value = serde_json::Value::String(wire_value.to_string());
1320
1321        assert_eq!(
1322            serde_json::from_value::<BybitExecType>(value.clone()).unwrap(),
1323            exec_type
1324        );
1325        assert_eq!(serde_json::to_value(exec_type).unwrap(), value);
1326    }
1327
1328    #[rstest]
1329    fn test_exec_type_unrecognized_value_deserializes_to_unknown() {
1330        let value = serde_json::Value::String("StockMerger".to_string());
1331
1332        assert_eq!(
1333            serde_json::from_value::<BybitExecType>(value).unwrap(),
1334            BybitExecType::Unknown
1335        );
1336    }
1337
1338    #[rstest]
1339    #[case(BybitAccountType::Unified, "UNIFIED")]
1340    #[case(BybitAccountType::Fund, "FUND")]
1341    fn test_account_type_round_trip(
1342        #[case] account_type: BybitAccountType,
1343        #[case] wire_value: &str,
1344    ) {
1345        let value = serde_json::Value::String(wire_value.to_string());
1346
1347        assert_eq!(
1348            serde_json::from_value::<BybitAccountType>(value.clone()).unwrap(),
1349            account_type
1350        );
1351        assert_eq!(serde_json::to_value(account_type).unwrap(), value);
1352    }
1353
1354    #[rstest]
1355    #[case(BybitTimeInForce::Gtc, "GTC")]
1356    #[case(BybitTimeInForce::Ioc, "IOC")]
1357    #[case(BybitTimeInForce::Fok, "FOK")]
1358    #[case(BybitTimeInForce::PostOnly, "PostOnly")]
1359    #[case(BybitTimeInForce::Rpi, "RPI")]
1360    fn test_time_in_force_round_trip(#[case] tif: BybitTimeInForce, #[case] wire_value: &str) {
1361        let value = serde_json::Value::String(wire_value.to_string());
1362
1363        assert_eq!(
1364            serde_json::from_value::<BybitTimeInForce>(value.clone()).unwrap(),
1365            tif
1366        );
1367        assert_eq!(serde_json::to_value(tif).unwrap(), value);
1368    }
1369
1370    #[rstest]
1371    #[case("CreateByAdl_PassThrough", BybitCreateType::CreateByAdlPassThrough)]
1372    #[case("CreateByLiq", BybitCreateType::CreateByLiq)]
1373    #[case(
1374        "CreateByTakeOver_PassThrough",
1375        BybitCreateType::CreateByTakeOverPassThrough
1376    )]
1377    #[case("CreateByTWAP", BybitCreateType::CreateByTwap)]
1378    #[case("CreateByFutureSpread", BybitCreateType::CreateByFutureSpread)]
1379    fn test_create_type_venue_wire_values(
1380        #[case] wire_value: &str,
1381        #[case] expected: BybitCreateType,
1382    ) {
1383        let value = serde_json::Value::String(wire_value.to_string());
1384
1385        assert_eq!(
1386            serde_json::from_value::<BybitCreateType>(value.clone()).unwrap(),
1387            expected
1388        );
1389        assert_eq!(serde_json::to_value(expected).unwrap(), value);
1390    }
1391
1392    #[rstest]
1393    #[case("CancelBySettle", BybitCancelType::CancelBySettle)]
1394    #[case("CancelBySmp", BybitCancelType::CancelBySmp)]
1395    #[case("CancelByDCP", BybitCancelType::CancelByDcp)]
1396    #[case("CancelAllBeforeLiq", BybitCancelType::CancelAllBeforeLiq)]
1397    #[case(
1398        "CancelByOCOTpCanceledBySlTriggered",
1399        BybitCancelType::CancelByOcoTpCanceledBySlTriggered
1400    )]
1401    fn test_cancel_type_venue_wire_values(
1402        #[case] wire_value: &str,
1403        #[case] expected: BybitCancelType,
1404    ) {
1405        let value = serde_json::Value::String(wire_value.to_string());
1406
1407        assert_eq!(
1408            serde_json::from_value::<BybitCancelType>(value.clone()).unwrap(),
1409            expected
1410        );
1411        assert_eq!(serde_json::to_value(expected).unwrap(), value);
1412    }
1413
1414    #[rstest]
1415    fn test_pending_open_instrument_status_round_trip() {
1416        let value = serde_json::Value::String("PendingOpen".to_string());
1417
1418        assert_eq!(
1419            serde_json::from_value::<BybitInstrumentStatus>(value.clone()).unwrap(),
1420            BybitInstrumentStatus::PendingOpen
1421        );
1422        assert_eq!(
1423            serde_json::to_value(BybitInstrumentStatus::PendingOpen).unwrap(),
1424            value
1425        );
1426    }
1427
1428    #[rstest]
1429    fn test_normal_spot_only_margin_trading_round_trip() {
1430        let value = serde_json::Value::String("normalSpotOnly".to_string());
1431
1432        assert_eq!(
1433            serde_json::from_value::<BybitMarginTrading>(value.clone()).unwrap(),
1434            BybitMarginTrading::NormalSpotOnly
1435        );
1436        assert_eq!(
1437            serde_json::to_value(BybitMarginTrading::NormalSpotOnly).unwrap(),
1438            value
1439        );
1440    }
1441
1442    #[rstest]
1443    #[case(BybitPositionStatus::Liq, "Liq")]
1444    #[case(BybitPositionStatus::Adl, "Adl")]
1445    fn test_position_status_liq_adl_round_trip(
1446        #[case] status: BybitPositionStatus,
1447        #[case] wire_value: &str,
1448    ) {
1449        let value = serde_json::Value::String(wire_value.to_string());
1450
1451        assert_eq!(
1452            serde_json::from_value::<BybitPositionStatus>(value.clone()).unwrap(),
1453            status
1454        );
1455        assert_eq!(serde_json::to_value(status).unwrap(), value);
1456    }
1457}