nautilus_model/instruments/
crypto_future.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
16use std::hash::{Hash, Hasher};
17
18use nautilus_core::{
19    correctness::{check_equal_u8, FAILED},
20    UnixNanos,
21};
22use rust_decimal::Decimal;
23use serde::{Deserialize, Serialize};
24use ustr::Ustr;
25
26use super::{any::InstrumentAny, Instrument};
27use crate::{
28    enums::{AssetClass, InstrumentClass, OptionKind},
29    identifiers::{InstrumentId, Symbol},
30    types::{
31        currency::Currency,
32        money::Money,
33        price::{check_positive_price, Price},
34        quantity::{check_positive_quantity, Quantity},
35    },
36};
37
38/// Represents a deliverable futures contract instrument, with crypto assets as underlying and for settlement.
39#[repr(C)]
40#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
41#[cfg_attr(
42    feature = "python",
43    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
44)]
45pub struct CryptoFuture {
46    /// The instrument ID for the instrument.
47    pub id: InstrumentId,
48    /// The raw/local/native symbol for the instrument, assigned by the venue.
49    pub raw_symbol: Symbol,
50    /// The underlying asset.
51    pub underlying: Currency,
52    /// The contract quote currency.
53    pub quote_currency: Currency,
54    /// The settlement currency.
55    pub settlement_currency: Currency,
56    /// If the instrument costing is inverse (quantity expressed in quote currency units).
57    pub is_inverse: bool,
58    /// UNIX timestamp (nanoseconds) for contract activation.
59    pub activation_ns: UnixNanos,
60    /// UNIX timestamp (nanoseconds) for contract expiration.
61    pub expiration_ns: UnixNanos,
62    /// The price decimal precision.
63    pub price_precision: u8,
64    /// The trading size decimal precision.
65    pub size_precision: u8,
66    /// The minimum price increment (tick size).
67    pub price_increment: Price,
68    /// The minimum size increment.
69    pub size_increment: Quantity,
70    /// The contract multiplier.
71    pub multiplier: Quantity,
72    /// The rounded lot unit size (standard/board).
73    pub lot_size: Quantity,
74    /// The initial (order) margin requirement in percentage of order value.
75    pub margin_init: Decimal,
76    /// The maintenance (position) margin in percentage of position value.
77    pub margin_maint: Decimal,
78    /// The fee rate for liquidity makers as a percentage of order value.
79    pub maker_fee: Decimal,
80    /// The fee rate for liquidity takers as a percentage of order value.
81    pub taker_fee: Decimal,
82    /// The maximum allowable order quantity.
83    pub max_quantity: Option<Quantity>,
84    /// The minimum allowable order quantity.
85    pub min_quantity: Option<Quantity>,
86    /// The maximum allowable order notional value.
87    pub max_notional: Option<Money>,
88    /// The minimum allowable order notional value.
89    pub min_notional: Option<Money>,
90    /// The maximum allowable quoted price.
91    pub max_price: Option<Price>,
92    /// The minimum allowable quoted price.
93    pub min_price: Option<Price>,
94    /// UNIX timestamp (nanoseconds) when the data event occurred.
95    pub ts_event: UnixNanos,
96    /// UNIX timestamp (nanoseconds) when the data object was initialized.
97    pub ts_init: UnixNanos,
98}
99
100impl CryptoFuture {
101    /// Creates a new [`CryptoFuture`] instance with correctness checking.
102    ///
103    /// # Notes
104    ///
105    /// PyO3 requires a `Result` type for proper error handling and stacktrace printing in Python.
106    #[allow(clippy::too_many_arguments)]
107    pub fn new_checked(
108        id: InstrumentId,
109        raw_symbol: Symbol,
110        underlying: Currency,
111        quote_currency: Currency,
112        settlement_currency: Currency,
113        is_inverse: bool,
114        activation_ns: UnixNanos,
115        expiration_ns: UnixNanos,
116        price_precision: u8,
117        size_precision: u8,
118        price_increment: Price,
119        size_increment: Quantity,
120        multiplier: Option<Quantity>,
121        lot_size: Option<Quantity>,
122        max_quantity: Option<Quantity>,
123        min_quantity: Option<Quantity>,
124        max_notional: Option<Money>,
125        min_notional: Option<Money>,
126        max_price: Option<Price>,
127        min_price: Option<Price>,
128        margin_init: Option<Decimal>,
129        margin_maint: Option<Decimal>,
130        maker_fee: Option<Decimal>,
131        taker_fee: Option<Decimal>,
132        ts_event: UnixNanos,
133        ts_init: UnixNanos,
134    ) -> anyhow::Result<Self> {
135        check_equal_u8(
136            price_precision,
137            price_increment.precision,
138            stringify!(price_precision),
139            stringify!(price_increment.precision),
140        )?;
141        check_equal_u8(
142            size_precision,
143            size_increment.precision,
144            stringify!(size_precision),
145            stringify!(size_increment.precision),
146        )?;
147        check_positive_price(price_increment.raw, stringify!(price_increment.raw))?;
148        check_positive_quantity(size_increment.raw, stringify!(size_increment.raw))?;
149
150        Ok(Self {
151            id,
152            raw_symbol,
153            underlying,
154            quote_currency,
155            settlement_currency,
156            is_inverse,
157            activation_ns,
158            expiration_ns,
159            price_precision,
160            size_precision,
161            price_increment,
162            size_increment,
163            multiplier: multiplier.unwrap_or(Quantity::from(1)),
164            lot_size: lot_size.unwrap_or(Quantity::from(1)),
165            margin_init: margin_init.unwrap_or_default(),
166            margin_maint: margin_maint.unwrap_or_default(),
167            maker_fee: maker_fee.unwrap_or_default(),
168            taker_fee: taker_fee.unwrap_or_default(),
169            max_quantity,
170            min_quantity,
171            max_notional,
172            min_notional,
173            max_price,
174            min_price,
175            ts_event,
176            ts_init,
177        })
178    }
179
180    /// Creates a new [`CryptoFuture`] instance.
181    #[allow(clippy::too_many_arguments)]
182    pub fn new(
183        id: InstrumentId,
184        raw_symbol: Symbol,
185        underlying: Currency,
186        quote_currency: Currency,
187        settlement_currency: Currency,
188        is_inverse: bool,
189        activation_ns: UnixNanos,
190        expiration_ns: UnixNanos,
191        price_precision: u8,
192        size_precision: u8,
193        price_increment: Price,
194        size_increment: Quantity,
195        multiplier: Option<Quantity>,
196        lot_size: Option<Quantity>,
197        max_quantity: Option<Quantity>,
198        min_quantity: Option<Quantity>,
199        max_notional: Option<Money>,
200        min_notional: Option<Money>,
201        max_price: Option<Price>,
202        min_price: Option<Price>,
203        margin_init: Option<Decimal>,
204        margin_maint: Option<Decimal>,
205        maker_fee: Option<Decimal>,
206        taker_fee: Option<Decimal>,
207        ts_event: UnixNanos,
208        ts_init: UnixNanos,
209    ) -> Self {
210        Self::new_checked(
211            id,
212            raw_symbol,
213            underlying,
214            quote_currency,
215            settlement_currency,
216            is_inverse,
217            activation_ns,
218            expiration_ns,
219            price_precision,
220            size_precision,
221            price_increment,
222            size_increment,
223            multiplier,
224            lot_size,
225            max_quantity,
226            min_quantity,
227            max_notional,
228            min_notional,
229            max_price,
230            min_price,
231            margin_init,
232            margin_maint,
233            maker_fee,
234            taker_fee,
235            ts_event,
236            ts_init,
237        )
238        .expect(FAILED)
239    }
240}
241
242impl PartialEq<Self> for CryptoFuture {
243    fn eq(&self, other: &Self) -> bool {
244        self.id == other.id
245    }
246}
247
248impl Eq for CryptoFuture {}
249
250impl Hash for CryptoFuture {
251    fn hash<H: Hasher>(&self, state: &mut H) {
252        self.id.hash(state);
253    }
254}
255
256impl Instrument for CryptoFuture {
257    fn into_any(self) -> InstrumentAny {
258        InstrumentAny::CryptoFuture(self)
259    }
260
261    fn id(&self) -> InstrumentId {
262        self.id
263    }
264
265    fn raw_symbol(&self) -> Symbol {
266        self.raw_symbol
267    }
268
269    fn asset_class(&self) -> AssetClass {
270        AssetClass::Cryptocurrency
271    }
272
273    fn instrument_class(&self) -> InstrumentClass {
274        InstrumentClass::Future
275    }
276
277    fn underlying(&self) -> Option<Ustr> {
278        Some(self.underlying.code)
279    }
280
281    fn base_currency(&self) -> Option<Currency> {
282        None
283    }
284
285    fn quote_currency(&self) -> Currency {
286        self.quote_currency
287    }
288
289    fn settlement_currency(&self) -> Currency {
290        self.settlement_currency
291    }
292
293    fn isin(&self) -> Option<Ustr> {
294        None
295    }
296
297    fn exchange(&self) -> Option<Ustr> {
298        None
299    }
300
301    fn option_kind(&self) -> Option<OptionKind> {
302        None
303    }
304
305    fn is_inverse(&self) -> bool {
306        self.is_inverse
307    }
308
309    fn price_precision(&self) -> u8 {
310        self.price_precision
311    }
312
313    fn size_precision(&self) -> u8 {
314        self.size_precision
315    }
316
317    fn price_increment(&self) -> Price {
318        self.price_increment
319    }
320
321    fn size_increment(&self) -> Quantity {
322        self.size_increment
323    }
324
325    fn multiplier(&self) -> Quantity {
326        Quantity::from(1)
327    }
328
329    fn lot_size(&self) -> Option<Quantity> {
330        Some(self.lot_size)
331    }
332
333    fn max_quantity(&self) -> Option<Quantity> {
334        self.max_quantity
335    }
336
337    fn min_quantity(&self) -> Option<Quantity> {
338        self.min_quantity
339    }
340
341    fn max_price(&self) -> Option<Price> {
342        self.max_price
343    }
344
345    fn min_price(&self) -> Option<Price> {
346        self.min_price
347    }
348
349    fn ts_event(&self) -> UnixNanos {
350        self.ts_event
351    }
352
353    fn ts_init(&self) -> UnixNanos {
354        self.ts_init
355    }
356
357    fn strike_price(&self) -> Option<Price> {
358        None
359    }
360
361    fn activation_ns(&self) -> Option<UnixNanos> {
362        Some(self.activation_ns)
363    }
364
365    fn expiration_ns(&self) -> Option<UnixNanos> {
366        Some(self.expiration_ns)
367    }
368
369    fn max_notional(&self) -> Option<Money> {
370        self.max_notional
371    }
372
373    fn min_notional(&self) -> Option<Money> {
374        self.min_notional
375    }
376}
377
378////////////////////////////////////////////////////////////////////////////////
379// Tests
380////////////////////////////////////////////////////////////////////////////////
381#[cfg(test)]
382mod tests {
383    use rstest::rstest;
384
385    use crate::instruments::{stubs::*, CryptoFuture};
386
387    #[rstest]
388    fn test_equality(crypto_future_btcusdt: CryptoFuture) {
389        let cloned = crypto_future_btcusdt;
390        assert_eq!(crypto_future_btcusdt, cloned);
391    }
392}