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(
628 Copy,
629 Clone,
630 Debug,
631 Display,
632 PartialEq,
633 Eq,
634 Hash,
635 AsRefStr,
636 EnumIter,
637 EnumString,
638 Serialize,
639 Deserialize,
640)]
641pub enum OKXAccountMode {
642 #[serde(rename = "Spot mode")]
643 Spot,
644 #[serde(rename = "Spot and futures mode")]
645 SpotAndFutures,
646 #[serde(rename = "Multi-currency margin mode")]
647 MultiCurrencyMarginMode,
648 #[serde(rename = "Portfolio margin mode")]
649 PortfolioMarginMode,
650}
651
652#[derive(
660 Copy,
661 Clone,
662 Default,
663 Debug,
664 Display,
665 PartialEq,
666 Eq,
667 Hash,
668 AsRefStr,
669 EnumIter,
670 EnumString,
671 Serialize,
672 Deserialize,
673)]
674#[serde(rename_all = "snake_case")]
675#[cfg_attr(
676 feature = "python",
677 pyo3::pyclass(
678 eq,
679 eq_int,
680 module = "nautilus_trader.adapters.okx",
681 from_py_object,
682 rename_all = "SCREAMING_SNAKE_CASE",
683 )
684)]
685#[cfg_attr(
686 feature = "python",
687 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
688)]
689pub enum OKXMarginMode {
690 #[serde(rename = "")]
691 #[default]
692 None,
693 Isolated,
694 Cross,
695}
696
697#[derive(
703 Copy,
704 Clone,
705 Default,
706 Debug,
707 Display,
708 PartialEq,
709 Eq,
710 Hash,
711 AsRefStr,
712 EnumIter,
713 EnumString,
714 Serialize,
715 Deserialize,
716)]
717#[cfg_attr(
718 feature = "python",
719 pyo3::pyclass(
720 eq,
721 eq_int,
722 module = "nautilus_trader.adapters.okx",
723 from_py_object,
724 rename_all = "SCREAMING_SNAKE_CASE",
725 )
726)]
727#[cfg_attr(
728 feature = "python",
729 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
730)]
731pub enum OKXPositionMode {
732 #[default]
733 #[serde(rename = "net_mode")]
734 NetMode,
735 #[serde(rename = "long_short_mode")]
736 LongShortMode,
737}
738
739#[derive(
740 Copy,
741 Clone,
742 Debug,
743 Display,
744 PartialEq,
745 Eq,
746 Hash,
747 AsRefStr,
748 EnumIter,
749 EnumString,
750 Serialize,
751 Deserialize,
752)]
753#[serde(rename_all = "snake_case")]
754pub enum OKXPositionSide {
755 #[serde(rename = "")]
756 None,
757 Net,
758 Long,
759 Short,
760}
761
762#[derive(
763 Copy,
764 Clone,
765 Debug,
766 Default,
767 Display,
768 PartialEq,
769 Eq,
770 Hash,
771 AsRefStr,
772 EnumIter,
773 EnumString,
774 Serialize,
775 Deserialize,
776)]
777#[serde(rename_all = "snake_case")]
778pub enum OKXSelfTradePreventionMode {
779 #[default]
780 #[serde(rename = "")]
781 None,
782 CancelMaker,
783 CancelTaker,
784 CancelBoth,
785}
786
787#[derive(
788 Copy,
789 Clone,
790 Debug,
791 Display,
792 PartialEq,
793 Eq,
794 Hash,
795 AsRefStr,
796 EnumIter,
797 EnumString,
798 Serialize,
799 Deserialize,
800)]
801#[serde(rename_all = "snake_case")]
802pub enum OKXTakeProfitKind {
803 #[serde(rename = "")]
804 None,
805 Condition,
806 Limit,
807}
808
809#[derive(
810 Copy,
811 Clone,
812 Debug,
813 Default,
814 Display,
815 PartialEq,
816 Eq,
817 Hash,
818 AsRefStr,
819 EnumIter,
820 EnumString,
821 Serialize,
822 Deserialize,
823)]
824#[serde(rename_all = "snake_case")]
825#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
826pub enum OKXTriggerType {
827 #[default]
828 #[serde(rename = "")]
829 None,
830 Last,
831 Index,
832 Mark,
833}
834
835impl From<TriggerType> for OKXTriggerType {
836 fn from(value: TriggerType) -> Self {
837 match value {
838 TriggerType::LastPrice => Self::Last,
839 TriggerType::MarkPrice => Self::Mark,
840 TriggerType::IndexPrice => Self::Index,
841 _ => Self::Last,
842 }
843 }
844}
845
846#[cfg(test)]
847mod tests {
848 use std::str::FromStr;
849
850 use nautilus_model::enums::{GreeksConvention, OptionKind, OrderStatus, OrderType};
851 use rstest::rstest;
852
853 use super::{
854 OKXAlgoOrderStatus, OKXAlgoOrderType, OKXGreeksType, OKXOptionType, OKXOrderStatus,
855 OKXOrderType, OKXRpiPermission, OKXTriggerType,
856 };
857
858 #[rstest]
859 fn test_okx_trigger_type_from_str_accepts_snake_case_values() {
860 assert_eq!(
861 OKXTriggerType::from_str("last").unwrap(),
862 OKXTriggerType::Last
863 );
864 assert_eq!(
865 OKXTriggerType::from_str("mark").unwrap(),
866 OKXTriggerType::Mark
867 );
868 assert_eq!(
869 OKXTriggerType::from_str("index").unwrap(),
870 OKXTriggerType::Index
871 );
872 }
873
874 #[rstest]
875 #[case(OKXGreeksType::Bs, "\"BS\"")]
876 #[case(OKXGreeksType::Pa, "\"PA\"")]
877 fn test_greeks_type_serde_roundtrip(#[case] input: OKXGreeksType, #[case] expected: &str) {
878 let json = serde_json::to_string(&input).unwrap();
879 assert_eq!(json, expected);
880 let parsed: OKXGreeksType = serde_json::from_str(expected).unwrap();
881 assert_eq!(parsed, input);
882 }
883
884 #[rstest]
885 fn test_greeks_type_default_is_bs() {
886 assert_eq!(OKXGreeksType::default(), OKXGreeksType::Bs);
887 }
888
889 #[rstest]
890 fn test_greeks_type_from_u8() {
891 assert_eq!(OKXGreeksType::from(0_u8), OKXGreeksType::Bs);
892 assert_eq!(OKXGreeksType::from(1_u8), OKXGreeksType::Pa);
893 assert_eq!(OKXGreeksType::from(99_u8), OKXGreeksType::Bs);
894 }
895
896 #[rstest]
897 #[case(GreeksConvention::BlackScholes, OKXGreeksType::Bs)]
898 #[case(GreeksConvention::PriceAdjusted, OKXGreeksType::Pa)]
899 fn test_greeks_type_convention_roundtrip(
900 #[case] convention: GreeksConvention,
901 #[case] expected: OKXGreeksType,
902 ) {
903 let mapped: OKXGreeksType = convention.into();
904 assert_eq!(mapped, expected);
905 let back: GreeksConvention = mapped.into();
906 assert_eq!(back, convention);
907 }
908
909 #[rstest]
910 fn test_op_fok_serializes_to_snake_case() {
911 let json = serde_json::to_string(&OKXOrderType::OpFok).unwrap();
912 assert_eq!(json, "\"op_fok\"");
913 }
914
915 #[rstest]
916 fn test_op_fok_deserializes_from_snake_case() {
917 let parsed: OKXOrderType = serde_json::from_str("\"op_fok\"").unwrap();
918 assert_eq!(parsed, OKXOrderType::OpFok);
919 }
920
921 #[rstest]
922 fn test_op_fok_converts_to_limit_order_type() {
923 let order_type: OrderType = OKXOrderType::OpFok.try_into().unwrap();
924 assert_eq!(order_type, OrderType::Limit);
925 }
926
927 #[rstest]
928 fn test_rpi_order_type_serializes_current_name_and_reads_legacy_alias() {
929 assert_eq!(
930 serde_json::to_string(&OKXOrderType::Rpi).unwrap(),
931 "\"rpi\""
932 );
933 assert_eq!(
934 serde_json::from_str::<OKXOrderType>("\"elp\"").unwrap(),
935 OKXOrderType::Rpi
936 );
937 assert_eq!(
938 OrderType::try_from(OKXOrderType::Rpi).unwrap(),
939 OrderType::Limit
940 );
941 }
942
943 #[rstest]
944 #[case("\"0\"", OKXRpiPermission::Disabled)]
945 #[case("\"1\"", OKXRpiPermission::Enabled)]
946 #[case("\"2\"", OKXRpiPermission::Permitted)]
947 fn test_rpi_permission_deserializes_string_codes(
948 #[case] json: &str,
949 #[case] expected: OKXRpiPermission,
950 ) {
951 assert_eq!(
952 serde_json::from_str::<OKXRpiPermission>(json).unwrap(),
953 expected
954 );
955 }
956
957 #[rstest]
958 #[case::call(OKXOptionType::Call, Ok(OptionKind::Call))]
959 #[case::put(OKXOptionType::Put, Ok(OptionKind::Put))]
960 #[case::none(OKXOptionType::None, Err(OKXOptionType::None))]
961 fn test_try_from_okx_option_type(
962 #[case] input: OKXOptionType,
963 #[case] expected: Result<OptionKind, OKXOptionType>,
964 ) {
965 let actual: Result<OptionKind, OKXOptionType> = input.try_into();
966 assert_eq!(actual, expected);
967 }
968
969 #[rstest]
970 #[case::canceled(OrderStatus::Canceled, Ok(OKXOrderStatus::Canceled))]
971 #[case::accepted(OrderStatus::Accepted, Ok(OKXOrderStatus::Live))]
972 #[case::partially_filled(OrderStatus::PartiallyFilled, Ok(OKXOrderStatus::PartiallyFilled))]
973 #[case::filled(OrderStatus::Filled, Ok(OKXOrderStatus::Filled))]
974 #[case::submitted(OrderStatus::Submitted, Err(OrderStatus::Submitted))]
975 #[case::pending_update(OrderStatus::PendingUpdate, Err(OrderStatus::PendingUpdate))]
976 #[case::pending_cancel(OrderStatus::PendingCancel, Err(OrderStatus::PendingCancel))]
977 #[case::triggered(OrderStatus::Triggered, Err(OrderStatus::Triggered))]
978 #[case::expired(OrderStatus::Expired, Err(OrderStatus::Expired))]
979 #[case::rejected(OrderStatus::Rejected, Err(OrderStatus::Rejected))]
980 fn test_try_from_order_status(
981 #[case] input: OrderStatus,
982 #[case] expected: Result<OKXOrderStatus, OrderStatus>,
983 ) {
984 let actual: Result<OKXOrderStatus, OrderStatus> = input.try_into();
985 assert_eq!(actual, expected);
986 }
987
988 #[rstest]
989 #[case::live(OKXOrderStatus::Live, Ok(OrderStatus::Accepted))]
990 #[case::partially_filled(OKXOrderStatus::PartiallyFilled, Ok(OrderStatus::PartiallyFilled))]
991 #[case::filled(OKXOrderStatus::Filled, Ok(OrderStatus::Filled))]
992 #[case::canceled(OKXOrderStatus::Canceled, Ok(OrderStatus::Canceled))]
993 #[case::mmp_canceled(OKXOrderStatus::MmpCanceled, Ok(OrderStatus::Canceled))]
994 #[case::unknown(OKXOrderStatus::Unknown, Err(OKXOrderStatus::Unknown))]
995 fn test_try_from_okx_order_status(
996 #[case] input: OKXOrderStatus,
997 #[case] expected: Result<OrderStatus, OKXOrderStatus>,
998 ) {
999 let actual: Result<OrderStatus, OKXOrderStatus> = input.try_into();
1000 assert_eq!(actual, expected);
1001 }
1002
1003 #[rstest]
1004 #[case::live(OKXAlgoOrderStatus::Live, Ok(OrderStatus::Accepted))]
1005 #[case::pause(OKXAlgoOrderStatus::Pause, Ok(OrderStatus::Accepted))]
1006 #[case::effective(OKXAlgoOrderStatus::Effective, Ok(OrderStatus::Triggered))]
1007 #[case::order_placed(OKXAlgoOrderStatus::OrderPlaced, Ok(OrderStatus::Triggered))]
1008 #[case::partially_effective(OKXAlgoOrderStatus::PartiallyEffective, Ok(OrderStatus::Triggered))]
1009 #[case::filled(OKXAlgoOrderStatus::Filled, Ok(OrderStatus::Filled))]
1010 #[case::canceled(OKXAlgoOrderStatus::Canceled, Ok(OrderStatus::Canceled))]
1011 #[case::order_failed(OKXAlgoOrderStatus::OrderFailed, Ok(OrderStatus::Rejected))]
1012 #[case::partially_failed(OKXAlgoOrderStatus::PartiallyFailed, Ok(OrderStatus::Rejected))]
1013 #[case::unknown(OKXAlgoOrderStatus::Unknown, Err(OKXAlgoOrderStatus::Unknown))]
1014 fn test_try_from_okx_algo_order_status(
1015 #[case] input: OKXAlgoOrderStatus,
1016 #[case] expected: Result<OrderStatus, OKXAlgoOrderStatus>,
1017 ) {
1018 let actual: Result<OrderStatus, OKXAlgoOrderStatus> = input.try_into();
1019 assert_eq!(actual, expected);
1020 }
1021
1022 #[rstest]
1023 fn test_okx_order_status_deserializes_unknown_state_as_unknown() {
1024 let parsed: OKXOrderStatus = serde_json::from_str("\"future_state\"").unwrap();
1025 assert_eq!(parsed, OKXOrderStatus::Unknown);
1026
1027 let parsed: OKXOrderStatus = serde_json::from_str("\"mmp_canceled\"").unwrap();
1028 assert_eq!(parsed, OKXOrderStatus::MmpCanceled);
1029 }
1030
1031 #[rstest]
1032 fn test_okx_algo_order_status_deserializes_unknown_state_as_unknown() {
1033 let parsed: OKXAlgoOrderStatus = serde_json::from_str("\"future_state\"").unwrap();
1034 assert_eq!(parsed, OKXAlgoOrderStatus::Unknown);
1035 }
1036
1037 #[rstest]
1038 fn test_okx_order_type_deserializes_unknown_type_as_other() {
1039 let parsed: OKXOrderType = serde_json::from_str("\"future_ord_type\"").unwrap();
1040 assert_eq!(parsed, OKXOrderType::Other);
1041 }
1042
1043 #[rstest]
1044 fn test_okx_algo_order_type_deserializes_chase_and_unknown() {
1045 let parsed: OKXAlgoOrderType = serde_json::from_str("\"chase\"").unwrap();
1046 assert_eq!(parsed, OKXAlgoOrderType::Chase);
1047
1048 let parsed: OKXAlgoOrderType = serde_json::from_str("\"future_algo_type\"").unwrap();
1049 assert_eq!(parsed, OKXAlgoOrderType::Other);
1050 }
1051}
1052
1053#[derive(
1055 Copy,
1056 Clone,
1057 Debug,
1058 Display,
1059 PartialEq,
1060 Eq,
1061 Hash,
1062 AsRefStr,
1063 EnumIter,
1064 EnumString,
1065 Serialize,
1066 Deserialize,
1067)]
1068#[serde(rename_all = "snake_case")]
1069#[strum(serialize_all = "snake_case")]
1070pub enum OKXTargetCurrency {
1071 BaseCcy,
1073 QuoteCcy,
1075}
1076
1077#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1079pub enum OKXBookChannel {
1080 Book,
1082 BookL2Tbt,
1084 Books50L2Tbt,
1086 BooksRpi,
1088 SprdBooks5,
1090}
1091
1092#[derive(
1094 Copy,
1095 Clone,
1096 Debug,
1097 Display,
1098 PartialEq,
1099 Eq,
1100 Hash,
1101 AsRefStr,
1102 EnumIter,
1103 EnumString,
1104 Serialize,
1105 Deserialize,
1106)]
1107pub enum OKXRpiPermission {
1108 #[serde(rename = "0")]
1110 Disabled,
1111 #[serde(rename = "1")]
1113 Enabled,
1114 #[serde(rename = "2")]
1116 Permitted,
1117}
1118
1119#[derive(
1133 Copy,
1134 Clone,
1135 Debug,
1136 Display,
1137 PartialEq,
1138 Eq,
1139 PartialOrd,
1140 Ord,
1141 Hash,
1142 AsRefStr,
1143 EnumIter,
1144 EnumString,
1145 Serialize,
1146 Deserialize,
1147)]
1148#[cfg_attr(
1149 feature = "python",
1150 pyo3::pyclass(
1151 module = "nautilus_trader.adapters.okx",
1152 from_py_object,
1153 rename_all = "SCREAMING_SNAKE_CASE",
1154 )
1155)]
1156#[cfg_attr(
1157 feature = "python",
1158 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
1159)]
1160pub enum OKXVipLevel {
1161 #[serde(rename = "0")]
1163 #[strum(serialize = "0")]
1164 Vip0 = 0,
1165 #[serde(rename = "1")]
1167 #[strum(serialize = "1")]
1168 Vip1 = 1,
1169 #[serde(rename = "2")]
1171 #[strum(serialize = "2")]
1172 Vip2 = 2,
1173 #[serde(rename = "3")]
1175 #[strum(serialize = "3")]
1176 Vip3 = 3,
1177 #[serde(rename = "4")]
1179 #[strum(serialize = "4")]
1180 Vip4 = 4,
1181 #[serde(rename = "5")]
1183 #[strum(serialize = "5")]
1184 Vip5 = 5,
1185 #[serde(rename = "6")]
1187 #[strum(serialize = "6")]
1188 Vip6 = 6,
1189 #[serde(rename = "7")]
1191 #[strum(serialize = "7")]
1192 Vip7 = 7,
1193 #[serde(rename = "8")]
1195 #[strum(serialize = "8")]
1196 Vip8 = 8,
1197 #[serde(rename = "9")]
1199 #[strum(serialize = "9")]
1200 Vip9 = 9,
1201}
1202
1203impl From<u8> for OKXVipLevel {
1204 fn from(value: u8) -> Self {
1205 match value {
1206 0 => Self::Vip0,
1207 1 => Self::Vip1,
1208 2 => Self::Vip2,
1209 3 => Self::Vip3,
1210 4 => Self::Vip4,
1211 5 => Self::Vip5,
1212 6 => Self::Vip6,
1213 7 => Self::Vip7,
1214 8 => Self::Vip8,
1215 9 => Self::Vip9,
1216 _ => {
1217 log::warn!("Invalid VIP level {value}, defaulting to Vip0");
1218 Self::Vip0
1219 }
1220 }
1221 }
1222}
1223
1224impl From<OKXSide> for OrderSide {
1225 fn from(side: OKXSide) -> Self {
1226 match side {
1227 OKXSide::Buy => Self::Buy,
1228 OKXSide::Sell => Self::Sell,
1229 }
1230 }
1231}
1232
1233impl From<OKXExecType> for LiquiditySide {
1234 fn from(exec: OKXExecType) -> Self {
1235 match exec {
1236 OKXExecType::Maker => Self::Maker,
1237 OKXExecType::Taker => Self::Taker,
1238 OKXExecType::None => Self::NoLiquiditySide,
1239 }
1240 }
1241}
1242
1243impl From<OKXPositionSide> for PositionSide {
1244 fn from(side: OKXPositionSide) -> Self {
1245 match side {
1246 OKXPositionSide::Long => Self::Long,
1247 OKXPositionSide::Short => Self::Short,
1248 _ => Self::Flat,
1249 }
1250 }
1251}
1252
1253impl TryFrom<OKXOrderStatus> for OrderStatus {
1254 type Error = OKXOrderStatus;
1255
1256 fn try_from(value: OKXOrderStatus) -> Result<Self, Self::Error> {
1261 match value {
1262 OKXOrderStatus::Live => Ok(Self::Accepted),
1263 OKXOrderStatus::PartiallyFilled => Ok(Self::PartiallyFilled),
1264 OKXOrderStatus::Filled => Ok(Self::Filled),
1265 OKXOrderStatus::Canceled | OKXOrderStatus::MmpCanceled => Ok(Self::Canceled),
1266 OKXOrderStatus::Unknown => Err(value),
1267 }
1268 }
1269}
1270
1271impl TryFrom<OKXAlgoOrderStatus> for OrderStatus {
1272 type Error = OKXAlgoOrderStatus;
1273
1274 fn try_from(value: OKXAlgoOrderStatus) -> Result<Self, Self::Error> {
1279 match value {
1280 OKXAlgoOrderStatus::Live | OKXAlgoOrderStatus::Pause => Ok(Self::Accepted),
1281 OKXAlgoOrderStatus::Effective
1282 | OKXAlgoOrderStatus::OrderPlaced
1283 | OKXAlgoOrderStatus::PartiallyEffective => Ok(Self::Triggered),
1284 OKXAlgoOrderStatus::Filled => Ok(Self::Filled),
1285 OKXAlgoOrderStatus::Canceled => Ok(Self::Canceled),
1286 OKXAlgoOrderStatus::OrderFailed | OKXAlgoOrderStatus::PartiallyFailed => {
1287 Ok(Self::Rejected)
1288 }
1289 OKXAlgoOrderStatus::Unknown => Err(value),
1290 }
1291 }
1292}
1293
1294impl TryFrom<OKXOrderType> for OrderType {
1295 type Error = OKXOrderType;
1296
1297 fn try_from(value: OKXOrderType) -> Result<Self, Self::Error> {
1302 match value {
1303 OKXOrderType::Market => Ok(Self::Market),
1304 OKXOrderType::Limit
1305 | OKXOrderType::Rpi
1306 | OKXOrderType::PostOnly
1307 | OKXOrderType::OptimalLimitIoc
1308 | OKXOrderType::Mmp
1309 | OKXOrderType::MmpAndPostOnly
1310 | OKXOrderType::Fok
1311 | OKXOrderType::OpFok
1312 | OKXOrderType::Ioc => Ok(Self::Limit),
1313 OKXOrderType::Trigger => Ok(Self::StopMarket),
1314 OKXOrderType::Other => Err(value),
1315 }
1316 }
1317}
1318
1319impl From<OrderType> for OKXOrderType {
1320 fn from(value: OrderType) -> Self {
1321 match value {
1322 OrderType::Market => Self::Market,
1323 OrderType::Limit => Self::Limit,
1324 OrderType::MarketToLimit => Self::Ioc,
1325 OrderType::StopMarket
1327 | OrderType::StopLimit
1328 | OrderType::MarketIfTouched
1329 | OrderType::LimitIfTouched
1330 | OrderType::TrailingStopMarket => {
1331 panic!("Conditional order types must use OKXAlgoOrderType")
1332 }
1333 _ => panic!("Invalid `OrderType` cannot be represented on OKX: {value:?}"),
1334 }
1335 }
1336}
1337
1338impl From<PositionSide> for OKXPositionSide {
1339 fn from(value: PositionSide) -> Self {
1340 match value {
1341 PositionSide::Long => Self::Long,
1342 PositionSide::Short => Self::Short,
1343 PositionSide::Flat => Self::None,
1344 }
1345 }
1346}
1347
1348#[derive(
1349 Copy,
1350 Clone,
1351 Debug,
1352 Display,
1353 PartialEq,
1354 Eq,
1355 Hash,
1356 AsRefStr,
1357 EnumIter,
1358 EnumString,
1359 Serialize,
1360 Deserialize,
1361)]
1362#[serde(rename_all = "snake_case")]
1363pub enum OKXAlgoOrderType {
1364 Conditional,
1365 Oco,
1366 Trigger,
1367 MoveOrderStop,
1368 Iceberg,
1369 Twap,
1370 Chase,
1371 #[serde(other)]
1373 Other,
1374}
1375
1376pub fn is_conditional_order(order_type: OrderType) -> bool {
1378 OKX_CONDITIONAL_ORDER_TYPES.contains(&order_type)
1379}
1380
1381pub fn is_advance_algo_order(order_type: OrderType) -> bool {
1383 OKX_ADVANCE_ALGO_ORDER_TYPES.contains(&order_type)
1384}
1385
1386pub fn conditional_order_to_algo_type(order_type: OrderType) -> anyhow::Result<OKXAlgoOrderType> {
1392 match order_type {
1393 OrderType::StopMarket
1394 | OrderType::StopLimit
1395 | OrderType::MarketIfTouched
1396 | OrderType::LimitIfTouched => Ok(OKXAlgoOrderType::Trigger),
1397 OrderType::TrailingStopMarket => Ok(OKXAlgoOrderType::MoveOrderStop),
1398 _ => anyhow::bail!("Not a conditional order type: {order_type:?}"),
1399 }
1400}
1401
1402#[derive(
1404 Copy,
1405 Clone,
1406 Debug,
1407 Display,
1408 PartialEq,
1409 Eq,
1410 Hash,
1411 AsRefStr,
1412 EnumIter,
1413 EnumString,
1414 Serialize,
1415 Deserialize,
1416)]
1417#[serde(rename_all = "snake_case")]
1418#[cfg_attr(
1419 feature = "python",
1420 pyo3::pyclass(
1421 eq,
1422 eq_int,
1423 module = "nautilus_trader.adapters.okx",
1424 from_py_object,
1425 rename_all = "SCREAMING_SNAKE_CASE",
1426 )
1427)]
1428#[cfg_attr(
1429 feature = "python",
1430 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
1431)]
1432pub enum OKXAlgoOrderStatus {
1433 Live,
1434 Pause,
1435 Effective,
1436 OrderPlaced,
1437 PartiallyEffective,
1438 Canceled,
1439 Filled,
1440 OrderFailed,
1441 PartiallyFailed,
1442 #[serde(other)]
1444 Unknown,
1445}
1446
1447#[derive(
1457 Copy,
1458 Clone,
1459 Debug,
1460 Display,
1461 PartialEq,
1462 Eq,
1463 Hash,
1464 AsRefStr,
1465 EnumIter,
1466 EnumString,
1467 Serialize,
1468 Deserialize,
1469)]
1470#[serde(rename_all = "snake_case")]
1471pub enum OKXOrderCategory {
1472 Normal,
1474 FullLiquidation,
1476 PartialLiquidation,
1478 Adl,
1480 Twap,
1482 Iceberg,
1484 Oco,
1486 Conditional,
1488 MoveOrderStop,
1490 Ddh,
1492 Delivery,
1494 #[serde(other)]
1496 Other,
1497}
1498
1499#[derive(
1500 Copy,
1501 Clone,
1502 Debug,
1503 Display,
1504 PartialEq,
1505 Eq,
1506 Hash,
1507 AsRefStr,
1508 EnumIter,
1509 EnumString,
1510 Serialize,
1511 Deserialize,
1512)]
1513pub enum OKXBarSize {
1514 #[serde(rename = "1s")]
1515 Second1,
1516 #[serde(rename = "1m")]
1517 Minute1,
1518 #[serde(rename = "3m")]
1519 Minute3,
1520 #[serde(rename = "5m")]
1521 Minute5,
1522 #[serde(rename = "15m")]
1523 Minute15,
1524 #[serde(rename = "30m")]
1525 Minute30,
1526 #[serde(rename = "1H")]
1527 Hour1,
1528 #[serde(rename = "2H")]
1529 Hour2,
1530 #[serde(rename = "4H")]
1531 Hour4,
1532 #[serde(rename = "6H")]
1533 Hour6,
1534 #[serde(rename = "12H")]
1535 Hour12,
1536 #[serde(rename = "1D")]
1537 Day1,
1538 #[serde(rename = "2D")]
1539 Day2,
1540 #[serde(rename = "3D")]
1541 Day3,
1542 #[serde(rename = "5D")]
1543 Day5,
1544 #[serde(rename = "1W")]
1545 Week1,
1546 #[serde(rename = "1M")]
1547 Month1,
1548 #[serde(rename = "3M")]
1549 Month3,
1550}
1551
1552#[derive(
1554 Copy,
1555 Clone,
1556 Debug,
1557 Default,
1558 Display,
1559 PartialEq,
1560 Eq,
1561 Hash,
1562 AsRefStr,
1563 EnumIter,
1564 EnumString,
1565 Serialize,
1566 Deserialize,
1567)]
1568#[serde(rename_all = "snake_case")]
1569pub enum OKXPriceType {
1570 #[default]
1572 #[serde(rename = "")]
1573 None,
1574 Px,
1576 Usd,
1578 Vol,
1580}
1581
1582#[derive(
1584 Copy,
1585 Clone,
1586 Debug,
1587 Default,
1588 Display,
1589 PartialEq,
1590 Eq,
1591 Hash,
1592 AsRefStr,
1593 EnumIter,
1594 EnumString,
1595 Serialize,
1596 Deserialize,
1597)]
1598#[serde(rename_all = "snake_case")]
1599pub enum OKXSettlementState {
1600 #[default]
1602 #[serde(rename = "")]
1603 None,
1604 Processing,
1606 Settled,
1608}
1609
1610#[derive(
1612 Copy,
1613 Clone,
1614 Debug,
1615 Default,
1616 Display,
1617 PartialEq,
1618 Eq,
1619 Hash,
1620 AsRefStr,
1621 EnumIter,
1622 EnumString,
1623 Serialize,
1624 Deserialize,
1625)]
1626#[serde(rename_all = "snake_case")]
1627pub enum OKXQuickMarginType {
1628 #[default]
1630 #[serde(rename = "")]
1631 None,
1632 Manual,
1634 AutoBorrow,
1636 AutoRepay,
1638}
1639
1640#[derive(
1642 Copy,
1643 Clone,
1644 Debug,
1645 Default,
1646 Display,
1647 PartialEq,
1648 Eq,
1649 Hash,
1650 AsRefStr,
1651 EnumIter,
1652 EnumString,
1653 Serialize,
1654 Deserialize,
1655)]
1656#[serde(rename_all = "lowercase")]
1657#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
1658#[cfg_attr(
1659 feature = "python",
1660 pyo3::pyclass(
1661 eq,
1662 eq_int,
1663 module = "nautilus_trader.adapters.okx",
1664 from_py_object,
1665 rename_all = "SCREAMING_SNAKE_CASE",
1666 )
1667)]
1668#[cfg_attr(
1669 feature = "python",
1670 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
1671)]
1672pub enum OKXEnvironment {
1673 #[default]
1675 Live,
1676 Demo,
1678}
1679
1680#[derive(
1686 Copy,
1687 Clone,
1688 Debug,
1689 Default,
1690 Display,
1691 PartialEq,
1692 Eq,
1693 Hash,
1694 AsRefStr,
1695 EnumIter,
1696 EnumString,
1697 Serialize,
1698 Deserialize,
1699)]
1700#[serde(rename_all = "lowercase")]
1701#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
1702#[cfg_attr(
1703 feature = "python",
1704 pyo3::pyclass(
1705 eq,
1706 eq_int,
1707 module = "nautilus_trader.adapters.okx",
1708 from_py_object,
1709 rename_all = "SCREAMING_SNAKE_CASE",
1710 )
1711)]
1712#[cfg_attr(
1713 feature = "python",
1714 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
1715)]
1716pub enum OKXRegion {
1717 #[default]
1719 Global,
1720 Eea,
1722 Us,
1724}