Skip to main content

nautilus_model/
enums.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Enumerations for the trading domain model.
17
18use std::{borrow::Cow, fmt::Display, marker::PhantomData, str::FromStr};
19
20use serde::{Deserialize, Deserializer, Serialize, Serializer};
21use strum::{AsRefStr, Display, EnumIter, EnumString, FromRepr};
22
23use crate::enum_strum_serde;
24
25/// Provides conversion from a `u8` value to an enum type.
26pub trait FromU8 {
27    /// Converts a `u8` value to the implementing type.
28    ///
29    /// Returns `None` if the value is not a valid representation.
30    fn from_u8(value: u8) -> Option<Self>
31    where
32        Self: Sized;
33}
34
35/// Provides conversion from a `u16` value to an enum type.
36pub trait FromU16 {
37    /// Converts a `u16` value to the implementing type.
38    ///
39    /// Returns `None` if the value is not a valid representation.
40    fn from_u16(value: u16) -> Option<Self>
41    where
42        Self: Sized;
43}
44
45/// An account type provided by a trading venue or broker.
46#[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    /// An account with unleveraged cash assets only.
81    Cash = 1,
82    /// An account which facilitates trading on margin, using account assets as collateral.
83    Margin = 2,
84    /// An account specific to betting markets.
85    Betting = 3,
86    /// An account which represents a blockchain wallet,
87    Wallet = 4,
88}
89
90/// An aggregation source for derived data.
91#[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    /// The data is externally aggregated (outside the Nautilus system boundary).
126    External = 1,
127    /// The data is internally aggregated (inside the Nautilus system boundary).
128    Internal = 2,
129}
130
131/// The side for the aggressing order of a trade in a market.
132#[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    /// There was no specific aggressor for the trade.
168    #[default]
169    NoAggressor = 0,
170    /// The BUY order was the aggressor for the trade.
171    ///
172    /// The deprecated `BUYER` serialization value is still accepted when parsing.
173    #[strum(serialize = "BUYER", to_string = "BUY")]
174    Buy = 1,
175    /// The SELL order was the aggressor for the trade.
176    ///
177    /// The deprecated `SELLER` serialization value is still accepted when parsing.
178    #[strum(serialize = "SELLER", to_string = "SELL")]
179    Sell = 2,
180}
181
182impl FromU8 for AggressorSide {
183    fn from_u8(value: u8) -> Option<Self> {
184        match value {
185            0 => Some(Self::NoAggressor),
186            1 => Some(Self::Buy),
187            2 => Some(Self::Sell),
188            _ => None,
189        }
190    }
191}
192
193/// A broad financial market asset class.
194#[repr(C)]
195#[derive(
196    Copy,
197    Clone,
198    Debug,
199    Display,
200    Hash,
201    PartialEq,
202    Eq,
203    PartialOrd,
204    Ord,
205    AsRefStr,
206    FromRepr,
207    EnumIter,
208    EnumString,
209)]
210#[strum(ascii_case_insensitive)]
211#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
212#[cfg_attr(
213    feature = "python",
214    pyo3::pyclass(
215        frozen,
216        eq,
217        eq_int,
218        module = "nautilus_trader.model",
219        from_py_object,
220        rename_all = "SCREAMING_SNAKE_CASE",
221    )
222)]
223#[cfg_attr(
224    feature = "python",
225    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
226)]
227#[allow(non_camel_case_types)]
228pub enum AssetClass {
229    /// Foreign exchange (FOREX) assets.
230    FX = 1,
231    /// Equity / stock assets.
232    Equity = 2,
233    /// Commodity assets.
234    Commodity = 3,
235    /// Debt based assets.
236    Debt = 4,
237    /// Index based assets (baskets).
238    Index = 5,
239    /// Cryptocurrency or crypto token assets.
240    Cryptocurrency = 6,
241    /// Alternative assets.
242    Alternative = 7,
243}
244
245impl FromU8 for AssetClass {
246    fn from_u8(value: u8) -> Option<Self> {
247        match value {
248            1 => Some(Self::FX),
249            2 => Some(Self::Equity),
250            3 => Some(Self::Commodity),
251            4 => Some(Self::Debt),
252            5 => Some(Self::Index),
253            6 => Some(Self::Cryptocurrency),
254            7 => Some(Self::Alternative),
255            _ => None,
256        }
257    }
258}
259
260/// The aggregation method through which a bar is generated and closed.
261#[repr(C)]
262#[derive(
263    Copy,
264    Clone,
265    Debug,
266    Display,
267    Hash,
268    PartialEq,
269    Eq,
270    PartialOrd,
271    Ord,
272    AsRefStr,
273    FromRepr,
274    EnumIter,
275    EnumString,
276)]
277#[strum(ascii_case_insensitive)]
278#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
279#[cfg_attr(
280    feature = "python",
281    pyo3::pyclass(
282        frozen,
283        eq,
284        eq_int,
285        module = "nautilus_trader.model",
286        from_py_object,
287        rename_all = "SCREAMING_SNAKE_CASE",
288    )
289)]
290#[cfg_attr(
291    feature = "python",
292    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
293)]
294pub enum BarAggregation {
295    /// Based on a number of ticks.
296    Tick = 1,
297    /// Based on the buy/sell imbalance of ticks.
298    TickImbalance = 2,
299    /// Based on sequential buy/sell runs of ticks.
300    TickRuns = 3,
301    /// Based on traded volume.
302    Volume = 4,
303    /// Based on the buy/sell imbalance of traded volume.
304    VolumeImbalance = 5,
305    /// Based on sequential runs of buy/sell traded volume.
306    VolumeRuns = 6,
307    /// Based on the 'notional' value of the instrument.
308    Value = 7,
309    /// Based on the buy/sell imbalance of trading by notional value.
310    ValueImbalance = 8,
311    /// Based on sequential buy/sell runs of trading by notional value.
312    ValueRuns = 9,
313    /// Based on time intervals with millisecond granularity.
314    Millisecond = 10,
315    /// Based on time intervals with second granularity.
316    Second = 11,
317    /// Based on time intervals with minute granularity.
318    Minute = 12,
319    /// Based on time intervals with hour granularity.
320    Hour = 13,
321    /// Based on time intervals with day granularity.
322    Day = 14,
323    /// Based on time intervals with week granularity.
324    Week = 15,
325    /// Based on time intervals with month granularity.
326    Month = 16,
327    /// Based on time intervals with year granularity.
328    Year = 17,
329    /// Based on fixed price movements (brick size).
330    Renko = 18,
331}
332
333/// The interval type for bar aggregation.
334#[repr(C)]
335#[derive(
336    Copy,
337    Clone,
338    Debug,
339    Default,
340    Display,
341    Hash,
342    PartialEq,
343    Eq,
344    PartialOrd,
345    Ord,
346    AsRefStr,
347    FromRepr,
348    EnumIter,
349    EnumString,
350)]
351#[strum(ascii_case_insensitive)]
352#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
353#[cfg_attr(
354    feature = "python",
355    pyo3::pyclass(
356        frozen,
357        eq,
358        eq_int,
359        module = "nautilus_trader.model",
360        from_py_object,
361        rename_all = "SCREAMING_SNAKE_CASE",
362    )
363)]
364#[cfg_attr(
365    feature = "python",
366    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
367)]
368pub enum BarIntervalType {
369    /// Left-open interval `(start, end]`: start is exclusive, end is inclusive (default).
370    #[default]
371    LeftOpen = 1,
372    /// Right-open interval `[start, end)`: start is inclusive, end is exclusive.
373    RightOpen = 2,
374}
375
376/// Represents the side of a bet in a betting market.
377#[repr(C)]
378#[derive(
379    Copy,
380    Clone,
381    Debug,
382    Display,
383    Hash,
384    PartialEq,
385    Eq,
386    PartialOrd,
387    Ord,
388    AsRefStr,
389    FromRepr,
390    EnumIter,
391    EnumString,
392)]
393#[strum(ascii_case_insensitive)]
394#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
395#[cfg_attr(
396    feature = "python",
397    pyo3::pyclass(
398        frozen,
399        eq,
400        eq_int,
401        module = "nautilus_trader.model",
402        from_py_object,
403        rename_all = "SCREAMING_SNAKE_CASE",
404    )
405)]
406#[cfg_attr(
407    feature = "python",
408    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
409)]
410pub enum BetSide {
411    /// A "Back" bet signifies support for a specific outcome.
412    Back = 1,
413    /// A "Lay" bet signifies opposition to a specific outcome.
414    Lay = 2,
415}
416
417impl BetSide {
418    /// Returns the opposite betting side.
419    #[must_use]
420    pub fn opposite(&self) -> Self {
421        match self {
422            Self::Back => Self::Lay,
423            Self::Lay => Self::Back,
424        }
425    }
426}
427
428impl From<OrderSide> for BetSide {
429    fn from(side: OrderSide) -> Self {
430        match side {
431            OrderSide::Buy => Self::Back,
432            OrderSide::Sell => Self::Lay,
433        }
434    }
435}
436
437/// The type of order book action for an order book event.
438#[repr(C)]
439#[derive(
440    Copy,
441    Clone,
442    Debug,
443    Display,
444    Hash,
445    PartialEq,
446    Eq,
447    PartialOrd,
448    Ord,
449    AsRefStr,
450    FromRepr,
451    EnumIter,
452    EnumString,
453)]
454#[strum(ascii_case_insensitive)]
455#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
456#[cfg_attr(
457    feature = "python",
458    pyo3::pyclass(
459        frozen,
460        eq,
461        eq_int,
462        module = "nautilus_trader.model",
463        from_py_object,
464        rename_all = "SCREAMING_SNAKE_CASE",
465    )
466)]
467#[cfg_attr(
468    feature = "python",
469    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
470)]
471pub enum BookAction {
472    /// An order is added to the book.
473    Add = 1,
474    /// An existing order in the book is updated/modified.
475    Update = 2,
476    /// An existing order in the book is deleted/canceled.
477    Delete = 3,
478    /// The state of the order book is cleared.
479    Clear = 4,
480}
481
482impl FromU8 for BookAction {
483    fn from_u8(value: u8) -> Option<Self> {
484        match value {
485            1 => Some(Self::Add),
486            2 => Some(Self::Update),
487            3 => Some(Self::Delete),
488            4 => Some(Self::Clear),
489            _ => None,
490        }
491    }
492}
493
494/// The order book type, representing the type of levels granularity and delta updating heuristics.
495#[repr(C)]
496#[derive(
497    Copy,
498    Clone,
499    Debug,
500    Display,
501    Hash,
502    PartialEq,
503    Eq,
504    PartialOrd,
505    Ord,
506    AsRefStr,
507    FromRepr,
508    EnumIter,
509    EnumString,
510)]
511#[strum(ascii_case_insensitive)]
512#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
513#[cfg_attr(
514    feature = "python",
515    pyo3::pyclass(
516        frozen,
517        eq,
518        eq_int,
519        module = "nautilus_trader.model",
520        from_py_object,
521        rename_all = "SCREAMING_SNAKE_CASE",
522    )
523)]
524#[cfg_attr(
525    feature = "python",
526    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
527)]
528#[allow(non_camel_case_types)]
529pub enum BookType {
530    /// Top-of-book best bid/ask, one level per side.
531    L1_MBP = 1,
532    /// Market by price, one order per level (aggregated).
533    L2_MBP = 2,
534    /// Market by order, multiple orders per level (full granularity).
535    L3_MBO = 3,
536}
537
538impl FromU8 for BookType {
539    fn from_u8(value: u8) -> Option<Self> {
540        match value {
541            1 => Some(Self::L1_MBP),
542            2 => Some(Self::L2_MBP),
543            3 => Some(Self::L3_MBO),
544            _ => None,
545        }
546    }
547}
548
549/// The order contingency type which specifies the behavior of linked orders.
550///
551/// [FIX 5.0 SP2 : ContingencyType <1385> field](https://www.onixs.biz/fix-dictionary/5.0.sp2/tagnum_1385.html).
552///
553/// Python retains `NO_CONTINGENCY` as a compatibility alias for `None`. The alias is not an enum
554/// variant and may be removed in a future version.
555#[repr(C)]
556#[derive(
557    Copy,
558    Clone,
559    Debug,
560    Display,
561    Hash,
562    PartialEq,
563    Eq,
564    PartialOrd,
565    Ord,
566    AsRefStr,
567    FromRepr,
568    EnumIter,
569    EnumString,
570)]
571#[strum(ascii_case_insensitive)]
572#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
573#[cfg_attr(
574    feature = "python",
575    pyo3::pyclass(
576        frozen,
577        eq,
578        eq_int,
579        module = "nautilus_trader.model",
580        from_py_object,
581        rename_all = "SCREAMING_SNAKE_CASE",
582    )
583)]
584#[cfg_attr(
585    feature = "python",
586    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
587)]
588pub enum ContingencyType {
589    /// One-Cancels-the-Other.
590    Oco = 1,
591    /// One-Triggers-the-Other.
592    Oto = 2,
593    /// One-Updates-the-Other (by proportional quantity).
594    Ouo = 3,
595}
596
597/// The price-adjustment scheme applied when stitching segment contracts into a
598/// continuous future series.
599///
600/// The direction (backward vs. forward) selects the anchor contract:
601/// - Backward modes anchor on the most recent contract; prices in older
602///   segments are shifted into the latest contract's frame.
603/// - Forward modes anchor on the first contract; prices in later segments
604///   are shifted into the first contract's frame.
605///
606/// The kind (spread vs. ratio) selects how each transition's offset is combined:
607/// - Spread modes accumulate additive offsets (`post_price - pre_price`).
608/// - Ratio modes accumulate multiplicative factors (`post_price / pre_price`)
609///   and require strictly positive prices.
610#[repr(C)]
611#[derive(
612    Copy,
613    Clone,
614    Debug,
615    Default,
616    Display,
617    Hash,
618    PartialEq,
619    Eq,
620    PartialOrd,
621    Ord,
622    AsRefStr,
623    FromRepr,
624    EnumIter,
625    EnumString,
626)]
627#[strum(ascii_case_insensitive)]
628#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
629#[cfg_attr(
630    feature = "python",
631    pyo3::pyclass(
632        frozen,
633        eq,
634        eq_int,
635        module = "nautilus_trader.model",
636        from_py_object,
637        rename_all = "SCREAMING_SNAKE_CASE",
638    )
639)]
640#[cfg_attr(
641    feature = "python",
642    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
643)]
644pub enum ContinuousFutureAdjustmentType {
645    /// Additive adjustment, anchored on the most recent contract.
646    #[default]
647    BackwardSpread = 1,
648    /// Additive adjustment, anchored on the first contract.
649    ForwardSpread = 2,
650    /// Multiplicative adjustment, anchored on the most recent contract.
651    BackwardRatio = 3,
652    /// Multiplicative adjustment, anchored on the first contract.
653    ForwardRatio = 4,
654}
655
656impl ContinuousFutureAdjustmentType {
657    /// Returns whether this mode accumulates multiplicative factors.
658    #[must_use]
659    pub const fn is_ratio(&self) -> bool {
660        matches!(self, Self::BackwardRatio | Self::ForwardRatio)
661    }
662
663    /// Returns whether this mode anchors on the most recent contract.
664    #[must_use]
665    pub const fn is_backward(&self) -> bool {
666        matches!(self, Self::BackwardSpread | Self::BackwardRatio)
667    }
668}
669
670/// The broad currency type.
671#[repr(C)]
672#[derive(
673    Copy,
674    Clone,
675    Debug,
676    Display,
677    Hash,
678    PartialEq,
679    Eq,
680    PartialOrd,
681    Ord,
682    AsRefStr,
683    FromRepr,
684    EnumIter,
685    EnumString,
686)]
687#[strum(ascii_case_insensitive)]
688#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
689#[cfg_attr(
690    feature = "python",
691    pyo3::pyclass(
692        frozen,
693        eq,
694        eq_int,
695        module = "nautilus_trader.model",
696        from_py_object,
697        rename_all = "SCREAMING_SNAKE_CASE",
698    )
699)]
700#[cfg_attr(
701    feature = "python",
702    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
703)]
704pub enum CurrencyType {
705    /// A type of cryptocurrency or crypto token.
706    Crypto = 1,
707    /// A type of currency issued by governments which is not backed by a commodity.
708    Fiat = 2,
709    /// A type of currency that is based on the value of an underlying commodity.
710    CommodityBacked = 3,
711}
712
713/// The instrument class.
714#[repr(C)]
715#[derive(
716    Copy,
717    Clone,
718    Debug,
719    Display,
720    Hash,
721    PartialEq,
722    Eq,
723    PartialOrd,
724    Ord,
725    AsRefStr,
726    FromRepr,
727    EnumIter,
728    EnumString,
729)]
730#[strum(ascii_case_insensitive)]
731#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
732#[cfg_attr(
733    feature = "python",
734    pyo3::pyclass(
735        frozen,
736        eq,
737        eq_int,
738        module = "nautilus_trader.model",
739        from_py_object,
740        rename_all = "SCREAMING_SNAKE_CASE",
741    )
742)]
743#[cfg_attr(
744    feature = "python",
745    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
746)]
747pub enum InstrumentClass {
748    /// A spot market instrument class. The current market price of an instrument that is bought or sold for immediate delivery and payment.
749    Spot = 1,
750    /// A swap instrument class. A derivative contract through which two parties exchange the cash flows or liabilities from two different financial instruments.
751    Swap = 2,
752    /// A futures contract instrument class. A legal agreement to buy or sell an asset at a predetermined price at a specified time in the future.
753    Future = 3,
754    /// A futures spread instrument class. A strategy involving the use of futures contracts to take advantage of price differentials between different contract months, underlying assets, or marketplaces.
755    FuturesSpread = 4,
756    /// A forward derivative instrument class. A customized contract between two parties to buy or sell an asset at a specified price on a future date.
757    Forward = 5,
758    /// A contract-for-difference (CFD) instrument class. A contract between an investor and a CFD broker to exchange the difference in the value of a financial product between the time the contract opens and closes.
759    Cfd = 6,
760    /// A bond instrument class. A type of debt investment where an investor loans money to an entity (typically corporate or governmental) which borrows the funds for a defined period of time at a variable or fixed interest rate.
761    Bond = 7,
762    /// An option contract instrument class. A type of derivative that gives the holder the right, but not the obligation, to buy or sell an underlying asset at a predetermined price before or at a certain future date.
763    Option = 8,
764    /// An option spread instrument class. A strategy involving the purchase and/or sale of multiple option contracts on the same underlying asset with different strike prices or expiration dates to hedge risk or speculate on price movements.
765    OptionSpread = 9,
766    /// A warrant instrument class. A derivative that gives the holder the right, but not the obligation, to buy or sell a security - most commonly an equity - at a certain price before expiration.
767    Warrant = 10,
768    /// A sports betting instrument class. A financialized derivative that allows wagering on the outcome of sports events using structured contracts or prediction markets.
769    SportsBetting = 11,
770    /// A binary option instrument class. A type of derivative where the payoff is either a fixed monetary amount or nothing, depending on whether the price of an underlying asset is above or below a predetermined level at expiration.
771    BinaryOption = 12,
772}
773
774impl InstrumentClass {
775    /// Returns whether this instrument class has an expiration.
776    #[must_use]
777    pub const fn has_expiration(&self) -> bool {
778        matches!(
779            self,
780            Self::Future | Self::FuturesSpread | Self::Option | Self::OptionSpread
781        )
782    }
783
784    /// Returns whether this instrument class allows negative prices.
785    #[must_use]
786    pub const fn allows_negative_price(&self) -> bool {
787        matches!(
788            self,
789            Self::Option | Self::FuturesSpread | Self::OptionSpread
790        )
791    }
792
793    /// Returns the [`InstrumentClass`] for the parent-symbol suffix, if recognised.
794    ///
795    /// Matches strict uppercase forms only. Both Databento-style abbreviations
796    /// (`FUT`, `OPT`) and long forms (`FUTURE`, `OPTION`) are accepted.
797    #[must_use]
798    pub fn try_from_parent_suffix(suffix: &str) -> Option<Self> {
799        match suffix {
800            "FUT" | "FUTURE" => Some(Self::Future),
801            "OPT" | "OPTION" => Some(Self::Option),
802            _ => None,
803        }
804    }
805
806    /// Returns the canonical parent-symbol suffix for this class, if one exists.
807    ///
808    /// Always emits the short form (`FUT`, `OPT`) so that adapters constructing
809    /// parent ids produce a single canonical string per class.
810    #[must_use]
811    pub const fn parent_suffix(self) -> Option<&'static str> {
812        match self {
813            Self::Future => Some("FUT"),
814            Self::Option => Some("OPT"),
815            _ => None,
816        }
817    }
818}
819
820/// The type of event for an instrument close.
821#[repr(C)]
822#[derive(
823    Copy,
824    Clone,
825    Debug,
826    Display,
827    Hash,
828    PartialEq,
829    Eq,
830    PartialOrd,
831    Ord,
832    AsRefStr,
833    FromRepr,
834    EnumIter,
835    EnumString,
836)]
837#[strum(ascii_case_insensitive)]
838#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
839#[cfg_attr(
840    feature = "python",
841    pyo3::pyclass(
842        frozen,
843        eq,
844        eq_int,
845        module = "nautilus_trader.model",
846        from_py_object,
847        rename_all = "SCREAMING_SNAKE_CASE",
848    )
849)]
850#[cfg_attr(
851    feature = "python",
852    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
853)]
854pub enum InstrumentCloseType {
855    /// When the market session ended.
856    EndOfSession = 1,
857    /// When the instrument expiration was reached.
858    ContractExpired = 2,
859}
860
861/// Convert the given `value` to an [`InstrumentCloseType`].
862impl FromU8 for InstrumentCloseType {
863    fn from_u8(value: u8) -> Option<Self> {
864        match value {
865            1 => Some(Self::EndOfSession),
866            2 => Some(Self::ContractExpired),
867            _ => None,
868        }
869    }
870}
871
872/// The liquidity side for a trade.
873#[repr(C)]
874#[derive(
875    Copy,
876    Clone,
877    Debug,
878    Display,
879    Hash,
880    PartialEq,
881    Eq,
882    PartialOrd,
883    Ord,
884    AsRefStr,
885    FromRepr,
886    EnumIter,
887    EnumString,
888)]
889#[strum(ascii_case_insensitive)]
890#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
891#[cfg_attr(
892    feature = "python",
893    pyo3::pyclass(
894        frozen,
895        eq,
896        eq_int,
897        module = "nautilus_trader.model",
898        from_py_object,
899        rename_all = "SCREAMING_SNAKE_CASE",
900    )
901)]
902#[cfg_attr(
903    feature = "python",
904    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
905)]
906pub enum LiquiditySide {
907    /// No liquidity side specified.
908    NoLiquiditySide = 0,
909    /// The order passively provided liquidity to the market to complete the trade (made a market).
910    Maker = 1,
911    /// The order aggressively took liquidity from the market to complete the trade.
912    Taker = 2,
913}
914
915/// The status of an individual market on a trading venue.
916#[repr(C)]
917#[derive(
918    Copy,
919    Clone,
920    Debug,
921    Display,
922    Hash,
923    PartialEq,
924    Eq,
925    PartialOrd,
926    Ord,
927    AsRefStr,
928    FromRepr,
929    EnumIter,
930    EnumString,
931)]
932#[strum(ascii_case_insensitive)]
933#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
934#[cfg_attr(
935    feature = "python",
936    pyo3::pyclass(
937        frozen,
938        eq,
939        eq_int,
940        module = "nautilus_trader.model",
941        from_py_object,
942        rename_all = "SCREAMING_SNAKE_CASE",
943    )
944)]
945#[cfg_attr(
946    feature = "python",
947    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
948)]
949pub enum MarketStatus {
950    /// The instrument is trading.
951    Open = 1,
952    /// Trading in the instrument has closed.
953    Closed = 2,
954    /// Trading in the instrument has been paused.
955    Paused = 3,
956    /// Trading in the instrument has been halted.
957    Halted = 4,
958    /// Trading in the instrument has been suspended.
959    Suspended = 5,
960    /// Trading in the instrument is not available.
961    NotAvailable = 6,
962}
963
964/// An action affecting the status of an individual market on a trading venue.
965#[repr(C)]
966#[derive(
967    Copy,
968    Clone,
969    Debug,
970    Display,
971    Hash,
972    PartialEq,
973    Eq,
974    PartialOrd,
975    Ord,
976    AsRefStr,
977    FromRepr,
978    EnumIter,
979    EnumString,
980)]
981#[strum(ascii_case_insensitive)]
982#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
983#[cfg_attr(
984    feature = "python",
985    pyo3::pyclass(
986        frozen,
987        eq,
988        eq_int,
989        module = "nautilus_trader.model",
990        from_py_object,
991        rename_all = "SCREAMING_SNAKE_CASE",
992    )
993)]
994#[cfg_attr(
995    feature = "python",
996    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
997)]
998pub enum MarketStatusAction {
999    /// No change.
1000    None = 0,
1001    /// The instrument is in a pre-open period.
1002    PreOpen = 1,
1003    /// The instrument is in a pre-cross period.
1004    PreCross = 2,
1005    /// The instrument is quoting but not trading.
1006    Quoting = 3,
1007    /// The instrument is in a cross/auction.
1008    Cross = 4,
1009    /// The instrument is being opened through a trading rotation.
1010    Rotation = 5,
1011    /// A new price indication is available for the instrument.
1012    NewPriceIndication = 6,
1013    /// The instrument is trading.
1014    Trading = 7,
1015    /// Trading in the instrument has been halted.
1016    Halt = 8,
1017    /// Trading in the instrument has been paused.
1018    Pause = 9,
1019    /// Trading in the instrument has been suspended.
1020    Suspend = 10,
1021    /// The instrument is in a pre-close period.
1022    PreClose = 11,
1023    /// Trading in the instrument has closed.
1024    Close = 12,
1025    /// The instrument is in a post-close period.
1026    PostClose = 13,
1027    /// A change in short-selling restrictions.
1028    ShortSellRestrictionChange = 14,
1029    /// The instrument is not available for trading, either trading has closed or been halted.
1030    NotAvailableForTrading = 15,
1031}
1032
1033/// Convert the given `value` to a [`MarketStatusAction`].
1034impl FromU16 for MarketStatusAction {
1035    fn from_u16(value: u16) -> Option<Self> {
1036        match value {
1037            0 => Some(Self::None),
1038            1 => Some(Self::PreOpen),
1039            2 => Some(Self::PreCross),
1040            3 => Some(Self::Quoting),
1041            4 => Some(Self::Cross),
1042            5 => Some(Self::Rotation),
1043            6 => Some(Self::NewPriceIndication),
1044            7 => Some(Self::Trading),
1045            8 => Some(Self::Halt),
1046            9 => Some(Self::Pause),
1047            10 => Some(Self::Suspend),
1048            11 => Some(Self::PreClose),
1049            12 => Some(Self::Close),
1050            13 => Some(Self::PostClose),
1051            14 => Some(Self::ShortSellRestrictionChange),
1052            15 => Some(Self::NotAvailableForTrading),
1053            _ => None,
1054        }
1055    }
1056}
1057
1058/// The order management system (OMS) type for a trading venue or trading strategy.
1059#[repr(C)]
1060#[derive(
1061    Copy,
1062    Clone,
1063    Debug,
1064    Default,
1065    Display,
1066    Hash,
1067    PartialEq,
1068    Eq,
1069    PartialOrd,
1070    Ord,
1071    AsRefStr,
1072    FromRepr,
1073    EnumIter,
1074    EnumString,
1075)]
1076#[strum(ascii_case_insensitive)]
1077#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1078#[cfg_attr(
1079    feature = "python",
1080    pyo3::pyclass(
1081        frozen,
1082        eq,
1083        eq_int,
1084        module = "nautilus_trader.model",
1085        from_py_object,
1086        rename_all = "SCREAMING_SNAKE_CASE",
1087    )
1088)]
1089#[cfg_attr(
1090    feature = "python",
1091    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1092)]
1093pub enum OmsType {
1094    /// There is no specific type of order management specified (will defer to the venue OMS).
1095    #[default]
1096    Unspecified = 0,
1097    /// The netting type where there is one position per instrument.
1098    Netting = 1,
1099    /// The hedging type where there can be multiple positions per instrument.
1100    /// This can be in LONG/SHORT directions, by position/ticket ID, or tracked virtually by
1101    /// Nautilus.
1102    Hedging = 2,
1103}
1104
1105/// The kind of option contract.
1106#[repr(C)]
1107#[derive(
1108    Copy,
1109    Clone,
1110    Debug,
1111    Display,
1112    Hash,
1113    PartialEq,
1114    Eq,
1115    PartialOrd,
1116    Ord,
1117    AsRefStr,
1118    FromRepr,
1119    EnumIter,
1120    EnumString,
1121)]
1122#[strum(ascii_case_insensitive)]
1123#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1124#[cfg_attr(
1125    feature = "python",
1126    pyo3::pyclass(
1127        frozen,
1128        eq,
1129        eq_int,
1130        module = "nautilus_trader.model",
1131        from_py_object,
1132        rename_all = "SCREAMING_SNAKE_CASE",
1133    )
1134)]
1135#[cfg_attr(
1136    feature = "python",
1137    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1138)]
1139pub enum OptionKind {
1140    /// A Call option gives the holder the right, but not the obligation, to buy an underlying asset at a specified strike price within a specified period of time.
1141    Call = 1,
1142    /// A Put option gives the holder the right, but not the obligation, to sell an underlying asset at a specified strike price within a specified period of time.
1143    Put = 2,
1144}
1145
1146/// The numeraire convention for option greeks published by a venue.
1147///
1148/// Crypto option venues commonly publish two parallel greek sets for the same
1149/// instrument: Black-Scholes greeks in USD, and price-adjusted greeks denominated
1150/// in the underlying/coin units. Deribit and OKX both expose the distinction;
1151/// see the OKX reference for the canonical definition:
1152/// <https://www.okx.com/docs-v5/en/#public-data-websocket-option-market-data>.
1153///
1154/// This is orthogonal to the percent-greeks transformation in the internal
1155/// [`GreeksCalculator`](../../../nautilus_common/greeks/struct.GreeksCalculator.html),
1156/// which rescales the delta/gamma input step rather than the numeraire.
1157#[repr(C)]
1158#[derive(
1159    Copy,
1160    Clone,
1161    Debug,
1162    Default,
1163    Display,
1164    Hash,
1165    PartialEq,
1166    Eq,
1167    PartialOrd,
1168    Ord,
1169    AsRefStr,
1170    FromRepr,
1171    EnumIter,
1172    EnumString,
1173)]
1174#[strum(ascii_case_insensitive)]
1175#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1176#[cfg_attr(
1177    feature = "python",
1178    pyo3::pyclass(
1179        frozen,
1180        eq,
1181        eq_int,
1182        module = "nautilus_trader.model",
1183        from_py_object,
1184        rename_all = "SCREAMING_SNAKE_CASE",
1185    )
1186)]
1187#[cfg_attr(
1188    feature = "python",
1189    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1190)]
1191pub enum GreeksConvention {
1192    /// Black-Scholes greeks in USD.
1193    #[default]
1194    BlackScholes = 1,
1195    /// Price-adjusted greeks in the underlying/coin units.
1196    PriceAdjusted = 2,
1197}
1198
1199/// Defines when OTO (One-Triggers-Other) child orders are released.
1200#[repr(C)]
1201#[derive(
1202    Copy,
1203    Clone,
1204    Debug,
1205    Default,
1206    Display,
1207    Hash,
1208    PartialEq,
1209    Eq,
1210    PartialOrd,
1211    Ord,
1212    AsRefStr,
1213    FromRepr,
1214    EnumIter,
1215    EnumString,
1216)]
1217#[strum(ascii_case_insensitive)]
1218#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1219#[cfg_attr(
1220    feature = "python",
1221    pyo3::pyclass(
1222        frozen,
1223        eq,
1224        eq_int,
1225        module = "nautilus_trader.model",
1226        from_py_object,
1227        rename_all = "SCREAMING_SNAKE_CASE",
1228    )
1229)]
1230#[cfg_attr(
1231    feature = "python",
1232    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1233)]
1234pub enum OtoTriggerMode {
1235    /// Release child order(s) pro-rata to each partial fill (default).
1236    #[default]
1237    Partial = 0,
1238    /// Release child order(s) only once the parent is fully filled.
1239    Full = 1,
1240}
1241
1242/// The order side (BUY or SELL).
1243///
1244/// Python retains `NO_ORDER_SIDE` as a compatibility alias for `None`. The alias is not an enum
1245/// variant and may be removed in a future version.
1246#[repr(C)]
1247#[derive(
1248    Copy,
1249    Clone,
1250    Debug,
1251    Display,
1252    Hash,
1253    PartialEq,
1254    Eq,
1255    PartialOrd,
1256    Ord,
1257    AsRefStr,
1258    FromRepr,
1259    EnumIter,
1260    EnumString,
1261)]
1262#[strum(ascii_case_insensitive)]
1263#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1264#[cfg_attr(
1265    feature = "python",
1266    pyo3::pyclass(
1267        frozen,
1268        eq,
1269        eq_int,
1270        module = "nautilus_trader.model",
1271        from_py_object,
1272        rename_all = "SCREAMING_SNAKE_CASE",
1273    )
1274)]
1275#[cfg_attr(
1276    feature = "python",
1277    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1278)]
1279pub enum OrderSide {
1280    /// The order is a BUY.
1281    Buy = 1,
1282    /// The order is a SELL.
1283    Sell = 2,
1284}
1285
1286impl OrderSide {
1287    /// Returns the opposite order side.
1288    #[must_use]
1289    pub fn opposite(&self) -> Self {
1290        match &self {
1291            Self::Buy => Self::Sell,
1292            Self::Sell => Self::Buy,
1293        }
1294    }
1295}
1296
1297/// The status for a specific order.
1298///
1299/// An order is considered _open_ for the following status:
1300///  - `ACCEPTED`
1301///  - `TRIGGERED`
1302///  - `PENDING_UPDATE`
1303///  - `PENDING_CANCEL`
1304///  - `PARTIALLY_FILLED`
1305///
1306/// An order is considered _in-flight_ for the following status:
1307///  - `SUBMITTED`
1308///  - `PENDING_UPDATE`
1309///  - `PENDING_CANCEL`
1310///
1311/// An order is considered _closed_ for the following status:
1312///  - `DENIED`
1313///  - `REJECTED`
1314///  - `CANCELED`
1315///  - `EXPIRED`
1316///  - `FILLED`
1317///  - `VOIDED`
1318#[repr(C)]
1319#[derive(
1320    Copy,
1321    Clone,
1322    Debug,
1323    Display,
1324    Hash,
1325    PartialEq,
1326    Eq,
1327    PartialOrd,
1328    Ord,
1329    AsRefStr,
1330    FromRepr,
1331    EnumIter,
1332    EnumString,
1333)]
1334#[strum(ascii_case_insensitive)]
1335#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1336#[cfg_attr(
1337    feature = "python",
1338    pyo3::pyclass(
1339        frozen,
1340        eq,
1341        eq_int,
1342        module = "nautilus_trader.model",
1343        from_py_object,
1344        rename_all = "SCREAMING_SNAKE_CASE",
1345    )
1346)]
1347#[cfg_attr(
1348    feature = "python",
1349    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1350)]
1351pub enum OrderStatus {
1352    /// The order is initialized (instantiated) within the Nautilus system.
1353    Initialized = 1,
1354    /// The order was denied by the Nautilus system, either for being invalid, unprocessable, or exceeding a risk limit.
1355    Denied = 2,
1356    /// The order became emulated by the Nautilus system in the `OrderEmulator` component.
1357    Emulated = 3,
1358    /// The order was released by the Nautilus system from the `OrderEmulator` component.
1359    Released = 4,
1360    /// The order was submitted by the Nautilus system to the external service or trading venue (awaiting acknowledgement).
1361    Submitted = 5,
1362    /// The order was acknowledged by the trading venue as being received and valid (may now be working).
1363    Accepted = 6,
1364    /// The order was rejected by the trading venue.
1365    Rejected = 7,
1366    /// The order was canceled (closed/done).
1367    Canceled = 8,
1368    /// The order reached a GTD expiration (closed/done).
1369    Expired = 9,
1370    /// The order STOP price was triggered on a trading venue.
1371    Triggered = 10,
1372    /// The order is currently pending a request to modify on a trading venue.
1373    PendingUpdate = 11,
1374    /// The order is currently pending a request to cancel on a trading venue.
1375    PendingCancel = 12,
1376    /// The order has been partially filled on a trading venue.
1377    PartiallyFilled = 13,
1378    /// The order has been completely filled on a trading venue (closed/done).
1379    Filled = 14,
1380    /// The order is terminal after an authoritative venue void or fill correction.
1381    Voided = 15,
1382}
1383
1384impl OrderStatus {
1385    /// Returns whether the order status represents an open/working order.
1386    #[must_use]
1387    pub const fn is_open(self) -> bool {
1388        matches!(
1389            self,
1390            Self::Submitted
1391                | Self::Accepted
1392                | Self::Triggered
1393                | Self::PendingUpdate
1394                | Self::PendingCancel
1395                | Self::PartiallyFilled
1396        )
1397    }
1398
1399    /// Returns whether the order status represents a terminal (closed) state.
1400    #[must_use]
1401    pub const fn is_closed(self) -> bool {
1402        matches!(
1403            self,
1404            Self::Denied
1405                | Self::Rejected
1406                | Self::Canceled
1407                | Self::Expired
1408                | Self::Filled
1409                | Self::Voided
1410        )
1411    }
1412
1413    /// Returns whether the order can be cancelled from this status.
1414    #[must_use]
1415    pub const fn is_cancellable(self) -> bool {
1416        matches!(
1417            self,
1418            Self::Accepted | Self::Triggered | Self::PendingUpdate | Self::PartiallyFilled
1419        )
1420    }
1421}
1422
1423/// The type of order.
1424#[repr(C)]
1425#[derive(
1426    Copy,
1427    Clone,
1428    Debug,
1429    Display,
1430    Hash,
1431    PartialEq,
1432    Eq,
1433    PartialOrd,
1434    Ord,
1435    AsRefStr,
1436    FromRepr,
1437    EnumIter,
1438    EnumString,
1439)]
1440#[strum(ascii_case_insensitive)]
1441#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1442#[cfg_attr(
1443    feature = "python",
1444    pyo3::pyclass(
1445        frozen,
1446        eq,
1447        eq_int,
1448        module = "nautilus_trader.model",
1449        from_py_object,
1450        rename_all = "SCREAMING_SNAKE_CASE",
1451    )
1452)]
1453#[cfg_attr(
1454    feature = "python",
1455    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1456)]
1457pub enum OrderType {
1458    /// A market order to buy or sell at the best available price in the current market.
1459    Market = 1,
1460    /// A limit order to buy or sell at a specific price or better.
1461    Limit = 2,
1462    /// A stop market order to buy or sell once the price reaches the specified stop/trigger price. When the stop price is reached, the order effectively becomes a market order.
1463    StopMarket = 3,
1464    /// A stop limit order to buy or sell which combines the features of a stop order and a limit order. Once the stop/trigger price is reached, a stop-limit order effectively becomes a limit order.
1465    StopLimit = 4,
1466    /// A market-to-limit order is a market order that is to be executed as a limit order at the current best market price after reaching the market.
1467    MarketToLimit = 5,
1468    /// A market-if-touched order effectively becomes a market order when the specified trigger price is reached.
1469    MarketIfTouched = 6,
1470    /// A limit-if-touched order effectively becomes a limit order when the specified trigger price is reached.
1471    LimitIfTouched = 7,
1472    /// A trailing stop market order sets the stop/trigger price at a fixed "trailing offset" amount from the market.
1473    TrailingStopMarket = 8,
1474    /// A trailing stop limit order combines the features of a trailing stop order with those of a limit order.
1475    TrailingStopLimit = 9,
1476}
1477
1478/// The type of position adjustment.
1479#[repr(C)]
1480#[derive(
1481    Copy,
1482    Clone,
1483    Debug,
1484    Display,
1485    Hash,
1486    PartialEq,
1487    Eq,
1488    PartialOrd,
1489    Ord,
1490    AsRefStr,
1491    FromRepr,
1492    EnumIter,
1493    EnumString,
1494)]
1495#[strum(ascii_case_insensitive)]
1496#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1497#[cfg_attr(
1498    feature = "python",
1499    pyo3::pyclass(
1500        frozen,
1501        eq,
1502        eq_int,
1503        module = "nautilus_trader.model",
1504        from_py_object,
1505        rename_all = "SCREAMING_SNAKE_CASE",
1506    )
1507)]
1508#[cfg_attr(
1509    feature = "python",
1510    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1511)]
1512pub enum PositionAdjustmentType {
1513    /// Commission adjustment affecting position quantity.
1514    Commission = 1,
1515    /// Funding payment affecting position realized PnL.
1516    Funding = 2,
1517}
1518
1519impl FromU8 for PositionAdjustmentType {
1520    fn from_u8(value: u8) -> Option<Self> {
1521        match value {
1522            1 => Some(Self::Commission),
1523            2 => Some(Self::Funding),
1524            _ => None,
1525        }
1526    }
1527}
1528
1529/// The position side (FLAT, LONG, or SHORT).
1530///
1531/// Python retains `NO_POSITION_SIDE` as a compatibility alias for `None`. The alias is not an enum
1532/// variant and may be removed in a future version.
1533#[repr(C)]
1534#[derive(
1535    Copy,
1536    Clone,
1537    Debug,
1538    Display,
1539    Hash,
1540    PartialEq,
1541    Eq,
1542    PartialOrd,
1543    Ord,
1544    AsRefStr,
1545    FromRepr,
1546    EnumIter,
1547    EnumString,
1548)]
1549#[strum(ascii_case_insensitive)]
1550#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1551#[cfg_attr(
1552    feature = "python",
1553    pyo3::pyclass(
1554        frozen,
1555        eq,
1556        eq_int,
1557        module = "nautilus_trader.model",
1558        from_py_object,
1559        rename_all = "SCREAMING_SNAKE_CASE",
1560    )
1561)]
1562#[cfg_attr(
1563    feature = "python",
1564    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1565)]
1566pub enum PositionSide {
1567    /// A neutral/flat position, where no position is currently held in the market.
1568    Flat = 1,
1569    /// A long position in the market, typically acquired through one or many BUY orders.
1570    Long = 2,
1571    /// A short position in the market, typically acquired through one or many SELL orders.
1572    Short = 3,
1573}
1574
1575/// Serde compatibility for an optional order side previously encoded with `NO_ORDER_SIDE`.
1576pub mod serde_option_order_side {
1577    use serde::{Deserializer, Serializer};
1578
1579    use super::{OrderSide, deserialize_optional_enum, serialize_optional_enum};
1580
1581    /// Serializes an optional order side using the legacy no-side token.
1582    ///
1583    /// # Errors
1584    ///
1585    /// Returns an error if the serializer cannot encode the value.
1586    pub fn serialize<S>(value: &Option<OrderSide>, serializer: S) -> Result<S::Ok, S::Error>
1587    where
1588        S: Serializer,
1589    {
1590        serialize_optional_enum(value.as_ref(), serializer, "NO_ORDER_SIDE")
1591    }
1592
1593    /// Deserializes an optional order side from a side token or null.
1594    ///
1595    /// # Errors
1596    ///
1597    /// Returns an error if the input is not a valid order side.
1598    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OrderSide>, D::Error>
1599    where
1600        D: Deserializer<'de>,
1601    {
1602        deserialize_optional_enum(
1603            deserializer,
1604            "NO_ORDER_SIDE",
1605            "BUY, SELL, NO_ORDER_SIDE, or null",
1606        )
1607    }
1608}
1609
1610/// Serde compatibility for an optional position side previously encoded with `NO_POSITION_SIDE`.
1611pub mod serde_option_position_side {
1612    use serde::{Deserializer, Serializer};
1613
1614    use super::{PositionSide, deserialize_optional_enum, serialize_optional_enum};
1615
1616    /// Serializes an optional position side using the legacy no-side token.
1617    ///
1618    /// # Errors
1619    ///
1620    /// Returns an error if the serializer cannot encode the value.
1621    pub fn serialize<S>(value: &Option<PositionSide>, serializer: S) -> Result<S::Ok, S::Error>
1622    where
1623        S: Serializer,
1624    {
1625        serialize_optional_enum(value.as_ref(), serializer, "NO_POSITION_SIDE")
1626    }
1627
1628    /// Deserializes an optional position side from a side token or null.
1629    ///
1630    /// # Errors
1631    ///
1632    /// Returns an error if the input is not a valid position side.
1633    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<PositionSide>, D::Error>
1634    where
1635        D: Deserializer<'de>,
1636    {
1637        deserialize_optional_enum(
1638            deserializer,
1639            "NO_POSITION_SIDE",
1640            "FLAT, LONG, SHORT, NO_POSITION_SIDE, or null",
1641        )
1642    }
1643}
1644
1645/// Serde compatibility for an optional contingency type previously encoded with `NO_CONTINGENCY`.
1646pub mod serde_option_contingency_type {
1647    use serde::{Deserializer, Serializer};
1648
1649    use super::{ContingencyType, deserialize_optional_enum, serialize_optional_enum};
1650
1651    /// Serializes an optional contingency type using the legacy no-contingency token.
1652    ///
1653    /// # Errors
1654    ///
1655    /// Returns an error if the serializer cannot encode the value.
1656    pub fn serialize<S>(value: &Option<ContingencyType>, serializer: S) -> Result<S::Ok, S::Error>
1657    where
1658        S: Serializer,
1659    {
1660        serialize_optional_enum(value.as_ref(), serializer, "NO_CONTINGENCY")
1661    }
1662
1663    /// Deserializes an optional contingency type from a contingency token or null.
1664    ///
1665    /// # Errors
1666    ///
1667    /// Returns an error if the input is not a valid contingency type.
1668    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<ContingencyType>, D::Error>
1669    where
1670        D: Deserializer<'de>,
1671    {
1672        deserialize_optional_enum(
1673            deserializer,
1674            "NO_CONTINGENCY",
1675            "OCO, OTO, OUO, NO_CONTINGENCY, or null",
1676        )
1677    }
1678}
1679
1680/// Serde compatibility for an optional trailing offset type previously encoded with
1681/// `NO_TRAILING_OFFSET`.
1682pub mod serde_option_trailing_offset_type {
1683    use serde::{Deserializer, Serializer};
1684
1685    use super::{TrailingOffsetType, deserialize_optional_enum, serialize_optional_enum};
1686
1687    /// Serializes an optional trailing offset type using the legacy no-offset token.
1688    ///
1689    /// # Errors
1690    ///
1691    /// Returns an error if the serializer cannot encode the value.
1692    pub fn serialize<S>(
1693        value: &Option<TrailingOffsetType>,
1694        serializer: S,
1695    ) -> Result<S::Ok, S::Error>
1696    where
1697        S: Serializer,
1698    {
1699        serialize_optional_enum(value.as_ref(), serializer, "NO_TRAILING_OFFSET")
1700    }
1701
1702    /// Deserializes an optional trailing offset type from an offset token or null.
1703    ///
1704    /// # Errors
1705    ///
1706    /// Returns an error if the input is not a valid trailing offset type.
1707    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<TrailingOffsetType>, D::Error>
1708    where
1709        D: Deserializer<'de>,
1710    {
1711        deserialize_optional_enum(
1712            deserializer,
1713            "NO_TRAILING_OFFSET",
1714            "PRICE, BASIS_POINTS, TICKS, PRICE_TIER, NO_TRAILING_OFFSET, or null",
1715        )
1716    }
1717}
1718
1719/// Serde compatibility for an optional trigger type previously encoded with `NO_TRIGGER`.
1720pub mod serde_option_trigger_type {
1721    use serde::{Deserializer, Serializer};
1722
1723    use super::{TriggerType, deserialize_optional_enum, serialize_optional_enum};
1724
1725    /// Serializes an optional trigger type using the legacy no-trigger token.
1726    ///
1727    /// # Errors
1728    ///
1729    /// Returns an error if the serializer cannot encode the value.
1730    pub fn serialize<S>(value: &Option<TriggerType>, serializer: S) -> Result<S::Ok, S::Error>
1731    where
1732        S: Serializer,
1733    {
1734        serialize_optional_enum(value.as_ref(), serializer, "NO_TRIGGER")
1735    }
1736
1737    /// Deserializes an optional trigger type from a trigger token or null.
1738    ///
1739    /// # Errors
1740    ///
1741    /// Returns an error if the input is not a valid trigger type.
1742    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<TriggerType>, D::Error>
1743    where
1744        D: Deserializer<'de>,
1745    {
1746        deserialize_optional_enum(
1747            deserializer,
1748            "NO_TRIGGER",
1749            "a trigger type, NO_TRIGGER, or null",
1750        )
1751    }
1752}
1753
1754fn serialize_optional_enum<S, T>(
1755    value: Option<&T>,
1756    serializer: S,
1757    none_token: &'static str,
1758) -> Result<S::Ok, S::Error>
1759where
1760    S: Serializer,
1761    T: AsRef<str>,
1762{
1763    serializer.serialize_str(value.map_or(none_token, AsRef::as_ref))
1764}
1765
1766fn deserialize_optional_enum<'de, D, T>(
1767    deserializer: D,
1768    none_token: &'static str,
1769    expected: &'static str,
1770) -> Result<Option<T>, D::Error>
1771where
1772    D: Deserializer<'de>,
1773    T: FromStr,
1774    T::Err: Display,
1775{
1776    struct OptionalEnumVisitor<T> {
1777        none_token: &'static str,
1778        expected: &'static str,
1779        marker: PhantomData<T>,
1780    }
1781
1782    impl<'de, T> serde::de::Visitor<'de> for OptionalEnumVisitor<T>
1783    where
1784        T: FromStr,
1785        T::Err: Display,
1786    {
1787        type Value = Option<T>;
1788
1789        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1790            formatter.write_str(self.expected)
1791        }
1792
1793        fn visit_none<E>(self) -> Result<Self::Value, E> {
1794            Ok(None)
1795        }
1796
1797        fn visit_unit<E>(self) -> Result<Self::Value, E> {
1798            Ok(None)
1799        }
1800
1801        fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1802        where
1803            D: Deserializer<'de>,
1804        {
1805            let value = Cow::<'de, str>::deserialize(deserializer)?;
1806            if value.eq_ignore_ascii_case(self.none_token) {
1807                Ok(None)
1808            } else {
1809                T::from_str(&value)
1810                    .map(Some)
1811                    .map_err(serde::de::Error::custom)
1812            }
1813        }
1814    }
1815
1816    deserializer.deserialize_option(OptionalEnumVisitor {
1817        none_token,
1818        expected,
1819        marker: PhantomData,
1820    })
1821}
1822
1823/// The type of price for an instrument in a market.
1824#[repr(C)]
1825#[derive(
1826    Copy,
1827    Clone,
1828    Debug,
1829    Display,
1830    Hash,
1831    PartialEq,
1832    Eq,
1833    PartialOrd,
1834    Ord,
1835    AsRefStr,
1836    FromRepr,
1837    EnumIter,
1838    EnumString,
1839)]
1840#[strum(ascii_case_insensitive)]
1841#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1842#[cfg_attr(
1843    feature = "python",
1844    pyo3::pyclass(
1845        frozen,
1846        eq,
1847        eq_int,
1848        module = "nautilus_trader.model",
1849        from_py_object,
1850        rename_all = "SCREAMING_SNAKE_CASE",
1851    )
1852)]
1853#[cfg_attr(
1854    feature = "python",
1855    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1856)]
1857pub enum PriceType {
1858    // Bar price sources are not yet consistent with mark/index price subscriptions. The open
1859    // decisions are whether to add a `PriceType::Index` variant, whether to aggregate bars
1860    // internally from mark/index updates, and what the documented source derivation order is.
1861    /// The best quoted price at which buyers are willing to buy a quantity of an instrument.
1862    /// Often considered the best bid in the order book.
1863    Bid = 1,
1864    /// The best quoted price at which sellers are willing to sell a quantity of an instrument.
1865    /// Often considered the best ask in the order book.
1866    Ask = 2,
1867    /// The arithmetic midpoint between the best bid and ask quotes.
1868    Mid = 3,
1869    /// The price at which the last trade of an instrument was executed.
1870    Last = 4,
1871    /// A reference price reflecting an instrument's fair value, often used for portfolio
1872    /// calculations and risk management.
1873    Mark = 5,
1874}
1875
1876/// A record flag bit field, indicating event end and data information.
1877#[repr(C)]
1878#[derive(
1879    Copy,
1880    Clone,
1881    Debug,
1882    Display,
1883    Hash,
1884    PartialEq,
1885    Eq,
1886    PartialOrd,
1887    Ord,
1888    AsRefStr,
1889    FromRepr,
1890    EnumIter,
1891    EnumString,
1892)]
1893#[strum(ascii_case_insensitive)]
1894#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1895#[cfg_attr(
1896    feature = "python",
1897    pyo3::pyclass(
1898        frozen,
1899        eq,
1900        eq_int,
1901        module = "nautilus_trader.model",
1902        from_py_object,
1903        rename_all = "SCREAMING_SNAKE_CASE",
1904    )
1905)]
1906#[cfg_attr(
1907    feature = "python",
1908    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1909)]
1910#[allow(non_camel_case_types)]
1911pub enum RecordFlag {
1912    /// Last message in the book event or packet from the venue for a given `instrument_id`.
1913    F_LAST = 1 << 7, // 128
1914    /// Top-of-book message, not an individual order.
1915    F_TOB = 1 << 6, // 64
1916    /// Message sourced from a replay, such as a snapshot server.
1917    F_SNAPSHOT = 1 << 5, // 32
1918    /// Aggregated price level message, not an individual order.
1919    F_MBP = 1 << 4, // 16
1920    /// Reserved for future use.
1921    RESERVED_2 = 1 << 3, // 8
1922    /// Reserved for future use.
1923    RESERVED_1 = 1 << 2, // 4
1924}
1925
1926impl RecordFlag {
1927    /// Checks if the flag matches a given value.
1928    #[must_use]
1929    pub fn matches(self, value: u8) -> bool {
1930        (self as u8) & value != 0
1931    }
1932}
1933
1934/// The 'Time in Force' instruction for an order.
1935#[repr(C)]
1936#[derive(
1937    Copy,
1938    Clone,
1939    Debug,
1940    Display,
1941    Hash,
1942    PartialEq,
1943    Eq,
1944    PartialOrd,
1945    Ord,
1946    AsRefStr,
1947    FromRepr,
1948    EnumIter,
1949    EnumString,
1950)]
1951#[strum(ascii_case_insensitive)]
1952#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1953#[cfg_attr(
1954    feature = "python",
1955    pyo3::pyclass(
1956        frozen,
1957        eq,
1958        eq_int,
1959        module = "nautilus_trader.model",
1960        from_py_object,
1961        rename_all = "SCREAMING_SNAKE_CASE",
1962    )
1963)]
1964#[cfg_attr(
1965    feature = "python",
1966    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
1967)]
1968pub enum TimeInForce {
1969    /// Good Till Cancel (GTC) - Remains active until canceled.
1970    Gtc = 1,
1971    /// Immediate or Cancel (IOC) - Executes immediately to the extent possible, with any unfilled portion canceled.
1972    Ioc = 2,
1973    /// Fill or Kill (FOK) - Executes in its entirety immediately or is canceled if full execution is not possible.
1974    Fok = 3,
1975    /// Good Till Date (GTD) - Remains active until the specified expiration date or time is reached.
1976    Gtd = 4,
1977    /// Day - Remains active until the close of the current trading session.
1978    Day = 5,
1979    /// At the Opening (ATO) - Executes at the market opening or expires if not filled.
1980    AtTheOpen = 6,
1981    /// At the Closing (ATC) - Executes at the market close or expires if not filled.
1982    AtTheClose = 7,
1983}
1984
1985/// The trading state for a node.
1986#[repr(C)]
1987#[derive(
1988    Copy,
1989    Clone,
1990    Debug,
1991    Display,
1992    Hash,
1993    PartialEq,
1994    Eq,
1995    PartialOrd,
1996    Ord,
1997    AsRefStr,
1998    FromRepr,
1999    EnumIter,
2000    EnumString,
2001)]
2002#[strum(ascii_case_insensitive)]
2003#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
2004#[cfg_attr(
2005    feature = "python",
2006    pyo3::pyclass(
2007        frozen,
2008        eq,
2009        eq_int,
2010        module = "nautilus_trader.model",
2011        from_py_object,
2012        rename_all = "SCREAMING_SNAKE_CASE",
2013    )
2014)]
2015#[cfg_attr(
2016    feature = "python",
2017    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
2018)]
2019pub enum TradingState {
2020    /// Normal trading operations.
2021    Active = 1,
2022    /// Trading is completely halted, no new order commands will be emitted.
2023    Halted = 2,
2024    /// Only order commands which would cancel order, or reduce position sizes are permitted.
2025    Reducing = 3,
2026}
2027
2028/// The trailing offset type for an order type which specifies a trailing stop/trigger or limit price.
2029///
2030/// Python retains `NO_TRAILING_OFFSET` as a compatibility alias for `None`. The alias is not an enum
2031/// variant and may be removed in a future version.
2032#[repr(C)]
2033#[derive(
2034    Copy,
2035    Clone,
2036    Debug,
2037    Display,
2038    Hash,
2039    PartialEq,
2040    Eq,
2041    PartialOrd,
2042    Ord,
2043    AsRefStr,
2044    FromRepr,
2045    EnumIter,
2046    EnumString,
2047)]
2048#[strum(ascii_case_insensitive)]
2049#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
2050#[cfg_attr(
2051    feature = "python",
2052    pyo3::pyclass(
2053        frozen,
2054        eq,
2055        eq_int,
2056        module = "nautilus_trader.model",
2057        from_py_object,
2058        rename_all = "SCREAMING_SNAKE_CASE",
2059    )
2060)]
2061#[cfg_attr(
2062    feature = "python",
2063    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
2064)]
2065pub enum TrailingOffsetType {
2066    /// The trailing offset is based on a market price.
2067    Price = 1,
2068    /// The trailing offset is based on a percentage represented in basis points, of a market price.
2069    BasisPoints = 2,
2070    /// The trailing offset is based on the number of ticks from a market price.
2071    Ticks = 3,
2072    /// The trailing offset is based on a price tier set by a specific trading venue.
2073    PriceTier = 4,
2074}
2075
2076/// The trigger type for the stop/trigger price of an order.
2077///
2078/// Python retains `NO_TRIGGER` as a compatibility alias for `None`. The alias is not an enum variant
2079/// and may be removed in a future version.
2080#[repr(C)]
2081#[derive(
2082    Copy,
2083    Clone,
2084    Debug,
2085    Display,
2086    Hash,
2087    PartialEq,
2088    Eq,
2089    PartialOrd,
2090    Ord,
2091    AsRefStr,
2092    FromRepr,
2093    EnumIter,
2094    EnumString,
2095)]
2096#[strum(ascii_case_insensitive)]
2097#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
2098#[cfg_attr(
2099    feature = "python",
2100    pyo3::pyclass(
2101        frozen,
2102        eq,
2103        eq_int,
2104        module = "nautilus_trader.model",
2105        from_py_object,
2106        rename_all = "SCREAMING_SNAKE_CASE",
2107    )
2108)]
2109#[cfg_attr(
2110    feature = "python",
2111    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
2112)]
2113pub enum TriggerType {
2114    /// The default trigger type set by the trading venue.
2115    Default = 1,
2116    /// Based on the last traded price for the instrument.
2117    LastPrice = 2,
2118    /// Based on the mark price for the instrument.
2119    MarkPrice = 3,
2120    /// Based on the index price for the instrument.
2121    IndexPrice = 4,
2122    /// Based on the top-of-book quoted prices for the instrument.
2123    BidAsk = 5,
2124    /// Based on a 'double match' of the last traded price for the instrument
2125    DoubleLast = 6,
2126    /// Based on a 'double match' of the bid/ask price for the instrument
2127    DoubleBidAsk = 7,
2128    /// Based on both the [`TriggerType::LastPrice`] and [`TriggerType::BidAsk`].
2129    LastOrBidAsk = 8,
2130    /// Based on the mid-point of the [`TriggerType::BidAsk`].
2131    MidPoint = 9,
2132}
2133
2134enum_strum_serde!(AccountType);
2135enum_strum_serde!(AggregationSource);
2136enum_strum_serde!(AggressorSide);
2137enum_strum_serde!(AssetClass);
2138enum_strum_serde!(BarAggregation);
2139enum_strum_serde!(BarIntervalType);
2140enum_strum_serde!(BookAction);
2141enum_strum_serde!(BookType);
2142enum_strum_serde!(ContingencyType);
2143enum_strum_serde!(ContinuousFutureAdjustmentType);
2144enum_strum_serde!(CurrencyType);
2145enum_strum_serde!(GreeksConvention);
2146enum_strum_serde!(InstrumentClass);
2147enum_strum_serde!(InstrumentCloseType);
2148enum_strum_serde!(LiquiditySide);
2149enum_strum_serde!(MarketStatus);
2150enum_strum_serde!(MarketStatusAction);
2151enum_strum_serde!(OmsType);
2152enum_strum_serde!(OptionKind);
2153enum_strum_serde!(OrderSide);
2154enum_strum_serde!(OrderStatus);
2155enum_strum_serde!(OrderType);
2156enum_strum_serde!(PositionAdjustmentType);
2157enum_strum_serde!(PositionSide);
2158enum_strum_serde!(PriceType);
2159enum_strum_serde!(RecordFlag);
2160enum_strum_serde!(TimeInForce);
2161enum_strum_serde!(TradingState);
2162enum_strum_serde!(TrailingOffsetType);
2163enum_strum_serde!(TriggerType);
2164
2165#[cfg(test)]
2166mod tests {
2167    use rstest::rstest;
2168
2169    use super::*;
2170
2171    #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2172    struct OptionalSides {
2173        #[serde(with = "serde_option_order_side")]
2174        order: Option<OrderSide>,
2175        #[serde(with = "serde_option_position_side")]
2176        position: Option<PositionSide>,
2177    }
2178
2179    #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2180    struct OptionalOrderTypes {
2181        #[serde(with = "serde_option_contingency_type")]
2182        contingency: Option<ContingencyType>,
2183        #[serde(with = "serde_option_trailing_offset_type")]
2184        trailing_offset: Option<TrailingOffsetType>,
2185        #[serde(with = "serde_option_trigger_type")]
2186        trigger: Option<TriggerType>,
2187    }
2188
2189    #[rstest]
2190    fn test_optional_sides_serde_preserves_legacy_none_tokens() {
2191        let value = OptionalSides {
2192            order: None,
2193            position: None,
2194        };
2195
2196        let json = serde_json::to_string(&value).unwrap();
2197        let decoded: OptionalSides = serde_json::from_str(&json).unwrap();
2198
2199        assert_eq!(
2200            json,
2201            r#"{"order":"NO_ORDER_SIDE","position":"NO_POSITION_SIDE"}"#
2202        );
2203        assert_eq!(decoded, value);
2204    }
2205
2206    #[rstest]
2207    fn test_optional_sides_serde_accepts_null_and_valid_sides() {
2208        let json = r#"{"order":null,"position":"LONG"}"#;
2209        let decoded: OptionalSides = serde_json::from_str(json).unwrap();
2210
2211        assert_eq!(
2212            decoded,
2213            OptionalSides {
2214                order: None,
2215                position: Some(PositionSide::Long),
2216            }
2217        );
2218    }
2219
2220    #[rstest]
2221    fn test_optional_order_types_serde_preserves_legacy_none_tokens() {
2222        let value = OptionalOrderTypes {
2223            contingency: None,
2224            trailing_offset: None,
2225            trigger: None,
2226        };
2227
2228        let json = serde_json::to_string(&value).unwrap();
2229        let decoded: OptionalOrderTypes = serde_json::from_str(&json).unwrap();
2230
2231        assert_eq!(
2232            json,
2233            r#"{"contingency":"NO_CONTINGENCY","trailing_offset":"NO_TRAILING_OFFSET","trigger":"NO_TRIGGER"}"#,
2234        );
2235        assert_eq!(decoded, value);
2236    }
2237
2238    #[rstest]
2239    fn test_optional_order_types_serde_accepts_null_and_valid_values() {
2240        let json = r#"{"contingency":null,"trailing_offset":"PRICE","trigger":"LAST_PRICE"}"#;
2241        let decoded: OptionalOrderTypes = serde_json::from_str(json).unwrap();
2242
2243        assert_eq!(
2244            decoded,
2245            OptionalOrderTypes {
2246                contingency: None,
2247                trailing_offset: Some(TrailingOffsetType::Price),
2248                trigger: Some(TriggerType::LastPrice),
2249            },
2250        );
2251    }
2252
2253    #[rstest]
2254    #[case(r#"{"contingency":"INVALID","trailing_offset":"NO_TRAILING_OFFSET","trigger":"NO_TRIGGER"}"#)]
2255    #[case(
2256        r#"{"contingency":"NO_CONTINGENCY","trailing_offset":"INVALID","trigger":"NO_TRIGGER"}"#
2257    )]
2258    #[case(r#"{"contingency":"NO_CONTINGENCY","trailing_offset":"NO_TRAILING_OFFSET","trigger":"INVALID"}"#)]
2259    fn test_optional_order_types_serde_rejects_invalid_values(#[case] json: &str) {
2260        assert!(serde_json::from_str::<OptionalOrderTypes>(json).is_err());
2261    }
2262
2263    #[rstest]
2264    #[case::no_aggressor(0, Some(AggressorSide::NoAggressor))]
2265    #[case::buy(1, Some(AggressorSide::Buy))]
2266    #[case::sell(2, Some(AggressorSide::Sell))]
2267    #[case::invalid(3, None)]
2268    #[case::max_u8(255, None)]
2269    fn test_aggressor_side_from_u8(#[case] value: u8, #[case] expected: Option<AggressorSide>) {
2270        assert_eq!(AggressorSide::from_u8(value), expected);
2271    }
2272
2273    #[rstest]
2274    #[case(AggressorSide::NoAggressor, "NO_AGGRESSOR")]
2275    #[case(AggressorSide::Buy, "BUY")]
2276    #[case(AggressorSide::Sell, "SELL")]
2277    fn test_aggressor_side_to_string(#[case] value: AggressorSide, #[case] expected: &str) {
2278        assert_eq!(value.to_string(), expected);
2279        assert_eq!(value.as_ref(), expected);
2280    }
2281
2282    #[rstest]
2283    #[case(AggressorSide::NoAggressor, "NO_AGGRESSOR")]
2284    #[case(AggressorSide::Buy, "BUY")]
2285    #[case(AggressorSide::Sell, "SELL")]
2286    #[case(AggressorSide::Buy, "BUYER")]
2287    #[case(AggressorSide::Sell, "SELLER")]
2288    #[case(AggressorSide::Buy, "buy")]
2289    #[case(AggressorSide::Sell, "seller")]
2290    fn test_aggressor_side_from_str(#[case] expected: AggressorSide, #[case] value: &str) {
2291        assert_eq!(AggressorSide::from_str(value), Ok(expected));
2292    }
2293
2294    #[rstest]
2295    #[case(AggressorSide::Buy, "\"BUY\"")]
2296    #[case(AggressorSide::Sell, "\"SELL\"")]
2297    #[case(AggressorSide::NoAggressor, "\"NO_AGGRESSOR\"")]
2298    fn test_aggressor_side_serde_roundtrip(#[case] input: AggressorSide, #[case] expected: &str) {
2299        let json = serde_json::to_string(&input).unwrap();
2300        assert_eq!(json, expected);
2301        let parsed: AggressorSide = serde_json::from_str(expected).unwrap();
2302        assert_eq!(parsed, input);
2303    }
2304
2305    #[rstest]
2306    #[case("BUYER", AggressorSide::Buy)]
2307    #[case("SELLER", AggressorSide::Sell)]
2308    fn test_aggressor_side_serde_accepts_historical(
2309        #[case] value: &str,
2310        #[case] expected: AggressorSide,
2311    ) {
2312        let parsed: AggressorSide = serde_json::from_str(&format!("\"{value}\"")).unwrap();
2313        assert_eq!(parsed, expected);
2314    }
2315
2316    #[rstest]
2317    #[case(GreeksConvention::BlackScholes, "\"BLACK_SCHOLES\"")]
2318    #[case(GreeksConvention::PriceAdjusted, "\"PRICE_ADJUSTED\"")]
2319    fn test_greeks_convention_serde_roundtrip(
2320        #[case] input: GreeksConvention,
2321        #[case] expected: &str,
2322    ) {
2323        let json = serde_json::to_string(&input).unwrap();
2324        assert_eq!(json, expected);
2325        let parsed: GreeksConvention = serde_json::from_str(expected).unwrap();
2326        assert_eq!(parsed, input);
2327    }
2328
2329    #[rstest]
2330    fn test_greeks_convention_default_is_black_scholes() {
2331        assert_eq!(GreeksConvention::default(), GreeksConvention::BlackScholes);
2332    }
2333
2334    #[rstest]
2335    #[case(ContinuousFutureAdjustmentType::BackwardSpread, false, true)]
2336    #[case(ContinuousFutureAdjustmentType::ForwardSpread, false, false)]
2337    #[case(ContinuousFutureAdjustmentType::BackwardRatio, true, true)]
2338    #[case(ContinuousFutureAdjustmentType::ForwardRatio, true, false)]
2339    fn test_continuous_future_adjustment_type_predicates(
2340        #[case] mode: ContinuousFutureAdjustmentType,
2341        #[case] expected_is_ratio: bool,
2342        #[case] expected_is_backward: bool,
2343    ) {
2344        assert_eq!(mode.is_ratio(), expected_is_ratio);
2345        assert_eq!(mode.is_backward(), expected_is_backward);
2346    }
2347
2348    #[rstest]
2349    #[case(ContinuousFutureAdjustmentType::BackwardSpread, "\"BACKWARD_SPREAD\"")]
2350    #[case(ContinuousFutureAdjustmentType::ForwardSpread, "\"FORWARD_SPREAD\"")]
2351    #[case(ContinuousFutureAdjustmentType::BackwardRatio, "\"BACKWARD_RATIO\"")]
2352    #[case(ContinuousFutureAdjustmentType::ForwardRatio, "\"FORWARD_RATIO\"")]
2353    fn test_continuous_future_adjustment_type_serde_roundtrip(
2354        #[case] input: ContinuousFutureAdjustmentType,
2355        #[case] expected: &str,
2356    ) {
2357        let json = serde_json::to_string(&input).unwrap();
2358        assert_eq!(json, expected);
2359        let parsed: ContinuousFutureAdjustmentType = serde_json::from_str(expected).unwrap();
2360        assert_eq!(parsed, input);
2361    }
2362
2363    #[rstest]
2364    fn test_continuous_future_adjustment_type_default_is_backward_spread() {
2365        assert_eq!(
2366            ContinuousFutureAdjustmentType::default(),
2367            ContinuousFutureAdjustmentType::BackwardSpread,
2368        );
2369    }
2370
2371    #[rstest]
2372    #[case(InstrumentClass::Option, true)]
2373    #[case(InstrumentClass::FuturesSpread, true)]
2374    #[case(InstrumentClass::OptionSpread, true)]
2375    #[case(InstrumentClass::Spot, false)]
2376    #[case(InstrumentClass::Swap, false)]
2377    #[case(InstrumentClass::Future, false)]
2378    #[case(InstrumentClass::Forward, false)]
2379    #[case(InstrumentClass::Cfd, false)]
2380    #[case(InstrumentClass::Bond, false)]
2381    #[case(InstrumentClass::Warrant, false)]
2382    #[case(InstrumentClass::SportsBetting, false)]
2383    #[case(InstrumentClass::BinaryOption, false)]
2384    fn test_instrument_class_allows_negative_price(
2385        #[case] class: InstrumentClass,
2386        #[case] expected: bool,
2387    ) {
2388        assert_eq!(class.allows_negative_price(), expected);
2389    }
2390
2391    #[rstest]
2392    #[case("FUT", Some(InstrumentClass::Future))]
2393    #[case("FUTURE", Some(InstrumentClass::Future))]
2394    #[case("OPT", Some(InstrumentClass::Option))]
2395    #[case("OPTION", Some(InstrumentClass::Option))]
2396    #[case("fut", None)]
2397    #[case("Fut", None)]
2398    #[case("option", None)]
2399    #[case("Option", None)]
2400    #[case("SPREAD", None)]
2401    #[case("UNKNOWN", None)]
2402    #[case("", None)]
2403    fn test_instrument_class_try_from_parent_suffix(
2404        #[case] suffix: &str,
2405        #[case] expected: Option<InstrumentClass>,
2406    ) {
2407        assert_eq!(InstrumentClass::try_from_parent_suffix(suffix), expected);
2408    }
2409
2410    #[rstest]
2411    #[case(InstrumentClass::Future, Some("FUT"))]
2412    #[case(InstrumentClass::Option, Some("OPT"))]
2413    #[case(InstrumentClass::Spot, None)]
2414    #[case(InstrumentClass::Swap, None)]
2415    #[case(InstrumentClass::FuturesSpread, None)]
2416    #[case(InstrumentClass::Forward, None)]
2417    #[case(InstrumentClass::Cfd, None)]
2418    #[case(InstrumentClass::Bond, None)]
2419    #[case(InstrumentClass::OptionSpread, None)]
2420    #[case(InstrumentClass::Warrant, None)]
2421    #[case(InstrumentClass::SportsBetting, None)]
2422    #[case(InstrumentClass::BinaryOption, None)]
2423    fn test_instrument_class_parent_suffix(
2424        #[case] class: InstrumentClass,
2425        #[case] expected: Option<&'static str>,
2426    ) {
2427        assert_eq!(class.parent_suffix(), expected);
2428    }
2429
2430    #[rstest]
2431    #[case(InstrumentClass::Future)]
2432    #[case(InstrumentClass::Option)]
2433    fn test_instrument_class_parent_suffix_roundtrip(#[case] class: InstrumentClass) {
2434        let suffix = class.parent_suffix().unwrap();
2435        assert_eq!(InstrumentClass::try_from_parent_suffix(suffix), Some(class));
2436    }
2437}