Skip to main content

nautilus_model/instruments/
any.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
16use 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    /// Returns true if the instrument is a spread instrument.
61    #[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}