use std::{borrow::Cow, fmt::Display, marker::PhantomData, str::FromStr};
use serde::{Deserialize, Deserializer, Serializer};
use strum::{AsRefStr, Display, EnumIter, EnumString, FromRepr};
use crate::enum_strum_serde;
pub trait FromU8 {
fn from_u8(value: u8) -> Option<Self>
where
Self: Sized;
}
pub trait FromU16 {
fn from_u16(value: u16) -> Option<Self>
where
Self: Sized;
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum AccountType {
Cash = 1,
Margin = 2,
Betting = 3,
Wallet = 4,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum AggregationSource {
External = 1,
Internal = 2,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Default,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum AggressorSide {
#[default]
NoAggressor = 0,
#[strum(serialize = "BUYER", to_string = "BUY")]
Buy = 1,
#[strum(serialize = "SELLER", to_string = "SELL")]
Sell = 2,
}
impl FromU8 for AggressorSide {
fn from_u8(value: u8) -> Option<Self> {
Self::from_repr(usize::from(value))
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
#[allow(non_camel_case_types)]
pub enum AssetClass {
FX = 1,
Equity = 2,
Commodity = 3,
Debt = 4,
Index = 5,
Cryptocurrency = 6,
Alternative = 7,
}
impl FromU8 for AssetClass {
fn from_u8(value: u8) -> Option<Self> {
Self::from_repr(usize::from(value))
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum BarAggregation {
Tick = 1,
TickImbalance = 2,
TickRuns = 3,
Volume = 4,
VolumeImbalance = 5,
VolumeRuns = 6,
Value = 7,
ValueImbalance = 8,
ValueRuns = 9,
Millisecond = 10,
Second = 11,
Minute = 12,
Hour = 13,
Day = 14,
Week = 15,
Month = 16,
Year = 17,
Renko = 18,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Default,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum BarIntervalType {
#[default]
LeftOpen = 1,
RightOpen = 2,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum BetSide {
Back = 1,
Lay = 2,
}
impl BetSide {
#[must_use]
pub fn opposite(&self) -> Self {
match self {
Self::Back => Self::Lay,
Self::Lay => Self::Back,
}
}
}
impl From<OrderSide> for BetSide {
fn from(side: OrderSide) -> Self {
match side {
OrderSide::Buy => Self::Back,
OrderSide::Sell => Self::Lay,
}
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum BookAction {
Add = 1,
Update = 2,
Delete = 3,
Clear = 4,
}
impl FromU8 for BookAction {
fn from_u8(value: u8) -> Option<Self> {
Self::from_repr(usize::from(value))
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
#[allow(non_camel_case_types)]
pub enum BookType {
L1_MBP = 1,
L2_MBP = 2,
L3_MBO = 3,
}
impl FromU8 for BookType {
fn from_u8(value: u8) -> Option<Self> {
Self::from_repr(usize::from(value))
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum ContingencyType {
Oco = 1,
Oto = 2,
Ouo = 3,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Default,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum ContinuousFutureAdjustmentType {
#[default]
BackwardSpread = 1,
ForwardSpread = 2,
BackwardRatio = 3,
ForwardRatio = 4,
}
impl ContinuousFutureAdjustmentType {
#[must_use]
pub const fn is_ratio(&self) -> bool {
matches!(self, Self::BackwardRatio | Self::ForwardRatio)
}
#[must_use]
pub const fn is_backward(&self) -> bool {
matches!(self, Self::BackwardSpread | Self::BackwardRatio)
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum CurrencyType {
Crypto = 1,
Fiat = 2,
CommodityBacked = 3,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum InstrumentClass {
Spot = 1,
Swap = 2,
Future = 3,
FuturesSpread = 4,
Forward = 5,
Cfd = 6,
Bond = 7,
Option = 8,
OptionSpread = 9,
Warrant = 10,
SportsBetting = 11,
BinaryOption = 12,
}
impl InstrumentClass {
#[must_use]
pub const fn has_expiration(&self) -> bool {
matches!(
self,
Self::Future | Self::FuturesSpread | Self::Option | Self::OptionSpread
)
}
#[must_use]
pub const fn allows_negative_price(&self) -> bool {
matches!(
self,
Self::Option | Self::FuturesSpread | Self::OptionSpread
)
}
#[must_use]
pub fn try_from_parent_suffix(suffix: &str) -> Option<Self> {
match suffix {
"FUT" | "FUTURE" => Some(Self::Future),
"OPT" | "OPTION" => Some(Self::Option),
_ => None,
}
}
#[must_use]
pub const fn parent_suffix(self) -> Option<&'static str> {
match self {
Self::Future => Some("FUT"),
Self::Option => Some("OPT"),
_ => None,
}
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum InstrumentCloseType {
EndOfSession = 1,
ContractExpired = 2,
}
impl FromU8 for InstrumentCloseType {
fn from_u8(value: u8) -> Option<Self> {
Self::from_repr(usize::from(value))
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum LiquiditySide {
NoLiquiditySide = 0,
Maker = 1,
Taker = 2,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum MarketStatus {
Open = 1,
Closed = 2,
Paused = 3,
Halted = 4,
Suspended = 5,
NotAvailable = 6,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum MarketStatusAction {
None = 0,
PreOpen = 1,
PreCross = 2,
Quoting = 3,
Cross = 4,
Rotation = 5,
NewPriceIndication = 6,
Trading = 7,
Halt = 8,
Pause = 9,
Suspend = 10,
PreClose = 11,
Close = 12,
PostClose = 13,
ShortSellRestrictionChange = 14,
NotAvailableForTrading = 15,
}
impl FromU16 for MarketStatusAction {
fn from_u16(value: u16) -> Option<Self> {
Self::from_repr(usize::from(value))
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Default,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum OmsType {
#[default]
Unspecified = 0,
Netting = 1,
Hedging = 2,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum OptionKind {
Call = 1,
Put = 2,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Default,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum GreeksConvention {
#[default]
BlackScholes = 1,
PriceAdjusted = 2,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Default,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum OtoTriggerMode {
#[default]
Partial = 0,
Full = 1,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum OrderSide {
Buy = 1,
Sell = 2,
}
impl OrderSide {
#[must_use]
pub fn opposite(&self) -> Self {
match &self {
Self::Buy => Self::Sell,
Self::Sell => Self::Buy,
}
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum OrderStatus {
Initialized = 1,
Denied = 2,
Emulated = 3,
Released = 4,
Submitted = 5,
Accepted = 6,
Rejected = 7,
Canceled = 8,
Expired = 9,
Triggered = 10,
PendingUpdate = 11,
PendingCancel = 12,
PartiallyFilled = 13,
Filled = 14,
Voided = 15,
}
impl OrderStatus {
#[must_use]
pub const fn is_open(self) -> bool {
matches!(
self,
Self::Accepted
| Self::Triggered
| Self::PendingUpdate
| Self::PendingCancel
| Self::PartiallyFilled
)
}
#[must_use]
pub const fn is_closed(self) -> bool {
matches!(
self,
Self::Denied
| Self::Rejected
| Self::Canceled
| Self::Expired
| Self::Filled
| Self::Voided
)
}
#[must_use]
pub const fn is_inflight(self) -> bool {
matches!(
self,
Self::Submitted | Self::PendingUpdate | Self::PendingCancel
)
}
#[must_use]
pub const fn is_cancellable(self) -> bool {
matches!(
self,
Self::Accepted | Self::Triggered | Self::PendingUpdate | Self::PartiallyFilled
)
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum OrderType {
Market = 1,
Limit = 2,
StopMarket = 3,
StopLimit = 4,
MarketToLimit = 5,
MarketIfTouched = 6,
LimitIfTouched = 7,
TrailingStopMarket = 8,
TrailingStopLimit = 9,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum PositionAdjustmentType {
Commission = 1,
Funding = 2,
}
impl FromU8 for PositionAdjustmentType {
fn from_u8(value: u8) -> Option<Self> {
Self::from_repr(usize::from(value))
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum PositionSide {
Flat = 1,
Long = 2,
Short = 3,
}
pub mod serde_option_order_side {
use serde::{Deserializer, Serializer};
use super::{OrderSide, deserialize_optional_enum, serialize_optional_enum};
pub fn serialize<S>(value: &Option<OrderSide>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_optional_enum(value.as_ref(), serializer, "NO_ORDER_SIDE")
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OrderSide>, D::Error>
where
D: Deserializer<'de>,
{
deserialize_optional_enum(
deserializer,
"NO_ORDER_SIDE",
"BUY, SELL, NO_ORDER_SIDE, or null",
)
}
}
pub mod serde_option_position_side {
use serde::{Deserializer, Serializer};
use super::{PositionSide, deserialize_optional_enum, serialize_optional_enum};
pub fn serialize<S>(value: &Option<PositionSide>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_optional_enum(value.as_ref(), serializer, "NO_POSITION_SIDE")
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<PositionSide>, D::Error>
where
D: Deserializer<'de>,
{
deserialize_optional_enum(
deserializer,
"NO_POSITION_SIDE",
"FLAT, LONG, SHORT, NO_POSITION_SIDE, or null",
)
}
}
pub mod serde_option_contingency_type {
use serde::{Deserializer, Serializer};
use super::{ContingencyType, deserialize_optional_enum, serialize_optional_enum};
pub fn serialize<S>(value: &Option<ContingencyType>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_optional_enum(value.as_ref(), serializer, "NO_CONTINGENCY")
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<ContingencyType>, D::Error>
where
D: Deserializer<'de>,
{
deserialize_optional_enum(
deserializer,
"NO_CONTINGENCY",
"OCO, OTO, OUO, NO_CONTINGENCY, or null",
)
}
}
pub mod serde_option_trailing_offset_type {
use serde::{Deserializer, Serializer};
use super::{TrailingOffsetType, deserialize_optional_enum, serialize_optional_enum};
pub fn serialize<S>(
value: &Option<TrailingOffsetType>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_optional_enum(value.as_ref(), serializer, "NO_TRAILING_OFFSET")
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<TrailingOffsetType>, D::Error>
where
D: Deserializer<'de>,
{
deserialize_optional_enum(
deserializer,
"NO_TRAILING_OFFSET",
"PRICE, BASIS_POINTS, TICKS, PRICE_TIER, NO_TRAILING_OFFSET, or null",
)
}
}
pub mod serde_option_trigger_type {
use serde::{Deserializer, Serializer};
use super::{TriggerType, deserialize_optional_enum, serialize_optional_enum};
pub fn serialize<S>(value: &Option<TriggerType>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_optional_enum(value.as_ref(), serializer, "NO_TRIGGER")
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<TriggerType>, D::Error>
where
D: Deserializer<'de>,
{
deserialize_optional_enum(
deserializer,
"NO_TRIGGER",
"a trigger type, NO_TRIGGER, or null",
)
}
}
fn serialize_optional_enum<S, T>(
value: Option<&T>,
serializer: S,
none_token: &'static str,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: AsRef<str>,
{
serializer.serialize_str(value.map_or(none_token, AsRef::as_ref))
}
fn deserialize_optional_enum<'de, D, T>(
deserializer: D,
none_token: &'static str,
expected: &'static str,
) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: Display,
{
struct OptionalEnumVisitor<T> {
none_token: &'static str,
expected: &'static str,
marker: PhantomData<T>,
}
impl<'de, T> serde::de::Visitor<'de> for OptionalEnumVisitor<T>
where
T: FromStr,
T::Err: Display,
{
type Value = Option<T>;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.expected)
}
fn visit_none<E>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_unit<E>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
let value = Cow::<'de, str>::deserialize(deserializer)?;
if value.eq_ignore_ascii_case(self.none_token) {
Ok(None)
} else {
T::from_str(&value)
.map(Some)
.map_err(serde::de::Error::custom)
}
}
}
deserializer.deserialize_option(OptionalEnumVisitor {
none_token,
expected,
marker: PhantomData,
})
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum PriceType {
Bid = 1,
Ask = 2,
Mid = 3,
Last = 4,
Mark = 5,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
#[allow(non_camel_case_types)]
pub enum RecordFlag {
F_LAST = 1 << 7, F_TOB = 1 << 6, F_SNAPSHOT = 1 << 5, F_MBP = 1 << 4, RESERVED_2 = 1 << 3, RESERVED_1 = 1 << 2, }
impl RecordFlag {
#[must_use]
pub fn matches(self, value: u8) -> bool {
(self as u8) & value != 0
}
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum TimeInForce {
Gtc = 1,
Ioc = 2,
Fok = 3,
Gtd = 4,
Day = 5,
AtTheOpen = 6,
AtTheClose = 7,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum TradingState {
Active = 1,
Reducing = 2,
Halted = 3,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum TrailingOffsetType {
Price = 1,
BasisPoints = 2,
Ticks = 3,
PriceTier = 4,
}
#[repr(C)]
#[derive(
Copy,
Clone,
Debug,
Display,
Hash,
PartialEq,
Eq,
PartialOrd,
Ord,
AsRefStr,
FromRepr,
EnumIter,
EnumString,
)]
#[strum(ascii_case_insensitive)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
frozen,
eq,
eq_int,
module = "nautilus_trader.model",
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE",
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.model")
)]
pub enum TriggerType {
Default = 1,
LastPrice = 2,
MarkPrice = 3,
IndexPrice = 4,
BidAsk = 5,
DoubleLast = 6,
DoubleBidAsk = 7,
LastOrBidAsk = 8,
MidPoint = 9,
}
enum_strum_serde!(AccountType);
enum_strum_serde!(AggregationSource);
enum_strum_serde!(AggressorSide);
enum_strum_serde!(AssetClass);
enum_strum_serde!(BarAggregation);
enum_strum_serde!(BarIntervalType);
enum_strum_serde!(BetSide);
enum_strum_serde!(BookAction);
enum_strum_serde!(BookType);
enum_strum_serde!(ContingencyType);
enum_strum_serde!(ContinuousFutureAdjustmentType);
enum_strum_serde!(CurrencyType);
enum_strum_serde!(GreeksConvention);
enum_strum_serde!(InstrumentClass);
enum_strum_serde!(InstrumentCloseType);
enum_strum_serde!(LiquiditySide);
enum_strum_serde!(MarketStatus);
enum_strum_serde!(MarketStatusAction);
enum_strum_serde!(OmsType);
enum_strum_serde!(OptionKind);
enum_strum_serde!(OrderSide);
enum_strum_serde!(OrderStatus);
enum_strum_serde!(OrderType);
enum_strum_serde!(OtoTriggerMode);
enum_strum_serde!(PositionAdjustmentType);
enum_strum_serde!(PositionSide);
enum_strum_serde!(PriceType);
enum_strum_serde!(RecordFlag);
enum_strum_serde!(TimeInForce);
enum_strum_serde!(TradingState);
enum_strum_serde!(TrailingOffsetType);
enum_strum_serde!(TriggerType);
#[cfg(test)]
mod tests {
use rstest::rstest;
use serde::{Serialize, de::DeserializeOwned};
use strum::IntoEnumIterator;
use super::*;
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct OptionalSides {
#[serde(with = "serde_option_order_side")]
order: Option<OrderSide>,
#[serde(with = "serde_option_position_side")]
position: Option<PositionSide>,
}
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct OptionalOrderTypes {
#[serde(with = "serde_option_contingency_type")]
contingency: Option<ContingencyType>,
#[serde(with = "serde_option_trailing_offset_type")]
trailing_offset: Option<TrailingOffsetType>,
#[serde(with = "serde_option_trigger_type")]
trigger: Option<TriggerType>,
}
#[rstest]
fn test_optional_sides_serde_preserves_legacy_none_tokens() {
let value = OptionalSides {
order: None,
position: None,
};
let json = serde_json::to_string(&value).unwrap();
let decoded: OptionalSides = serde_json::from_str(&json).unwrap();
assert_eq!(
json,
r#"{"order":"NO_ORDER_SIDE","position":"NO_POSITION_SIDE"}"#
);
assert_eq!(decoded, value);
}
#[rstest]
fn test_optional_sides_serde_accepts_null_and_valid_sides() {
let json = r#"{"order":null,"position":"LONG"}"#;
let decoded: OptionalSides = serde_json::from_str(json).unwrap();
assert_eq!(
decoded,
OptionalSides {
order: None,
position: Some(PositionSide::Long),
}
);
}
#[rstest]
fn test_optional_order_types_serde_preserves_legacy_none_tokens() {
let value = OptionalOrderTypes {
contingency: None,
trailing_offset: None,
trigger: None,
};
let json = serde_json::to_string(&value).unwrap();
let decoded: OptionalOrderTypes = serde_json::from_str(&json).unwrap();
assert_eq!(
json,
r#"{"contingency":"NO_CONTINGENCY","trailing_offset":"NO_TRAILING_OFFSET","trigger":"NO_TRIGGER"}"#,
);
assert_eq!(decoded, value);
}
#[rstest]
fn test_optional_order_types_serde_accepts_null_and_valid_values() {
let json = r#"{"contingency":null,"trailing_offset":"PRICE","trigger":"LAST_PRICE"}"#;
let decoded: OptionalOrderTypes = serde_json::from_str(json).unwrap();
assert_eq!(
decoded,
OptionalOrderTypes {
contingency: None,
trailing_offset: Some(TrailingOffsetType::Price),
trigger: Some(TriggerType::LastPrice),
},
);
}
#[rstest]
#[case(r#"{"contingency":"INVALID","trailing_offset":"NO_TRAILING_OFFSET","trigger":"NO_TRIGGER"}"#)]
#[case(
r#"{"contingency":"NO_CONTINGENCY","trailing_offset":"INVALID","trigger":"NO_TRIGGER"}"#
)]
#[case(r#"{"contingency":"NO_CONTINGENCY","trailing_offset":"NO_TRAILING_OFFSET","trigger":"INVALID"}"#)]
fn test_optional_order_types_serde_rejects_invalid_values(#[case] json: &str) {
assert!(serde_json::from_str::<OptionalOrderTypes>(json).is_err());
}
#[rstest]
#[case::bet_side_back(BetSide::Back, r#""BACK""#)]
#[case::bet_side_lay(BetSide::Lay, r#""LAY""#)]
fn test_bet_side_serde_uses_canonical_label(#[case] value: BetSide, #[case] expected: &str) {
let json = serde_json::to_string(&value).unwrap();
assert_eq!(json, expected);
assert_eq!(serde_json::from_str::<BetSide>(&json).unwrap(), value);
}
#[rstest]
#[case::oto_trigger_mode_partial(OtoTriggerMode::Partial, r#""PARTIAL""#)]
#[case::oto_trigger_mode_full(OtoTriggerMode::Full, r#""FULL""#)]
fn test_oto_trigger_mode_serde_uses_canonical_label(
#[case] value: OtoTriggerMode,
#[case] expected: &str,
) {
let json = serde_json::to_string(&value).unwrap();
assert_eq!(json, expected);
assert_eq!(
serde_json::from_str::<OtoTriggerMode>(&json).unwrap(),
value
);
}
#[rstest]
#[case::pascal(r#""Back""#, BetSide::Back)]
#[case::lower(r#""lay""#, BetSide::Lay)]
fn test_bet_side_serde_is_case_insensitive(#[case] json: &str, #[case] expected: BetSide) {
assert_eq!(serde_json::from_str::<BetSide>(json).unwrap(), expected);
}
#[rstest]
#[case::wrong_domain(r#""BUY""#)]
#[case::empty(r#""""#)]
fn test_bet_side_serde_rejects_unknown_label(#[case] json: &str) {
assert!(serde_json::from_str::<BetSide>(json).is_err());
}
#[rstest]
#[case::no_aggressor(0, Some(AggressorSide::NoAggressor))]
#[case::buy(1, Some(AggressorSide::Buy))]
#[case::sell(2, Some(AggressorSide::Sell))]
#[case::invalid(3, None)]
#[case::max_u8(255, None)]
fn test_aggressor_side_from_u8(#[case] value: u8, #[case] expected: Option<AggressorSide>) {
assert_eq!(AggressorSide::from_u8(value), expected);
}
#[rstest]
#[case::active(TradingState::Active, 1)]
#[case::reducing(TradingState::Reducing, 2)]
#[case::halted(TradingState::Halted, 3)]
fn test_trading_state_discriminants(#[case] state: TradingState, #[case] value: usize) {
assert_eq!(state as usize, value);
assert_eq!(TradingState::from_repr(value), Some(state));
}
#[rstest]
#[case(AggressorSide::NoAggressor, "NO_AGGRESSOR")]
#[case(AggressorSide::Buy, "BUY")]
#[case(AggressorSide::Sell, "SELL")]
fn test_aggressor_side_to_string(#[case] value: AggressorSide, #[case] expected: &str) {
assert_eq!(value.to_string(), expected);
assert_eq!(value.as_ref(), expected);
}
#[rstest]
#[case(AggressorSide::NoAggressor, "NO_AGGRESSOR")]
#[case(AggressorSide::Buy, "BUY")]
#[case(AggressorSide::Sell, "SELL")]
#[case(AggressorSide::Buy, "BUYER")]
#[case(AggressorSide::Sell, "SELLER")]
#[case(AggressorSide::Buy, "buy")]
#[case(AggressorSide::Sell, "seller")]
fn test_aggressor_side_from_str(#[case] expected: AggressorSide, #[case] value: &str) {
assert_eq!(AggressorSide::from_str(value), Ok(expected));
}
#[rstest]
#[case(AggressorSide::Buy, "\"BUY\"")]
#[case(AggressorSide::Sell, "\"SELL\"")]
#[case(AggressorSide::NoAggressor, "\"NO_AGGRESSOR\"")]
fn test_aggressor_side_serde_roundtrip(#[case] input: AggressorSide, #[case] expected: &str) {
let json = serde_json::to_string(&input).unwrap();
assert_eq!(json, expected);
let parsed: AggressorSide = serde_json::from_str(expected).unwrap();
assert_eq!(parsed, input);
}
#[rstest]
#[case("BUYER", AggressorSide::Buy)]
#[case("SELLER", AggressorSide::Sell)]
fn test_aggressor_side_serde_accepts_historical(
#[case] value: &str,
#[case] expected: AggressorSide,
) {
let parsed: AggressorSide = serde_json::from_str(&format!("\"{value}\"")).unwrap();
assert_eq!(parsed, expected);
}
#[rstest]
#[case(GreeksConvention::BlackScholes, "\"BLACK_SCHOLES\"")]
#[case(GreeksConvention::PriceAdjusted, "\"PRICE_ADJUSTED\"")]
fn test_greeks_convention_serde_roundtrip(
#[case] input: GreeksConvention,
#[case] expected: &str,
) {
let json = serde_json::to_string(&input).unwrap();
assert_eq!(json, expected);
let parsed: GreeksConvention = serde_json::from_str(expected).unwrap();
assert_eq!(parsed, input);
}
#[rstest]
fn test_greeks_convention_default_is_black_scholes() {
assert_eq!(GreeksConvention::default(), GreeksConvention::BlackScholes);
}
#[rstest]
#[case(ContinuousFutureAdjustmentType::BackwardSpread, false, true)]
#[case(ContinuousFutureAdjustmentType::ForwardSpread, false, false)]
#[case(ContinuousFutureAdjustmentType::BackwardRatio, true, true)]
#[case(ContinuousFutureAdjustmentType::ForwardRatio, true, false)]
fn test_continuous_future_adjustment_type_predicates(
#[case] mode: ContinuousFutureAdjustmentType,
#[case] expected_is_ratio: bool,
#[case] expected_is_backward: bool,
) {
assert_eq!(mode.is_ratio(), expected_is_ratio);
assert_eq!(mode.is_backward(), expected_is_backward);
}
#[rstest]
#[case(ContinuousFutureAdjustmentType::BackwardSpread, "\"BACKWARD_SPREAD\"")]
#[case(ContinuousFutureAdjustmentType::ForwardSpread, "\"FORWARD_SPREAD\"")]
#[case(ContinuousFutureAdjustmentType::BackwardRatio, "\"BACKWARD_RATIO\"")]
#[case(ContinuousFutureAdjustmentType::ForwardRatio, "\"FORWARD_RATIO\"")]
fn test_continuous_future_adjustment_type_serde_roundtrip(
#[case] input: ContinuousFutureAdjustmentType,
#[case] expected: &str,
) {
let json = serde_json::to_string(&input).unwrap();
assert_eq!(json, expected);
let parsed: ContinuousFutureAdjustmentType = serde_json::from_str(expected).unwrap();
assert_eq!(parsed, input);
}
#[rstest]
fn test_continuous_future_adjustment_type_default_is_backward_spread() {
assert_eq!(
ContinuousFutureAdjustmentType::default(),
ContinuousFutureAdjustmentType::BackwardSpread,
);
}
#[rstest]
#[case(InstrumentClass::Option, true)]
#[case(InstrumentClass::FuturesSpread, true)]
#[case(InstrumentClass::OptionSpread, true)]
#[case(InstrumentClass::Spot, false)]
#[case(InstrumentClass::Swap, false)]
#[case(InstrumentClass::Future, false)]
#[case(InstrumentClass::Forward, false)]
#[case(InstrumentClass::Cfd, false)]
#[case(InstrumentClass::Bond, false)]
#[case(InstrumentClass::Warrant, false)]
#[case(InstrumentClass::SportsBetting, false)]
#[case(InstrumentClass::BinaryOption, false)]
fn test_instrument_class_allows_negative_price(
#[case] class: InstrumentClass,
#[case] expected: bool,
) {
assert_eq!(class.allows_negative_price(), expected);
}
#[rstest]
#[case("FUT", Some(InstrumentClass::Future))]
#[case("FUTURE", Some(InstrumentClass::Future))]
#[case("OPT", Some(InstrumentClass::Option))]
#[case("OPTION", Some(InstrumentClass::Option))]
#[case("fut", None)]
#[case("Fut", None)]
#[case("option", None)]
#[case("Option", None)]
#[case("SPREAD", None)]
#[case("UNKNOWN", None)]
#[case("", None)]
fn test_instrument_class_try_from_parent_suffix(
#[case] suffix: &str,
#[case] expected: Option<InstrumentClass>,
) {
assert_eq!(InstrumentClass::try_from_parent_suffix(suffix), expected);
}
#[rstest]
#[case(InstrumentClass::Future, Some("FUT"))]
#[case(InstrumentClass::Option, Some("OPT"))]
#[case(InstrumentClass::Spot, None)]
#[case(InstrumentClass::Swap, None)]
#[case(InstrumentClass::FuturesSpread, None)]
#[case(InstrumentClass::Forward, None)]
#[case(InstrumentClass::Cfd, None)]
#[case(InstrumentClass::Bond, None)]
#[case(InstrumentClass::OptionSpread, None)]
#[case(InstrumentClass::Warrant, None)]
#[case(InstrumentClass::SportsBetting, None)]
#[case(InstrumentClass::BinaryOption, None)]
fn test_instrument_class_parent_suffix(
#[case] class: InstrumentClass,
#[case] expected: Option<&'static str>,
) {
assert_eq!(class.parent_suffix(), expected);
}
#[rstest]
#[case(InstrumentClass::Future)]
#[case(InstrumentClass::Option)]
fn test_instrument_class_parent_suffix_roundtrip(#[case] class: InstrumentClass) {
let suffix = class.parent_suffix().unwrap();
assert_eq!(InstrumentClass::try_from_parent_suffix(suffix), Some(class));
}
fn assert_enum_string_contract<T>(type_name: &str)
where
T: IntoEnumIterator
+ Copy
+ std::fmt::Debug
+ PartialEq
+ Display
+ AsRef<str>
+ FromStr
+ Serialize
+ DeserializeOwned,
{
for variant in T::iter() {
let wire = variant.to_string();
assert_eq!(
variant.as_ref(),
wire,
"{type_name}::{variant:?} must expose the same name through `AsRef` and `Display`",
);
assert_eq!(
T::from_str(&wire).ok(),
Some(variant),
"{type_name} must parse `{wire}` back to {variant:?}",
);
assert_eq!(
T::from_str(&wire.to_ascii_lowercase()).ok(),
Some(variant),
"{type_name} must parse `{wire}` case-insensitively",
);
let json = serde_json::to_string(&variant).unwrap();
assert_eq!(
json,
format!("\"{wire}\""),
"{type_name}::{variant:?} must serialize as its display name",
);
assert_eq!(
serde_json::from_str::<T>(&json).unwrap(),
variant,
"{type_name}::{variant:?} must round-trip through JSON",
);
}
}
#[rstest]
fn test_enum_string_and_serde_contract() {
macro_rules! assert_contract {
($($t:ty),+ $(,)?) => {
$(assert_enum_string_contract::<$t>(stringify!($t));)+
};
}
assert_contract!(
AccountType,
AggregationSource,
AggressorSide,
AssetClass,
BarAggregation,
BarIntervalType,
BetSide,
BookAction,
BookType,
ContingencyType,
ContinuousFutureAdjustmentType,
CurrencyType,
GreeksConvention,
InstrumentClass,
InstrumentCloseType,
LiquiditySide,
MarketStatus,
MarketStatusAction,
OmsType,
OptionKind,
OrderSide,
OrderStatus,
OrderType,
OtoTriggerMode,
PositionAdjustmentType,
PositionSide,
PriceType,
RecordFlag,
TimeInForce,
TradingState,
TrailingOffsetType,
TriggerType,
);
}
const EXPECTED_WIRE_NAMES: &[&str] = &[
"AccountType::Betting=BETTING",
"AccountType::Cash=CASH",
"AccountType::Margin=MARGIN",
"AccountType::Wallet=WALLET",
"AggregationSource::External=EXTERNAL",
"AggregationSource::Internal=INTERNAL",
"AggressorSide::Buy=BUY",
"AggressorSide::NoAggressor=NO_AGGRESSOR",
"AggressorSide::Sell=SELL",
"AssetClass::Alternative=ALTERNATIVE",
"AssetClass::Commodity=COMMODITY",
"AssetClass::Cryptocurrency=CRYPTOCURRENCY",
"AssetClass::Debt=DEBT",
"AssetClass::Equity=EQUITY",
"AssetClass::FX=FX",
"AssetClass::Index=INDEX",
"BarAggregation::Day=DAY",
"BarAggregation::Hour=HOUR",
"BarAggregation::Millisecond=MILLISECOND",
"BarAggregation::Minute=MINUTE",
"BarAggregation::Month=MONTH",
"BarAggregation::Renko=RENKO",
"BarAggregation::Second=SECOND",
"BarAggregation::Tick=TICK",
"BarAggregation::TickImbalance=TICK_IMBALANCE",
"BarAggregation::TickRuns=TICK_RUNS",
"BarAggregation::Value=VALUE",
"BarAggregation::ValueImbalance=VALUE_IMBALANCE",
"BarAggregation::ValueRuns=VALUE_RUNS",
"BarAggregation::Volume=VOLUME",
"BarAggregation::VolumeImbalance=VOLUME_IMBALANCE",
"BarAggregation::VolumeRuns=VOLUME_RUNS",
"BarAggregation::Week=WEEK",
"BarAggregation::Year=YEAR",
"BarIntervalType::LeftOpen=LEFT_OPEN",
"BarIntervalType::RightOpen=RIGHT_OPEN",
"BetSide::Back=BACK",
"BetSide::Lay=LAY",
"BookAction::Add=ADD",
"BookAction::Clear=CLEAR",
"BookAction::Delete=DELETE",
"BookAction::Update=UPDATE",
"BookType::L1_MBP=L1_MBP",
"BookType::L2_MBP=L2_MBP",
"BookType::L3_MBO=L3_MBO",
"ContingencyType::Oco=OCO",
"ContingencyType::Oto=OTO",
"ContingencyType::Ouo=OUO",
"ContinuousFutureAdjustmentType::BackwardRatio=BACKWARD_RATIO",
"ContinuousFutureAdjustmentType::BackwardSpread=BACKWARD_SPREAD",
"ContinuousFutureAdjustmentType::ForwardRatio=FORWARD_RATIO",
"ContinuousFutureAdjustmentType::ForwardSpread=FORWARD_SPREAD",
"CurrencyType::CommodityBacked=COMMODITY_BACKED",
"CurrencyType::Crypto=CRYPTO",
"CurrencyType::Fiat=FIAT",
"GreeksConvention::BlackScholes=BLACK_SCHOLES",
"GreeksConvention::PriceAdjusted=PRICE_ADJUSTED",
"InstrumentClass::BinaryOption=BINARY_OPTION",
"InstrumentClass::Bond=BOND",
"InstrumentClass::Cfd=CFD",
"InstrumentClass::Forward=FORWARD",
"InstrumentClass::Future=FUTURE",
"InstrumentClass::FuturesSpread=FUTURES_SPREAD",
"InstrumentClass::Option=OPTION",
"InstrumentClass::OptionSpread=OPTION_SPREAD",
"InstrumentClass::SportsBetting=SPORTS_BETTING",
"InstrumentClass::Spot=SPOT",
"InstrumentClass::Swap=SWAP",
"InstrumentClass::Warrant=WARRANT",
"InstrumentCloseType::ContractExpired=CONTRACT_EXPIRED",
"InstrumentCloseType::EndOfSession=END_OF_SESSION",
"LiquiditySide::Maker=MAKER",
"LiquiditySide::NoLiquiditySide=NO_LIQUIDITY_SIDE",
"LiquiditySide::Taker=TAKER",
"MarketStatus::Closed=CLOSED",
"MarketStatus::Halted=HALTED",
"MarketStatus::NotAvailable=NOT_AVAILABLE",
"MarketStatus::Open=OPEN",
"MarketStatus::Paused=PAUSED",
"MarketStatus::Suspended=SUSPENDED",
"MarketStatusAction::Close=CLOSE",
"MarketStatusAction::Cross=CROSS",
"MarketStatusAction::Halt=HALT",
"MarketStatusAction::NewPriceIndication=NEW_PRICE_INDICATION",
"MarketStatusAction::None=NONE",
"MarketStatusAction::NotAvailableForTrading=NOT_AVAILABLE_FOR_TRADING",
"MarketStatusAction::Pause=PAUSE",
"MarketStatusAction::PostClose=POST_CLOSE",
"MarketStatusAction::PreClose=PRE_CLOSE",
"MarketStatusAction::PreCross=PRE_CROSS",
"MarketStatusAction::PreOpen=PRE_OPEN",
"MarketStatusAction::Quoting=QUOTING",
"MarketStatusAction::Rotation=ROTATION",
"MarketStatusAction::ShortSellRestrictionChange=SHORT_SELL_RESTRICTION_CHANGE",
"MarketStatusAction::Suspend=SUSPEND",
"MarketStatusAction::Trading=TRADING",
"OmsType::Hedging=HEDGING",
"OmsType::Netting=NETTING",
"OmsType::Unspecified=UNSPECIFIED",
"OptionKind::Call=CALL",
"OptionKind::Put=PUT",
"OrderSide::Buy=BUY",
"OrderSide::Sell=SELL",
"OrderStatus::Accepted=ACCEPTED",
"OrderStatus::Canceled=CANCELED",
"OrderStatus::Denied=DENIED",
"OrderStatus::Emulated=EMULATED",
"OrderStatus::Expired=EXPIRED",
"OrderStatus::Filled=FILLED",
"OrderStatus::Initialized=INITIALIZED",
"OrderStatus::PartiallyFilled=PARTIALLY_FILLED",
"OrderStatus::PendingCancel=PENDING_CANCEL",
"OrderStatus::PendingUpdate=PENDING_UPDATE",
"OrderStatus::Rejected=REJECTED",
"OrderStatus::Released=RELEASED",
"OrderStatus::Submitted=SUBMITTED",
"OrderStatus::Triggered=TRIGGERED",
"OrderStatus::Voided=VOIDED",
"OrderType::Limit=LIMIT",
"OrderType::LimitIfTouched=LIMIT_IF_TOUCHED",
"OrderType::Market=MARKET",
"OrderType::MarketIfTouched=MARKET_IF_TOUCHED",
"OrderType::MarketToLimit=MARKET_TO_LIMIT",
"OrderType::StopLimit=STOP_LIMIT",
"OrderType::StopMarket=STOP_MARKET",
"OrderType::TrailingStopLimit=TRAILING_STOP_LIMIT",
"OrderType::TrailingStopMarket=TRAILING_STOP_MARKET",
"OtoTriggerMode::Full=FULL",
"OtoTriggerMode::Partial=PARTIAL",
"PositionAdjustmentType::Commission=COMMISSION",
"PositionAdjustmentType::Funding=FUNDING",
"PositionSide::Flat=FLAT",
"PositionSide::Long=LONG",
"PositionSide::Short=SHORT",
"PriceType::Ask=ASK",
"PriceType::Bid=BID",
"PriceType::Last=LAST",
"PriceType::Mark=MARK",
"PriceType::Mid=MID",
"RecordFlag::F_LAST=F_LAST",
"RecordFlag::F_MBP=F_MBP",
"RecordFlag::F_SNAPSHOT=F_SNAPSHOT",
"RecordFlag::F_TOB=F_TOB",
"RecordFlag::RESERVED_1=RESERVED_1",
"RecordFlag::RESERVED_2=RESERVED_2",
"TimeInForce::AtTheClose=AT_THE_CLOSE",
"TimeInForce::AtTheOpen=AT_THE_OPEN",
"TimeInForce::Day=DAY",
"TimeInForce::Fok=FOK",
"TimeInForce::Gtc=GTC",
"TimeInForce::Gtd=GTD",
"TimeInForce::Ioc=IOC",
"TradingState::Active=ACTIVE",
"TradingState::Halted=HALTED",
"TradingState::Reducing=REDUCING",
"TrailingOffsetType::BasisPoints=BASIS_POINTS",
"TrailingOffsetType::Price=PRICE",
"TrailingOffsetType::PriceTier=PRICE_TIER",
"TrailingOffsetType::Ticks=TICKS",
"TriggerType::BidAsk=BID_ASK",
"TriggerType::Default=DEFAULT",
"TriggerType::DoubleBidAsk=DOUBLE_BID_ASK",
"TriggerType::DoubleLast=DOUBLE_LAST",
"TriggerType::IndexPrice=INDEX_PRICE",
"TriggerType::LastOrBidAsk=LAST_OR_BID_ASK",
"TriggerType::LastPrice=LAST_PRICE",
"TriggerType::MarkPrice=MARK_PRICE",
"TriggerType::MidPoint=MID_POINT",
];
#[rstest]
fn test_enum_wire_names_are_stable() {
let mut actual = Vec::new();
macro_rules! collect_wire_names {
($($t:ty),+ $(,)?) => {
$(for variant in <$t>::iter() {
actual.push(format!("{}::{variant:?}={variant}", stringify!($t)));
})+
};
}
collect_wire_names!(
AccountType,
AggregationSource,
AggressorSide,
AssetClass,
BarAggregation,
BarIntervalType,
BetSide,
BookAction,
BookType,
ContingencyType,
ContinuousFutureAdjustmentType,
CurrencyType,
GreeksConvention,
InstrumentClass,
InstrumentCloseType,
LiquiditySide,
MarketStatus,
MarketStatusAction,
OmsType,
OptionKind,
OrderSide,
OrderStatus,
OrderType,
OtoTriggerMode,
PositionAdjustmentType,
PositionSide,
PriceType,
RecordFlag,
TimeInForce,
TradingState,
TrailingOffsetType,
TriggerType,
);
actual.sort();
assert_eq!(
actual.len(),
EXPECTED_WIRE_NAMES.len(),
"variant count changed; update `EXPECTED_WIRE_NAMES`",
);
for (got, expected) in actual.iter().zip(EXPECTED_WIRE_NAMES) {
assert_eq!(got, expected);
}
}
macro_rules! assert_numeric_mapping {
($t:ty, $from:ident, $width:ty, $($value:literal => $variant:expr),+ $(,)?) => {{
let valid: &[$width] = &[$($value),+];
$(
assert_eq!(
<$t>::$from($value),
Some($variant),
"{}::{}({}) must map to the pinned variant",
stringify!($t),
stringify!($from),
$value,
);
)+
for value in <$width>::MIN..=<$width>::MAX {
if !valid.contains(&value) {
assert_eq!(
<$t>::$from(value),
None,
"{} must reject {value}",
stringify!($t),
);
}
}
}};
}
#[rstest]
fn test_from_u8_pins_numeric_discriminants() {
assert_numeric_mapping!(
AggressorSide, from_u8, u8,
0 => AggressorSide::NoAggressor,
1 => AggressorSide::Buy,
2 => AggressorSide::Sell,
);
assert_numeric_mapping!(
AssetClass, from_u8, u8,
1 => AssetClass::FX,
2 => AssetClass::Equity,
3 => AssetClass::Commodity,
4 => AssetClass::Debt,
5 => AssetClass::Index,
6 => AssetClass::Cryptocurrency,
7 => AssetClass::Alternative,
);
assert_numeric_mapping!(
BookAction, from_u8, u8,
1 => BookAction::Add,
2 => BookAction::Update,
3 => BookAction::Delete,
4 => BookAction::Clear,
);
assert_numeric_mapping!(
BookType, from_u8, u8,
1 => BookType::L1_MBP,
2 => BookType::L2_MBP,
3 => BookType::L3_MBO,
);
assert_numeric_mapping!(
InstrumentCloseType, from_u8, u8,
1 => InstrumentCloseType::EndOfSession,
2 => InstrumentCloseType::ContractExpired,
);
assert_numeric_mapping!(
PositionAdjustmentType, from_u8, u8,
1 => PositionAdjustmentType::Commission,
2 => PositionAdjustmentType::Funding,
);
}
#[rstest]
fn test_market_status_action_from_u16_pins_numeric_discriminants() {
assert_numeric_mapping!(
MarketStatusAction, from_u16, u16,
0 => MarketStatusAction::None,
1 => MarketStatusAction::PreOpen,
2 => MarketStatusAction::PreCross,
3 => MarketStatusAction::Quoting,
4 => MarketStatusAction::Cross,
5 => MarketStatusAction::Rotation,
6 => MarketStatusAction::NewPriceIndication,
7 => MarketStatusAction::Trading,
8 => MarketStatusAction::Halt,
9 => MarketStatusAction::Pause,
10 => MarketStatusAction::Suspend,
11 => MarketStatusAction::PreClose,
12 => MarketStatusAction::Close,
13 => MarketStatusAction::PostClose,
14 => MarketStatusAction::ShortSellRestrictionChange,
15 => MarketStatusAction::NotAvailableForTrading,
);
}
#[rstest]
#[case(OrderStatus::Initialized, false, false, false, false)]
#[case(OrderStatus::Denied, false, true, false, false)]
#[case(OrderStatus::Emulated, false, false, false, false)]
#[case(OrderStatus::Released, false, false, false, false)]
#[case(OrderStatus::Submitted, false, false, false, true)]
#[case(OrderStatus::Accepted, true, false, true, false)]
#[case(OrderStatus::Rejected, false, true, false, false)]
#[case(OrderStatus::Canceled, false, true, false, false)]
#[case(OrderStatus::Expired, false, true, false, false)]
#[case(OrderStatus::Triggered, true, false, true, false)]
#[case(OrderStatus::PendingUpdate, true, false, true, true)]
#[case(OrderStatus::PendingCancel, true, false, false, true)]
#[case(OrderStatus::PartiallyFilled, true, false, true, false)]
#[case(OrderStatus::Filled, false, true, false, false)]
#[case(OrderStatus::Voided, false, true, false, false)]
fn test_order_status_predicates(
#[case] status: OrderStatus,
#[case] expected_open: bool,
#[case] expected_closed: bool,
#[case] expected_cancellable: bool,
#[case] expected_inflight: bool,
) {
assert_eq!(status.is_open(), expected_open, "{status} is_open");
assert_eq!(status.is_closed(), expected_closed, "{status} is_closed");
assert_eq!(
status.is_cancellable(),
expected_cancellable,
"{status} is_cancellable",
);
assert_eq!(
status.is_inflight(),
expected_inflight,
"{status} is_inflight"
);
}
#[rstest]
fn test_order_status_predicates_hold_across_all_variants() {
for status in OrderStatus::iter() {
assert!(
!(status.is_open() && status.is_closed()),
"{status} cannot be both open and closed",
);
assert!(
!status.is_cancellable() || status.is_open(),
"{status} must be open to be cancellable",
);
assert!(
!(status.is_inflight() && status.is_closed()),
"{status} cannot be both in-flight and closed",
);
}
}
#[rstest]
#[case(OrderSide::Buy, OrderSide::Sell)]
#[case(OrderSide::Sell, OrderSide::Buy)]
fn test_order_side_opposite(#[case] side: OrderSide, #[case] expected: OrderSide) {
assert_eq!(side.opposite(), expected);
assert_eq!(side.opposite().opposite(), side);
}
#[rstest]
#[case(BetSide::Back, BetSide::Lay)]
#[case(BetSide::Lay, BetSide::Back)]
fn test_bet_side_opposite(#[case] side: BetSide, #[case] expected: BetSide) {
assert_eq!(side.opposite(), expected);
assert_eq!(side.opposite().opposite(), side);
}
#[rstest]
#[case(OrderSide::Buy, BetSide::Back)]
#[case(OrderSide::Sell, BetSide::Lay)]
fn test_bet_side_from_order_side(#[case] side: OrderSide, #[case] expected: BetSide) {
assert_eq!(BetSide::from(side), expected);
}
#[rstest]
#[case(RecordFlag::F_LAST, 0b0000_0000, false)]
#[case(RecordFlag::F_LAST, 0b1000_0000, true)]
#[case(RecordFlag::F_LAST, 0b0111_1111, false)]
#[case(RecordFlag::F_TOB, 0b1100_0000, true)]
#[case(RecordFlag::F_TOB, 0b1000_0000, false)]
#[case(RecordFlag::F_SNAPSHOT, 0b0010_0000, true)]
#[case(RecordFlag::F_SNAPSHOT, 0b1101_1111, false)]
#[case(RecordFlag::F_MBP, 0b0001_0000, true)]
#[case(RecordFlag::RESERVED_2, 0b0000_1000, true)]
#[case(RecordFlag::RESERVED_1, 0b0000_0100, true)]
#[case(RecordFlag::RESERVED_1, 0b0000_1000, false)]
#[case(RecordFlag::F_LAST, u8::MAX, true)]
fn test_record_flag_matches(
#[case] flag: RecordFlag,
#[case] value: u8,
#[case] expected: bool,
) {
assert_eq!(flag.matches(value), expected);
}
#[rstest]
fn test_record_flag_matches_only_its_own_bit() {
for flag in RecordFlag::iter() {
let bit = flag as u8;
assert_eq!(bit.count_ones(), 1, "{flag} must occupy exactly one bit");
assert!(flag.matches(bit), "{flag} must match its own bit");
assert!(
!flag.matches(!bit),
"{flag} must not match the inverse mask"
);
}
}
#[rstest]
#[case(InstrumentClass::Future, true)]
#[case(InstrumentClass::FuturesSpread, true)]
#[case(InstrumentClass::Option, true)]
#[case(InstrumentClass::OptionSpread, true)]
#[case(InstrumentClass::Spot, false)]
#[case(InstrumentClass::Swap, false)]
#[case(InstrumentClass::Forward, false)]
#[case(InstrumentClass::Cfd, false)]
#[case(InstrumentClass::Bond, false)]
#[case(InstrumentClass::Warrant, false)]
#[case(InstrumentClass::SportsBetting, false)]
#[case(InstrumentClass::BinaryOption, false)]
fn test_instrument_class_has_expiration(
#[case] class: InstrumentClass,
#[case] expected: bool,
) {
assert_eq!(class.has_expiration(), expected);
}
#[rstest]
fn test_optional_sides_serde_round_trips_present_values() {
let value = OptionalSides {
order: Some(OrderSide::Sell),
position: Some(PositionSide::Short),
};
let json = serde_json::to_string(&value).unwrap();
assert_eq!(json, r#"{"order":"SELL","position":"SHORT"}"#);
assert_eq!(serde_json::from_str::<OptionalSides>(&json).unwrap(), value);
}
#[rstest]
#[case(r#"{"order":"no_order_side","position":"FLAT"}"#)]
#[case(r#"{"order":"No_Order_Side","position":"FLAT"}"#)]
fn test_optional_sides_serde_accepts_none_token_in_any_case(#[case] json: &str) {
let decoded: OptionalSides = serde_json::from_str(json).unwrap();
assert_eq!(
decoded,
OptionalSides {
order: None,
position: Some(PositionSide::Flat),
},
);
}
#[rstest]
#[case(r#"{"order":"INVALID","position":"FLAT"}"#)]
#[case(r#"{"order":5,"position":"FLAT"}"#)]
#[case(r#"{"order":"BUY","position":"INVALID"}"#)]
#[case(r#"{"order":"BUY","position":true}"#)]
fn test_optional_sides_serde_rejects_invalid_values(#[case] json: &str) {
assert!(serde_json::from_str::<OptionalSides>(json).is_err());
}
}