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