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, OrderSideSpecified,
20    OrderStatus, OrderType, 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<OrderSideSpecified> for OKXSide {
98    fn from(value: OrderSideSpecified) -> Self {
99        match value {
100            OrderSideSpecified::Buy => Self::Buy,
101            OrderSideSpecified::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::Buyer,
110            OKXSide::Sell => Self::Seller,
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    PostOnly,        // limit only, requires "px" to be provided
137    Fok,             // Market order if "px" is not provided, otherwise limit order
138    Ioc,             // Market order if "px" is not provided, otherwise limit order
139    OptimalLimitIoc, // Market order with immediate-or-cancel order
140    Mmp,             // Market Maker Protection (only applicable to Option in Portfolio Margin mode)
141    MmpAndPostOnly, // Market Maker Protection and Post-only order(only applicable to Option in Portfolio Margin mode)
142    OpFok,          // Fill-or-Kill for options (only applicable to Option)
143    Trigger,        // Conditional/algo order (stop orders, etc.)
144}
145
146/// Represents the possible states of an order throughout its lifecycle.
147#[derive(
148    Copy,
149    Clone,
150    Debug,
151    Display,
152    PartialEq,
153    Eq,
154    Hash,
155    AsRefStr,
156    EnumIter,
157    EnumString,
158    Serialize,
159    Deserialize,
160)]
161#[serde(rename_all = "snake_case")]
162#[cfg_attr(
163    feature = "python",
164    pyo3::pyclass(
165        eq,
166        eq_int,
167        module = "nautilus_trader.core.nautilus_pyo3.okx",
168        from_py_object,
169        rename_all = "SCREAMING_SNAKE_CASE",
170    )
171)]
172#[cfg_attr(
173    feature = "python",
174    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.okx")
175)]
176pub enum OKXOrderStatus {
177    Canceled,
178    Live,
179    Effective,
180    PartiallyFilled,
181    Filled,
182    MmpCanceled,
183    OrderPlaced,
184}
185
186impl TryFrom<OrderStatus> for OKXOrderStatus {
187    type Error = OrderStatus;
188
189    /// Converts a Nautilus [`OrderStatus`] into the matching [`OKXOrderStatus`].
190    ///
191    /// Returns the original [`OrderStatus`] in the error case for any variant
192    /// that has no representable OKX equivalent (e.g. `Submitted`, `PendingNew`,
193    /// `Triggered`, `PendingCancel`, `Expired`, `Rejected`).
194    fn try_from(value: OrderStatus) -> Result<Self, Self::Error> {
195        match value {
196            OrderStatus::Canceled => Ok(Self::Canceled),
197            OrderStatus::Accepted => Ok(Self::Live),
198            OrderStatus::PartiallyFilled => Ok(Self::PartiallyFilled),
199            OrderStatus::Filled => Ok(Self::Filled),
200            other => Err(other),
201        }
202    }
203}
204
205/// Represents the type of execution that generated a trade.
206#[derive(
207    Copy,
208    Clone,
209    Debug,
210    Default,
211    Display,
212    PartialEq,
213    Eq,
214    Hash,
215    AsRefStr,
216    EnumIter,
217    EnumString,
218    Serialize,
219    Deserialize,
220)]
221pub enum OKXExecType {
222    #[serde(rename = "")]
223    #[default]
224    None,
225    #[serde(rename = "T")]
226    Taker,
227    #[serde(rename = "M")]
228    Maker,
229}
230
231impl From<LiquiditySide> for OKXExecType {
232    fn from(value: LiquiditySide) -> Self {
233        match value {
234            LiquiditySide::NoLiquiditySide => Self::None,
235            LiquiditySide::Taker => Self::Taker,
236            LiquiditySide::Maker => Self::Maker,
237        }
238    }
239}
240
241/// Represents instrument types on OKX.
242#[derive(
243    Copy,
244    Clone,
245    Debug,
246    Display,
247    Default,
248    PartialEq,
249    Eq,
250    Hash,
251    AsRefStr,
252    EnumIter,
253    EnumString,
254    Serialize,
255    Deserialize,
256)]
257#[serde(rename_all = "UPPERCASE")]
258#[cfg_attr(
259    feature = "python",
260    pyo3::pyclass(
261        eq,
262        eq_int,
263        module = "nautilus_trader.core.nautilus_pyo3.okx",
264        from_py_object,
265        rename_all = "SCREAMING_SNAKE_CASE",
266    )
267)]
268#[cfg_attr(
269    feature = "python",
270    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.okx")
271)]
272pub enum OKXInstrumentType {
273    #[default]
274    Any,
275    /// Spot products.
276    Spot,
277    /// Margin products.
278    Margin,
279    /// Swap products.
280    Swap,
281    /// Futures products.
282    Futures,
283    /// Option products.
284    Option,
285}
286
287/// Represents an instrument status on OKX.
288#[derive(
289    Copy,
290    Clone,
291    Debug,
292    Display,
293    PartialEq,
294    Eq,
295    Hash,
296    AsRefStr,
297    EnumIter,
298    EnumString,
299    Serialize,
300    Deserialize,
301)]
302#[serde(rename_all = "snake_case")]
303pub enum OKXInstrumentStatus {
304    Live,
305    Suspend,
306    Preopen,
307    Test,
308}
309
310/// Represents an instrument contract type on OKX.
311#[derive(
312    Copy,
313    Clone,
314    Default,
315    Debug,
316    Display,
317    PartialEq,
318    Eq,
319    Hash,
320    AsRefStr,
321    EnumIter,
322    EnumString,
323    Serialize,
324    Deserialize,
325)]
326#[serde(rename_all = "snake_case")]
327#[cfg_attr(
328    feature = "python",
329    pyo3::pyclass(
330        eq,
331        eq_int,
332        module = "nautilus_trader.core.nautilus_pyo3.okx",
333        from_py_object,
334        rename_all = "SCREAMING_SNAKE_CASE",
335    )
336)]
337#[cfg_attr(
338    feature = "python",
339    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.okx")
340)]
341pub enum OKXContractType {
342    #[serde(rename = "")]
343    #[default]
344    None,
345    Linear,
346    Inverse,
347}
348
349/// Represents an option type on OKX.
350#[derive(
351    Copy,
352    Clone,
353    Debug,
354    Display,
355    PartialEq,
356    Eq,
357    Hash,
358    AsRefStr,
359    EnumIter,
360    EnumString,
361    Serialize,
362    Deserialize,
363)]
364pub enum OKXOptionType {
365    #[serde(rename = "")]
366    None,
367    #[serde(rename = "C")]
368    Call,
369    #[serde(rename = "P")]
370    Put,
371}
372
373impl TryFrom<OKXOptionType> for OptionKind {
374    type Error = OKXOptionType;
375
376    /// Converts an OKX option type into the matching Nautilus [`OptionKind`].
377    ///
378    /// Returns the source variant in the error case for [`OKXOptionType::None`]
379    /// (sent by OKX as an empty `optType` for non-option instruments and the
380    /// occasional malformed payload). Callers should skip such instruments
381    /// rather than treating the unknown variant as a default option kind.
382    fn try_from(option_type: OKXOptionType) -> Result<Self, Self::Error> {
383        match option_type {
384            OKXOptionType::Call => Ok(Self::Call),
385            OKXOptionType::Put => Ok(Self::Put),
386            other => Err(other),
387        }
388    }
389}
390
391/// Represents the convention used for option greeks on OKX.
392///
393/// OKX publishes two parallel greek sets on `opt-summary` and related endpoints:
394/// Black-Scholes greeks denominated in USD, and price-adjusted greeks denominated
395/// in the underlying/coin units.
396#[derive(
397    Copy,
398    Clone,
399    Debug,
400    Default,
401    Display,
402    PartialEq,
403    Eq,
404    Hash,
405    AsRefStr,
406    EnumIter,
407    EnumString,
408    Serialize,
409    Deserialize,
410)]
411#[serde(rename_all = "UPPERCASE")]
412#[strum(serialize_all = "UPPERCASE", ascii_case_insensitive)]
413#[cfg_attr(
414    feature = "python",
415    pyo3::pyclass(
416        eq,
417        eq_int,
418        module = "nautilus_trader.core.nautilus_pyo3.okx",
419        from_py_object,
420        rename_all = "SCREAMING_SNAKE_CASE",
421    )
422)]
423#[cfg_attr(
424    feature = "python",
425    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.okx")
426)]
427pub enum OKXGreeksType {
428    /// Black-Scholes greeks in USD.
429    #[default]
430    Bs = 0,
431    /// Price-adjusted greeks in the underlying/coin units.
432    Pa = 1,
433}
434
435impl From<u8> for OKXGreeksType {
436    fn from(value: u8) -> Self {
437        match value {
438            0 => Self::Bs,
439            1 => Self::Pa,
440            _ => {
441                log::warn!("Invalid OKXGreeksType {value}, defaulting to Bs");
442                Self::Bs
443            }
444        }
445    }
446}
447
448impl From<GreeksConvention> for OKXGreeksType {
449    fn from(convention: GreeksConvention) -> Self {
450        match convention {
451            GreeksConvention::BlackScholes => Self::Bs,
452            GreeksConvention::PriceAdjusted => Self::Pa,
453        }
454    }
455}
456
457impl From<OKXGreeksType> for GreeksConvention {
458    fn from(greeks_type: OKXGreeksType) -> Self {
459        match greeks_type {
460            OKXGreeksType::Bs => Self::BlackScholes,
461            OKXGreeksType::Pa => Self::PriceAdjusted,
462        }
463    }
464}
465
466/// Represents the trading mode for OKX orders.
467#[derive(
468    Copy,
469    Clone,
470    Debug,
471    Display,
472    Default,
473    PartialEq,
474    Eq,
475    Hash,
476    AsRefStr,
477    EnumIter,
478    EnumString,
479    Serialize,
480    Deserialize,
481)]
482#[serde(rename_all = "snake_case")]
483#[strum(ascii_case_insensitive)]
484#[cfg_attr(
485    feature = "python",
486    pyo3::pyclass(
487        eq,
488        eq_int,
489        module = "nautilus_trader.core.nautilus_pyo3.okx",
490        from_py_object,
491        rename_all = "SCREAMING_SNAKE_CASE",
492    )
493)]
494#[cfg_attr(
495    feature = "python",
496    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.okx")
497)]
498pub enum OKXTradeMode {
499    #[default]
500    Cash,
501    Isolated,
502    Cross,
503    #[strum(serialize = "spot_isolated")]
504    SpotIsolated,
505}
506
507/// Represents an OKX account mode.
508///
509/// # References
510///
511/// <https://www.okx.com/docs-v5/en/#overview-account-mode>
512#[derive(
513    Copy,
514    Clone,
515    Debug,
516    Display,
517    PartialEq,
518    Eq,
519    Hash,
520    AsRefStr,
521    EnumIter,
522    EnumString,
523    Serialize,
524    Deserialize,
525)]
526pub enum OKXAccountMode {
527    #[serde(rename = "Spot mode")]
528    Spot,
529    #[serde(rename = "Spot and futures mode")]
530    SpotAndFutures,
531    #[serde(rename = "Multi-currency margin mode")]
532    MultiCurrencyMarginMode,
533    #[serde(rename = "Portfolio margin mode")]
534    PortfolioMarginMode,
535}
536
537/// Represents the margin mode for OKX accounts.
538///
539/// # Reference
540///
541/// - <https://www.okx.com/en-au/help/iv-isolated-margin-mode>
542/// - <https://www.okx.com/en-au/help/iii-single-currency-margin-cross-margin-trading>
543/// - <https://www.okx.com/en-au/help/iv-multi-currency-margin-mode-cross-margin-trading>
544#[derive(
545    Copy,
546    Clone,
547    Default,
548    Debug,
549    Display,
550    PartialEq,
551    Eq,
552    Hash,
553    AsRefStr,
554    EnumIter,
555    EnumString,
556    Serialize,
557    Deserialize,
558)]
559#[serde(rename_all = "snake_case")]
560#[cfg_attr(
561    feature = "python",
562    pyo3::pyclass(
563        eq,
564        eq_int,
565        module = "nautilus_trader.core.nautilus_pyo3.okx",
566        from_py_object,
567        rename_all = "SCREAMING_SNAKE_CASE",
568    )
569)]
570#[cfg_attr(
571    feature = "python",
572    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.okx")
573)]
574pub enum OKXMarginMode {
575    #[serde(rename = "")]
576    #[default]
577    None,
578    Isolated,
579    Cross,
580}
581
582/// Represents the position mode for OKX accounts.
583///
584/// # References
585///
586/// <https://www.okx.com/docs-v5/en/#trading-account-rest-api-set-position-mode>
587#[derive(
588    Copy,
589    Clone,
590    Default,
591    Debug,
592    Display,
593    PartialEq,
594    Eq,
595    Hash,
596    AsRefStr,
597    EnumIter,
598    EnumString,
599    Serialize,
600    Deserialize,
601)]
602#[cfg_attr(
603    feature = "python",
604    pyo3::pyclass(
605        eq,
606        eq_int,
607        module = "nautilus_trader.core.nautilus_pyo3.okx",
608        from_py_object,
609        rename_all = "SCREAMING_SNAKE_CASE",
610    )
611)]
612#[cfg_attr(
613    feature = "python",
614    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.okx")
615)]
616pub enum OKXPositionMode {
617    #[default]
618    #[serde(rename = "net_mode")]
619    NetMode,
620    #[serde(rename = "long_short_mode")]
621    LongShortMode,
622}
623
624#[derive(
625    Copy,
626    Clone,
627    Debug,
628    Display,
629    PartialEq,
630    Eq,
631    Hash,
632    AsRefStr,
633    EnumIter,
634    EnumString,
635    Serialize,
636    Deserialize,
637)]
638#[serde(rename_all = "snake_case")]
639pub enum OKXPositionSide {
640    #[serde(rename = "")]
641    None,
642    Net,
643    Long,
644    Short,
645}
646
647#[derive(
648    Copy,
649    Clone,
650    Debug,
651    Default,
652    Display,
653    PartialEq,
654    Eq,
655    Hash,
656    AsRefStr,
657    EnumIter,
658    EnumString,
659    Serialize,
660    Deserialize,
661)]
662#[serde(rename_all = "snake_case")]
663pub enum OKXSelfTradePreventionMode {
664    #[default]
665    #[serde(rename = "")]
666    None,
667    CancelMaker,
668    CancelTaker,
669    CancelBoth,
670}
671
672#[derive(
673    Copy,
674    Clone,
675    Debug,
676    Display,
677    PartialEq,
678    Eq,
679    Hash,
680    AsRefStr,
681    EnumIter,
682    EnumString,
683    Serialize,
684    Deserialize,
685)]
686#[serde(rename_all = "snake_case")]
687pub enum OKXTakeProfitKind {
688    #[serde(rename = "")]
689    None,
690    Condition,
691    Limit,
692}
693
694#[derive(
695    Copy,
696    Clone,
697    Debug,
698    Default,
699    Display,
700    PartialEq,
701    Eq,
702    Hash,
703    AsRefStr,
704    EnumIter,
705    EnumString,
706    Serialize,
707    Deserialize,
708)]
709#[serde(rename_all = "snake_case")]
710#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
711pub enum OKXTriggerType {
712    #[default]
713    #[serde(rename = "")]
714    None,
715    Last,
716    Index,
717    Mark,
718}
719
720impl From<TriggerType> for OKXTriggerType {
721    fn from(value: TriggerType) -> Self {
722        match value {
723            TriggerType::LastPrice => Self::Last,
724            TriggerType::MarkPrice => Self::Mark,
725            TriggerType::IndexPrice => Self::Index,
726            _ => Self::Last,
727        }
728    }
729}
730
731#[cfg(test)]
732mod tests {
733    use std::str::FromStr;
734
735    use nautilus_model::enums::{GreeksConvention, OptionKind, OrderStatus};
736    use rstest::rstest;
737
738    use super::{OKXGreeksType, OKXOptionType, OKXOrderStatus, OKXOrderType, OKXTriggerType};
739
740    #[rstest]
741    fn test_okx_trigger_type_from_str_accepts_snake_case_values() {
742        assert_eq!(
743            OKXTriggerType::from_str("last").unwrap(),
744            OKXTriggerType::Last
745        );
746        assert_eq!(
747            OKXTriggerType::from_str("mark").unwrap(),
748            OKXTriggerType::Mark
749        );
750        assert_eq!(
751            OKXTriggerType::from_str("index").unwrap(),
752            OKXTriggerType::Index
753        );
754    }
755
756    #[rstest]
757    #[case(OKXGreeksType::Bs, "\"BS\"")]
758    #[case(OKXGreeksType::Pa, "\"PA\"")]
759    fn test_greeks_type_serde_roundtrip(#[case] input: OKXGreeksType, #[case] expected: &str) {
760        let json = serde_json::to_string(&input).unwrap();
761        assert_eq!(json, expected);
762        let parsed: OKXGreeksType = serde_json::from_str(expected).unwrap();
763        assert_eq!(parsed, input);
764    }
765
766    #[rstest]
767    fn test_greeks_type_default_is_bs() {
768        assert_eq!(OKXGreeksType::default(), OKXGreeksType::Bs);
769    }
770
771    #[rstest]
772    fn test_greeks_type_from_u8() {
773        assert_eq!(OKXGreeksType::from(0_u8), OKXGreeksType::Bs);
774        assert_eq!(OKXGreeksType::from(1_u8), OKXGreeksType::Pa);
775        assert_eq!(OKXGreeksType::from(99_u8), OKXGreeksType::Bs);
776    }
777
778    #[rstest]
779    #[case(GreeksConvention::BlackScholes, OKXGreeksType::Bs)]
780    #[case(GreeksConvention::PriceAdjusted, OKXGreeksType::Pa)]
781    fn test_greeks_type_convention_roundtrip(
782        #[case] convention: GreeksConvention,
783        #[case] expected: OKXGreeksType,
784    ) {
785        let mapped: OKXGreeksType = convention.into();
786        assert_eq!(mapped, expected);
787        let back: GreeksConvention = mapped.into();
788        assert_eq!(back, convention);
789    }
790
791    #[rstest]
792    fn test_op_fok_serializes_to_snake_case() {
793        let json = serde_json::to_string(&OKXOrderType::OpFok).unwrap();
794        assert_eq!(json, "\"op_fok\"");
795    }
796
797    #[rstest]
798    fn test_op_fok_deserializes_from_snake_case() {
799        let parsed: OKXOrderType = serde_json::from_str("\"op_fok\"").unwrap();
800        assert_eq!(parsed, OKXOrderType::OpFok);
801    }
802
803    #[rstest]
804    fn test_op_fok_converts_to_limit_order_type() {
805        use nautilus_model::enums::OrderType;
806        let order_type: OrderType = OKXOrderType::OpFok.into();
807        assert_eq!(order_type, OrderType::Limit);
808    }
809
810    #[rstest]
811    #[case::call(OKXOptionType::Call, Ok(OptionKind::Call))]
812    #[case::put(OKXOptionType::Put, Ok(OptionKind::Put))]
813    #[case::none(OKXOptionType::None, Err(OKXOptionType::None))]
814    fn test_try_from_okx_option_type(
815        #[case] input: OKXOptionType,
816        #[case] expected: Result<OptionKind, OKXOptionType>,
817    ) {
818        let actual: Result<OptionKind, OKXOptionType> = input.try_into();
819        assert_eq!(actual, expected);
820    }
821
822    #[rstest]
823    #[case::canceled(OrderStatus::Canceled, Ok(OKXOrderStatus::Canceled))]
824    #[case::accepted(OrderStatus::Accepted, Ok(OKXOrderStatus::Live))]
825    #[case::partially_filled(OrderStatus::PartiallyFilled, Ok(OKXOrderStatus::PartiallyFilled))]
826    #[case::filled(OrderStatus::Filled, Ok(OKXOrderStatus::Filled))]
827    #[case::submitted(OrderStatus::Submitted, Err(OrderStatus::Submitted))]
828    #[case::pending_update(OrderStatus::PendingUpdate, Err(OrderStatus::PendingUpdate))]
829    #[case::pending_cancel(OrderStatus::PendingCancel, Err(OrderStatus::PendingCancel))]
830    #[case::triggered(OrderStatus::Triggered, Err(OrderStatus::Triggered))]
831    #[case::expired(OrderStatus::Expired, Err(OrderStatus::Expired))]
832    #[case::rejected(OrderStatus::Rejected, Err(OrderStatus::Rejected))]
833    fn test_try_from_order_status(
834        #[case] input: OrderStatus,
835        #[case] expected: Result<OKXOrderStatus, OrderStatus>,
836    ) {
837        let actual: Result<OKXOrderStatus, OrderStatus> = input.try_into();
838        assert_eq!(actual, expected);
839    }
840}
841
842/// Represents the target currency for order quantity.
843#[derive(
844    Copy,
845    Clone,
846    Debug,
847    Display,
848    PartialEq,
849    Eq,
850    Hash,
851    AsRefStr,
852    EnumIter,
853    EnumString,
854    Serialize,
855    Deserialize,
856)]
857#[serde(rename_all = "snake_case")]
858#[strum(serialize_all = "snake_case")]
859pub enum OKXTargetCurrency {
860    /// Base currency.
861    BaseCcy,
862    /// Quote currency.
863    QuoteCcy,
864}
865
866/// Represents an OKX order book channel.
867#[derive(Copy, Clone, Debug, PartialEq, Eq)]
868pub enum OKXBookChannel {
869    /// Standard depth-first book channel (`books`).
870    Book,
871    /// Low-latency 400-depth channel (`books-l2-tbt`).
872    BookL2Tbt,
873    /// Low-latency 50-depth channel (`books50-l2-tbt`).
874    Books50L2Tbt,
875}
876
877/// Represents OKX VIP level tiers for trading fee structure and API limits.
878///
879/// VIP levels determine:
880/// - Trading fee discounts.
881/// - API rate limits.
882/// - Access to advanced order book channels (L2/L3 depth).
883///
884/// Higher VIP levels (VIP4+) get access to:
885/// - "books50-l2-tbt" channel (50 depth, 10ms updates).
886/// - "bbo-tbt" channel (1 depth, 10ms updates).
887///
888/// VIP5+ get access to:
889/// - "books-l2-tbt" channel (400 depth, 10ms updates).
890#[derive(
891    Copy,
892    Clone,
893    Debug,
894    Display,
895    PartialEq,
896    Eq,
897    PartialOrd,
898    Ord,
899    Hash,
900    AsRefStr,
901    EnumIter,
902    EnumString,
903    Serialize,
904    Deserialize,
905)]
906#[cfg_attr(
907    feature = "python",
908    pyo3::pyclass(
909        module = "nautilus_trader.core.nautilus_pyo3.okx",
910        from_py_object,
911        rename_all = "SCREAMING_SNAKE_CASE",
912    )
913)]
914#[cfg_attr(
915    feature = "python",
916    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.okx")
917)]
918pub enum OKXVipLevel {
919    /// VIP level 0 (default tier).
920    #[serde(rename = "0")]
921    #[strum(serialize = "0")]
922    Vip0 = 0,
923    /// VIP level 1.
924    #[serde(rename = "1")]
925    #[strum(serialize = "1")]
926    Vip1 = 1,
927    /// VIP level 2.
928    #[serde(rename = "2")]
929    #[strum(serialize = "2")]
930    Vip2 = 2,
931    /// VIP level 3.
932    #[serde(rename = "3")]
933    #[strum(serialize = "3")]
934    Vip3 = 3,
935    /// VIP level 4 (can access books50-l2-tbt channel).
936    #[serde(rename = "4")]
937    #[strum(serialize = "4")]
938    Vip4 = 4,
939    /// VIP level 5 (can access books-l2-tbt channel).
940    #[serde(rename = "5")]
941    #[strum(serialize = "5")]
942    Vip5 = 5,
943    /// VIP level 6.
944    #[serde(rename = "6")]
945    #[strum(serialize = "6")]
946    Vip6 = 6,
947    /// VIP level 7.
948    #[serde(rename = "7")]
949    #[strum(serialize = "7")]
950    Vip7 = 7,
951    /// VIP level 8.
952    #[serde(rename = "8")]
953    #[strum(serialize = "8")]
954    Vip8 = 8,
955    /// VIP level 9 (highest tier).
956    #[serde(rename = "9")]
957    #[strum(serialize = "9")]
958    Vip9 = 9,
959}
960
961impl From<u8> for OKXVipLevel {
962    fn from(value: u8) -> Self {
963        match value {
964            0 => Self::Vip0,
965            1 => Self::Vip1,
966            2 => Self::Vip2,
967            3 => Self::Vip3,
968            4 => Self::Vip4,
969            5 => Self::Vip5,
970            6 => Self::Vip6,
971            7 => Self::Vip7,
972            8 => Self::Vip8,
973            9 => Self::Vip9,
974            _ => {
975                log::warn!("Invalid VIP level {value}, defaulting to Vip0");
976                Self::Vip0
977            }
978        }
979    }
980}
981
982impl From<OKXSide> for OrderSide {
983    fn from(side: OKXSide) -> Self {
984        match side {
985            OKXSide::Buy => Self::Buy,
986            OKXSide::Sell => Self::Sell,
987        }
988    }
989}
990
991impl From<OKXExecType> for LiquiditySide {
992    fn from(exec: OKXExecType) -> Self {
993        match exec {
994            OKXExecType::Maker => Self::Maker,
995            OKXExecType::Taker => Self::Taker,
996            OKXExecType::None => Self::NoLiquiditySide,
997        }
998    }
999}
1000
1001impl From<OKXPositionSide> for PositionSide {
1002    fn from(side: OKXPositionSide) -> Self {
1003        match side {
1004            OKXPositionSide::Long => Self::Long,
1005            OKXPositionSide::Short => Self::Short,
1006            _ => Self::Flat,
1007        }
1008    }
1009}
1010
1011impl From<OKXOrderStatus> for OrderStatus {
1012    fn from(status: OKXOrderStatus) -> Self {
1013        match status {
1014            OKXOrderStatus::Live => Self::Accepted,
1015            OKXOrderStatus::Effective => Self::Triggered,
1016            OKXOrderStatus::PartiallyFilled => Self::PartiallyFilled,
1017            OKXOrderStatus::Filled => Self::Filled,
1018            OKXOrderStatus::Canceled | OKXOrderStatus::MmpCanceled => Self::Canceled,
1019            OKXOrderStatus::OrderPlaced => Self::Triggered,
1020        }
1021    }
1022}
1023
1024impl From<OKXOrderType> for OrderType {
1025    fn from(ord_type: OKXOrderType) -> Self {
1026        match ord_type {
1027            OKXOrderType::Market => Self::Market,
1028            OKXOrderType::Limit
1029            | OKXOrderType::PostOnly
1030            | OKXOrderType::OptimalLimitIoc
1031            | OKXOrderType::Mmp
1032            | OKXOrderType::MmpAndPostOnly
1033            | OKXOrderType::Fok
1034            | OKXOrderType::OpFok
1035            | OKXOrderType::Ioc => Self::Limit,
1036            OKXOrderType::Trigger => Self::StopMarket,
1037        }
1038    }
1039}
1040
1041impl From<OrderType> for OKXOrderType {
1042    fn from(value: OrderType) -> Self {
1043        match value {
1044            OrderType::Market => Self::Market,
1045            OrderType::Limit => Self::Limit,
1046            OrderType::MarketToLimit => Self::Ioc,
1047            // Conditional orders will be handled separately via algo orders
1048            OrderType::StopMarket
1049            | OrderType::StopLimit
1050            | OrderType::MarketIfTouched
1051            | OrderType::LimitIfTouched
1052            | OrderType::TrailingStopMarket => {
1053                panic!("Conditional order types must use OKXAlgoOrderType")
1054            }
1055            _ => panic!("Invalid `OrderType` cannot be represented on OKX: {value:?}"),
1056        }
1057    }
1058}
1059
1060impl From<PositionSide> for OKXPositionSide {
1061    fn from(value: PositionSide) -> Self {
1062        match value {
1063            PositionSide::Long => Self::Long,
1064            PositionSide::Short => Self::Short,
1065            _ => Self::None,
1066        }
1067    }
1068}
1069
1070#[derive(
1071    Copy,
1072    Clone,
1073    Debug,
1074    Display,
1075    PartialEq,
1076    Eq,
1077    Hash,
1078    AsRefStr,
1079    EnumIter,
1080    EnumString,
1081    Serialize,
1082    Deserialize,
1083)]
1084#[serde(rename_all = "snake_case")]
1085pub enum OKXAlgoOrderType {
1086    Conditional,
1087    Oco,
1088    Trigger,
1089    MoveOrderStop,
1090    Iceberg,
1091    Twap,
1092}
1093
1094/// Helper to determine if an order type requires algo order handling.
1095pub fn is_conditional_order(order_type: OrderType) -> bool {
1096    OKX_CONDITIONAL_ORDER_TYPES.contains(&order_type)
1097}
1098
1099/// Helper to determine if an order type requires the advance algo cancel endpoint.
1100pub fn is_advance_algo_order(order_type: OrderType) -> bool {
1101    OKX_ADVANCE_ALGO_ORDER_TYPES.contains(&order_type)
1102}
1103
1104/// Converts Nautilus conditional order types to OKX algo order type.
1105///
1106/// # Errors
1107///
1108/// Returns an error if the provided `order_type` is not a conditional order type.
1109pub fn conditional_order_to_algo_type(order_type: OrderType) -> anyhow::Result<OKXAlgoOrderType> {
1110    match order_type {
1111        OrderType::StopMarket
1112        | OrderType::StopLimit
1113        | OrderType::MarketIfTouched
1114        | OrderType::LimitIfTouched => Ok(OKXAlgoOrderType::Trigger),
1115        OrderType::TrailingStopMarket => Ok(OKXAlgoOrderType::MoveOrderStop),
1116        _ => anyhow::bail!("Not a conditional order type: {order_type:?}"),
1117    }
1118}
1119
1120#[derive(
1121    Copy,
1122    Clone,
1123    Debug,
1124    Display,
1125    PartialEq,
1126    Eq,
1127    Hash,
1128    AsRefStr,
1129    EnumIter,
1130    EnumString,
1131    Serialize,
1132    Deserialize,
1133)]
1134#[serde(rename_all = "snake_case")]
1135pub enum OKXAlgoOrderStatus {
1136    Live,
1137    Pause,
1138    PartiallyEffective,
1139    Effective,
1140    Canceled,
1141    OrderFailed,
1142    PartiallyFailed,
1143}
1144
1145#[derive(
1146    Copy,
1147    Clone,
1148    Debug,
1149    Display,
1150    PartialEq,
1151    Eq,
1152    Hash,
1153    AsRefStr,
1154    EnumIter,
1155    EnumString,
1156    Serialize,
1157    Deserialize,
1158)]
1159pub enum OKXTransactionType {
1160    #[serde(rename = "1")]
1161    Buy,
1162    #[serde(rename = "2")]
1163    Sell,
1164    #[serde(rename = "3")]
1165    OpenLong,
1166    #[serde(rename = "4")]
1167    OpenShort,
1168    #[serde(rename = "5")]
1169    CloseLong,
1170    #[serde(rename = "6")]
1171    CloseShort,
1172    #[serde(rename = "100")]
1173    PartialLiquidationCloseLong,
1174    #[serde(rename = "101")]
1175    PartialLiquidationCloseShort,
1176    #[serde(rename = "102")]
1177    PartialLiquidationBuy,
1178    #[serde(rename = "103")]
1179    PartialLiquidationSell,
1180    #[serde(rename = "104")]
1181    LiquidationLong,
1182    #[serde(rename = "105")]
1183    LiquidationShort,
1184    #[serde(rename = "106")]
1185    LiquidationBuy,
1186    #[serde(rename = "107")]
1187    LiquidationSell,
1188    #[serde(rename = "110")]
1189    LiquidationTransferIn,
1190    #[serde(rename = "111")]
1191    LiquidationTransferOut,
1192    #[serde(rename = "118")]
1193    SystemTokenConversionTransferIn,
1194    #[serde(rename = "119")]
1195    SystemTokenConversionTransferOut,
1196    #[serde(rename = "125")]
1197    AdlCloseLong,
1198    #[serde(rename = "126")]
1199    AdlCloseShort,
1200    #[serde(rename = "127")]
1201    AdlBuy,
1202    #[serde(rename = "128")]
1203    AdlSell,
1204    #[serde(rename = "212")]
1205    AutoBorrowOfQuickMargin,
1206    #[serde(rename = "213")]
1207    AutoRepayOfQuickMargin,
1208    #[serde(rename = "204")]
1209    BlockTradeBuy,
1210    #[serde(rename = "205")]
1211    BlockTradeSell,
1212    #[serde(rename = "206")]
1213    BlockTradeOpenLong,
1214    #[serde(rename = "207")]
1215    BlockTradeOpenShort,
1216    #[serde(rename = "208")]
1217    BlockTradeCloseOpen,
1218    #[serde(rename = "209")]
1219    BlockTradeCloseShort,
1220    #[serde(rename = "270")]
1221    SpreadTradingBuy,
1222    #[serde(rename = "271")]
1223    SpreadTradingSell,
1224    #[serde(rename = "272")]
1225    SpreadTradingOpenLong,
1226    #[serde(rename = "273")]
1227    SpreadTradingOpenShort,
1228    #[serde(rename = "274")]
1229    SpreadTradingCloseLong,
1230    #[serde(rename = "275")]
1231    SpreadTradingCloseShort,
1232}
1233
1234/// Represents the category of an order on OKX.
1235///
1236/// The category field indicates whether an order is a normal trade, liquidation,
1237/// auto-deleveraging (ADL) event, or algorithmic order type. This is critical for
1238/// risk management and proper handling of exchange-generated orders.
1239///
1240/// # References
1241///
1242/// <https://www.okx.com/docs-v5/en/#order-book-trading-ws-order-channel>
1243#[derive(
1244    Copy,
1245    Clone,
1246    Debug,
1247    Display,
1248    PartialEq,
1249    Eq,
1250    Hash,
1251    AsRefStr,
1252    EnumIter,
1253    EnumString,
1254    Serialize,
1255    Deserialize,
1256)]
1257#[serde(rename_all = "snake_case")]
1258pub enum OKXOrderCategory {
1259    /// Normal trading order.
1260    Normal,
1261    /// Full liquidation order (position completely closed by exchange).
1262    FullLiquidation,
1263    /// Partial liquidation order (position partially closed by exchange).
1264    PartialLiquidation,
1265    /// Auto-deleveraging order (position closed to offset counterparty liquidation).
1266    Adl,
1267    /// Time-Weighted Average Price algorithmic order.
1268    Twap,
1269    /// Iceberg algorithmic order (hidden quantity).
1270    Iceberg,
1271    /// One-Cancels-the-Other algorithmic order.
1272    Oco,
1273    /// Conditional/trigger order.
1274    Conditional,
1275    /// Move order stop algorithmic order.
1276    MoveOrderStop,
1277    /// Delivery and exercise (for futures/options settlement).
1278    Ddh,
1279    /// Unknown or future category (graceful fallback).
1280    #[serde(other)]
1281    Other,
1282}
1283
1284#[derive(
1285    Copy,
1286    Clone,
1287    Debug,
1288    Display,
1289    PartialEq,
1290    Eq,
1291    Hash,
1292    AsRefStr,
1293    EnumIter,
1294    EnumString,
1295    Serialize,
1296    Deserialize,
1297)]
1298pub enum OKXBarSize {
1299    #[serde(rename = "1s")]
1300    Second1,
1301    #[serde(rename = "1m")]
1302    Minute1,
1303    #[serde(rename = "3m")]
1304    Minute3,
1305    #[serde(rename = "5m")]
1306    Minute5,
1307    #[serde(rename = "15m")]
1308    Minute15,
1309    #[serde(rename = "30m")]
1310    Minute30,
1311    #[serde(rename = "1H")]
1312    Hour1,
1313    #[serde(rename = "2H")]
1314    Hour2,
1315    #[serde(rename = "4H")]
1316    Hour4,
1317    #[serde(rename = "6H")]
1318    Hour6,
1319    #[serde(rename = "12H")]
1320    Hour12,
1321    #[serde(rename = "1D")]
1322    Day1,
1323    #[serde(rename = "2D")]
1324    Day2,
1325    #[serde(rename = "3D")]
1326    Day3,
1327    #[serde(rename = "5D")]
1328    Day5,
1329    #[serde(rename = "1W")]
1330    Week1,
1331    #[serde(rename = "1M")]
1332    Month1,
1333    #[serde(rename = "3M")]
1334    Month3,
1335}
1336
1337/// Options price type for order pricing.
1338#[derive(
1339    Copy,
1340    Clone,
1341    Debug,
1342    Default,
1343    Display,
1344    PartialEq,
1345    Eq,
1346    Hash,
1347    AsRefStr,
1348    EnumIter,
1349    EnumString,
1350    Serialize,
1351    Deserialize,
1352)]
1353#[serde(rename_all = "snake_case")]
1354pub enum OKXPriceType {
1355    /// No price type specified.
1356    #[default]
1357    #[serde(rename = "")]
1358    None,
1359    /// Standard price.
1360    Px,
1361    /// Price in USD.
1362    Usd,
1363    /// Price in implied volatility.
1364    Vol,
1365}
1366
1367/// Funding rate settlement state.
1368#[derive(
1369    Copy,
1370    Clone,
1371    Debug,
1372    Default,
1373    Display,
1374    PartialEq,
1375    Eq,
1376    Hash,
1377    AsRefStr,
1378    EnumIter,
1379    EnumString,
1380    Serialize,
1381    Deserialize,
1382)]
1383#[serde(rename_all = "snake_case")]
1384pub enum OKXSettlementState {
1385    /// No settlement state.
1386    #[default]
1387    #[serde(rename = "")]
1388    None,
1389    /// Settlement in progress.
1390    Processing,
1391    /// Settlement completed.
1392    Settled,
1393}
1394
1395/// Quick margin type for order margin management.
1396#[derive(
1397    Copy,
1398    Clone,
1399    Debug,
1400    Default,
1401    Display,
1402    PartialEq,
1403    Eq,
1404    Hash,
1405    AsRefStr,
1406    EnumIter,
1407    EnumString,
1408    Serialize,
1409    Deserialize,
1410)]
1411#[serde(rename_all = "snake_case")]
1412pub enum OKXQuickMarginType {
1413    /// No quick margin type.
1414    #[default]
1415    #[serde(rename = "")]
1416    None,
1417    /// Manual margin management.
1418    Manual,
1419    /// Auto borrow margin.
1420    AutoBorrow,
1421    /// Auto repay margin.
1422    AutoRepay,
1423}
1424
1425/// OKX API environment.
1426#[derive(
1427    Copy,
1428    Clone,
1429    Debug,
1430    Default,
1431    Display,
1432    PartialEq,
1433    Eq,
1434    Hash,
1435    AsRefStr,
1436    EnumIter,
1437    EnumString,
1438    Serialize,
1439    Deserialize,
1440)]
1441#[serde(rename_all = "lowercase")]
1442#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
1443#[cfg_attr(
1444    feature = "python",
1445    pyo3::pyclass(
1446        eq,
1447        eq_int,
1448        module = "nautilus_trader.core.nautilus_pyo3.okx",
1449        from_py_object,
1450        rename_all = "SCREAMING_SNAKE_CASE",
1451    )
1452)]
1453#[cfg_attr(
1454    feature = "python",
1455    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.okx")
1456)]
1457pub enum OKXEnvironment {
1458    /// Live trading environment.
1459    #[default]
1460    Live,
1461    /// Demo trading environment.
1462    Demo,
1463}