Skip to main content

nautilus_okx/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 mapping OKX concepts onto idiomatic Nautilus variants.
17
18use nautilus_model::enums::{
19    AggressorSide, GreeksConvention, LiquiditySide, OptionKind, OrderSide, OrderStatus, OrderType,
20    PositionSide, TriggerType,
21};
22use serde::{Deserialize, Serialize};
23use strum::{AsRefStr, Display, EnumIter, EnumString};
24
25use crate::common::consts::{OKX_ADVANCE_ALGO_ORDER_TYPES, OKX_CONDITIONAL_ORDER_TYPES};
26
27/// Represents the type of book action.
28#[derive(
29    Copy,
30    Clone,
31    Debug,
32    Display,
33    PartialEq,
34    Eq,
35    Hash,
36    AsRefStr,
37    EnumIter,
38    EnumString,
39    Serialize,
40    Deserialize,
41)]
42#[serde(rename_all = "lowercase")]
43pub enum OKXBookAction {
44    /// Incremental update.
45    Update,
46    /// Full snapshot.
47    Snapshot,
48}
49
50/// Represents the possible states of an order throughout its lifecycle.
51#[derive(
52    Copy,
53    Clone,
54    Debug,
55    Display,
56    PartialEq,
57    Eq,
58    Hash,
59    AsRefStr,
60    EnumIter,
61    EnumString,
62    Serialize,
63    Deserialize,
64)]
65pub enum OKXCandleConfirm {
66    /// K-line is incomplete.
67    #[serde(rename = "0")]
68    Partial,
69    /// K-line is completed.
70    #[serde(rename = "1")]
71    Closed,
72}
73
74/// Represents the side of an order or trade (Buy/Sell).
75#[derive(
76    Copy,
77    Clone,
78    Debug,
79    Display,
80    PartialEq,
81    Eq,
82    Hash,
83    AsRefStr,
84    EnumIter,
85    EnumString,
86    Serialize,
87    Deserialize,
88)]
89#[serde(rename_all = "snake_case")]
90pub enum OKXSide {
91    /// Buy side of a trade or order.
92    Buy,
93    /// Sell side of a trade or order.
94    Sell,
95}
96
97impl From<OrderSide> for OKXSide {
98    fn from(value: OrderSide) -> Self {
99        match value {
100            OrderSide::Buy => Self::Buy,
101            OrderSide::Sell => Self::Sell,
102        }
103    }
104}
105
106impl From<OKXSide> for AggressorSide {
107    fn from(value: OKXSide) -> Self {
108        match value {
109            OKXSide::Buy => Self::Buy,
110            OKXSide::Sell => Self::Sell,
111        }
112    }
113}
114
115/// Represents the available order types on OKX.
116#[derive(
117    Copy,
118    Clone,
119    Debug,
120    Display,
121    PartialEq,
122    Eq,
123    Hash,
124    AsRefStr,
125    EnumIter,
126    EnumString,
127    Serialize,
128    Deserialize,
129)]
130#[serde(rename_all = "snake_case")]
131pub enum OKXOrderType {
132    /// Market order, executed immediately at current market price.
133    Market,
134    /// Limit order, executed only at specified price or better.
135    Limit,
136    /// Retail Price Improvement order.
137    #[serde(alias = "elp")]
138    Rpi,
139    PostOnly,        // limit only, requires "px" to be provided
140    Fok,             // Market order if "px" is not provided, otherwise limit order
141    Ioc,             // Market order if "px" is not provided, otherwise limit order
142    OptimalLimitIoc, // Market order with immediate-or-cancel order
143    Mmp,             // Market Maker Protection (only applicable to Option in Portfolio Margin mode)
144    MmpAndPostOnly, // Market Maker Protection and Post-only order(only applicable to Option in Portfolio Margin mode)
145    OpFok,          // Fill-or-Kill for options (only applicable to Option)
146    Trigger,        // Conditional/algo order (stop orders, etc.)
147    /// Forward-compatible fallback for order types OKX adds later.
148    #[serde(other)]
149    Other,
150}
151
152/// Represents the possible states of an order throughout its lifecycle.
153#[derive(
154    Copy,
155    Clone,
156    Debug,
157    Display,
158    PartialEq,
159    Eq,
160    Hash,
161    AsRefStr,
162    EnumIter,
163    EnumString,
164    Serialize,
165    Deserialize,
166)]
167#[serde(rename_all = "snake_case")]
168#[cfg_attr(
169    feature = "python",
170    pyo3::pyclass(
171        eq,
172        eq_int,
173        module = "nautilus_trader.adapters.okx",
174        from_py_object,
175        rename_all = "SCREAMING_SNAKE_CASE",
176    )
177)]
178#[cfg_attr(
179    feature = "python",
180    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
181)]
182pub enum OKXOrderStatus {
183    Canceled,
184    Live,
185    PartiallyFilled,
186    Filled,
187    MmpCanceled,
188    /// Forward-compatible fallback for order states OKX adds later.
189    #[serde(other)]
190    Unknown,
191}
192
193impl TryFrom<OrderStatus> for OKXOrderStatus {
194    type Error = OrderStatus;
195
196    /// Converts a Nautilus [`OrderStatus`] into the matching [`OKXOrderStatus`].
197    ///
198    /// Returns the original [`OrderStatus`] in the error case for any variant
199    /// that has no representable OKX equivalent (e.g. `Submitted`, `PendingNew`,
200    /// `Triggered`, `PendingCancel`, `Expired`, `Rejected`).
201    fn try_from(value: OrderStatus) -> Result<Self, Self::Error> {
202        match value {
203            OrderStatus::Canceled => Ok(Self::Canceled),
204            OrderStatus::Accepted => Ok(Self::Live),
205            OrderStatus::PartiallyFilled => Ok(Self::PartiallyFilled),
206            OrderStatus::Filled => Ok(Self::Filled),
207            other => Err(other),
208        }
209    }
210}
211
212/// Represents the type of execution that generated a trade.
213#[derive(
214    Copy,
215    Clone,
216    Debug,
217    Default,
218    Display,
219    PartialEq,
220    Eq,
221    Hash,
222    AsRefStr,
223    EnumIter,
224    EnumString,
225    Serialize,
226    Deserialize,
227)]
228pub enum OKXExecType {
229    #[serde(rename = "")]
230    #[default]
231    None,
232    #[serde(rename = "T")]
233    Taker,
234    #[serde(rename = "M")]
235    Maker,
236}
237
238impl From<LiquiditySide> for OKXExecType {
239    fn from(value: LiquiditySide) -> Self {
240        match value {
241            LiquiditySide::NoLiquiditySide => Self::None,
242            LiquiditySide::Taker => Self::Taker,
243            LiquiditySide::Maker => Self::Maker,
244        }
245    }
246}
247
248/// Represents instrument types on OKX.
249#[derive(
250    Copy,
251    Clone,
252    Debug,
253    Display,
254    Default,
255    PartialEq,
256    Eq,
257    Hash,
258    AsRefStr,
259    EnumIter,
260    EnumString,
261    Serialize,
262    Deserialize,
263)]
264#[serde(rename_all = "UPPERCASE")]
265#[cfg_attr(
266    feature = "python",
267    pyo3::pyclass(
268        eq,
269        eq_int,
270        module = "nautilus_trader.adapters.okx",
271        from_py_object,
272        rename_all = "SCREAMING_SNAKE_CASE",
273    )
274)]
275#[cfg_attr(
276    feature = "python",
277    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
278)]
279pub enum OKXInstrumentType {
280    #[default]
281    /// Any product type.
282    Any,
283    /// Spot products.
284    Spot,
285    /// Margin products.
286    Margin,
287    /// Swap products.
288    Swap,
289    /// Futures products.
290    Futures,
291    /// Option products.
292    Option,
293    /// Event contract products.
294    Events,
295}
296
297/// Represents an OKX instrument category code (the `instCategory` field).
298///
299/// OKX also returns a deprecated `category` field that is effectively always
300/// `"1"`; `instCategory` is the meaningful value that drives asset-class
301/// mapping. Unknown or future codes fall back to
302/// [`OKXInstrumentCategory::Unknown`] rather than failing to parse.
303#[derive(
304    Copy,
305    Clone,
306    Debug,
307    Default,
308    Display,
309    PartialEq,
310    Eq,
311    Hash,
312    AsRefStr,
313    EnumIter,
314    EnumString,
315    Serialize,
316    Deserialize,
317)]
318pub enum OKXInstrumentCategory {
319    /// Cryptocurrency (`"1"`).
320    #[serde(rename = "1")]
321    #[strum(serialize = "1")]
322    Crypto,
323    /// Equity-linked (`"3"`).
324    #[serde(rename = "3")]
325    #[strum(serialize = "3")]
326    Equity,
327    /// Commodity-linked (`"4"`).
328    #[serde(rename = "4")]
329    #[strum(serialize = "4")]
330    Commodity,
331    /// FX-linked (`"5"`).
332    #[serde(rename = "5")]
333    #[strum(serialize = "5")]
334    Fx,
335    /// Debt-linked (`"6"`).
336    #[serde(rename = "6")]
337    #[strum(serialize = "6")]
338    Debt,
339    /// Unknown or future category code.
340    #[default]
341    #[serde(other)]
342    #[strum(serialize = "")]
343    Unknown,
344}
345
346/// Represents an instrument status on OKX.
347#[derive(
348    Copy,
349    Clone,
350    Debug,
351    Display,
352    PartialEq,
353    Eq,
354    Hash,
355    AsRefStr,
356    EnumIter,
357    EnumString,
358    Serialize,
359    Deserialize,
360)]
361#[serde(rename_all = "snake_case")]
362pub enum OKXInstrumentStatus {
363    Live,
364    Suspend,
365    Preopen,
366    Test,
367    PostOnly,
368    Rebase,
369    Settling,
370    /// Unknown or future status.
371    #[serde(other)]
372    Unknown,
373}
374
375/// Represents a spread type on OKX.
376#[derive(
377    Copy,
378    Clone,
379    Debug,
380    Display,
381    PartialEq,
382    Eq,
383    Hash,
384    AsRefStr,
385    EnumIter,
386    EnumString,
387    Serialize,
388    Deserialize,
389)]
390#[serde(rename_all = "snake_case")]
391pub enum OKXSpreadType {
392    Linear,
393    Inverse,
394    Hybrid,
395    /// Unknown or future spread type.
396    #[serde(other)]
397    Unknown,
398}
399
400/// Represents a spread status on OKX.
401#[derive(
402    Copy,
403    Clone,
404    Debug,
405    Display,
406    PartialEq,
407    Eq,
408    Hash,
409    AsRefStr,
410    EnumIter,
411    EnumString,
412    Serialize,
413    Deserialize,
414)]
415#[serde(rename_all = "snake_case")]
416pub enum OKXSpreadState {
417    Live,
418    Suspend,
419    Expired,
420    /// Unknown or future status.
421    #[serde(other)]
422    Unknown,
423}
424
425/// Represents an instrument contract type on OKX.
426#[derive(
427    Copy,
428    Clone,
429    Default,
430    Debug,
431    Display,
432    PartialEq,
433    Eq,
434    Hash,
435    AsRefStr,
436    EnumIter,
437    EnumString,
438    Serialize,
439    Deserialize,
440)]
441#[serde(rename_all = "snake_case")]
442#[cfg_attr(
443    feature = "python",
444    pyo3::pyclass(
445        eq,
446        eq_int,
447        module = "nautilus_trader.adapters.okx",
448        from_py_object,
449        rename_all = "SCREAMING_SNAKE_CASE",
450    )
451)]
452#[cfg_attr(
453    feature = "python",
454    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
455)]
456pub enum OKXContractType {
457    #[serde(rename = "")]
458    #[default]
459    None,
460    Linear,
461    Inverse,
462}
463
464/// Represents an option type on OKX.
465#[derive(
466    Copy,
467    Clone,
468    Debug,
469    Display,
470    PartialEq,
471    Eq,
472    Hash,
473    AsRefStr,
474    EnumIter,
475    EnumString,
476    Serialize,
477    Deserialize,
478)]
479pub enum OKXOptionType {
480    #[serde(rename = "")]
481    None,
482    #[serde(rename = "C")]
483    Call,
484    #[serde(rename = "P")]
485    Put,
486}
487
488impl TryFrom<OKXOptionType> for OptionKind {
489    type Error = OKXOptionType;
490
491    /// Converts an OKX option type into the matching Nautilus [`OptionKind`].
492    ///
493    /// Returns the source variant in the error case for [`OKXOptionType::None`]
494    /// (sent by OKX as an empty `optType` for non-option instruments and the
495    /// occasional malformed payload). Callers should skip such instruments
496    /// rather than treating the unknown variant as a default option kind.
497    fn try_from(option_type: OKXOptionType) -> Result<Self, Self::Error> {
498        match option_type {
499            OKXOptionType::Call => Ok(Self::Call),
500            OKXOptionType::Put => Ok(Self::Put),
501            other => Err(other),
502        }
503    }
504}
505
506/// Represents the convention used for option greeks on OKX.
507///
508/// OKX publishes two parallel greek sets on `opt-summary` and related endpoints:
509/// Black-Scholes greeks denominated in USD, and price-adjusted greeks denominated
510/// in the underlying/coin units.
511#[derive(
512    Copy,
513    Clone,
514    Debug,
515    Default,
516    Display,
517    PartialEq,
518    Eq,
519    Hash,
520    AsRefStr,
521    EnumIter,
522    EnumString,
523    Serialize,
524    Deserialize,
525)]
526#[serde(rename_all = "UPPERCASE")]
527#[strum(serialize_all = "UPPERCASE", ascii_case_insensitive)]
528#[cfg_attr(
529    feature = "python",
530    pyo3::pyclass(
531        eq,
532        eq_int,
533        module = "nautilus_trader.adapters.okx",
534        from_py_object,
535        rename_all = "SCREAMING_SNAKE_CASE",
536    )
537)]
538#[cfg_attr(
539    feature = "python",
540    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
541)]
542pub enum OKXGreeksType {
543    /// Black-Scholes greeks in USD.
544    #[default]
545    Bs = 0,
546    /// Price-adjusted greeks in the underlying/coin units.
547    Pa = 1,
548}
549
550impl From<u8> for OKXGreeksType {
551    fn from(value: u8) -> Self {
552        match value {
553            0 => Self::Bs,
554            1 => Self::Pa,
555            _ => {
556                log::warn!("Invalid OKXGreeksType {value}, defaulting to Bs");
557                Self::Bs
558            }
559        }
560    }
561}
562
563impl From<GreeksConvention> for OKXGreeksType {
564    fn from(convention: GreeksConvention) -> Self {
565        match convention {
566            GreeksConvention::BlackScholes => Self::Bs,
567            GreeksConvention::PriceAdjusted => Self::Pa,
568        }
569    }
570}
571
572impl From<OKXGreeksType> for GreeksConvention {
573    fn from(greeks_type: OKXGreeksType) -> Self {
574        match greeks_type {
575            OKXGreeksType::Bs => Self::BlackScholes,
576            OKXGreeksType::Pa => Self::PriceAdjusted,
577        }
578    }
579}
580
581/// Represents the trading mode for OKX orders.
582#[derive(
583    Copy,
584    Clone,
585    Debug,
586    Display,
587    Default,
588    PartialEq,
589    Eq,
590    Hash,
591    AsRefStr,
592    EnumIter,
593    EnumString,
594    Serialize,
595    Deserialize,
596)]
597#[serde(rename_all = "snake_case")]
598#[strum(ascii_case_insensitive)]
599#[cfg_attr(
600    feature = "python",
601    pyo3::pyclass(
602        eq,
603        eq_int,
604        module = "nautilus_trader.adapters.okx",
605        from_py_object,
606        rename_all = "SCREAMING_SNAKE_CASE",
607    )
608)]
609#[cfg_attr(
610    feature = "python",
611    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
612)]
613pub enum OKXTradeMode {
614    #[default]
615    Cash,
616    Isolated,
617    Cross,
618    #[strum(serialize = "spot_isolated")]
619    SpotIsolated,
620}
621
622/// Represents the margin mode for OKX accounts.
623///
624/// # Reference
625///
626/// - <https://www.okx.com/en-au/help/iv-isolated-margin-mode>
627/// - <https://www.okx.com/en-au/help/iii-single-currency-margin-cross-margin-trading>
628/// - <https://www.okx.com/en-au/help/iv-multi-currency-margin-mode-cross-margin-trading>
629#[derive(
630    Copy,
631    Clone,
632    Default,
633    Debug,
634    Display,
635    PartialEq,
636    Eq,
637    Hash,
638    AsRefStr,
639    EnumIter,
640    EnumString,
641    Serialize,
642    Deserialize,
643)]
644#[serde(rename_all = "snake_case")]
645#[cfg_attr(
646    feature = "python",
647    pyo3::pyclass(
648        eq,
649        eq_int,
650        module = "nautilus_trader.adapters.okx",
651        from_py_object,
652        rename_all = "SCREAMING_SNAKE_CASE",
653    )
654)]
655#[cfg_attr(
656    feature = "python",
657    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
658)]
659pub enum OKXMarginMode {
660    #[serde(rename = "")]
661    #[default]
662    None,
663    Isolated,
664    Cross,
665}
666
667/// Represents the position mode for OKX accounts.
668///
669/// # References
670///
671/// <https://www.okx.com/docs-v5/en/#trading-account-rest-api-set-position-mode>
672#[derive(
673    Copy,
674    Clone,
675    Default,
676    Debug,
677    Display,
678    PartialEq,
679    Eq,
680    Hash,
681    AsRefStr,
682    EnumIter,
683    EnumString,
684    Serialize,
685    Deserialize,
686)]
687#[cfg_attr(
688    feature = "python",
689    pyo3::pyclass(
690        eq,
691        eq_int,
692        module = "nautilus_trader.adapters.okx",
693        from_py_object,
694        rename_all = "SCREAMING_SNAKE_CASE",
695    )
696)]
697#[cfg_attr(
698    feature = "python",
699    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
700)]
701pub enum OKXPositionMode {
702    #[default]
703    #[serde(rename = "net_mode")]
704    NetMode,
705    #[serde(rename = "long_short_mode")]
706    LongShortMode,
707}
708
709/// Represents the account mode reported by OKX account configuration.
710#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
711pub enum OKXAccountLevel {
712    /// Spot mode.
713    #[serde(rename = "1")]
714    Spot,
715    /// Futures mode.
716    #[serde(rename = "2")]
717    Futures,
718    /// Multi-currency margin mode.
719    #[serde(rename = "3")]
720    MultiCurrencyMargin,
721    /// Portfolio margin mode.
722    #[serde(rename = "4")]
723    PortfolioMargin,
724}
725
726/// Represents the fee-charging currency configured for an OKX account.
727#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
728pub enum OKXFeeType {
729    /// Fees are charged in the currency received from the trade.
730    #[serde(rename = "0")]
731    ReceivedCurrency,
732    /// Fees are always charged in the trading pair's quote currency.
733    #[serde(rename = "1")]
734    QuoteCurrency,
735}
736
737/// Represents a permission of the requesting OKX API key or access token.
738#[derive(Copy, Clone, Debug, PartialEq, Eq, AsRefStr, Serialize, Deserialize)]
739#[serde(rename_all = "snake_case")]
740#[strum(serialize_all = "snake_case")]
741pub enum OKXApiKeyPermission {
742    /// Read permission.
743    ReadOnly,
744    /// Trading permission.
745    Trade,
746    /// Withdrawal permission.
747    Withdraw,
748}
749
750#[derive(
751    Copy,
752    Clone,
753    Debug,
754    Display,
755    PartialEq,
756    Eq,
757    Hash,
758    AsRefStr,
759    EnumIter,
760    EnumString,
761    Serialize,
762    Deserialize,
763)]
764#[serde(rename_all = "snake_case")]
765pub enum OKXPositionSide {
766    #[serde(rename = "")]
767    None,
768    Net,
769    Long,
770    Short,
771}
772
773#[derive(
774    Copy,
775    Clone,
776    Debug,
777    Default,
778    Display,
779    PartialEq,
780    Eq,
781    Hash,
782    AsRefStr,
783    EnumIter,
784    EnumString,
785    Serialize,
786    Deserialize,
787)]
788#[serde(rename_all = "snake_case")]
789pub enum OKXSelfTradePreventionMode {
790    #[default]
791    #[serde(rename = "")]
792    None,
793    CancelMaker,
794    CancelTaker,
795    CancelBoth,
796}
797
798#[derive(
799    Copy,
800    Clone,
801    Debug,
802    Default,
803    Display,
804    PartialEq,
805    Eq,
806    Hash,
807    AsRefStr,
808    EnumIter,
809    EnumString,
810    Serialize,
811    Deserialize,
812)]
813#[serde(rename_all = "snake_case")]
814#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
815pub enum OKXTriggerType {
816    #[default]
817    #[serde(rename = "")]
818    None,
819    Last,
820    Index,
821    Mark,
822}
823
824impl From<TriggerType> for OKXTriggerType {
825    fn from(value: TriggerType) -> Self {
826        match value {
827            TriggerType::LastPrice => Self::Last,
828            TriggerType::MarkPrice => Self::Mark,
829            TriggerType::IndexPrice => Self::Index,
830            _ => Self::Last,
831        }
832    }
833}
834
835#[cfg(test)]
836mod tests {
837    use std::str::FromStr;
838
839    use nautilus_model::enums::{GreeksConvention, OptionKind, OrderStatus, OrderType};
840    use rstest::rstest;
841
842    use super::{
843        OKXAlgoOrderStatus, OKXAlgoOrderType, OKXGreeksType, OKXOptionType, OKXOrderCategory,
844        OKXOrderStatus, OKXOrderType, OKXPriceType, OKXRpiPermission, OKXTriggerType,
845    };
846
847    #[rstest]
848    fn test_okx_trigger_type_from_str_accepts_snake_case_values() {
849        assert_eq!(
850            OKXTriggerType::from_str("last").unwrap(),
851            OKXTriggerType::Last
852        );
853        assert_eq!(
854            OKXTriggerType::from_str("mark").unwrap(),
855            OKXTriggerType::Mark
856        );
857        assert_eq!(
858            OKXTriggerType::from_str("index").unwrap(),
859            OKXTriggerType::Index
860        );
861    }
862
863    #[rstest]
864    #[case(OKXGreeksType::Bs, "\"BS\"")]
865    #[case(OKXGreeksType::Pa, "\"PA\"")]
866    fn test_greeks_type_serde_roundtrip(#[case] input: OKXGreeksType, #[case] expected: &str) {
867        let json = serde_json::to_string(&input).unwrap();
868        assert_eq!(json, expected);
869        let parsed: OKXGreeksType = serde_json::from_str(expected).unwrap();
870        assert_eq!(parsed, input);
871    }
872
873    #[rstest]
874    fn test_greeks_type_default_is_bs() {
875        assert_eq!(OKXGreeksType::default(), OKXGreeksType::Bs);
876    }
877
878    #[rstest]
879    fn test_greeks_type_from_u8() {
880        assert_eq!(OKXGreeksType::from(0_u8), OKXGreeksType::Bs);
881        assert_eq!(OKXGreeksType::from(1_u8), OKXGreeksType::Pa);
882        assert_eq!(OKXGreeksType::from(99_u8), OKXGreeksType::Bs);
883    }
884
885    #[rstest]
886    #[case(GreeksConvention::BlackScholes, OKXGreeksType::Bs)]
887    #[case(GreeksConvention::PriceAdjusted, OKXGreeksType::Pa)]
888    fn test_greeks_type_convention_roundtrip(
889        #[case] convention: GreeksConvention,
890        #[case] expected: OKXGreeksType,
891    ) {
892        let mapped: OKXGreeksType = convention.into();
893        assert_eq!(mapped, expected);
894        let back: GreeksConvention = mapped.into();
895        assert_eq!(back, convention);
896    }
897
898    #[rstest]
899    fn test_op_fok_serializes_to_snake_case() {
900        let json = serde_json::to_string(&OKXOrderType::OpFok).unwrap();
901        assert_eq!(json, "\"op_fok\"");
902    }
903
904    #[rstest]
905    fn test_op_fok_deserializes_from_snake_case() {
906        let parsed: OKXOrderType = serde_json::from_str("\"op_fok\"").unwrap();
907        assert_eq!(parsed, OKXOrderType::OpFok);
908    }
909
910    #[rstest]
911    fn test_op_fok_converts_to_limit_order_type() {
912        let order_type: OrderType = OKXOrderType::OpFok.try_into().unwrap();
913        assert_eq!(order_type, OrderType::Limit);
914    }
915
916    #[rstest]
917    fn test_rpi_order_type_serializes_current_name_and_reads_legacy_alias() {
918        assert_eq!(
919            serde_json::to_string(&OKXOrderType::Rpi).unwrap(),
920            "\"rpi\""
921        );
922        assert_eq!(
923            serde_json::from_str::<OKXOrderType>("\"elp\"").unwrap(),
924            OKXOrderType::Rpi
925        );
926        assert_eq!(
927            OrderType::try_from(OKXOrderType::Rpi).unwrap(),
928            OrderType::Limit
929        );
930    }
931
932    #[rstest]
933    #[case("\"0\"", OKXRpiPermission::Disabled)]
934    #[case("\"1\"", OKXRpiPermission::Enabled)]
935    #[case("\"2\"", OKXRpiPermission::Permitted)]
936    fn test_rpi_permission_deserializes_string_codes(
937        #[case] json: &str,
938        #[case] expected: OKXRpiPermission,
939    ) {
940        assert_eq!(
941            serde_json::from_str::<OKXRpiPermission>(json).unwrap(),
942            expected
943        );
944    }
945
946    #[rstest]
947    #[case::call(OKXOptionType::Call, Ok(OptionKind::Call))]
948    #[case::put(OKXOptionType::Put, Ok(OptionKind::Put))]
949    #[case::none(OKXOptionType::None, Err(OKXOptionType::None))]
950    fn test_try_from_okx_option_type(
951        #[case] input: OKXOptionType,
952        #[case] expected: Result<OptionKind, OKXOptionType>,
953    ) {
954        let actual: Result<OptionKind, OKXOptionType> = input.try_into();
955        assert_eq!(actual, expected);
956    }
957
958    #[rstest]
959    #[case::canceled(OrderStatus::Canceled, Ok(OKXOrderStatus::Canceled))]
960    #[case::accepted(OrderStatus::Accepted, Ok(OKXOrderStatus::Live))]
961    #[case::partially_filled(OrderStatus::PartiallyFilled, Ok(OKXOrderStatus::PartiallyFilled))]
962    #[case::filled(OrderStatus::Filled, Ok(OKXOrderStatus::Filled))]
963    #[case::submitted(OrderStatus::Submitted, Err(OrderStatus::Submitted))]
964    #[case::pending_update(OrderStatus::PendingUpdate, Err(OrderStatus::PendingUpdate))]
965    #[case::pending_cancel(OrderStatus::PendingCancel, Err(OrderStatus::PendingCancel))]
966    #[case::triggered(OrderStatus::Triggered, Err(OrderStatus::Triggered))]
967    #[case::expired(OrderStatus::Expired, Err(OrderStatus::Expired))]
968    #[case::rejected(OrderStatus::Rejected, Err(OrderStatus::Rejected))]
969    fn test_try_from_order_status(
970        #[case] input: OrderStatus,
971        #[case] expected: Result<OKXOrderStatus, OrderStatus>,
972    ) {
973        let actual: Result<OKXOrderStatus, OrderStatus> = input.try_into();
974        assert_eq!(actual, expected);
975    }
976
977    #[rstest]
978    #[case::live(OKXOrderStatus::Live, Ok(OrderStatus::Accepted))]
979    #[case::partially_filled(OKXOrderStatus::PartiallyFilled, Ok(OrderStatus::PartiallyFilled))]
980    #[case::filled(OKXOrderStatus::Filled, Ok(OrderStatus::Filled))]
981    #[case::canceled(OKXOrderStatus::Canceled, Ok(OrderStatus::Canceled))]
982    #[case::mmp_canceled(OKXOrderStatus::MmpCanceled, Ok(OrderStatus::Canceled))]
983    #[case::unknown(OKXOrderStatus::Unknown, Err(OKXOrderStatus::Unknown))]
984    fn test_try_from_okx_order_status(
985        #[case] input: OKXOrderStatus,
986        #[case] expected: Result<OrderStatus, OKXOrderStatus>,
987    ) {
988        let actual: Result<OrderStatus, OKXOrderStatus> = input.try_into();
989        assert_eq!(actual, expected);
990    }
991
992    #[rstest]
993    #[case::live(OKXAlgoOrderStatus::Live, Ok(OrderStatus::Accepted))]
994    #[case::pause(OKXAlgoOrderStatus::Pause, Ok(OrderStatus::Accepted))]
995    #[case::effective(OKXAlgoOrderStatus::Effective, Ok(OrderStatus::Triggered))]
996    #[case::order_placed(OKXAlgoOrderStatus::OrderPlaced, Ok(OrderStatus::Triggered))]
997    #[case::partially_effective(OKXAlgoOrderStatus::PartiallyEffective, Ok(OrderStatus::Triggered))]
998    #[case::filled(OKXAlgoOrderStatus::Filled, Ok(OrderStatus::Filled))]
999    #[case::canceled(OKXAlgoOrderStatus::Canceled, Ok(OrderStatus::Canceled))]
1000    #[case::order_failed(OKXAlgoOrderStatus::OrderFailed, Ok(OrderStatus::Rejected))]
1001    #[case::partially_failed(OKXAlgoOrderStatus::PartiallyFailed, Ok(OrderStatus::Rejected))]
1002    #[case::unknown(OKXAlgoOrderStatus::Unknown, Err(OKXAlgoOrderStatus::Unknown))]
1003    fn test_try_from_okx_algo_order_status(
1004        #[case] input: OKXAlgoOrderStatus,
1005        #[case] expected: Result<OrderStatus, OKXAlgoOrderStatus>,
1006    ) {
1007        let actual: Result<OrderStatus, OKXAlgoOrderStatus> = input.try_into();
1008        assert_eq!(actual, expected);
1009    }
1010
1011    #[rstest]
1012    fn test_okx_order_status_deserializes_unknown_state_as_unknown() {
1013        let parsed: OKXOrderStatus = serde_json::from_str("\"future_state\"").unwrap();
1014        assert_eq!(parsed, OKXOrderStatus::Unknown);
1015
1016        let parsed: OKXOrderStatus = serde_json::from_str("\"mmp_canceled\"").unwrap();
1017        assert_eq!(parsed, OKXOrderStatus::MmpCanceled);
1018    }
1019
1020    #[rstest]
1021    fn test_okx_algo_order_status_deserializes_unknown_state_as_unknown() {
1022        let parsed: OKXAlgoOrderStatus = serde_json::from_str("\"future_state\"").unwrap();
1023        assert_eq!(parsed, OKXAlgoOrderStatus::Unknown);
1024    }
1025
1026    #[rstest]
1027    fn test_okx_order_type_deserializes_unknown_type_as_other() {
1028        let parsed: OKXOrderType = serde_json::from_str("\"future_ord_type\"").unwrap();
1029        assert_eq!(parsed, OKXOrderType::Other);
1030    }
1031
1032    #[rstest]
1033    fn test_okx_algo_order_type_deserializes_chase_and_unknown() {
1034        let parsed: OKXAlgoOrderType = serde_json::from_str("\"chase\"").unwrap();
1035        assert_eq!(parsed, OKXAlgoOrderType::Chase);
1036
1037        let parsed: OKXAlgoOrderType = serde_json::from_str("\"future_algo_type\"").unwrap();
1038        assert_eq!(parsed, OKXAlgoOrderType::Other);
1039    }
1040
1041    #[rstest]
1042    fn test_okx_algo_order_type_deserializes_smart_iceberg() {
1043        let parsed: OKXAlgoOrderType = serde_json::from_str("\"smart_iceberg\"").unwrap();
1044        assert_eq!(parsed, OKXAlgoOrderType::SmartIceberg);
1045
1046        let json = serde_json::to_string(&OKXAlgoOrderType::SmartIceberg).unwrap();
1047        assert_eq!(json, "\"smart_iceberg\"");
1048    }
1049
1050    #[rstest]
1051    fn test_okx_order_category_deserializes_auto_conversion() {
1052        let parsed: OKXOrderCategory = serde_json::from_str("\"auto_conversion\"").unwrap();
1053        assert_eq!(parsed, OKXOrderCategory::AutoConversion);
1054
1055        let json = serde_json::to_string(&OKXOrderCategory::AutoConversion).unwrap();
1056        assert_eq!(json, "\"auto_conversion\"");
1057    }
1058
1059    #[rstest]
1060    #[case("\"\"", OKXPriceType::None)]
1061    #[case("\"px\"", OKXPriceType::Px)]
1062    #[case("\"pxUsd\"", OKXPriceType::Usd)]
1063    #[case("\"pxVol\"", OKXPriceType::Vol)]
1064    fn test_okx_price_type_deserializes_documented_values(
1065        #[case] json: &str,
1066        #[case] expected: OKXPriceType,
1067    ) {
1068        let parsed: OKXPriceType = serde_json::from_str(json).unwrap();
1069        assert_eq!(parsed, expected);
1070
1071        let serialized = serde_json::to_string(&expected).unwrap();
1072        assert_eq!(serialized, json);
1073    }
1074}
1075
1076/// Represents the target currency for order quantity.
1077#[derive(
1078    Copy,
1079    Clone,
1080    Debug,
1081    Display,
1082    PartialEq,
1083    Eq,
1084    Hash,
1085    AsRefStr,
1086    EnumIter,
1087    EnumString,
1088    Serialize,
1089    Deserialize,
1090)]
1091#[serde(rename_all = "snake_case")]
1092#[strum(serialize_all = "snake_case")]
1093pub enum OKXTargetCurrency {
1094    /// Base currency.
1095    BaseCcy,
1096    /// Quote currency.
1097    QuoteCcy,
1098}
1099
1100/// Represents an OKX order book channel.
1101#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1102pub enum OKXBookChannel {
1103    /// Standard depth-first book channel (`books`).
1104    Book,
1105    /// Low-latency 400-depth channel (`books-l2-tbt`).
1106    BookL2Tbt,
1107    /// Low-latency 50-depth channel (`books50-l2-tbt`).
1108    Books50L2Tbt,
1109    /// Retail Price Improvement 400-depth channel (`books-rpi`).
1110    BooksRpi,
1111    /// Spread 5-depth snapshot channel (`sprd-books5`).
1112    SprdBooks5,
1113}
1114
1115/// Represents an account's RPI permission for an instrument.
1116#[derive(
1117    Copy,
1118    Clone,
1119    Debug,
1120    Display,
1121    PartialEq,
1122    Eq,
1123    Hash,
1124    AsRefStr,
1125    EnumIter,
1126    EnumString,
1127    Serialize,
1128    Deserialize,
1129)]
1130pub enum OKXRpiPermission {
1131    /// RPI is not enabled for the instrument.
1132    #[serde(rename = "0")]
1133    Disabled,
1134    /// RPI is enabled, but the account cannot place RPI orders.
1135    #[serde(rename = "1")]
1136    Enabled,
1137    /// RPI is enabled and the account can place RPI orders.
1138    #[serde(rename = "2")]
1139    Permitted,
1140}
1141
1142/// Represents OKX VIP level tiers for trading fee structure and API limits.
1143///
1144/// VIP levels determine:
1145/// - Trading fee discounts.
1146/// - API rate limits.
1147/// - Access to advanced order book channels (L2/L3 depth).
1148///
1149/// VIP4 and above get access to:
1150/// - "books-l2-tbt" channel (400 depth, 10ms updates).
1151/// - "books50-l2-tbt" channel (50 depth, 10ms updates).
1152#[derive(
1153    Copy,
1154    Clone,
1155    Debug,
1156    Display,
1157    PartialEq,
1158    Eq,
1159    PartialOrd,
1160    Ord,
1161    Hash,
1162    AsRefStr,
1163    EnumIter,
1164    EnumString,
1165    Serialize,
1166    Deserialize,
1167)]
1168#[cfg_attr(
1169    feature = "python",
1170    pyo3::pyclass(
1171        module = "nautilus_trader.adapters.okx",
1172        from_py_object,
1173        rename_all = "SCREAMING_SNAKE_CASE",
1174    )
1175)]
1176#[cfg_attr(
1177    feature = "python",
1178    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
1179)]
1180pub enum OKXVipLevel {
1181    /// VIP level 0 (default tier).
1182    #[serde(rename = "0")]
1183    #[strum(serialize = "0")]
1184    Vip0 = 0,
1185    /// VIP level 1.
1186    #[serde(rename = "1")]
1187    #[strum(serialize = "1")]
1188    Vip1 = 1,
1189    /// VIP level 2.
1190    #[serde(rename = "2")]
1191    #[strum(serialize = "2")]
1192    Vip2 = 2,
1193    /// VIP level 3.
1194    #[serde(rename = "3")]
1195    #[strum(serialize = "3")]
1196    Vip3 = 3,
1197    /// VIP level 4 (can access books-l2-tbt and books50-l2-tbt channels).
1198    #[serde(rename = "4")]
1199    #[strum(serialize = "4")]
1200    Vip4 = 4,
1201    /// VIP level 5.
1202    #[serde(rename = "5")]
1203    #[strum(serialize = "5")]
1204    Vip5 = 5,
1205    /// VIP level 6.
1206    #[serde(rename = "6")]
1207    #[strum(serialize = "6")]
1208    Vip6 = 6,
1209    /// VIP level 7.
1210    #[serde(rename = "7")]
1211    #[strum(serialize = "7")]
1212    Vip7 = 7,
1213    /// VIP level 8.
1214    #[serde(rename = "8")]
1215    #[strum(serialize = "8")]
1216    Vip8 = 8,
1217    /// VIP level 9 (highest tier).
1218    #[serde(rename = "9")]
1219    #[strum(serialize = "9")]
1220    Vip9 = 9,
1221}
1222
1223impl From<u8> for OKXVipLevel {
1224    fn from(value: u8) -> Self {
1225        match value {
1226            0 => Self::Vip0,
1227            1 => Self::Vip1,
1228            2 => Self::Vip2,
1229            3 => Self::Vip3,
1230            4 => Self::Vip4,
1231            5 => Self::Vip5,
1232            6 => Self::Vip6,
1233            7 => Self::Vip7,
1234            8 => Self::Vip8,
1235            9 => Self::Vip9,
1236            _ => {
1237                log::warn!("Invalid VIP level {value}, defaulting to Vip0");
1238                Self::Vip0
1239            }
1240        }
1241    }
1242}
1243
1244impl From<OKXSide> for OrderSide {
1245    fn from(side: OKXSide) -> Self {
1246        match side {
1247            OKXSide::Buy => Self::Buy,
1248            OKXSide::Sell => Self::Sell,
1249        }
1250    }
1251}
1252
1253impl From<OKXExecType> for LiquiditySide {
1254    fn from(exec: OKXExecType) -> Self {
1255        match exec {
1256            OKXExecType::Maker => Self::Maker,
1257            OKXExecType::Taker => Self::Taker,
1258            OKXExecType::None => Self::NoLiquiditySide,
1259        }
1260    }
1261}
1262
1263impl From<OKXPositionSide> for PositionSide {
1264    fn from(side: OKXPositionSide) -> Self {
1265        match side {
1266            OKXPositionSide::Long => Self::Long,
1267            OKXPositionSide::Short => Self::Short,
1268            _ => Self::Flat,
1269        }
1270    }
1271}
1272
1273impl TryFrom<OKXOrderStatus> for OrderStatus {
1274    type Error = OKXOrderStatus;
1275
1276    /// Converts an OKX order status into the matching Nautilus [`OrderStatus`].
1277    ///
1278    /// Returns the source variant in the error case for [`OKXOrderStatus::Unknown`],
1279    /// which carries any order state OKX adds after this mapping was written.
1280    fn try_from(value: OKXOrderStatus) -> Result<Self, Self::Error> {
1281        match value {
1282            OKXOrderStatus::Live => Ok(Self::Accepted),
1283            OKXOrderStatus::PartiallyFilled => Ok(Self::PartiallyFilled),
1284            OKXOrderStatus::Filled => Ok(Self::Filled),
1285            OKXOrderStatus::Canceled | OKXOrderStatus::MmpCanceled => Ok(Self::Canceled),
1286            OKXOrderStatus::Unknown => Err(value),
1287        }
1288    }
1289}
1290
1291impl TryFrom<OKXAlgoOrderStatus> for OrderStatus {
1292    type Error = OKXAlgoOrderStatus;
1293
1294    /// Converts an OKX algo order status into the matching Nautilus [`OrderStatus`].
1295    ///
1296    /// Returns the source variant in the error case for [`OKXAlgoOrderStatus::Unknown`],
1297    /// which carries any algo order state OKX adds after this mapping was written.
1298    fn try_from(value: OKXAlgoOrderStatus) -> Result<Self, Self::Error> {
1299        match value {
1300            OKXAlgoOrderStatus::Live | OKXAlgoOrderStatus::Pause => Ok(Self::Accepted),
1301            OKXAlgoOrderStatus::Effective
1302            | OKXAlgoOrderStatus::OrderPlaced
1303            | OKXAlgoOrderStatus::PartiallyEffective => Ok(Self::Triggered),
1304            OKXAlgoOrderStatus::Filled => Ok(Self::Filled),
1305            OKXAlgoOrderStatus::Canceled => Ok(Self::Canceled),
1306            OKXAlgoOrderStatus::OrderFailed | OKXAlgoOrderStatus::PartiallyFailed => {
1307                Ok(Self::Rejected)
1308            }
1309            OKXAlgoOrderStatus::Unknown => Err(value),
1310        }
1311    }
1312}
1313
1314impl TryFrom<OKXOrderType> for OrderType {
1315    type Error = OKXOrderType;
1316
1317    /// Converts an OKX order type into the matching Nautilus [`OrderType`].
1318    ///
1319    /// Returns the source variant in the error case for [`OKXOrderType::Other`],
1320    /// which carries any order type OKX adds after this mapping was written.
1321    fn try_from(value: OKXOrderType) -> Result<Self, Self::Error> {
1322        match value {
1323            OKXOrderType::Market => Ok(Self::Market),
1324            OKXOrderType::Limit
1325            | OKXOrderType::Rpi
1326            | OKXOrderType::PostOnly
1327            | OKXOrderType::OptimalLimitIoc
1328            | OKXOrderType::Mmp
1329            | OKXOrderType::MmpAndPostOnly
1330            | OKXOrderType::Fok
1331            | OKXOrderType::OpFok
1332            | OKXOrderType::Ioc => Ok(Self::Limit),
1333            OKXOrderType::Trigger => Ok(Self::StopMarket),
1334            OKXOrderType::Other => Err(value),
1335        }
1336    }
1337}
1338
1339impl From<OrderType> for OKXOrderType {
1340    fn from(value: OrderType) -> Self {
1341        match value {
1342            OrderType::Market => Self::Market,
1343            OrderType::Limit => Self::Limit,
1344            OrderType::MarketToLimit => Self::Ioc,
1345            // Conditional orders will be handled separately via algo orders
1346            OrderType::StopMarket
1347            | OrderType::StopLimit
1348            | OrderType::MarketIfTouched
1349            | OrderType::LimitIfTouched
1350            | OrderType::TrailingStopMarket => {
1351                panic!("Conditional order types must use OKXAlgoOrderType")
1352            }
1353            _ => panic!("Invalid `OrderType` cannot be represented on OKX: {value:?}"),
1354        }
1355    }
1356}
1357
1358impl From<PositionSide> for OKXPositionSide {
1359    fn from(value: PositionSide) -> Self {
1360        match value {
1361            PositionSide::Long => Self::Long,
1362            PositionSide::Short => Self::Short,
1363            PositionSide::Flat => Self::None,
1364        }
1365    }
1366}
1367
1368#[derive(
1369    Copy,
1370    Clone,
1371    Debug,
1372    Display,
1373    PartialEq,
1374    Eq,
1375    Hash,
1376    AsRefStr,
1377    EnumIter,
1378    EnumString,
1379    Serialize,
1380    Deserialize,
1381)]
1382#[serde(rename_all = "snake_case")]
1383pub enum OKXAlgoOrderType {
1384    Conditional,
1385    Oco,
1386    Trigger,
1387    MoveOrderStop,
1388    Iceberg,
1389    SmartIceberg,
1390    Twap,
1391    Chase,
1392    /// Forward-compatible fallback for algo order types OKX adds later.
1393    #[serde(other)]
1394    Other,
1395}
1396
1397/// Returns whether an order type requires algo order handling.
1398pub fn is_conditional_order(order_type: OrderType) -> bool {
1399    OKX_CONDITIONAL_ORDER_TYPES.contains(&order_type)
1400}
1401
1402/// Returns whether an order type requires the advance algo cancel endpoint.
1403pub fn is_advance_algo_order(order_type: OrderType) -> bool {
1404    OKX_ADVANCE_ALGO_ORDER_TYPES.contains(&order_type)
1405}
1406
1407/// Converts Nautilus conditional order types to OKX algo order type.
1408///
1409/// # Errors
1410///
1411/// Returns an error if the provided `order_type` is not a conditional order type.
1412pub fn conditional_order_to_algo_type(order_type: OrderType) -> anyhow::Result<OKXAlgoOrderType> {
1413    match order_type {
1414        OrderType::StopMarket
1415        | OrderType::StopLimit
1416        | OrderType::MarketIfTouched
1417        | OrderType::LimitIfTouched => Ok(OKXAlgoOrderType::Trigger),
1418        OrderType::TrailingStopMarket => Ok(OKXAlgoOrderType::MoveOrderStop),
1419        _ => anyhow::bail!("Not a conditional order type: {order_type:?}"),
1420    }
1421}
1422
1423/// Represents the state of an algo (trigger/OCO/conditional) order on OKX.
1424#[derive(
1425    Copy,
1426    Clone,
1427    Debug,
1428    Display,
1429    PartialEq,
1430    Eq,
1431    Hash,
1432    AsRefStr,
1433    EnumIter,
1434    EnumString,
1435    Serialize,
1436    Deserialize,
1437)]
1438#[serde(rename_all = "snake_case")]
1439#[cfg_attr(
1440    feature = "python",
1441    pyo3::pyclass(
1442        eq,
1443        eq_int,
1444        module = "nautilus_trader.adapters.okx",
1445        from_py_object,
1446        rename_all = "SCREAMING_SNAKE_CASE",
1447    )
1448)]
1449#[cfg_attr(
1450    feature = "python",
1451    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
1452)]
1453pub enum OKXAlgoOrderStatus {
1454    Live,
1455    Pause,
1456    Effective,
1457    OrderPlaced,
1458    PartiallyEffective,
1459    Canceled,
1460    Filled,
1461    OrderFailed,
1462    PartiallyFailed,
1463    /// Forward-compatible fallback for algo order states OKX adds later.
1464    #[serde(other)]
1465    Unknown,
1466}
1467
1468/// Represents the category of an order on OKX.
1469///
1470/// The category field indicates whether an order is a normal trade, liquidation,
1471/// auto-deleveraging (ADL) event, or algorithmic order type. This is critical for
1472/// risk management and proper handling of exchange-generated orders.
1473///
1474/// # References
1475///
1476/// <https://www.okx.com/docs-v5/en/#order-book-trading-ws-order-channel>
1477#[derive(
1478    Copy,
1479    Clone,
1480    Debug,
1481    Display,
1482    PartialEq,
1483    Eq,
1484    Hash,
1485    AsRefStr,
1486    EnumIter,
1487    EnumString,
1488    Serialize,
1489    Deserialize,
1490)]
1491#[serde(rename_all = "snake_case")]
1492pub enum OKXOrderCategory {
1493    /// Normal trading order.
1494    Normal,
1495    /// Full liquidation order (position completely closed by exchange).
1496    FullLiquidation,
1497    /// Partial liquidation order (position partially closed by exchange).
1498    PartialLiquidation,
1499    /// Auto-deleveraging order (position closed to offset counterparty liquidation).
1500    Adl,
1501    /// Time-Weighted Average Price algorithmic order.
1502    Twap,
1503    /// Iceberg algorithmic order (hidden quantity).
1504    Iceberg,
1505    /// One-Cancels-the-Other algorithmic order.
1506    Oco,
1507    /// Conditional/trigger order.
1508    Conditional,
1509    /// Move order stop algorithmic order.
1510    MoveOrderStop,
1511    /// Delivery and exercise (for futures/options settlement).
1512    Ddh,
1513    /// Event contract settlement fill.
1514    Delivery,
1515    /// System-triggered asset conversion.
1516    AutoConversion,
1517    /// Unknown or future category (graceful fallback).
1518    #[serde(other)]
1519    Other,
1520}
1521
1522#[derive(
1523    Copy,
1524    Clone,
1525    Debug,
1526    Display,
1527    PartialEq,
1528    Eq,
1529    Hash,
1530    AsRefStr,
1531    EnumIter,
1532    EnumString,
1533    Serialize,
1534    Deserialize,
1535)]
1536pub enum OKXBarSize {
1537    #[serde(rename = "1s")]
1538    Second1,
1539    #[serde(rename = "1m")]
1540    Minute1,
1541    #[serde(rename = "3m")]
1542    Minute3,
1543    #[serde(rename = "5m")]
1544    Minute5,
1545    #[serde(rename = "15m")]
1546    Minute15,
1547    #[serde(rename = "30m")]
1548    Minute30,
1549    #[serde(rename = "1H")]
1550    Hour1,
1551    #[serde(rename = "2H")]
1552    Hour2,
1553    #[serde(rename = "4H")]
1554    Hour4,
1555    #[serde(rename = "6H")]
1556    Hour6,
1557    #[serde(rename = "12H")]
1558    Hour12,
1559    #[serde(rename = "1D")]
1560    Day1,
1561    #[serde(rename = "2D")]
1562    Day2,
1563    #[serde(rename = "3D")]
1564    Day3,
1565    #[serde(rename = "5D")]
1566    Day5,
1567    #[serde(rename = "1W")]
1568    Week1,
1569    #[serde(rename = "1M")]
1570    Month1,
1571    #[serde(rename = "3M")]
1572    Month3,
1573}
1574
1575/// Options price type for order pricing.
1576#[derive(
1577    Copy,
1578    Clone,
1579    Debug,
1580    Default,
1581    Display,
1582    PartialEq,
1583    Eq,
1584    Hash,
1585    AsRefStr,
1586    EnumIter,
1587    EnumString,
1588    Serialize,
1589    Deserialize,
1590)]
1591#[serde(rename_all = "snake_case")]
1592pub enum OKXPriceType {
1593    /// No price type specified.
1594    #[default]
1595    #[serde(rename = "")]
1596    None,
1597    /// Standard price.
1598    Px,
1599    /// Price in USD.
1600    #[serde(rename = "pxUsd")]
1601    Usd,
1602    /// Price in implied volatility.
1603    #[serde(rename = "pxVol")]
1604    Vol,
1605}
1606
1607/// Funding rate settlement state.
1608#[derive(
1609    Copy,
1610    Clone,
1611    Debug,
1612    Default,
1613    Display,
1614    PartialEq,
1615    Eq,
1616    Hash,
1617    AsRefStr,
1618    EnumIter,
1619    EnumString,
1620    Serialize,
1621    Deserialize,
1622)]
1623#[serde(rename_all = "snake_case")]
1624pub enum OKXSettlementState {
1625    /// No settlement state.
1626    #[default]
1627    #[serde(rename = "")]
1628    None,
1629    /// Settlement in progress.
1630    Processing,
1631    /// Settlement completed.
1632    Settled,
1633}
1634
1635/// Quick margin type for order margin management.
1636#[derive(
1637    Copy,
1638    Clone,
1639    Debug,
1640    Default,
1641    Display,
1642    PartialEq,
1643    Eq,
1644    Hash,
1645    AsRefStr,
1646    EnumIter,
1647    EnumString,
1648    Serialize,
1649    Deserialize,
1650)]
1651#[serde(rename_all = "snake_case")]
1652pub enum OKXQuickMarginType {
1653    /// No quick margin type.
1654    #[default]
1655    #[serde(rename = "")]
1656    None,
1657    /// Manual margin management.
1658    Manual,
1659    /// Auto borrow margin.
1660    AutoBorrow,
1661    /// Auto repay margin.
1662    AutoRepay,
1663}
1664
1665/// OKX API environment.
1666#[derive(
1667    Copy,
1668    Clone,
1669    Debug,
1670    Default,
1671    Display,
1672    PartialEq,
1673    Eq,
1674    Hash,
1675    AsRefStr,
1676    EnumIter,
1677    EnumString,
1678    Serialize,
1679    Deserialize,
1680)]
1681#[serde(rename_all = "lowercase")]
1682#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
1683#[cfg_attr(
1684    feature = "python",
1685    pyo3::pyclass(
1686        eq,
1687        eq_int,
1688        module = "nautilus_trader.adapters.okx",
1689        from_py_object,
1690        rename_all = "SCREAMING_SNAKE_CASE",
1691    )
1692)]
1693#[cfg_attr(
1694    feature = "python",
1695    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
1696)]
1697pub enum OKXEnvironment {
1698    /// Live trading environment.
1699    #[default]
1700    Live,
1701    /// Demo trading environment.
1702    Demo,
1703}
1704
1705/// OKX API region.
1706///
1707/// Selects the regional endpoint set. OKX serves region-specific hosts and an
1708/// API key registered in one region is rejected by another region's endpoints
1709/// (returning `API key doesn't exist`).
1710#[derive(
1711    Copy,
1712    Clone,
1713    Debug,
1714    Default,
1715    Display,
1716    PartialEq,
1717    Eq,
1718    Hash,
1719    AsRefStr,
1720    EnumIter,
1721    EnumString,
1722    Serialize,
1723    Deserialize,
1724)]
1725#[serde(rename_all = "lowercase")]
1726#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
1727#[cfg_attr(
1728    feature = "python",
1729    pyo3::pyclass(
1730        eq,
1731        eq_int,
1732        module = "nautilus_trader.adapters.okx",
1733        from_py_object,
1734        rename_all = "SCREAMING_SNAKE_CASE",
1735    )
1736)]
1737#[cfg_attr(
1738    feature = "python",
1739    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
1740)]
1741pub enum OKXRegion {
1742    /// Global endpoints (accounts registered on www.okx.com).
1743    #[default]
1744    Global,
1745    /// European Economic Area endpoints (accounts registered on my.okx.com).
1746    Eea,
1747    /// United States and Australia endpoints (accounts registered on app.okx.com).
1748    Us,
1749}