1use 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#[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 Update,
46 Snapshot,
48}
49
50#[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 #[serde(rename = "0")]
68 Partial,
69 #[serde(rename = "1")]
71 Closed,
72}
73
74#[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,
93 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#[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,
134 Limit,
136 #[serde(alias = "elp")]
138 Rpi,
139 PostOnly, Fok, Ioc, OptimalLimitIoc, Mmp, MmpAndPostOnly, OpFok, Trigger, #[serde(other)]
149 Other,
150}
151
152#[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 #[serde(other)]
190 Unknown,
191}
192
193impl TryFrom<OrderStatus> for OKXOrderStatus {
194 type Error = OrderStatus;
195
196 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#[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#[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,
283 Spot,
285 Margin,
287 Swap,
289 Futures,
291 Option,
293 Events,
295}
296
297#[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 #[serde(rename = "1")]
321 #[strum(serialize = "1")]
322 Crypto,
323 #[serde(rename = "3")]
325 #[strum(serialize = "3")]
326 Equity,
327 #[serde(rename = "4")]
329 #[strum(serialize = "4")]
330 Commodity,
331 #[serde(rename = "5")]
333 #[strum(serialize = "5")]
334 Fx,
335 #[serde(rename = "6")]
337 #[strum(serialize = "6")]
338 Debt,
339 #[default]
341 #[serde(other)]
342 #[strum(serialize = "")]
343 Unknown,
344}
345
346#[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 #[serde(other)]
372 Unknown,
373}
374
375#[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 #[serde(other)]
397 Unknown,
398}
399
400#[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 #[serde(other)]
422 Unknown,
423}
424
425#[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#[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 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#[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 #[default]
545 Bs = 0,
546 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#[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#[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#[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#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
711pub enum OKXAccountLevel {
712 #[serde(rename = "1")]
714 Spot,
715 #[serde(rename = "2")]
717 Futures,
718 #[serde(rename = "3")]
720 MultiCurrencyMargin,
721 #[serde(rename = "4")]
723 PortfolioMargin,
724}
725
726#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
728pub enum OKXFeeType {
729 #[serde(rename = "0")]
731 ReceivedCurrency,
732 #[serde(rename = "1")]
734 QuoteCurrency,
735}
736
737#[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 ReadOnly,
744 Trade,
746 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#[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 BaseCcy,
1096 QuoteCcy,
1098}
1099
1100#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1102pub enum OKXBookChannel {
1103 Book,
1105 BookL2Tbt,
1107 Books50L2Tbt,
1109 BooksRpi,
1111 SprdBooks5,
1113}
1114
1115#[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 #[serde(rename = "0")]
1133 Disabled,
1134 #[serde(rename = "1")]
1136 Enabled,
1137 #[serde(rename = "2")]
1139 Permitted,
1140}
1141
1142#[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 #[serde(rename = "0")]
1183 #[strum(serialize = "0")]
1184 Vip0 = 0,
1185 #[serde(rename = "1")]
1187 #[strum(serialize = "1")]
1188 Vip1 = 1,
1189 #[serde(rename = "2")]
1191 #[strum(serialize = "2")]
1192 Vip2 = 2,
1193 #[serde(rename = "3")]
1195 #[strum(serialize = "3")]
1196 Vip3 = 3,
1197 #[serde(rename = "4")]
1199 #[strum(serialize = "4")]
1200 Vip4 = 4,
1201 #[serde(rename = "5")]
1203 #[strum(serialize = "5")]
1204 Vip5 = 5,
1205 #[serde(rename = "6")]
1207 #[strum(serialize = "6")]
1208 Vip6 = 6,
1209 #[serde(rename = "7")]
1211 #[strum(serialize = "7")]
1212 Vip7 = 7,
1213 #[serde(rename = "8")]
1215 #[strum(serialize = "8")]
1216 Vip8 = 8,
1217 #[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 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 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 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 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 #[serde(other)]
1394 Other,
1395}
1396
1397pub fn is_conditional_order(order_type: OrderType) -> bool {
1399 OKX_CONDITIONAL_ORDER_TYPES.contains(&order_type)
1400}
1401
1402pub fn is_advance_algo_order(order_type: OrderType) -> bool {
1404 OKX_ADVANCE_ALGO_ORDER_TYPES.contains(&order_type)
1405}
1406
1407pub 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#[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 #[serde(other)]
1465 Unknown,
1466}
1467
1468#[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,
1495 FullLiquidation,
1497 PartialLiquidation,
1499 Adl,
1501 Twap,
1503 Iceberg,
1505 Oco,
1507 Conditional,
1509 MoveOrderStop,
1511 Ddh,
1513 Delivery,
1515 AutoConversion,
1517 #[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#[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 #[default]
1595 #[serde(rename = "")]
1596 None,
1597 Px,
1599 #[serde(rename = "pxUsd")]
1601 Usd,
1602 #[serde(rename = "pxVol")]
1604 Vol,
1605}
1606
1607#[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 #[default]
1627 #[serde(rename = "")]
1628 None,
1629 Processing,
1631 Settled,
1633}
1634
1635#[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 #[default]
1655 #[serde(rename = "")]
1656 None,
1657 Manual,
1659 AutoBorrow,
1661 AutoRepay,
1663}
1664
1665#[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 #[default]
1700 Live,
1701 Demo,
1703}
1704
1705#[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 #[default]
1744 Global,
1745 Eea,
1747 Us,
1749}