1use std::{borrow::Cow, fmt::Display, marker::PhantomData, str::FromStr};
19
20use serde::{Deserialize, Deserializer, Serializer};
21use strum::{AsRefStr, Display, EnumIter, EnumString, FromRepr};
22
23use crate::enum_strum_serde;
24
25pub trait FromU8 {
27 fn from_u8(value: u8) -> Option<Self>
31 where
32 Self: Sized;
33}
34
35pub trait FromU16 {
37 fn from_u16(value: u16) -> Option<Self>
41 where
42 Self: Sized;
43}
44
45#[repr(C)]
47#[derive(
48 Copy,
49 Clone,
50 Debug,
51 Display,
52 Hash,
53 PartialEq,
54 Eq,
55 PartialOrd,
56 Ord,
57 AsRefStr,
58 FromRepr,
59 EnumIter,
60 EnumString,
61)]
62#[strum(ascii_case_insensitive)]
63#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
64#[cfg_attr(
65 feature = "python",
66 pyo3::pyclass(
67 frozen,
68 eq,
69 eq_int,
70 module = "nautilus_trader.model",
71 from_py_object,
72 rename_all = "SCREAMING_SNAKE_CASE",
73 )
74)]
75#[cfg_attr(
76 feature = "python",
77 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
78)]
79pub enum AccountType {
80 Cash = 1,
82 Margin = 2,
84 Betting = 3,
86 Wallet = 4,
88}
89
90#[repr(C)]
92#[derive(
93 Copy,
94 Clone,
95 Debug,
96 Display,
97 Hash,
98 PartialEq,
99 Eq,
100 PartialOrd,
101 Ord,
102 AsRefStr,
103 FromRepr,
104 EnumIter,
105 EnumString,
106)]
107#[strum(ascii_case_insensitive)]
108#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
109#[cfg_attr(
110 feature = "python",
111 pyo3::pyclass(
112 frozen,
113 eq,
114 eq_int,
115 module = "nautilus_trader.model",
116 from_py_object,
117 rename_all = "SCREAMING_SNAKE_CASE",
118 )
119)]
120#[cfg_attr(
121 feature = "python",
122 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
123)]
124pub enum AggregationSource {
125 External = 1,
127 Internal = 2,
129}
130
131#[repr(C)]
133#[derive(
134 Copy,
135 Clone,
136 Debug,
137 Default,
138 Display,
139 Hash,
140 PartialEq,
141 Eq,
142 PartialOrd,
143 Ord,
144 AsRefStr,
145 FromRepr,
146 EnumIter,
147 EnumString,
148)]
149#[strum(ascii_case_insensitive)]
150#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
151#[cfg_attr(
152 feature = "python",
153 pyo3::pyclass(
154 frozen,
155 eq,
156 eq_int,
157 module = "nautilus_trader.model",
158 from_py_object,
159 rename_all = "SCREAMING_SNAKE_CASE",
160 )
161)]
162#[cfg_attr(
163 feature = "python",
164 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
165)]
166pub enum AggressorSide {
167 #[default]
169 NoAggressor = 0,
170 #[strum(serialize = "BUYER", to_string = "BUY")]
174 Buy = 1,
175 #[strum(serialize = "SELLER", to_string = "SELL")]
179 Sell = 2,
180}
181
182impl FromU8 for AggressorSide {
183 fn from_u8(value: u8) -> Option<Self> {
184 Self::from_repr(usize::from(value))
185 }
186}
187
188#[repr(C)]
190#[derive(
191 Copy,
192 Clone,
193 Debug,
194 Display,
195 Hash,
196 PartialEq,
197 Eq,
198 PartialOrd,
199 Ord,
200 AsRefStr,
201 FromRepr,
202 EnumIter,
203 EnumString,
204)]
205#[strum(ascii_case_insensitive)]
206#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
207#[cfg_attr(
208 feature = "python",
209 pyo3::pyclass(
210 frozen,
211 eq,
212 eq_int,
213 module = "nautilus_trader.model",
214 from_py_object,
215 rename_all = "SCREAMING_SNAKE_CASE",
216 )
217)]
218#[cfg_attr(
219 feature = "python",
220 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
221)]
222#[allow(non_camel_case_types)]
223pub enum AssetClass {
224 FX = 1,
226 Equity = 2,
228 Commodity = 3,
230 Debt = 4,
232 Index = 5,
234 Cryptocurrency = 6,
236 Alternative = 7,
238}
239
240impl FromU8 for AssetClass {
241 fn from_u8(value: u8) -> Option<Self> {
242 Self::from_repr(usize::from(value))
243 }
244}
245
246#[repr(C)]
248#[derive(
249 Copy,
250 Clone,
251 Debug,
252 Default,
253 Display,
254 Hash,
255 PartialEq,
256 Eq,
257 PartialOrd,
258 Ord,
259 AsRefStr,
260 FromRepr,
261 EnumIter,
262 EnumString,
263)]
264#[strum(ascii_case_insensitive)]
265#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
266#[cfg_attr(
267 feature = "python",
268 pyo3::pyclass(
269 frozen,
270 eq,
271 eq_int,
272 module = "nautilus_trader.model",
273 from_py_object,
274 rename_all = "SCREAMING_SNAKE_CASE",
275 )
276)]
277#[cfg_attr(
278 feature = "python",
279 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
280)]
281pub enum AvgPxReconciliation {
282 #[default]
285 Match = 1,
286 OpeningOnly = 2,
290}
291
292#[repr(C)]
294#[derive(
295 Copy,
296 Clone,
297 Debug,
298 Display,
299 Hash,
300 PartialEq,
301 Eq,
302 PartialOrd,
303 Ord,
304 AsRefStr,
305 FromRepr,
306 EnumIter,
307 EnumString,
308)]
309#[strum(ascii_case_insensitive)]
310#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
311#[cfg_attr(
312 feature = "python",
313 pyo3::pyclass(
314 frozen,
315 eq,
316 eq_int,
317 module = "nautilus_trader.model",
318 from_py_object,
319 rename_all = "SCREAMING_SNAKE_CASE",
320 )
321)]
322#[cfg_attr(
323 feature = "python",
324 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
325)]
326pub enum BarAggregation {
327 Tick = 1,
329 TickImbalance = 2,
331 TickRuns = 3,
333 Volume = 4,
335 VolumeImbalance = 5,
337 VolumeRuns = 6,
339 Value = 7,
341 ValueImbalance = 8,
343 ValueRuns = 9,
345 Millisecond = 10,
347 Second = 11,
349 Minute = 12,
351 Hour = 13,
353 Day = 14,
355 Week = 15,
357 Month = 16,
359 Year = 17,
361 Renko = 18,
363}
364
365#[repr(C)]
367#[derive(
368 Copy,
369 Clone,
370 Debug,
371 Default,
372 Display,
373 Hash,
374 PartialEq,
375 Eq,
376 PartialOrd,
377 Ord,
378 AsRefStr,
379 FromRepr,
380 EnumIter,
381 EnumString,
382)]
383#[strum(ascii_case_insensitive)]
384#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
385#[cfg_attr(
386 feature = "python",
387 pyo3::pyclass(
388 frozen,
389 eq,
390 eq_int,
391 module = "nautilus_trader.model",
392 from_py_object,
393 rename_all = "SCREAMING_SNAKE_CASE",
394 )
395)]
396#[cfg_attr(
397 feature = "python",
398 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
399)]
400pub enum BarIntervalType {
401 #[default]
403 LeftOpen = 1,
404 RightOpen = 2,
406}
407
408#[repr(C)]
410#[derive(
411 Copy,
412 Clone,
413 Debug,
414 Display,
415 Hash,
416 PartialEq,
417 Eq,
418 PartialOrd,
419 Ord,
420 AsRefStr,
421 FromRepr,
422 EnumIter,
423 EnumString,
424)]
425#[strum(ascii_case_insensitive)]
426#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
427#[cfg_attr(
428 feature = "python",
429 pyo3::pyclass(
430 frozen,
431 eq,
432 eq_int,
433 module = "nautilus_trader.model",
434 from_py_object,
435 rename_all = "SCREAMING_SNAKE_CASE",
436 )
437)]
438#[cfg_attr(
439 feature = "python",
440 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
441)]
442pub enum BetSide {
443 Back = 1,
445 Lay = 2,
447}
448
449impl BetSide {
450 #[must_use]
452 pub fn opposite(&self) -> Self {
453 match self {
454 Self::Back => Self::Lay,
455 Self::Lay => Self::Back,
456 }
457 }
458}
459
460impl From<OrderSide> for BetSide {
461 fn from(side: OrderSide) -> Self {
462 match side {
463 OrderSide::Buy => Self::Back,
464 OrderSide::Sell => Self::Lay,
465 }
466 }
467}
468
469#[repr(C)]
471#[derive(
472 Copy,
473 Clone,
474 Debug,
475 Display,
476 Hash,
477 PartialEq,
478 Eq,
479 PartialOrd,
480 Ord,
481 AsRefStr,
482 FromRepr,
483 EnumIter,
484 EnumString,
485)]
486#[strum(ascii_case_insensitive)]
487#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
488#[cfg_attr(
489 feature = "python",
490 pyo3::pyclass(
491 frozen,
492 eq,
493 eq_int,
494 module = "nautilus_trader.model",
495 from_py_object,
496 rename_all = "SCREAMING_SNAKE_CASE",
497 )
498)]
499#[cfg_attr(
500 feature = "python",
501 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
502)]
503pub enum BookAction {
504 Add = 1,
506 Update = 2,
508 Delete = 3,
510 Clear = 4,
512}
513
514impl FromU8 for BookAction {
515 fn from_u8(value: u8) -> Option<Self> {
516 Self::from_repr(usize::from(value))
517 }
518}
519
520#[repr(C)]
522#[derive(
523 Copy,
524 Clone,
525 Debug,
526 Display,
527 Hash,
528 PartialEq,
529 Eq,
530 PartialOrd,
531 Ord,
532 AsRefStr,
533 FromRepr,
534 EnumIter,
535 EnumString,
536)]
537#[strum(ascii_case_insensitive)]
538#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
539#[cfg_attr(
540 feature = "python",
541 pyo3::pyclass(
542 frozen,
543 eq,
544 eq_int,
545 module = "nautilus_trader.model",
546 from_py_object,
547 rename_all = "SCREAMING_SNAKE_CASE",
548 )
549)]
550#[cfg_attr(
551 feature = "python",
552 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
553)]
554#[allow(non_camel_case_types)]
555pub enum BookType {
556 L1_MBP = 1,
558 L2_MBP = 2,
560 L3_MBO = 3,
562}
563
564impl FromU8 for BookType {
565 fn from_u8(value: u8) -> Option<Self> {
566 Self::from_repr(usize::from(value))
567 }
568}
569
570#[repr(C)]
577#[derive(
578 Copy,
579 Clone,
580 Debug,
581 Display,
582 Hash,
583 PartialEq,
584 Eq,
585 PartialOrd,
586 Ord,
587 AsRefStr,
588 FromRepr,
589 EnumIter,
590 EnumString,
591)]
592#[strum(ascii_case_insensitive)]
593#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
594#[cfg_attr(
595 feature = "python",
596 pyo3::pyclass(
597 frozen,
598 eq,
599 eq_int,
600 module = "nautilus_trader.model",
601 from_py_object,
602 rename_all = "SCREAMING_SNAKE_CASE",
603 )
604)]
605#[cfg_attr(
606 feature = "python",
607 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
608)]
609pub enum ContingencyType {
610 Oco = 1,
612 Oto = 2,
614 Ouo = 3,
616}
617
618#[repr(C)]
632#[derive(
633 Copy,
634 Clone,
635 Debug,
636 Default,
637 Display,
638 Hash,
639 PartialEq,
640 Eq,
641 PartialOrd,
642 Ord,
643 AsRefStr,
644 FromRepr,
645 EnumIter,
646 EnumString,
647)]
648#[strum(ascii_case_insensitive)]
649#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
650#[cfg_attr(
651 feature = "python",
652 pyo3::pyclass(
653 frozen,
654 eq,
655 eq_int,
656 module = "nautilus_trader.model",
657 from_py_object,
658 rename_all = "SCREAMING_SNAKE_CASE",
659 )
660)]
661#[cfg_attr(
662 feature = "python",
663 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
664)]
665pub enum ContinuousFutureAdjustmentType {
666 #[default]
668 BackwardSpread = 1,
669 ForwardSpread = 2,
671 BackwardRatio = 3,
673 ForwardRatio = 4,
675}
676
677impl ContinuousFutureAdjustmentType {
678 #[must_use]
680 pub const fn is_ratio(&self) -> bool {
681 matches!(self, Self::BackwardRatio | Self::ForwardRatio)
682 }
683
684 #[must_use]
686 pub const fn is_backward(&self) -> bool {
687 matches!(self, Self::BackwardSpread | Self::BackwardRatio)
688 }
689}
690
691#[repr(C)]
693#[derive(
694 Copy,
695 Clone,
696 Debug,
697 Display,
698 Hash,
699 PartialEq,
700 Eq,
701 PartialOrd,
702 Ord,
703 AsRefStr,
704 FromRepr,
705 EnumIter,
706 EnumString,
707)]
708#[strum(ascii_case_insensitive)]
709#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
710#[cfg_attr(
711 feature = "python",
712 pyo3::pyclass(
713 frozen,
714 eq,
715 eq_int,
716 module = "nautilus_trader.model",
717 from_py_object,
718 rename_all = "SCREAMING_SNAKE_CASE",
719 )
720)]
721#[cfg_attr(
722 feature = "python",
723 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
724)]
725pub enum CurrencyType {
726 Crypto = 1,
728 Fiat = 2,
730 CommodityBacked = 3,
732}
733
734#[repr(C)]
736#[derive(
737 Copy,
738 Clone,
739 Debug,
740 Display,
741 Hash,
742 PartialEq,
743 Eq,
744 PartialOrd,
745 Ord,
746 AsRefStr,
747 FromRepr,
748 EnumIter,
749 EnumString,
750)]
751#[strum(ascii_case_insensitive)]
752#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
753#[cfg_attr(
754 feature = "python",
755 pyo3::pyclass(
756 frozen,
757 eq,
758 eq_int,
759 module = "nautilus_trader.model",
760 from_py_object,
761 rename_all = "SCREAMING_SNAKE_CASE",
762 )
763)]
764#[cfg_attr(
765 feature = "python",
766 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
767)]
768pub enum InstrumentClass {
769 Spot = 1,
771 Swap = 2,
773 Future = 3,
775 FuturesSpread = 4,
777 Forward = 5,
779 Cfd = 6,
781 Bond = 7,
783 Option = 8,
785 OptionSpread = 9,
787 Warrant = 10,
789 SportsBetting = 11,
791 BinaryOption = 12,
793}
794
795impl InstrumentClass {
796 #[must_use]
798 pub const fn has_expiration(&self) -> bool {
799 matches!(
800 self,
801 Self::Future | Self::FuturesSpread | Self::Option | Self::OptionSpread
802 )
803 }
804
805 #[must_use]
811 pub const fn allows_negative_price(&self) -> bool {
812 matches!(
813 self,
814 Self::Future | Self::Option | Self::FuturesSpread | Self::OptionSpread
815 )
816 }
817
818 #[must_use]
820 pub const fn is_premium_based(&self) -> bool {
821 matches!(
822 self,
823 Self::Option | Self::OptionSpread | Self::BinaryOption | Self::Warrant
824 )
825 }
826
827 #[must_use]
830 pub const fn divides_notional_by_price(&self, is_inverse: bool) -> bool {
831 is_inverse && !self.is_premium_based()
832 }
833
834 #[must_use]
839 pub fn try_from_parent_suffix(suffix: &str) -> Option<Self> {
840 match suffix {
841 "FUT" | "FUTURE" => Some(Self::Future),
842 "OPT" | "OPTION" => Some(Self::Option),
843 _ => None,
844 }
845 }
846
847 #[must_use]
852 pub const fn parent_suffix(self) -> Option<&'static str> {
853 match self {
854 Self::Future => Some("FUT"),
855 Self::Option => Some("OPT"),
856 _ => None,
857 }
858 }
859}
860
861#[repr(C)]
863#[derive(
864 Copy,
865 Clone,
866 Debug,
867 Display,
868 Hash,
869 PartialEq,
870 Eq,
871 PartialOrd,
872 Ord,
873 AsRefStr,
874 FromRepr,
875 EnumIter,
876 EnumString,
877)]
878#[strum(ascii_case_insensitive)]
879#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
880#[cfg_attr(
881 feature = "python",
882 pyo3::pyclass(
883 frozen,
884 eq,
885 eq_int,
886 module = "nautilus_trader.model",
887 from_py_object,
888 rename_all = "SCREAMING_SNAKE_CASE",
889 )
890)]
891#[cfg_attr(
892 feature = "python",
893 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
894)]
895pub enum InstrumentCloseType {
896 EndOfSession = 1,
898 ContractExpired = 2,
900}
901
902impl FromU8 for InstrumentCloseType {
904 fn from_u8(value: u8) -> Option<Self> {
905 Self::from_repr(usize::from(value))
906 }
907}
908
909#[repr(C)]
911#[derive(
912 Copy,
913 Clone,
914 Debug,
915 Display,
916 Hash,
917 PartialEq,
918 Eq,
919 PartialOrd,
920 Ord,
921 AsRefStr,
922 FromRepr,
923 EnumIter,
924 EnumString,
925)]
926#[strum(ascii_case_insensitive)]
927#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
928#[cfg_attr(
929 feature = "python",
930 pyo3::pyclass(
931 frozen,
932 eq,
933 eq_int,
934 module = "nautilus_trader.model",
935 from_py_object,
936 rename_all = "SCREAMING_SNAKE_CASE",
937 )
938)]
939#[cfg_attr(
940 feature = "python",
941 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
942)]
943pub enum LiquiditySide {
944 NoLiquiditySide = 0,
946 Maker = 1,
948 Taker = 2,
950}
951
952#[repr(C)]
954#[derive(
955 Copy,
956 Clone,
957 Debug,
958 Display,
959 Hash,
960 PartialEq,
961 Eq,
962 PartialOrd,
963 Ord,
964 AsRefStr,
965 FromRepr,
966 EnumIter,
967 EnumString,
968)]
969#[strum(ascii_case_insensitive)]
970#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
971#[cfg_attr(
972 feature = "python",
973 pyo3::pyclass(
974 frozen,
975 eq,
976 eq_int,
977 module = "nautilus_trader.model",
978 from_py_object,
979 rename_all = "SCREAMING_SNAKE_CASE",
980 )
981)]
982#[cfg_attr(
983 feature = "python",
984 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
985)]
986pub enum MarketStatus {
987 Open = 1,
989 Closed = 2,
991 Paused = 3,
993 Halted = 4,
995 Suspended = 5,
997 NotAvailable = 6,
999}
1000
1001#[repr(C)]
1003#[derive(
1004 Copy,
1005 Clone,
1006 Debug,
1007 Display,
1008 Hash,
1009 PartialEq,
1010 Eq,
1011 PartialOrd,
1012 Ord,
1013 AsRefStr,
1014 FromRepr,
1015 EnumIter,
1016 EnumString,
1017)]
1018#[strum(ascii_case_insensitive)]
1019#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1020#[cfg_attr(
1021 feature = "python",
1022 pyo3::pyclass(
1023 frozen,
1024 eq,
1025 eq_int,
1026 module = "nautilus_trader.model",
1027 from_py_object,
1028 rename_all = "SCREAMING_SNAKE_CASE",
1029 )
1030)]
1031#[cfg_attr(
1032 feature = "python",
1033 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1034)]
1035pub enum MarketStatusAction {
1036 None = 0,
1038 PreOpen = 1,
1040 PreCross = 2,
1042 Quoting = 3,
1044 Cross = 4,
1046 Rotation = 5,
1048 NewPriceIndication = 6,
1050 Trading = 7,
1052 Halt = 8,
1054 Pause = 9,
1056 Suspend = 10,
1058 PreClose = 11,
1060 Close = 12,
1062 PostClose = 13,
1064 ShortSellRestrictionChange = 14,
1066 NotAvailableForTrading = 15,
1068}
1069
1070impl FromU16 for MarketStatusAction {
1072 fn from_u16(value: u16) -> Option<Self> {
1073 Self::from_repr(usize::from(value))
1074 }
1075}
1076
1077#[repr(C)]
1079#[derive(
1080 Copy,
1081 Clone,
1082 Debug,
1083 Default,
1084 Display,
1085 Hash,
1086 PartialEq,
1087 Eq,
1088 PartialOrd,
1089 Ord,
1090 AsRefStr,
1091 FromRepr,
1092 EnumIter,
1093 EnumString,
1094)]
1095#[strum(ascii_case_insensitive)]
1096#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1097#[cfg_attr(
1098 feature = "python",
1099 pyo3::pyclass(
1100 frozen,
1101 eq,
1102 eq_int,
1103 module = "nautilus_trader.model",
1104 from_py_object,
1105 rename_all = "SCREAMING_SNAKE_CASE",
1106 )
1107)]
1108#[cfg_attr(
1109 feature = "python",
1110 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1111)]
1112pub enum OmsType {
1113 #[default]
1115 Unspecified = 0,
1116 Netting = 1,
1118 Hedging = 2,
1122}
1123
1124#[repr(C)]
1126#[derive(
1127 Copy,
1128 Clone,
1129 Debug,
1130 Display,
1131 Hash,
1132 PartialEq,
1133 Eq,
1134 PartialOrd,
1135 Ord,
1136 AsRefStr,
1137 FromRepr,
1138 EnumIter,
1139 EnumString,
1140)]
1141#[strum(ascii_case_insensitive)]
1142#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1143#[cfg_attr(
1144 feature = "python",
1145 pyo3::pyclass(
1146 frozen,
1147 eq,
1148 eq_int,
1149 module = "nautilus_trader.model",
1150 from_py_object,
1151 rename_all = "SCREAMING_SNAKE_CASE",
1152 )
1153)]
1154#[cfg_attr(
1155 feature = "python",
1156 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1157)]
1158pub enum OptionKind {
1159 Call = 1,
1161 Put = 2,
1163}
1164
1165#[repr(C)]
1167#[derive(
1168 Copy,
1169 Clone,
1170 Debug,
1171 Default,
1172 Display,
1173 Hash,
1174 PartialEq,
1175 Eq,
1176 PartialOrd,
1177 Ord,
1178 AsRefStr,
1179 FromRepr,
1180 EnumIter,
1181 EnumString,
1182)]
1183#[strum(ascii_case_insensitive)]
1184#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1185#[cfg_attr(
1186 feature = "python",
1187 pyo3::pyclass(
1188 frozen,
1189 eq,
1190 eq_int,
1191 module = "nautilus_trader.model",
1192 from_py_object,
1193 rename_all = "SCREAMING_SNAKE_CASE",
1194 )
1195)]
1196#[cfg_attr(
1197 feature = "python",
1198 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1199)]
1200pub enum OptionSideFilter {
1201 #[default]
1203 Both = 1,
1204 Calls = 2,
1206 Puts = 3,
1208 Otm = 4,
1210}
1211
1212impl OptionSideFilter {
1213 #[must_use]
1216 pub fn includes(&self, kind: OptionKind, position: std::cmp::Ordering) -> bool {
1217 match self {
1218 Self::Both => true,
1219 Self::Calls => kind == OptionKind::Call,
1220 Self::Puts => kind == OptionKind::Put,
1221 Self::Otm => match position {
1222 std::cmp::Ordering::Less => kind == OptionKind::Put,
1223 std::cmp::Ordering::Greater => kind == OptionKind::Call,
1224 std::cmp::Ordering::Equal => true,
1225 },
1226 }
1227 }
1228}
1229
1230#[repr(C)]
1242#[derive(
1243 Copy,
1244 Clone,
1245 Debug,
1246 Default,
1247 Display,
1248 Hash,
1249 PartialEq,
1250 Eq,
1251 PartialOrd,
1252 Ord,
1253 AsRefStr,
1254 FromRepr,
1255 EnumIter,
1256 EnumString,
1257)]
1258#[strum(ascii_case_insensitive)]
1259#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1260#[cfg_attr(
1261 feature = "python",
1262 pyo3::pyclass(
1263 frozen,
1264 eq,
1265 eq_int,
1266 module = "nautilus_trader.model",
1267 from_py_object,
1268 rename_all = "SCREAMING_SNAKE_CASE",
1269 )
1270)]
1271#[cfg_attr(
1272 feature = "python",
1273 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1274)]
1275pub enum GreeksConvention {
1276 #[default]
1278 BlackScholes = 1,
1279 PriceAdjusted = 2,
1281}
1282
1283#[repr(C)]
1285#[derive(
1286 Copy,
1287 Clone,
1288 Debug,
1289 Default,
1290 Display,
1291 Hash,
1292 PartialEq,
1293 Eq,
1294 PartialOrd,
1295 Ord,
1296 AsRefStr,
1297 FromRepr,
1298 EnumIter,
1299 EnumString,
1300)]
1301#[strum(ascii_case_insensitive)]
1302#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1303#[cfg_attr(
1304 feature = "python",
1305 pyo3::pyclass(
1306 frozen,
1307 eq,
1308 eq_int,
1309 module = "nautilus_trader.model",
1310 from_py_object,
1311 rename_all = "SCREAMING_SNAKE_CASE",
1312 )
1313)]
1314#[cfg_attr(
1315 feature = "python",
1316 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1317)]
1318pub enum OtoTriggerMode {
1319 #[default]
1321 Partial = 0,
1322 Full = 1,
1324}
1325
1326#[repr(C)]
1331#[derive(
1332 Copy,
1333 Clone,
1334 Debug,
1335 Display,
1336 Hash,
1337 PartialEq,
1338 Eq,
1339 PartialOrd,
1340 Ord,
1341 AsRefStr,
1342 FromRepr,
1343 EnumIter,
1344 EnumString,
1345)]
1346#[strum(ascii_case_insensitive)]
1347#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1348#[cfg_attr(
1349 feature = "python",
1350 pyo3::pyclass(
1351 frozen,
1352 eq,
1353 eq_int,
1354 module = "nautilus_trader.model",
1355 from_py_object,
1356 rename_all = "SCREAMING_SNAKE_CASE",
1357 )
1358)]
1359#[cfg_attr(
1360 feature = "python",
1361 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1362)]
1363pub enum OrderSide {
1364 Buy = 1,
1366 Sell = 2,
1368}
1369
1370impl OrderSide {
1371 #[must_use]
1373 pub fn opposite(&self) -> Self {
1374 match &self {
1375 Self::Buy => Self::Sell,
1376 Self::Sell => Self::Buy,
1377 }
1378 }
1379}
1380
1381#[repr(C)]
1403#[derive(
1404 Copy,
1405 Clone,
1406 Debug,
1407 Display,
1408 Hash,
1409 PartialEq,
1410 Eq,
1411 PartialOrd,
1412 Ord,
1413 AsRefStr,
1414 FromRepr,
1415 EnumIter,
1416 EnumString,
1417)]
1418#[strum(ascii_case_insensitive)]
1419#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1420#[cfg_attr(
1421 feature = "python",
1422 pyo3::pyclass(
1423 frozen,
1424 eq,
1425 eq_int,
1426 module = "nautilus_trader.model",
1427 from_py_object,
1428 rename_all = "SCREAMING_SNAKE_CASE",
1429 )
1430)]
1431#[cfg_attr(
1432 feature = "python",
1433 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1434)]
1435pub enum OrderStatus {
1436 Initialized = 1,
1438 Denied = 2,
1440 Emulated = 3,
1442 Released = 4,
1444 Submitted = 5,
1446 Accepted = 6,
1448 Rejected = 7,
1450 Canceled = 8,
1452 Expired = 9,
1454 Triggered = 10,
1456 PendingUpdate = 11,
1458 PendingCancel = 12,
1460 PartiallyFilled = 13,
1462 Filled = 14,
1464 Voided = 15,
1466}
1467
1468impl OrderStatus {
1469 #[must_use]
1476 pub const fn is_open(self) -> bool {
1477 matches!(
1478 self,
1479 Self::Accepted
1480 | Self::Triggered
1481 | Self::PendingUpdate
1482 | Self::PendingCancel
1483 | Self::PartiallyFilled
1484 )
1485 }
1486
1487 #[must_use]
1489 pub const fn is_closed(self) -> bool {
1490 matches!(
1491 self,
1492 Self::Denied
1493 | Self::Rejected
1494 | Self::Canceled
1495 | Self::Expired
1496 | Self::Filled
1497 | Self::Voided
1498 )
1499 }
1500
1501 #[must_use]
1506 pub const fn is_inflight(self) -> bool {
1507 matches!(
1508 self,
1509 Self::Submitted | Self::PendingUpdate | Self::PendingCancel
1510 )
1511 }
1512
1513 #[must_use]
1515 pub const fn is_cancellable(self) -> bool {
1516 matches!(
1517 self,
1518 Self::Accepted | Self::Triggered | Self::PendingUpdate | Self::PartiallyFilled
1519 )
1520 }
1521}
1522
1523#[repr(C)]
1525#[derive(
1526 Copy,
1527 Clone,
1528 Debug,
1529 Display,
1530 Hash,
1531 PartialEq,
1532 Eq,
1533 PartialOrd,
1534 Ord,
1535 AsRefStr,
1536 FromRepr,
1537 EnumIter,
1538 EnumString,
1539)]
1540#[strum(ascii_case_insensitive)]
1541#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1542#[cfg_attr(
1543 feature = "python",
1544 pyo3::pyclass(
1545 frozen,
1546 eq,
1547 eq_int,
1548 module = "nautilus_trader.model",
1549 from_py_object,
1550 rename_all = "SCREAMING_SNAKE_CASE",
1551 )
1552)]
1553#[cfg_attr(
1554 feature = "python",
1555 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1556)]
1557pub enum OrderType {
1558 Market = 1,
1560 Limit = 2,
1562 StopMarket = 3,
1564 StopLimit = 4,
1566 MarketToLimit = 5,
1568 MarketIfTouched = 6,
1570 LimitIfTouched = 7,
1572 TrailingStopMarket = 8,
1574 TrailingStopLimit = 9,
1576}
1577
1578#[repr(C)]
1580#[derive(
1581 Copy,
1582 Clone,
1583 Debug,
1584 Display,
1585 Hash,
1586 PartialEq,
1587 Eq,
1588 PartialOrd,
1589 Ord,
1590 AsRefStr,
1591 FromRepr,
1592 EnumIter,
1593 EnumString,
1594)]
1595#[strum(ascii_case_insensitive)]
1596#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1597#[cfg_attr(
1598 feature = "python",
1599 pyo3::pyclass(
1600 frozen,
1601 eq,
1602 eq_int,
1603 module = "nautilus_trader.model",
1604 from_py_object,
1605 rename_all = "SCREAMING_SNAKE_CASE",
1606 )
1607)]
1608#[cfg_attr(
1609 feature = "python",
1610 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1611)]
1612pub enum PositionAdjustmentType {
1613 Commission = 1,
1615 Funding = 2,
1617}
1618
1619impl FromU8 for PositionAdjustmentType {
1620 fn from_u8(value: u8) -> Option<Self> {
1621 Self::from_repr(usize::from(value))
1622 }
1623}
1624
1625#[repr(C)]
1630#[derive(
1631 Copy,
1632 Clone,
1633 Debug,
1634 Display,
1635 Hash,
1636 PartialEq,
1637 Eq,
1638 PartialOrd,
1639 Ord,
1640 AsRefStr,
1641 FromRepr,
1642 EnumIter,
1643 EnumString,
1644)]
1645#[strum(ascii_case_insensitive)]
1646#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1647#[cfg_attr(
1648 feature = "python",
1649 pyo3::pyclass(
1650 frozen,
1651 eq,
1652 eq_int,
1653 module = "nautilus_trader.model",
1654 from_py_object,
1655 rename_all = "SCREAMING_SNAKE_CASE",
1656 )
1657)]
1658#[cfg_attr(
1659 feature = "python",
1660 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1661)]
1662pub enum PositionSide {
1663 Flat = 1,
1665 Long = 2,
1667 Short = 3,
1669}
1670
1671pub mod serde_option_order_side {
1673 use serde::{Deserializer, Serializer};
1674
1675 use super::{OrderSide, deserialize_optional_enum, serialize_optional_enum};
1676
1677 pub fn serialize<S>(value: &Option<OrderSide>, serializer: S) -> Result<S::Ok, S::Error>
1683 where
1684 S: Serializer,
1685 {
1686 serialize_optional_enum(value.as_ref(), serializer, "NO_ORDER_SIDE")
1687 }
1688
1689 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OrderSide>, D::Error>
1695 where
1696 D: Deserializer<'de>,
1697 {
1698 deserialize_optional_enum(
1699 deserializer,
1700 "NO_ORDER_SIDE",
1701 "BUY, SELL, NO_ORDER_SIDE, or null",
1702 )
1703 }
1704}
1705
1706pub mod serde_option_position_side {
1708 use serde::{Deserializer, Serializer};
1709
1710 use super::{PositionSide, deserialize_optional_enum, serialize_optional_enum};
1711
1712 pub fn serialize<S>(value: &Option<PositionSide>, serializer: S) -> Result<S::Ok, S::Error>
1718 where
1719 S: Serializer,
1720 {
1721 serialize_optional_enum(value.as_ref(), serializer, "NO_POSITION_SIDE")
1722 }
1723
1724 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<PositionSide>, D::Error>
1730 where
1731 D: Deserializer<'de>,
1732 {
1733 deserialize_optional_enum(
1734 deserializer,
1735 "NO_POSITION_SIDE",
1736 "FLAT, LONG, SHORT, NO_POSITION_SIDE, or null",
1737 )
1738 }
1739}
1740
1741pub mod serde_option_contingency_type {
1743 use serde::{Deserializer, Serializer};
1744
1745 use super::{ContingencyType, deserialize_optional_enum, serialize_optional_enum};
1746
1747 pub fn serialize<S>(value: &Option<ContingencyType>, serializer: S) -> Result<S::Ok, S::Error>
1753 where
1754 S: Serializer,
1755 {
1756 serialize_optional_enum(value.as_ref(), serializer, "NO_CONTINGENCY")
1757 }
1758
1759 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<ContingencyType>, D::Error>
1765 where
1766 D: Deserializer<'de>,
1767 {
1768 deserialize_optional_enum(
1769 deserializer,
1770 "NO_CONTINGENCY",
1771 "OCO, OTO, OUO, NO_CONTINGENCY, or null",
1772 )
1773 }
1774}
1775
1776pub mod serde_option_trailing_offset_type {
1779 use serde::{Deserializer, Serializer};
1780
1781 use super::{TrailingOffsetType, deserialize_optional_enum, serialize_optional_enum};
1782
1783 pub fn serialize<S>(
1789 value: &Option<TrailingOffsetType>,
1790 serializer: S,
1791 ) -> Result<S::Ok, S::Error>
1792 where
1793 S: Serializer,
1794 {
1795 serialize_optional_enum(value.as_ref(), serializer, "NO_TRAILING_OFFSET")
1796 }
1797
1798 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<TrailingOffsetType>, D::Error>
1804 where
1805 D: Deserializer<'de>,
1806 {
1807 deserialize_optional_enum(
1808 deserializer,
1809 "NO_TRAILING_OFFSET",
1810 "PRICE, BASIS_POINTS, TICKS, PRICE_TIER, NO_TRAILING_OFFSET, or null",
1811 )
1812 }
1813}
1814
1815pub mod serde_option_trigger_type {
1817 use serde::{Deserializer, Serializer};
1818
1819 use super::{TriggerType, deserialize_optional_enum, serialize_optional_enum};
1820
1821 pub fn serialize<S>(value: &Option<TriggerType>, serializer: S) -> Result<S::Ok, S::Error>
1827 where
1828 S: Serializer,
1829 {
1830 serialize_optional_enum(value.as_ref(), serializer, "NO_TRIGGER")
1831 }
1832
1833 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<TriggerType>, D::Error>
1839 where
1840 D: Deserializer<'de>,
1841 {
1842 deserialize_optional_enum(
1843 deserializer,
1844 "NO_TRIGGER",
1845 "a trigger type, NO_TRIGGER, or null",
1846 )
1847 }
1848}
1849
1850fn serialize_optional_enum<S, T>(
1851 value: Option<&T>,
1852 serializer: S,
1853 none_token: &'static str,
1854) -> Result<S::Ok, S::Error>
1855where
1856 S: Serializer,
1857 T: AsRef<str>,
1858{
1859 serializer.serialize_str(value.map_or(none_token, AsRef::as_ref))
1860}
1861
1862fn deserialize_optional_enum<'de, D, T>(
1863 deserializer: D,
1864 none_token: &'static str,
1865 expected: &'static str,
1866) -> Result<Option<T>, D::Error>
1867where
1868 D: Deserializer<'de>,
1869 T: FromStr,
1870 T::Err: Display,
1871{
1872 struct OptionalEnumVisitor<T> {
1873 none_token: &'static str,
1874 expected: &'static str,
1875 marker: PhantomData<T>,
1876 }
1877
1878 impl<'de, T> serde::de::Visitor<'de> for OptionalEnumVisitor<T>
1879 where
1880 T: FromStr,
1881 T::Err: Display,
1882 {
1883 type Value = Option<T>;
1884
1885 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1886 formatter.write_str(self.expected)
1887 }
1888
1889 fn visit_none<E>(self) -> Result<Self::Value, E> {
1890 Ok(None)
1891 }
1892
1893 fn visit_unit<E>(self) -> Result<Self::Value, E> {
1894 Ok(None)
1895 }
1896
1897 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1898 where
1899 D: Deserializer<'de>,
1900 {
1901 let value = Cow::<'de, str>::deserialize(deserializer)?;
1902 if value.eq_ignore_ascii_case(self.none_token) {
1903 Ok(None)
1904 } else {
1905 T::from_str(&value)
1906 .map(Some)
1907 .map_err(serde::de::Error::custom)
1908 }
1909 }
1910 }
1911
1912 deserializer.deserialize_option(OptionalEnumVisitor {
1913 none_token,
1914 expected,
1915 marker: PhantomData,
1916 })
1917}
1918
1919#[repr(C)]
1921#[derive(
1922 Copy,
1923 Clone,
1924 Debug,
1925 Display,
1926 Hash,
1927 PartialEq,
1928 Eq,
1929 PartialOrd,
1930 Ord,
1931 AsRefStr,
1932 FromRepr,
1933 EnumIter,
1934 EnumString,
1935)]
1936#[strum(ascii_case_insensitive)]
1937#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1938#[cfg_attr(
1939 feature = "python",
1940 pyo3::pyclass(
1941 frozen,
1942 eq,
1943 eq_int,
1944 module = "nautilus_trader.model",
1945 from_py_object,
1946 rename_all = "SCREAMING_SNAKE_CASE",
1947 )
1948)]
1949#[cfg_attr(
1950 feature = "python",
1951 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1952)]
1953pub enum PriceType {
1954 Bid = 1,
1960 Ask = 2,
1963 Mid = 3,
1965 Last = 4,
1967 Mark = 5,
1970}
1971
1972#[repr(C)]
1974#[derive(
1975 Copy,
1976 Clone,
1977 Debug,
1978 Display,
1979 Hash,
1980 PartialEq,
1981 Eq,
1982 PartialOrd,
1983 Ord,
1984 AsRefStr,
1985 FromRepr,
1986 EnumIter,
1987 EnumString,
1988)]
1989#[strum(ascii_case_insensitive)]
1990#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1991#[cfg_attr(
1992 feature = "python",
1993 pyo3::pyclass(
1994 frozen,
1995 eq,
1996 eq_int,
1997 module = "nautilus_trader.model",
1998 from_py_object,
1999 rename_all = "SCREAMING_SNAKE_CASE",
2000 )
2001)]
2002#[cfg_attr(
2003 feature = "python",
2004 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
2005)]
2006#[allow(non_camel_case_types)]
2007pub enum RecordFlag {
2008 F_LAST = 1 << 7, F_TOB = 1 << 6, F_SNAPSHOT = 1 << 5, F_MBP = 1 << 4, RESERVED_2 = 1 << 3, RESERVED_1 = 1 << 2, }
2021
2022impl RecordFlag {
2023 #[must_use]
2025 pub fn matches(self, value: u8) -> bool {
2026 (self as u8) & value != 0
2027 }
2028}
2029
2030#[repr(C)]
2032#[derive(
2033 Copy,
2034 Clone,
2035 Debug,
2036 Display,
2037 Hash,
2038 PartialEq,
2039 Eq,
2040 PartialOrd,
2041 Ord,
2042 AsRefStr,
2043 FromRepr,
2044 EnumIter,
2045 EnumString,
2046)]
2047#[strum(ascii_case_insensitive)]
2048#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
2049#[cfg_attr(
2050 feature = "python",
2051 pyo3::pyclass(
2052 frozen,
2053 eq,
2054 eq_int,
2055 module = "nautilus_trader.model",
2056 from_py_object,
2057 rename_all = "SCREAMING_SNAKE_CASE",
2058 )
2059)]
2060#[cfg_attr(
2061 feature = "python",
2062 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
2063)]
2064pub enum TimeInForce {
2065 Gtc = 1,
2067 Ioc = 2,
2069 Fok = 3,
2071 Gtd = 4,
2073 Day = 5,
2075 AtTheOpen = 6,
2077 AtTheClose = 7,
2079}
2080
2081#[repr(C)]
2083#[derive(
2084 Copy,
2085 Clone,
2086 Debug,
2087 Display,
2088 Hash,
2089 PartialEq,
2090 Eq,
2091 PartialOrd,
2092 Ord,
2093 AsRefStr,
2094 FromRepr,
2095 EnumIter,
2096 EnumString,
2097)]
2098#[strum(ascii_case_insensitive)]
2099#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
2100#[cfg_attr(
2101 feature = "python",
2102 pyo3::pyclass(
2103 frozen,
2104 eq,
2105 eq_int,
2106 module = "nautilus_trader.model",
2107 from_py_object,
2108 rename_all = "SCREAMING_SNAKE_CASE",
2109 )
2110)]
2111#[cfg_attr(
2112 feature = "python",
2113 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
2114)]
2115pub enum TradingState {
2116 Active = 1,
2118 Reducing = 2,
2120 Halted = 3,
2122}
2123
2124#[repr(C)]
2129#[derive(
2130 Copy,
2131 Clone,
2132 Debug,
2133 Display,
2134 Hash,
2135 PartialEq,
2136 Eq,
2137 PartialOrd,
2138 Ord,
2139 AsRefStr,
2140 FromRepr,
2141 EnumIter,
2142 EnumString,
2143)]
2144#[strum(ascii_case_insensitive)]
2145#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
2146#[cfg_attr(
2147 feature = "python",
2148 pyo3::pyclass(
2149 frozen,
2150 eq,
2151 eq_int,
2152 module = "nautilus_trader.model",
2153 from_py_object,
2154 rename_all = "SCREAMING_SNAKE_CASE",
2155 )
2156)]
2157#[cfg_attr(
2158 feature = "python",
2159 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
2160)]
2161pub enum TrailingOffsetType {
2162 Price = 1,
2164 BasisPoints = 2,
2166 Ticks = 3,
2168 PriceTier = 4,
2170}
2171
2172#[repr(C)]
2177#[derive(
2178 Copy,
2179 Clone,
2180 Debug,
2181 Display,
2182 Hash,
2183 PartialEq,
2184 Eq,
2185 PartialOrd,
2186 Ord,
2187 AsRefStr,
2188 FromRepr,
2189 EnumIter,
2190 EnumString,
2191)]
2192#[strum(ascii_case_insensitive)]
2193#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
2194#[cfg_attr(
2195 feature = "python",
2196 pyo3::pyclass(
2197 frozen,
2198 eq,
2199 eq_int,
2200 module = "nautilus_trader.model",
2201 from_py_object,
2202 rename_all = "SCREAMING_SNAKE_CASE",
2203 )
2204)]
2205#[cfg_attr(
2206 feature = "python",
2207 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
2208)]
2209pub enum TriggerType {
2210 Default = 1,
2212 LastPrice = 2,
2214 MarkPrice = 3,
2216 IndexPrice = 4,
2218 BidAsk = 5,
2220 DoubleLast = 6,
2222 DoubleBidAsk = 7,
2224 LastOrBidAsk = 8,
2226 MidPoint = 9,
2228}
2229
2230enum_strum_serde!(AccountType);
2231enum_strum_serde!(AggregationSource);
2232enum_strum_serde!(AggressorSide);
2233enum_strum_serde!(AssetClass);
2234enum_strum_serde!(AvgPxReconciliation);
2235enum_strum_serde!(BarAggregation);
2236enum_strum_serde!(BarIntervalType);
2237enum_strum_serde!(BetSide);
2238enum_strum_serde!(BookAction);
2239enum_strum_serde!(BookType);
2240enum_strum_serde!(ContingencyType);
2241enum_strum_serde!(ContinuousFutureAdjustmentType);
2242enum_strum_serde!(CurrencyType);
2243enum_strum_serde!(GreeksConvention);
2244enum_strum_serde!(InstrumentClass);
2245enum_strum_serde!(InstrumentCloseType);
2246enum_strum_serde!(LiquiditySide);
2247enum_strum_serde!(MarketStatus);
2248enum_strum_serde!(MarketStatusAction);
2249enum_strum_serde!(OmsType);
2250enum_strum_serde!(OptionKind);
2251enum_strum_serde!(OptionSideFilter);
2252enum_strum_serde!(OrderSide);
2253enum_strum_serde!(OrderStatus);
2254enum_strum_serde!(OrderType);
2255enum_strum_serde!(OtoTriggerMode);
2256enum_strum_serde!(PositionAdjustmentType);
2257enum_strum_serde!(PositionSide);
2258enum_strum_serde!(PriceType);
2259enum_strum_serde!(RecordFlag);
2260enum_strum_serde!(TimeInForce);
2261enum_strum_serde!(TradingState);
2262enum_strum_serde!(TrailingOffsetType);
2263enum_strum_serde!(TriggerType);
2264
2265#[cfg(test)]
2266mod tests {
2267 use rstest::rstest;
2268 use serde::{Serialize, de::DeserializeOwned};
2269 use strum::IntoEnumIterator;
2270
2271 use super::*;
2272
2273 #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2274 struct OptionalSides {
2275 #[serde(with = "serde_option_order_side")]
2276 order: Option<OrderSide>,
2277 #[serde(with = "serde_option_position_side")]
2278 position: Option<PositionSide>,
2279 }
2280
2281 #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2282 struct OptionalOrderTypes {
2283 #[serde(with = "serde_option_contingency_type")]
2284 contingency: Option<ContingencyType>,
2285 #[serde(with = "serde_option_trailing_offset_type")]
2286 trailing_offset: Option<TrailingOffsetType>,
2287 #[serde(with = "serde_option_trigger_type")]
2288 trigger: Option<TriggerType>,
2289 }
2290
2291 #[rstest]
2292 fn test_optional_sides_serde_preserves_legacy_none_tokens() {
2293 let value = OptionalSides {
2294 order: None,
2295 position: None,
2296 };
2297
2298 let json = serde_json::to_string(&value).unwrap();
2299 let decoded: OptionalSides = serde_json::from_str(&json).unwrap();
2300
2301 assert_eq!(
2302 json,
2303 r#"{"order":"NO_ORDER_SIDE","position":"NO_POSITION_SIDE"}"#
2304 );
2305 assert_eq!(decoded, value);
2306 }
2307
2308 #[rstest]
2309 fn test_optional_sides_serde_accepts_null_and_valid_sides() {
2310 let json = r#"{"order":null,"position":"LONG"}"#;
2311 let decoded: OptionalSides = serde_json::from_str(json).unwrap();
2312
2313 assert_eq!(
2314 decoded,
2315 OptionalSides {
2316 order: None,
2317 position: Some(PositionSide::Long),
2318 }
2319 );
2320 }
2321
2322 #[rstest]
2323 fn test_optional_order_types_serde_preserves_legacy_none_tokens() {
2324 let value = OptionalOrderTypes {
2325 contingency: None,
2326 trailing_offset: None,
2327 trigger: None,
2328 };
2329
2330 let json = serde_json::to_string(&value).unwrap();
2331 let decoded: OptionalOrderTypes = serde_json::from_str(&json).unwrap();
2332
2333 assert_eq!(
2334 json,
2335 r#"{"contingency":"NO_CONTINGENCY","trailing_offset":"NO_TRAILING_OFFSET","trigger":"NO_TRIGGER"}"#,
2336 );
2337 assert_eq!(decoded, value);
2338 }
2339
2340 #[rstest]
2341 fn test_optional_order_types_serde_accepts_null_and_valid_values() {
2342 let json = r#"{"contingency":null,"trailing_offset":"PRICE","trigger":"LAST_PRICE"}"#;
2343 let decoded: OptionalOrderTypes = serde_json::from_str(json).unwrap();
2344
2345 assert_eq!(
2346 decoded,
2347 OptionalOrderTypes {
2348 contingency: None,
2349 trailing_offset: Some(TrailingOffsetType::Price),
2350 trigger: Some(TriggerType::LastPrice),
2351 },
2352 );
2353 }
2354
2355 #[rstest]
2356 #[case(r#"{"contingency":"INVALID","trailing_offset":"NO_TRAILING_OFFSET","trigger":"NO_TRIGGER"}"#)]
2357 #[case(
2358 r#"{"contingency":"NO_CONTINGENCY","trailing_offset":"INVALID","trigger":"NO_TRIGGER"}"#
2359 )]
2360 #[case(r#"{"contingency":"NO_CONTINGENCY","trailing_offset":"NO_TRAILING_OFFSET","trigger":"INVALID"}"#)]
2361 fn test_optional_order_types_serde_rejects_invalid_values(#[case] json: &str) {
2362 assert!(serde_json::from_str::<OptionalOrderTypes>(json).is_err());
2363 }
2364
2365 #[rstest]
2366 #[case::bet_side_back(BetSide::Back, r#""BACK""#)]
2367 #[case::bet_side_lay(BetSide::Lay, r#""LAY""#)]
2368 fn test_bet_side_serde_uses_canonical_label(#[case] value: BetSide, #[case] expected: &str) {
2369 let json = serde_json::to_string(&value).unwrap();
2370
2371 assert_eq!(json, expected);
2372 assert_eq!(serde_json::from_str::<BetSide>(&json).unwrap(), value);
2373 }
2374
2375 #[rstest]
2376 #[case::oto_trigger_mode_partial(OtoTriggerMode::Partial, r#""PARTIAL""#)]
2377 #[case::oto_trigger_mode_full(OtoTriggerMode::Full, r#""FULL""#)]
2378 fn test_oto_trigger_mode_serde_uses_canonical_label(
2379 #[case] value: OtoTriggerMode,
2380 #[case] expected: &str,
2381 ) {
2382 let json = serde_json::to_string(&value).unwrap();
2383
2384 assert_eq!(json, expected);
2385 assert_eq!(
2386 serde_json::from_str::<OtoTriggerMode>(&json).unwrap(),
2387 value
2388 );
2389 }
2390
2391 #[rstest]
2392 #[case::pascal(r#""Back""#, BetSide::Back)]
2393 #[case::lower(r#""lay""#, BetSide::Lay)]
2394 fn test_bet_side_serde_is_case_insensitive(#[case] json: &str, #[case] expected: BetSide) {
2395 assert_eq!(serde_json::from_str::<BetSide>(json).unwrap(), expected);
2396 }
2397
2398 #[rstest]
2399 #[case::wrong_domain(r#""BUY""#)]
2400 #[case::empty(r#""""#)]
2401 fn test_bet_side_serde_rejects_unknown_label(#[case] json: &str) {
2402 assert!(serde_json::from_str::<BetSide>(json).is_err());
2403 }
2404
2405 #[rstest]
2406 #[case::no_aggressor(0, Some(AggressorSide::NoAggressor))]
2407 #[case::buy(1, Some(AggressorSide::Buy))]
2408 #[case::sell(2, Some(AggressorSide::Sell))]
2409 #[case::invalid(3, None)]
2410 #[case::max_u8(255, None)]
2411 fn test_aggressor_side_from_u8(#[case] value: u8, #[case] expected: Option<AggressorSide>) {
2412 assert_eq!(AggressorSide::from_u8(value), expected);
2413 }
2414
2415 #[rstest]
2416 #[case::active(TradingState::Active, 1)]
2417 #[case::reducing(TradingState::Reducing, 2)]
2418 #[case::halted(TradingState::Halted, 3)]
2419 fn test_trading_state_discriminants(#[case] state: TradingState, #[case] value: usize) {
2420 assert_eq!(state as usize, value);
2421 assert_eq!(TradingState::from_repr(value), Some(state));
2422 }
2423
2424 #[rstest]
2425 #[case(AggressorSide::NoAggressor, "NO_AGGRESSOR")]
2426 #[case(AggressorSide::Buy, "BUY")]
2427 #[case(AggressorSide::Sell, "SELL")]
2428 fn test_aggressor_side_to_string(#[case] value: AggressorSide, #[case] expected: &str) {
2429 assert_eq!(value.to_string(), expected);
2430 assert_eq!(value.as_ref(), expected);
2431 }
2432
2433 #[rstest]
2434 #[case(AggressorSide::NoAggressor, "NO_AGGRESSOR")]
2435 #[case(AggressorSide::Buy, "BUY")]
2436 #[case(AggressorSide::Sell, "SELL")]
2437 #[case(AggressorSide::Buy, "BUYER")]
2438 #[case(AggressorSide::Sell, "SELLER")]
2439 #[case(AggressorSide::Buy, "buy")]
2440 #[case(AggressorSide::Sell, "seller")]
2441 fn test_aggressor_side_from_str(#[case] expected: AggressorSide, #[case] value: &str) {
2442 assert_eq!(AggressorSide::from_str(value), Ok(expected));
2443 }
2444
2445 #[rstest]
2446 #[case(AggressorSide::Buy, "\"BUY\"")]
2447 #[case(AggressorSide::Sell, "\"SELL\"")]
2448 #[case(AggressorSide::NoAggressor, "\"NO_AGGRESSOR\"")]
2449 fn test_aggressor_side_serde_roundtrip(#[case] input: AggressorSide, #[case] expected: &str) {
2450 let json = serde_json::to_string(&input).unwrap();
2451 assert_eq!(json, expected);
2452 let parsed: AggressorSide = serde_json::from_str(expected).unwrap();
2453 assert_eq!(parsed, input);
2454 }
2455
2456 #[rstest]
2457 #[case("BUYER", AggressorSide::Buy)]
2458 #[case("SELLER", AggressorSide::Sell)]
2459 fn test_aggressor_side_serde_accepts_historical(
2460 #[case] value: &str,
2461 #[case] expected: AggressorSide,
2462 ) {
2463 let parsed: AggressorSide = serde_json::from_str(&format!("\"{value}\"")).unwrap();
2464 assert_eq!(parsed, expected);
2465 }
2466
2467 #[rstest]
2468 #[case(GreeksConvention::BlackScholes, "\"BLACK_SCHOLES\"")]
2469 #[case(GreeksConvention::PriceAdjusted, "\"PRICE_ADJUSTED\"")]
2470 fn test_greeks_convention_serde_roundtrip(
2471 #[case] input: GreeksConvention,
2472 #[case] expected: &str,
2473 ) {
2474 let json = serde_json::to_string(&input).unwrap();
2475 assert_eq!(json, expected);
2476 let parsed: GreeksConvention = serde_json::from_str(expected).unwrap();
2477 assert_eq!(parsed, input);
2478 }
2479
2480 #[rstest]
2481 fn test_greeks_convention_default_is_black_scholes() {
2482 assert_eq!(GreeksConvention::default(), GreeksConvention::BlackScholes);
2483 }
2484
2485 #[rstest]
2486 #[case(ContinuousFutureAdjustmentType::BackwardSpread, false, true)]
2487 #[case(ContinuousFutureAdjustmentType::ForwardSpread, false, false)]
2488 #[case(ContinuousFutureAdjustmentType::BackwardRatio, true, true)]
2489 #[case(ContinuousFutureAdjustmentType::ForwardRatio, true, false)]
2490 fn test_continuous_future_adjustment_type_predicates(
2491 #[case] mode: ContinuousFutureAdjustmentType,
2492 #[case] expected_is_ratio: bool,
2493 #[case] expected_is_backward: bool,
2494 ) {
2495 assert_eq!(mode.is_ratio(), expected_is_ratio);
2496 assert_eq!(mode.is_backward(), expected_is_backward);
2497 }
2498
2499 #[rstest]
2500 #[case(ContinuousFutureAdjustmentType::BackwardSpread, "\"BACKWARD_SPREAD\"")]
2501 #[case(ContinuousFutureAdjustmentType::ForwardSpread, "\"FORWARD_SPREAD\"")]
2502 #[case(ContinuousFutureAdjustmentType::BackwardRatio, "\"BACKWARD_RATIO\"")]
2503 #[case(ContinuousFutureAdjustmentType::ForwardRatio, "\"FORWARD_RATIO\"")]
2504 fn test_continuous_future_adjustment_type_serde_roundtrip(
2505 #[case] input: ContinuousFutureAdjustmentType,
2506 #[case] expected: &str,
2507 ) {
2508 let json = serde_json::to_string(&input).unwrap();
2509 assert_eq!(json, expected);
2510 let parsed: ContinuousFutureAdjustmentType = serde_json::from_str(expected).unwrap();
2511 assert_eq!(parsed, input);
2512 }
2513
2514 #[rstest]
2515 fn test_continuous_future_adjustment_type_default_is_backward_spread() {
2516 assert_eq!(
2517 ContinuousFutureAdjustmentType::default(),
2518 ContinuousFutureAdjustmentType::BackwardSpread,
2519 );
2520 }
2521
2522 #[rstest]
2523 #[case(InstrumentClass::Future, true)]
2524 #[case(InstrumentClass::Option, true)]
2525 #[case(InstrumentClass::FuturesSpread, true)]
2526 #[case(InstrumentClass::OptionSpread, true)]
2527 #[case(InstrumentClass::Spot, false)]
2528 #[case(InstrumentClass::Swap, false)]
2529 #[case(InstrumentClass::Forward, false)]
2530 #[case(InstrumentClass::Cfd, false)]
2531 #[case(InstrumentClass::Bond, false)]
2532 #[case(InstrumentClass::Warrant, false)]
2533 #[case(InstrumentClass::SportsBetting, false)]
2534 #[case(InstrumentClass::BinaryOption, false)]
2535 fn test_instrument_class_allows_negative_price(
2536 #[case] class: InstrumentClass,
2537 #[case] expected: bool,
2538 ) {
2539 assert_eq!(class.allows_negative_price(), expected);
2540 }
2541
2542 #[rstest]
2543 #[case(InstrumentClass::Option, true)]
2544 #[case(InstrumentClass::OptionSpread, true)]
2545 #[case(InstrumentClass::BinaryOption, true)]
2546 #[case(InstrumentClass::Warrant, true)]
2547 #[case(InstrumentClass::Spot, false)]
2548 #[case(InstrumentClass::Swap, false)]
2549 #[case(InstrumentClass::Future, false)]
2550 #[case(InstrumentClass::FuturesSpread, false)]
2551 #[case(InstrumentClass::Forward, false)]
2552 #[case(InstrumentClass::Cfd, false)]
2553 #[case(InstrumentClass::Bond, false)]
2554 #[case(InstrumentClass::SportsBetting, false)]
2555 fn test_instrument_class_is_premium_based(
2556 #[case] class: InstrumentClass,
2557 #[case] expected: bool,
2558 ) {
2559 assert_eq!(class.is_premium_based(), expected);
2560 }
2561
2562 #[rstest]
2563 #[case(InstrumentClass::Option, true, false)]
2564 #[case(InstrumentClass::OptionSpread, true, false)]
2565 #[case(InstrumentClass::BinaryOption, true, false)]
2566 #[case(InstrumentClass::Warrant, true, false)]
2567 #[case(InstrumentClass::Spot, true, true)]
2568 #[case(InstrumentClass::Swap, true, true)]
2569 #[case(InstrumentClass::Future, true, true)]
2570 #[case(InstrumentClass::FuturesSpread, true, true)]
2571 #[case(InstrumentClass::Forward, true, true)]
2572 #[case(InstrumentClass::Cfd, true, true)]
2573 #[case(InstrumentClass::Bond, true, true)]
2574 #[case(InstrumentClass::SportsBetting, true, true)]
2575 #[case(InstrumentClass::Future, false, false)]
2576 #[case(InstrumentClass::Option, false, false)]
2577 fn test_instrument_class_divides_notional_by_price(
2578 #[case] class: InstrumentClass,
2579 #[case] is_inverse: bool,
2580 #[case] expected: bool,
2581 ) {
2582 assert_eq!(class.divides_notional_by_price(is_inverse), expected);
2583 }
2584
2585 #[rstest]
2586 #[case("FUT", Some(InstrumentClass::Future))]
2587 #[case("FUTURE", Some(InstrumentClass::Future))]
2588 #[case("OPT", Some(InstrumentClass::Option))]
2589 #[case("OPTION", Some(InstrumentClass::Option))]
2590 #[case("fut", None)]
2591 #[case("Fut", None)]
2592 #[case("option", None)]
2593 #[case("Option", None)]
2594 #[case("SPREAD", None)]
2595 #[case("UNKNOWN", None)]
2596 #[case("", None)]
2597 fn test_instrument_class_try_from_parent_suffix(
2598 #[case] suffix: &str,
2599 #[case] expected: Option<InstrumentClass>,
2600 ) {
2601 assert_eq!(InstrumentClass::try_from_parent_suffix(suffix), expected);
2602 }
2603
2604 #[rstest]
2605 #[case(InstrumentClass::Future, Some("FUT"))]
2606 #[case(InstrumentClass::Option, Some("OPT"))]
2607 #[case(InstrumentClass::Spot, None)]
2608 #[case(InstrumentClass::Swap, None)]
2609 #[case(InstrumentClass::FuturesSpread, None)]
2610 #[case(InstrumentClass::Forward, None)]
2611 #[case(InstrumentClass::Cfd, None)]
2612 #[case(InstrumentClass::Bond, None)]
2613 #[case(InstrumentClass::OptionSpread, None)]
2614 #[case(InstrumentClass::Warrant, None)]
2615 #[case(InstrumentClass::SportsBetting, None)]
2616 #[case(InstrumentClass::BinaryOption, None)]
2617 fn test_instrument_class_parent_suffix(
2618 #[case] class: InstrumentClass,
2619 #[case] expected: Option<&'static str>,
2620 ) {
2621 assert_eq!(class.parent_suffix(), expected);
2622 }
2623
2624 #[rstest]
2625 #[case(InstrumentClass::Future)]
2626 #[case(InstrumentClass::Option)]
2627 fn test_instrument_class_parent_suffix_roundtrip(#[case] class: InstrumentClass) {
2628 let suffix = class.parent_suffix().unwrap();
2629 assert_eq!(InstrumentClass::try_from_parent_suffix(suffix), Some(class));
2630 }
2631
2632 fn assert_enum_string_contract<T>(type_name: &str)
2636 where
2637 T: IntoEnumIterator
2638 + Copy
2639 + std::fmt::Debug
2640 + PartialEq
2641 + Display
2642 + AsRef<str>
2643 + FromStr
2644 + Serialize
2645 + DeserializeOwned,
2646 {
2647 for variant in T::iter() {
2648 let wire = variant.to_string();
2649 assert_eq!(
2650 variant.as_ref(),
2651 wire,
2652 "{type_name}::{variant:?} must expose the same name through `AsRef` and `Display`",
2653 );
2654 assert_eq!(
2655 T::from_str(&wire).ok(),
2656 Some(variant),
2657 "{type_name} must parse `{wire}` back to {variant:?}",
2658 );
2659 assert_eq!(
2660 T::from_str(&wire.to_ascii_lowercase()).ok(),
2661 Some(variant),
2662 "{type_name} must parse `{wire}` case-insensitively",
2663 );
2664 let json = serde_json::to_string(&variant).unwrap();
2665 assert_eq!(
2666 json,
2667 format!("\"{wire}\""),
2668 "{type_name}::{variant:?} must serialize as its display name",
2669 );
2670 assert_eq!(
2671 serde_json::from_str::<T>(&json).unwrap(),
2672 variant,
2673 "{type_name}::{variant:?} must round-trip through JSON",
2674 );
2675 }
2676 }
2677
2678 #[rstest]
2679 fn test_enum_string_and_serde_contract() {
2680 macro_rules! assert_contract {
2681 ($($t:ty),+ $(,)?) => {
2682 $(assert_enum_string_contract::<$t>(stringify!($t));)+
2683 };
2684 }
2685
2686 assert_contract!(
2687 AccountType,
2688 AggregationSource,
2689 AggressorSide,
2690 AssetClass,
2691 AvgPxReconciliation,
2692 BarAggregation,
2693 BarIntervalType,
2694 BetSide,
2695 BookAction,
2696 BookType,
2697 ContingencyType,
2698 ContinuousFutureAdjustmentType,
2699 CurrencyType,
2700 GreeksConvention,
2701 InstrumentClass,
2702 InstrumentCloseType,
2703 LiquiditySide,
2704 MarketStatus,
2705 MarketStatusAction,
2706 OmsType,
2707 OptionKind,
2708 OrderSide,
2709 OrderStatus,
2710 OrderType,
2711 OtoTriggerMode,
2712 PositionAdjustmentType,
2713 PositionSide,
2714 PriceType,
2715 RecordFlag,
2716 TimeInForce,
2717 TradingState,
2718 TrailingOffsetType,
2719 TriggerType,
2720 );
2721 }
2722
2723 const EXPECTED_WIRE_NAMES: &[&str] = &[
2728 "AccountType::Betting=BETTING",
2729 "AccountType::Cash=CASH",
2730 "AccountType::Margin=MARGIN",
2731 "AccountType::Wallet=WALLET",
2732 "AggregationSource::External=EXTERNAL",
2733 "AggregationSource::Internal=INTERNAL",
2734 "AggressorSide::Buy=BUY",
2735 "AggressorSide::NoAggressor=NO_AGGRESSOR",
2736 "AggressorSide::Sell=SELL",
2737 "AssetClass::Alternative=ALTERNATIVE",
2738 "AssetClass::Commodity=COMMODITY",
2739 "AssetClass::Cryptocurrency=CRYPTOCURRENCY",
2740 "AssetClass::Debt=DEBT",
2741 "AssetClass::Equity=EQUITY",
2742 "AssetClass::FX=FX",
2743 "AssetClass::Index=INDEX",
2744 "AvgPxReconciliation::Match=MATCH",
2745 "AvgPxReconciliation::OpeningOnly=OPENING_ONLY",
2746 "BarAggregation::Day=DAY",
2747 "BarAggregation::Hour=HOUR",
2748 "BarAggregation::Millisecond=MILLISECOND",
2749 "BarAggregation::Minute=MINUTE",
2750 "BarAggregation::Month=MONTH",
2751 "BarAggregation::Renko=RENKO",
2752 "BarAggregation::Second=SECOND",
2753 "BarAggregation::Tick=TICK",
2754 "BarAggregation::TickImbalance=TICK_IMBALANCE",
2755 "BarAggregation::TickRuns=TICK_RUNS",
2756 "BarAggregation::Value=VALUE",
2757 "BarAggregation::ValueImbalance=VALUE_IMBALANCE",
2758 "BarAggregation::ValueRuns=VALUE_RUNS",
2759 "BarAggregation::Volume=VOLUME",
2760 "BarAggregation::VolumeImbalance=VOLUME_IMBALANCE",
2761 "BarAggregation::VolumeRuns=VOLUME_RUNS",
2762 "BarAggregation::Week=WEEK",
2763 "BarAggregation::Year=YEAR",
2764 "BarIntervalType::LeftOpen=LEFT_OPEN",
2765 "BarIntervalType::RightOpen=RIGHT_OPEN",
2766 "BetSide::Back=BACK",
2767 "BetSide::Lay=LAY",
2768 "BookAction::Add=ADD",
2769 "BookAction::Clear=CLEAR",
2770 "BookAction::Delete=DELETE",
2771 "BookAction::Update=UPDATE",
2772 "BookType::L1_MBP=L1_MBP",
2773 "BookType::L2_MBP=L2_MBP",
2774 "BookType::L3_MBO=L3_MBO",
2775 "ContingencyType::Oco=OCO",
2776 "ContingencyType::Oto=OTO",
2777 "ContingencyType::Ouo=OUO",
2778 "ContinuousFutureAdjustmentType::BackwardRatio=BACKWARD_RATIO",
2779 "ContinuousFutureAdjustmentType::BackwardSpread=BACKWARD_SPREAD",
2780 "ContinuousFutureAdjustmentType::ForwardRatio=FORWARD_RATIO",
2781 "ContinuousFutureAdjustmentType::ForwardSpread=FORWARD_SPREAD",
2782 "CurrencyType::CommodityBacked=COMMODITY_BACKED",
2783 "CurrencyType::Crypto=CRYPTO",
2784 "CurrencyType::Fiat=FIAT",
2785 "GreeksConvention::BlackScholes=BLACK_SCHOLES",
2786 "GreeksConvention::PriceAdjusted=PRICE_ADJUSTED",
2787 "InstrumentClass::BinaryOption=BINARY_OPTION",
2788 "InstrumentClass::Bond=BOND",
2789 "InstrumentClass::Cfd=CFD",
2790 "InstrumentClass::Forward=FORWARD",
2791 "InstrumentClass::Future=FUTURE",
2792 "InstrumentClass::FuturesSpread=FUTURES_SPREAD",
2793 "InstrumentClass::Option=OPTION",
2794 "InstrumentClass::OptionSpread=OPTION_SPREAD",
2795 "InstrumentClass::SportsBetting=SPORTS_BETTING",
2796 "InstrumentClass::Spot=SPOT",
2797 "InstrumentClass::Swap=SWAP",
2798 "InstrumentClass::Warrant=WARRANT",
2799 "InstrumentCloseType::ContractExpired=CONTRACT_EXPIRED",
2800 "InstrumentCloseType::EndOfSession=END_OF_SESSION",
2801 "LiquiditySide::Maker=MAKER",
2802 "LiquiditySide::NoLiquiditySide=NO_LIQUIDITY_SIDE",
2803 "LiquiditySide::Taker=TAKER",
2804 "MarketStatus::Closed=CLOSED",
2805 "MarketStatus::Halted=HALTED",
2806 "MarketStatus::NotAvailable=NOT_AVAILABLE",
2807 "MarketStatus::Open=OPEN",
2808 "MarketStatus::Paused=PAUSED",
2809 "MarketStatus::Suspended=SUSPENDED",
2810 "MarketStatusAction::Close=CLOSE",
2811 "MarketStatusAction::Cross=CROSS",
2812 "MarketStatusAction::Halt=HALT",
2813 "MarketStatusAction::NewPriceIndication=NEW_PRICE_INDICATION",
2814 "MarketStatusAction::None=NONE",
2815 "MarketStatusAction::NotAvailableForTrading=NOT_AVAILABLE_FOR_TRADING",
2816 "MarketStatusAction::Pause=PAUSE",
2817 "MarketStatusAction::PostClose=POST_CLOSE",
2818 "MarketStatusAction::PreClose=PRE_CLOSE",
2819 "MarketStatusAction::PreCross=PRE_CROSS",
2820 "MarketStatusAction::PreOpen=PRE_OPEN",
2821 "MarketStatusAction::Quoting=QUOTING",
2822 "MarketStatusAction::Rotation=ROTATION",
2823 "MarketStatusAction::ShortSellRestrictionChange=SHORT_SELL_RESTRICTION_CHANGE",
2824 "MarketStatusAction::Suspend=SUSPEND",
2825 "MarketStatusAction::Trading=TRADING",
2826 "OmsType::Hedging=HEDGING",
2827 "OmsType::Netting=NETTING",
2828 "OmsType::Unspecified=UNSPECIFIED",
2829 "OptionKind::Call=CALL",
2830 "OptionKind::Put=PUT",
2831 "OrderSide::Buy=BUY",
2832 "OrderSide::Sell=SELL",
2833 "OrderStatus::Accepted=ACCEPTED",
2834 "OrderStatus::Canceled=CANCELED",
2835 "OrderStatus::Denied=DENIED",
2836 "OrderStatus::Emulated=EMULATED",
2837 "OrderStatus::Expired=EXPIRED",
2838 "OrderStatus::Filled=FILLED",
2839 "OrderStatus::Initialized=INITIALIZED",
2840 "OrderStatus::PartiallyFilled=PARTIALLY_FILLED",
2841 "OrderStatus::PendingCancel=PENDING_CANCEL",
2842 "OrderStatus::PendingUpdate=PENDING_UPDATE",
2843 "OrderStatus::Rejected=REJECTED",
2844 "OrderStatus::Released=RELEASED",
2845 "OrderStatus::Submitted=SUBMITTED",
2846 "OrderStatus::Triggered=TRIGGERED",
2847 "OrderStatus::Voided=VOIDED",
2848 "OrderType::Limit=LIMIT",
2849 "OrderType::LimitIfTouched=LIMIT_IF_TOUCHED",
2850 "OrderType::Market=MARKET",
2851 "OrderType::MarketIfTouched=MARKET_IF_TOUCHED",
2852 "OrderType::MarketToLimit=MARKET_TO_LIMIT",
2853 "OrderType::StopLimit=STOP_LIMIT",
2854 "OrderType::StopMarket=STOP_MARKET",
2855 "OrderType::TrailingStopLimit=TRAILING_STOP_LIMIT",
2856 "OrderType::TrailingStopMarket=TRAILING_STOP_MARKET",
2857 "OtoTriggerMode::Full=FULL",
2858 "OtoTriggerMode::Partial=PARTIAL",
2859 "PositionAdjustmentType::Commission=COMMISSION",
2860 "PositionAdjustmentType::Funding=FUNDING",
2861 "PositionSide::Flat=FLAT",
2862 "PositionSide::Long=LONG",
2863 "PositionSide::Short=SHORT",
2864 "PriceType::Ask=ASK",
2865 "PriceType::Bid=BID",
2866 "PriceType::Last=LAST",
2867 "PriceType::Mark=MARK",
2868 "PriceType::Mid=MID",
2869 "RecordFlag::F_LAST=F_LAST",
2870 "RecordFlag::F_MBP=F_MBP",
2871 "RecordFlag::F_SNAPSHOT=F_SNAPSHOT",
2872 "RecordFlag::F_TOB=F_TOB",
2873 "RecordFlag::RESERVED_1=RESERVED_1",
2874 "RecordFlag::RESERVED_2=RESERVED_2",
2875 "TimeInForce::AtTheClose=AT_THE_CLOSE",
2876 "TimeInForce::AtTheOpen=AT_THE_OPEN",
2877 "TimeInForce::Day=DAY",
2878 "TimeInForce::Fok=FOK",
2879 "TimeInForce::Gtc=GTC",
2880 "TimeInForce::Gtd=GTD",
2881 "TimeInForce::Ioc=IOC",
2882 "TradingState::Active=ACTIVE",
2883 "TradingState::Halted=HALTED",
2884 "TradingState::Reducing=REDUCING",
2885 "TrailingOffsetType::BasisPoints=BASIS_POINTS",
2886 "TrailingOffsetType::Price=PRICE",
2887 "TrailingOffsetType::PriceTier=PRICE_TIER",
2888 "TrailingOffsetType::Ticks=TICKS",
2889 "TriggerType::BidAsk=BID_ASK",
2890 "TriggerType::Default=DEFAULT",
2891 "TriggerType::DoubleBidAsk=DOUBLE_BID_ASK",
2892 "TriggerType::DoubleLast=DOUBLE_LAST",
2893 "TriggerType::IndexPrice=INDEX_PRICE",
2894 "TriggerType::LastOrBidAsk=LAST_OR_BID_ASK",
2895 "TriggerType::LastPrice=LAST_PRICE",
2896 "TriggerType::MarkPrice=MARK_PRICE",
2897 "TriggerType::MidPoint=MID_POINT",
2898 ];
2899
2900 #[rstest]
2901 fn test_enum_wire_names_are_stable() {
2902 let mut actual = Vec::new();
2903 macro_rules! collect_wire_names {
2904 ($($t:ty),+ $(,)?) => {
2905 $(for variant in <$t>::iter() {
2906 actual.push(format!("{}::{variant:?}={variant}", stringify!($t)));
2907 })+
2908 };
2909 }
2910
2911 collect_wire_names!(
2912 AccountType,
2913 AggregationSource,
2914 AggressorSide,
2915 AssetClass,
2916 AvgPxReconciliation,
2917 BarAggregation,
2918 BarIntervalType,
2919 BetSide,
2920 BookAction,
2921 BookType,
2922 ContingencyType,
2923 ContinuousFutureAdjustmentType,
2924 CurrencyType,
2925 GreeksConvention,
2926 InstrumentClass,
2927 InstrumentCloseType,
2928 LiquiditySide,
2929 MarketStatus,
2930 MarketStatusAction,
2931 OmsType,
2932 OptionKind,
2933 OrderSide,
2934 OrderStatus,
2935 OrderType,
2936 OtoTriggerMode,
2937 PositionAdjustmentType,
2938 PositionSide,
2939 PriceType,
2940 RecordFlag,
2941 TimeInForce,
2942 TradingState,
2943 TrailingOffsetType,
2944 TriggerType,
2945 );
2946
2947 actual.sort();
2948
2949 assert_eq!(
2950 actual.len(),
2951 EXPECTED_WIRE_NAMES.len(),
2952 "variant count changed; update `EXPECTED_WIRE_NAMES`",
2953 );
2954
2955 for (got, expected) in actual.iter().zip(EXPECTED_WIRE_NAMES) {
2956 assert_eq!(got, expected);
2957 }
2958 }
2959
2960 macro_rules! assert_numeric_mapping {
2968 ($t:ty, $from:ident, $width:ty, $($value:literal => $variant:expr),+ $(,)?) => {{
2969 let valid: &[$width] = &[$($value),+];
2970 $(
2971 assert_eq!(
2972 <$t>::$from($value),
2973 Some($variant),
2974 "{}::{}({}) must map to the pinned variant",
2975 stringify!($t),
2976 stringify!($from),
2977 $value,
2978 );
2979 )+
2980
2981 for value in <$width>::MIN..=<$width>::MAX {
2982 if !valid.contains(&value) {
2983 assert_eq!(
2984 <$t>::$from(value),
2985 None,
2986 "{} must reject {value}",
2987 stringify!($t),
2988 );
2989 }
2990 }
2991 }};
2992 }
2993
2994 #[rstest]
2995 fn test_from_u8_pins_numeric_discriminants() {
2996 assert_numeric_mapping!(
2997 AggressorSide, from_u8, u8,
2998 0 => AggressorSide::NoAggressor,
2999 1 => AggressorSide::Buy,
3000 2 => AggressorSide::Sell,
3001 );
3002 assert_numeric_mapping!(
3003 AssetClass, from_u8, u8,
3004 1 => AssetClass::FX,
3005 2 => AssetClass::Equity,
3006 3 => AssetClass::Commodity,
3007 4 => AssetClass::Debt,
3008 5 => AssetClass::Index,
3009 6 => AssetClass::Cryptocurrency,
3010 7 => AssetClass::Alternative,
3011 );
3012 assert_numeric_mapping!(
3013 BookAction, from_u8, u8,
3014 1 => BookAction::Add,
3015 2 => BookAction::Update,
3016 3 => BookAction::Delete,
3017 4 => BookAction::Clear,
3018 );
3019 assert_numeric_mapping!(
3020 BookType, from_u8, u8,
3021 1 => BookType::L1_MBP,
3022 2 => BookType::L2_MBP,
3023 3 => BookType::L3_MBO,
3024 );
3025 assert_numeric_mapping!(
3026 InstrumentCloseType, from_u8, u8,
3027 1 => InstrumentCloseType::EndOfSession,
3028 2 => InstrumentCloseType::ContractExpired,
3029 );
3030 assert_numeric_mapping!(
3031 PositionAdjustmentType, from_u8, u8,
3032 1 => PositionAdjustmentType::Commission,
3033 2 => PositionAdjustmentType::Funding,
3034 );
3035 }
3036
3037 #[rstest]
3038 fn test_market_status_action_from_u16_pins_numeric_discriminants() {
3039 assert_numeric_mapping!(
3040 MarketStatusAction, from_u16, u16,
3041 0 => MarketStatusAction::None,
3042 1 => MarketStatusAction::PreOpen,
3043 2 => MarketStatusAction::PreCross,
3044 3 => MarketStatusAction::Quoting,
3045 4 => MarketStatusAction::Cross,
3046 5 => MarketStatusAction::Rotation,
3047 6 => MarketStatusAction::NewPriceIndication,
3048 7 => MarketStatusAction::Trading,
3049 8 => MarketStatusAction::Halt,
3050 9 => MarketStatusAction::Pause,
3051 10 => MarketStatusAction::Suspend,
3052 11 => MarketStatusAction::PreClose,
3053 12 => MarketStatusAction::Close,
3054 13 => MarketStatusAction::PostClose,
3055 14 => MarketStatusAction::ShortSellRestrictionChange,
3056 15 => MarketStatusAction::NotAvailableForTrading,
3057 );
3058 }
3059
3060 #[rstest]
3061 #[case(OrderStatus::Initialized, false, false, false, false)]
3062 #[case(OrderStatus::Denied, false, true, false, false)]
3063 #[case(OrderStatus::Emulated, false, false, false, false)]
3064 #[case(OrderStatus::Released, false, false, false, false)]
3065 #[case(OrderStatus::Submitted, false, false, false, true)]
3066 #[case(OrderStatus::Accepted, true, false, true, false)]
3067 #[case(OrderStatus::Rejected, false, true, false, false)]
3068 #[case(OrderStatus::Canceled, false, true, false, false)]
3069 #[case(OrderStatus::Expired, false, true, false, false)]
3070 #[case(OrderStatus::Triggered, true, false, true, false)]
3071 #[case(OrderStatus::PendingUpdate, true, false, true, true)]
3072 #[case(OrderStatus::PendingCancel, true, false, false, true)]
3073 #[case(OrderStatus::PartiallyFilled, true, false, true, false)]
3074 #[case(OrderStatus::Filled, false, true, false, false)]
3075 #[case(OrderStatus::Voided, false, true, false, false)]
3076 fn test_order_status_predicates(
3077 #[case] status: OrderStatus,
3078 #[case] expected_open: bool,
3079 #[case] expected_closed: bool,
3080 #[case] expected_cancellable: bool,
3081 #[case] expected_inflight: bool,
3082 ) {
3083 assert_eq!(status.is_open(), expected_open, "{status} is_open");
3084 assert_eq!(status.is_closed(), expected_closed, "{status} is_closed");
3085 assert_eq!(
3086 status.is_cancellable(),
3087 expected_cancellable,
3088 "{status} is_cancellable",
3089 );
3090 assert_eq!(
3091 status.is_inflight(),
3092 expected_inflight,
3093 "{status} is_inflight"
3094 );
3095 }
3096
3097 #[rstest]
3098 fn test_order_status_predicates_hold_across_all_variants() {
3099 for status in OrderStatus::iter() {
3100 assert!(
3101 !(status.is_open() && status.is_closed()),
3102 "{status} cannot be both open and closed",
3103 );
3104 assert!(
3105 !status.is_cancellable() || status.is_open(),
3106 "{status} must be open to be cancellable",
3107 );
3108 assert!(
3109 !(status.is_inflight() && status.is_closed()),
3110 "{status} cannot be both in-flight and closed",
3111 );
3112 }
3113 }
3114
3115 #[rstest]
3116 #[case(OrderSide::Buy, OrderSide::Sell)]
3117 #[case(OrderSide::Sell, OrderSide::Buy)]
3118 fn test_order_side_opposite(#[case] side: OrderSide, #[case] expected: OrderSide) {
3119 assert_eq!(side.opposite(), expected);
3120 assert_eq!(side.opposite().opposite(), side);
3121 }
3122
3123 #[rstest]
3124 #[case(BetSide::Back, BetSide::Lay)]
3125 #[case(BetSide::Lay, BetSide::Back)]
3126 fn test_bet_side_opposite(#[case] side: BetSide, #[case] expected: BetSide) {
3127 assert_eq!(side.opposite(), expected);
3128 assert_eq!(side.opposite().opposite(), side);
3129 }
3130
3131 #[rstest]
3132 #[case(OrderSide::Buy, BetSide::Back)]
3133 #[case(OrderSide::Sell, BetSide::Lay)]
3134 fn test_bet_side_from_order_side(#[case] side: OrderSide, #[case] expected: BetSide) {
3135 assert_eq!(BetSide::from(side), expected);
3136 }
3137
3138 #[rstest]
3139 #[case(RecordFlag::F_LAST, 0b0000_0000, false)]
3140 #[case(RecordFlag::F_LAST, 0b1000_0000, true)]
3141 #[case(RecordFlag::F_LAST, 0b0111_1111, false)]
3142 #[case(RecordFlag::F_TOB, 0b1100_0000, true)]
3143 #[case(RecordFlag::F_TOB, 0b1000_0000, false)]
3144 #[case(RecordFlag::F_SNAPSHOT, 0b0010_0000, true)]
3145 #[case(RecordFlag::F_SNAPSHOT, 0b1101_1111, false)]
3146 #[case(RecordFlag::F_MBP, 0b0001_0000, true)]
3147 #[case(RecordFlag::RESERVED_2, 0b0000_1000, true)]
3148 #[case(RecordFlag::RESERVED_1, 0b0000_0100, true)]
3149 #[case(RecordFlag::RESERVED_1, 0b0000_1000, false)]
3150 #[case(RecordFlag::F_LAST, u8::MAX, true)]
3151 fn test_record_flag_matches(
3152 #[case] flag: RecordFlag,
3153 #[case] value: u8,
3154 #[case] expected: bool,
3155 ) {
3156 assert_eq!(flag.matches(value), expected);
3157 }
3158
3159 #[rstest]
3160 fn test_record_flag_matches_only_its_own_bit() {
3161 for flag in RecordFlag::iter() {
3162 let bit = flag as u8;
3163 assert_eq!(bit.count_ones(), 1, "{flag} must occupy exactly one bit");
3164 assert!(flag.matches(bit), "{flag} must match its own bit");
3165 assert!(
3166 !flag.matches(!bit),
3167 "{flag} must not match the inverse mask"
3168 );
3169 }
3170 }
3171
3172 #[rstest]
3173 #[case(InstrumentClass::Future, true)]
3174 #[case(InstrumentClass::FuturesSpread, true)]
3175 #[case(InstrumentClass::Option, true)]
3176 #[case(InstrumentClass::OptionSpread, true)]
3177 #[case(InstrumentClass::Spot, false)]
3178 #[case(InstrumentClass::Swap, false)]
3179 #[case(InstrumentClass::Forward, false)]
3180 #[case(InstrumentClass::Cfd, false)]
3181 #[case(InstrumentClass::Bond, false)]
3182 #[case(InstrumentClass::Warrant, false)]
3183 #[case(InstrumentClass::SportsBetting, false)]
3184 #[case(InstrumentClass::BinaryOption, false)]
3185 fn test_instrument_class_has_expiration(
3186 #[case] class: InstrumentClass,
3187 #[case] expected: bool,
3188 ) {
3189 assert_eq!(class.has_expiration(), expected);
3190 }
3191
3192 #[rstest]
3193 fn test_optional_sides_serde_round_trips_present_values() {
3194 let value = OptionalSides {
3195 order: Some(OrderSide::Sell),
3196 position: Some(PositionSide::Short),
3197 };
3198
3199 let json = serde_json::to_string(&value).unwrap();
3200
3201 assert_eq!(json, r#"{"order":"SELL","position":"SHORT"}"#);
3202 assert_eq!(serde_json::from_str::<OptionalSides>(&json).unwrap(), value);
3203 }
3204
3205 #[rstest]
3206 #[case(r#"{"order":"no_order_side","position":"FLAT"}"#)]
3207 #[case(r#"{"order":"No_Order_Side","position":"FLAT"}"#)]
3208 fn test_optional_sides_serde_accepts_none_token_in_any_case(#[case] json: &str) {
3209 let decoded: OptionalSides = serde_json::from_str(json).unwrap();
3210
3211 assert_eq!(
3212 decoded,
3213 OptionalSides {
3214 order: None,
3215 position: Some(PositionSide::Flat),
3216 },
3217 );
3218 }
3219
3220 #[rstest]
3221 #[case(r#"{"order":"INVALID","position":"FLAT"}"#)]
3222 #[case(r#"{"order":5,"position":"FLAT"}"#)]
3223 #[case(r#"{"order":"BUY","position":"INVALID"}"#)]
3224 #[case(r#"{"order":"BUY","position":true}"#)]
3225 fn test_optional_sides_serde_rejects_invalid_values(#[case] json: &str) {
3226 assert!(serde_json::from_str::<OptionalSides>(json).is_err());
3227 }
3228
3229 #[rstest]
3230 #[case(
3231 OptionSideFilter::Both,
3232 OptionKind::Call,
3233 std::cmp::Ordering::Less,
3234 true
3235 )]
3236 #[case(
3237 OptionSideFilter::Both,
3238 OptionKind::Put,
3239 std::cmp::Ordering::Greater,
3240 true
3241 )]
3242 #[case(
3243 OptionSideFilter::Calls,
3244 OptionKind::Call,
3245 std::cmp::Ordering::Less,
3246 true
3247 )]
3248 #[case(
3249 OptionSideFilter::Calls,
3250 OptionKind::Put,
3251 std::cmp::Ordering::Less,
3252 false
3253 )]
3254 #[case(
3255 OptionSideFilter::Puts,
3256 OptionKind::Put,
3257 std::cmp::Ordering::Greater,
3258 true
3259 )]
3260 #[case(
3261 OptionSideFilter::Puts,
3262 OptionKind::Call,
3263 std::cmp::Ordering::Greater,
3264 false
3265 )]
3266 #[case(OptionSideFilter::Otm, OptionKind::Put, std::cmp::Ordering::Less, true)]
3267 #[case(
3268 OptionSideFilter::Otm,
3269 OptionKind::Call,
3270 std::cmp::Ordering::Less,
3271 false
3272 )]
3273 #[case(
3274 OptionSideFilter::Otm,
3275 OptionKind::Call,
3276 std::cmp::Ordering::Greater,
3277 true
3278 )]
3279 #[case(
3280 OptionSideFilter::Otm,
3281 OptionKind::Put,
3282 std::cmp::Ordering::Greater,
3283 false
3284 )]
3285 #[case(
3286 OptionSideFilter::Otm,
3287 OptionKind::Call,
3288 std::cmp::Ordering::Equal,
3289 true
3290 )]
3291 #[case(
3292 OptionSideFilter::Otm,
3293 OptionKind::Put,
3294 std::cmp::Ordering::Equal,
3295 true
3296 )]
3297 fn test_option_side_filter_includes(
3298 #[case] filter: OptionSideFilter,
3299 #[case] kind: OptionKind,
3300 #[case] position: std::cmp::Ordering,
3301 #[case] expected: bool,
3302 ) {
3303 assert_eq!(filter.includes(kind, position), expected);
3304 }
3305}