nautilus_model/instruments/
mod.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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//! Instrument definitions for the trading domain model.
17
18pub mod any;
19pub mod betting;
20pub mod binary_option;
21pub mod crypto_future;
22pub mod crypto_option;
23pub mod crypto_perpetual;
24pub mod currency_pair;
25pub mod equity;
26pub mod futures_contract;
27pub mod futures_spread;
28pub mod option_contract;
29pub mod option_spread;
30pub mod synthetic;
31
32#[cfg(feature = "stubs")]
33pub mod stubs;
34
35use enum_dispatch::enum_dispatch;
36use nautilus_core::UnixNanos;
37use rust_decimal::Decimal;
38use rust_decimal_macros::dec;
39use ustr::Ustr;
40
41// Re-exports
42pub use crate::instruments::{
43    any::InstrumentAny, betting::BettingInstrument, binary_option::BinaryOption,
44    crypto_future::CryptoFuture, crypto_option::CryptoOption, crypto_perpetual::CryptoPerpetual,
45    currency_pair::CurrencyPair, equity::Equity, futures_contract::FuturesContract,
46    futures_spread::FuturesSpread, option_contract::OptionContract, option_spread::OptionSpread,
47    synthetic::SyntheticInstrument,
48};
49use crate::{
50    enums::{AssetClass, InstrumentClass, OptionKind},
51    identifiers::{InstrumentId, Symbol, Venue},
52    types::{Currency, Money, Price, Quantity},
53};
54
55#[enum_dispatch]
56pub trait Instrument: 'static + Send {
57    fn into_any(self) -> InstrumentAny;
58    fn id(&self) -> InstrumentId;
59    fn symbol(&self) -> Symbol {
60        self.id().symbol
61    }
62    fn venue(&self) -> Venue {
63        self.id().venue
64    }
65    fn raw_symbol(&self) -> Symbol;
66    fn asset_class(&self) -> AssetClass;
67    fn instrument_class(&self) -> InstrumentClass;
68    fn underlying(&self) -> Option<Ustr>;
69    fn base_currency(&self) -> Option<Currency>;
70    fn quote_currency(&self) -> Currency;
71    fn settlement_currency(&self) -> Currency;
72    fn cost_currency(&self) -> Currency {
73        if self.is_inverse() {
74            self.base_currency()
75                .expect("Inverse instruments must have a base currency")
76        } else {
77            self.quote_currency()
78        }
79    }
80    fn isin(&self) -> Option<Ustr>;
81    fn option_kind(&self) -> Option<OptionKind>;
82    fn exchange(&self) -> Option<Ustr>;
83    fn strike_price(&self) -> Option<Price>;
84    fn activation_ns(&self) -> Option<UnixNanos>;
85    fn expiration_ns(&self) -> Option<UnixNanos>;
86    fn is_inverse(&self) -> bool;
87    fn is_quanto(&self) -> bool {
88        if let Some(base_currency) = self.base_currency() {
89            self.settlement_currency() != base_currency
90        } else {
91            false
92        }
93    }
94    fn price_precision(&self) -> u8;
95    fn size_precision(&self) -> u8;
96    fn price_increment(&self) -> Price;
97    fn size_increment(&self) -> Quantity;
98    fn multiplier(&self) -> Quantity;
99    fn lot_size(&self) -> Option<Quantity>;
100    fn max_quantity(&self) -> Option<Quantity>;
101    fn min_quantity(&self) -> Option<Quantity>;
102    fn max_notional(&self) -> Option<Money>;
103    fn min_notional(&self) -> Option<Money>;
104    fn max_price(&self) -> Option<Price>;
105    fn min_price(&self) -> Option<Price>;
106    fn margin_init(&self) -> Decimal {
107        dec!(0) // Temporary until separate fee models
108    }
109
110    fn margin_maint(&self) -> Decimal {
111        dec!(0) // Temporary until separate fee models
112    }
113
114    fn maker_fee(&self) -> Decimal {
115        dec!(0) // Temporary until separate fee models
116    }
117
118    fn taker_fee(&self) -> Decimal {
119        dec!(0) // Temporary until separate fee models
120    }
121    fn ts_event(&self) -> UnixNanos;
122    fn ts_init(&self) -> UnixNanos;
123
124    /// Creates a new `Price` from the given `value` with the correct price precision for the instrument.
125    fn make_price(&self, value: f64) -> Price {
126        Price::new(value, self.price_precision())
127    }
128
129    /// Creates a new `Quantity` from the given `value` with the correct size precision for the instrument.
130    fn make_qty(&self, value: f64) -> Quantity {
131        Quantity::new(value, self.size_precision())
132    }
133
134    /// Calculates the notional value from the given parameters.
135    /// The `use_quote_for_inverse` flag is only applicable for inverse instruments.
136    ///
137    /// # Panics
138    ///
139    /// This function panics:
140    /// - If instrument is inverse and not `use_quote_for_inverse`, with no base currency.
141    fn calculate_notional_value(
142        &self,
143        quantity: Quantity,
144        price: Price,
145        use_quote_for_inverse: Option<bool>,
146    ) -> Money {
147        let use_quote_for_inverse = use_quote_for_inverse.unwrap_or(false);
148        let (amount, currency) = if self.is_inverse() {
149            if use_quote_for_inverse {
150                (quantity.as_f64(), self.quote_currency())
151            } else {
152                let amount =
153                    quantity.as_f64() * self.multiplier().as_f64() * (1.0 / price.as_f64());
154                let currency = self
155                    .base_currency()
156                    .expect("Error: no base currency for notional calculation");
157                (amount, currency)
158            }
159        } else {
160            let amount = quantity.as_f64() * self.multiplier().as_f64() * price.as_f64();
161            let currency = self.quote_currency();
162            (amount, currency)
163        };
164
165        Money::new(amount, currency)
166    }
167
168    /// Returns the equivalent quantity of the base asset.
169    fn calculate_base_quantity(&self, quantity: Quantity, last_px: Price) -> Quantity {
170        let value = quantity.as_f64() * (1.0 / last_px.as_f64());
171        Quantity::new(value, self.size_precision())
172    }
173}
174
175pub const EXPIRING_INSTRUMENT_TYPES: [InstrumentClass; 4] = [
176    InstrumentClass::Future,
177    InstrumentClass::FuturesSpread,
178    InstrumentClass::Option,
179    InstrumentClass::OptionSpread,
180];