nautilus_model/instruments/
any.rs1use enum_dispatch::enum_dispatch;
17use serde::{Deserialize, Serialize};
18
19use super::{
20 Instrument, betting::BettingInstrument, binary_option::BinaryOption, cfd::Cfd,
21 commodity::Commodity, crypto_future::CryptoFuture, crypto_futures_spread::CryptoFuturesSpread,
22 crypto_option::CryptoOption, crypto_option_spread::CryptoOptionSpread,
23 crypto_perpetual::CryptoPerpetual, currency_pair::CurrencyPair, equity::Equity,
24 futures_contract::FuturesContract, futures_spread::FuturesSpread,
25 index_instrument::IndexInstrument, option_contract::OptionContract,
26 option_spread::OptionSpread, perpetual_contract::PerpetualContract,
27 tokenized_asset::TokenizedAsset,
28};
29use crate::types::{Price, Quantity};
30
31#[derive(Clone, Debug, Serialize, Deserialize)]
32#[enum_dispatch(Instrument)]
33pub enum InstrumentAny {
34 Betting(BettingInstrument),
35 BinaryOption(BinaryOption),
36 Cfd(Cfd),
37 Commodity(Commodity),
38 CryptoFuture(CryptoFuture),
39 CryptoFuturesSpread(CryptoFuturesSpread),
40 CryptoOption(CryptoOption),
41 CryptoOptionSpread(CryptoOptionSpread),
42 CryptoPerpetual(CryptoPerpetual),
43 CurrencyPair(CurrencyPair),
44 Equity(Equity),
45 FuturesContract(FuturesContract),
46 FuturesSpread(FuturesSpread),
47 IndexInstrument(IndexInstrument),
48 OptionContract(OptionContract),
49 OptionSpread(OptionSpread),
50 PerpetualContract(PerpetualContract),
51 TokenizedAsset(TokenizedAsset),
52}
53
54impl InstrumentAny {
55 #[must_use]
56 pub fn get_base_quantity(&self, quantity: Quantity, last_px: Price) -> Quantity {
57 self.calculate_base_quantity(quantity, last_px)
58 }
59
60 #[must_use]
62 pub fn is_spread(&self) -> bool {
63 matches!(
64 self,
65 Self::FuturesSpread(_)
66 | Self::OptionSpread(_)
67 | Self::CryptoFuturesSpread(_)
68 | Self::CryptoOptionSpread(_)
69 )
70 }
71}
72
73impl PartialEq for InstrumentAny {
74 fn eq(&self, other: &Self) -> bool {
75 self.id() == other.id()
76 }
77}
78
79impl crate::data::HasTsInit for InstrumentAny {
80 fn ts_init(&self) -> nautilus_core::UnixNanos {
81 Instrument::ts_init(self)
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use rstest::rstest;
88
89 use super::*;
90 use crate::instruments::stubs::*;
91
92 #[rstest]
93 #[case::betting(InstrumentAny::Betting(betting()), "3.33")]
94 #[case::binary_option(InstrumentAny::BinaryOption(binary_option()), "3.33")]
95 #[case::cfd(InstrumentAny::Cfd(cfd_gold()), "3")]
96 #[case::commodity(InstrumentAny::Commodity(commodity_gold()), "3")]
97 #[case::crypto_future(
98 InstrumentAny::CryptoFuture(crypto_future_btcusdt(
99 2,
100 6,
101 Price::from("0.01"),
102 Quantity::from("0.000001"),
103 )),
104 "3.333333"
105 )]
106 #[case::crypto_futures_spread(
107 InstrumentAny::CryptoFuturesSpread(crypto_futures_spread_btc_deribit()),
108 "3"
109 )]
110 #[case::crypto_option(
111 InstrumentAny::CryptoOption(crypto_option_btc_deribit(
112 3,
113 1,
114 Price::from("0.001"),
115 Quantity::from("0.1"),
116 )),
117 "3.3"
118 )]
119 #[case::crypto_option_spread(
120 InstrumentAny::CryptoOptionSpread(crypto_option_spread_btc_deribit()),
121 "3.3"
122 )]
123 #[case::crypto_perpetual(InstrumentAny::CryptoPerpetual(crypto_perpetual_ethusdt()), "3.333")]
124 #[case::currency_pair(InstrumentAny::CurrencyPair(currency_pair_btcusdt()), "3.333333")]
125 #[case::equity(InstrumentAny::Equity(equity_aapl()), "3")]
126 #[case::futures_contract(InstrumentAny::FuturesContract(futures_contract_es(None, None)), "3")]
127 #[case::futures_spread(InstrumentAny::FuturesSpread(futures_spread_es()), "3")]
128 #[case::index(InstrumentAny::IndexInstrument(index_instrument_spx()), "3")]
129 #[case::option_contract(InstrumentAny::OptionContract(option_contract_appl()), "3")]
130 #[case::option_spread(InstrumentAny::OptionSpread(option_spread()), "3")]
131 #[case::perpetual_contract(InstrumentAny::PerpetualContract(perpetual_contract_eurusd()), "3")]
132 #[case::tokenized_asset(InstrumentAny::TokenizedAsset(tokenized_asset_aaplx()), "3.3333")]
133 fn test_get_base_quantity(#[case] instrument: InstrumentAny, #[case] expected: &str) {
134 let quantity = instrument.get_base_quantity(Quantity::from("10"), Price::from("3"));
135 let expected = Quantity::from(expected);
136
137 assert_eq!(quantity.as_decimal(), expected.as_decimal());
138 assert_eq!(quantity.precision, expected.precision);
139 }
140
141 #[rstest]
142 #[case::futures_spread(InstrumentAny::FuturesSpread(futures_spread_es()), true)]
143 #[case::option_spread(InstrumentAny::OptionSpread(option_spread()), true)]
144 #[case::crypto_futures_spread(
145 InstrumentAny::CryptoFuturesSpread(crypto_futures_spread_btc_deribit()),
146 true
147 )]
148 #[case::crypto_option_spread(
149 InstrumentAny::CryptoOptionSpread(crypto_option_spread_btc_deribit()),
150 true
151 )]
152 #[case::crypto_future(
153 InstrumentAny::CryptoFuture(crypto_future_btcusdt(
154 2,
155 6,
156 crate::types::Price::from("0.01"),
157 crate::types::Quantity::from("0.000001"),
158 )),
159 false
160 )]
161 #[case::crypto_option(
162 InstrumentAny::CryptoOption(crypto_option_btc_deribit(
163 3,
164 1,
165 crate::types::Price::from("0.001"),
166 crate::types::Quantity::from("0.1"),
167 )),
168 false
169 )]
170 fn test_is_spread(#[case] instrument: InstrumentAny, #[case] expected: bool) {
171 assert_eq!(instrument.is_spread(), expected);
172 }
173}