nautilus_model/python/instruments/
futures_spread.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::{
17    collections::hash_map::DefaultHasher,
18    hash::{Hash, Hasher},
19};
20
21use nautilus_core::python::{serialization::from_dict_pyo3, to_pyvalue_err};
22use pyo3::{basic::CompareOp, prelude::*, types::PyDict};
23use rust_decimal::Decimal;
24use ustr::Ustr;
25
26use crate::{
27    enums::AssetClass,
28    identifiers::{InstrumentId, Symbol},
29    instruments::FuturesSpread,
30    types::{Currency, Price, Quantity},
31};
32
33#[pymethods]
34impl FuturesSpread {
35    #[allow(clippy::too_many_arguments)]
36    #[new]
37    #[pyo3(signature = (id, raw_symbol, asset_class, underlying, strategy_type, activation_ns, expiration_ns, currency, price_precision, price_increment, multiplier, lot_size, ts_event, ts_init, max_quantity=None, min_quantity=None, max_price=None, min_price=None, margin_init=None, margin_maint=None, maker_fee=None, taker_fee=None, exchange=None))]
38    fn py_new(
39        id: InstrumentId,
40        raw_symbol: Symbol,
41        asset_class: AssetClass,
42        underlying: String,
43        strategy_type: String,
44        activation_ns: u64,
45        expiration_ns: u64,
46        currency: Currency,
47        price_precision: u8,
48        price_increment: Price,
49        multiplier: Quantity,
50        lot_size: Quantity,
51        ts_event: u64,
52        ts_init: u64,
53        max_quantity: Option<Quantity>,
54        min_quantity: Option<Quantity>,
55        max_price: Option<Price>,
56        min_price: Option<Price>,
57        margin_init: Option<Decimal>,
58        margin_maint: Option<Decimal>,
59        maker_fee: Option<Decimal>,
60        taker_fee: Option<Decimal>,
61        exchange: Option<String>,
62    ) -> PyResult<Self> {
63        Self::new_checked(
64            id,
65            raw_symbol,
66            asset_class,
67            exchange.map(|x| Ustr::from(&x)),
68            underlying.into(),
69            strategy_type.into(),
70            activation_ns.into(),
71            expiration_ns.into(),
72            currency,
73            price_precision,
74            price_increment,
75            multiplier,
76            lot_size,
77            max_quantity,
78            min_quantity,
79            max_price,
80            min_price,
81            margin_init,
82            margin_maint,
83            maker_fee,
84            taker_fee,
85            ts_event.into(),
86            ts_init.into(),
87        )
88        .map_err(to_pyvalue_err)
89    }
90
91    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
92        match op {
93            CompareOp::Eq => self.eq(other).into_py(py),
94            _ => panic!("Not implemented"),
95        }
96    }
97
98    fn __hash__(&self) -> isize {
99        let mut hasher = DefaultHasher::new();
100        self.hash(&mut hasher);
101        hasher.finish() as isize
102    }
103
104    #[getter]
105    fn type_str(&self) -> &str {
106        stringify!(FuturesSpread)
107    }
108
109    #[getter]
110    #[pyo3(name = "id")]
111    fn py_id(&self) -> InstrumentId {
112        self.id
113    }
114
115    #[getter]
116    #[pyo3(name = "raw_symbol")]
117    fn py_raw_symbol(&self) -> Symbol {
118        self.raw_symbol
119    }
120
121    #[getter]
122    #[pyo3(name = "asset_class")]
123    fn py_asset_class(&self) -> AssetClass {
124        self.asset_class
125    }
126
127    #[getter]
128    #[pyo3(name = "exchange")]
129    fn py_exchange(&self) -> Option<String> {
130        self.exchange.map(|e| e.to_string())
131    }
132
133    #[getter]
134    #[pyo3(name = "underlying")]
135    fn py_underlying(&self) -> &str {
136        self.underlying.as_str()
137    }
138
139    #[getter]
140    #[pyo3(name = "strategy_type")]
141    fn py_strategy_type(&self) -> &str {
142        self.strategy_type.as_str()
143    }
144
145    #[getter]
146    #[pyo3(name = "activation_ns")]
147    fn py_activation_ns(&self) -> u64 {
148        self.activation_ns.as_u64()
149    }
150
151    #[getter]
152    #[pyo3(name = "expiration_ns")]
153    fn py_expiration_ns(&self) -> u64 {
154        self.expiration_ns.as_u64()
155    }
156
157    #[getter]
158    #[pyo3(name = "currency")]
159    fn py_currency(&self) -> Currency {
160        self.currency
161    }
162
163    #[getter]
164    #[pyo3(name = "price_precision")]
165    fn py_price_precision(&self) -> u8 {
166        self.price_precision
167    }
168
169    #[getter]
170    #[pyo3(name = "price_increment")]
171    fn py_price_increment(&self) -> Price {
172        self.price_increment
173    }
174
175    #[getter]
176    #[pyo3(name = "size_increment")]
177    fn py_size_increment(&self) -> Quantity {
178        self.size_increment
179    }
180
181    #[getter]
182    #[pyo3(name = "size_precision")]
183    fn py_size_precision(&self) -> u8 {
184        self.size_precision
185    }
186
187    #[getter]
188    #[pyo3(name = "multiplier")]
189    fn py_multiplier(&self) -> Quantity {
190        self.multiplier
191    }
192
193    #[getter]
194    #[pyo3(name = "lot_size")]
195    fn py_lot_size(&self) -> Quantity {
196        self.lot_size
197    }
198
199    #[getter]
200    #[pyo3(name = "max_quantity")]
201    fn py_max_quantity(&self) -> Option<Quantity> {
202        self.max_quantity
203    }
204
205    #[getter]
206    #[pyo3(name = "min_quantity")]
207    fn py_min_quantity(&self) -> Option<Quantity> {
208        self.min_quantity
209    }
210
211    #[getter]
212    #[pyo3(name = "max_price")]
213    fn py_max_price(&self) -> Option<Price> {
214        self.max_price
215    }
216
217    #[getter]
218    #[pyo3(name = "min_price")]
219    fn py_min_price(&self) -> Option<Price> {
220        self.min_price
221    }
222
223    #[getter]
224    #[pyo3(name = "margin_init")]
225    fn py_margin_init(&self) -> Decimal {
226        self.margin_init
227    }
228
229    #[getter]
230    #[pyo3(name = "margin_maint")]
231    fn py_margin_maint(&self) -> Decimal {
232        self.margin_maint
233    }
234
235    #[getter]
236    #[pyo3(name = "info")]
237    fn py_info(&self, py: Python<'_>) -> PyResult<PyObject> {
238        Ok(PyDict::new(py).into())
239    }
240
241    #[getter]
242    #[pyo3(name = "ts_event")]
243    fn py_ts_event(&self) -> u64 {
244        self.ts_event.as_u64()
245    }
246
247    #[getter]
248    #[pyo3(name = "ts_init")]
249    fn py_ts_init(&self) -> u64 {
250        self.ts_init.as_u64()
251    }
252
253    #[staticmethod]
254    #[pyo3(name = "from_dict")]
255    fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
256        from_dict_pyo3(py, values)
257    }
258
259    #[pyo3(name = "to_dict")]
260    fn py_to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
261        let dict = PyDict::new(py);
262        dict.set_item("type", stringify!(FuturesSpread))?;
263        dict.set_item("id", self.id.to_string())?;
264        dict.set_item("raw_symbol", self.raw_symbol.to_string())?;
265        dict.set_item("asset_class", self.asset_class.to_string())?;
266        dict.set_item("underlying", self.underlying.to_string())?;
267        dict.set_item("strategy_type", self.strategy_type.to_string())?;
268        dict.set_item("activation_ns", self.activation_ns.as_u64())?;
269        dict.set_item("expiration_ns", self.expiration_ns.as_u64())?;
270        dict.set_item("currency", self.currency.code.to_string())?;
271        dict.set_item("price_precision", self.price_precision)?;
272        dict.set_item("price_increment", self.price_increment.to_string())?;
273        dict.set_item("size_increment", self.size_increment.to_string())?;
274        dict.set_item("size_precision", self.size_precision)?;
275        dict.set_item("multiplier", self.multiplier.to_string())?;
276        dict.set_item("lot_size", self.lot_size.to_string())?;
277        dict.set_item("margin_init", self.margin_init.to_string())?;
278        dict.set_item("margin_maint", self.margin_maint.to_string())?;
279        dict.set_item("maker_fee", self.maker_fee.to_string())?;
280        dict.set_item("taker_fee", self.taker_fee.to_string())?;
281        dict.set_item("info", PyDict::new(py))?;
282        dict.set_item("ts_event", self.ts_event.as_u64())?;
283        dict.set_item("ts_init", self.ts_init.as_u64())?;
284        match self.max_quantity {
285            Some(value) => dict.set_item("max_quantity", value.to_string())?,
286            None => dict.set_item("max_quantity", py.None())?,
287        }
288        match self.min_quantity {
289            Some(value) => dict.set_item("min_quantity", value.to_string())?,
290            None => dict.set_item("min_quantity", py.None())?,
291        }
292        match self.max_price {
293            Some(value) => dict.set_item("max_price", value.to_string())?,
294            None => dict.set_item("max_price", py.None())?,
295        }
296        match self.min_price {
297            Some(value) => dict.set_item("min_price", value.to_string())?,
298            None => dict.set_item("min_price", py.None())?,
299        }
300        match self.exchange {
301            Some(value) => dict.set_item("exchange", value.to_string())?,
302            None => dict.set_item("exchange", py.None())?,
303        }
304        Ok(dict.into())
305    }
306}