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