nautilus_model/python/instruments/
equity.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    identifiers::{InstrumentId, Symbol},
28    instruments::Equity,
29    types::{Currency, Price, Quantity},
30};
31
32#[pymethods]
33impl Equity {
34    #[allow(clippy::too_many_arguments)]
35    #[new]
36    #[pyo3(signature = (id, raw_symbol, currency, price_precision, price_increment, ts_event, ts_init, isin=None, lot_size=None, max_quantity=None, min_quantity=None, max_price=None, min_price=None, margin_init=None, margin_maint=None, maker_fee=None, taker_fee=None))]
37    fn py_new(
38        id: InstrumentId,
39        raw_symbol: Symbol,
40        currency: Currency,
41        price_precision: u8,
42        price_increment: Price,
43        ts_event: u64,
44        ts_init: u64,
45        isin: Option<String>,
46        lot_size: Option<Quantity>,
47        max_quantity: Option<Quantity>,
48        min_quantity: Option<Quantity>,
49        max_price: Option<Price>,
50        min_price: Option<Price>,
51        margin_init: Option<Decimal>,
52        margin_maint: Option<Decimal>,
53        maker_fee: Option<Decimal>,
54        taker_fee: Option<Decimal>,
55    ) -> PyResult<Self> {
56        Self::new_checked(
57            id,
58            raw_symbol,
59            isin.map(|x| Ustr::from(&x)),
60            currency,
61            price_precision,
62            price_increment,
63            lot_size,
64            max_quantity,
65            min_quantity,
66            max_price,
67            min_price,
68            margin_init,
69            margin_maint,
70            maker_fee,
71            taker_fee,
72            ts_event.into(),
73            ts_init.into(),
74        )
75        .map_err(to_pyvalue_err)
76    }
77
78    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
79        match op {
80            CompareOp::Eq => self.eq(other).into_py(py),
81            _ => panic!("Not implemented"),
82        }
83    }
84
85    fn __hash__(&self) -> isize {
86        let mut hasher = DefaultHasher::new();
87        self.hash(&mut hasher);
88        hasher.finish() as isize
89    }
90
91    #[getter]
92    fn type_str(&self) -> &str {
93        stringify!(Equity)
94    }
95
96    #[getter]
97    #[pyo3(name = "id")]
98    fn py_id(&self) -> InstrumentId {
99        self.id
100    }
101
102    #[getter]
103    #[pyo3(name = "raw_symbol")]
104    fn py_raw_symbol(&self) -> Symbol {
105        self.raw_symbol
106    }
107
108    #[getter]
109    #[pyo3(name = "isin")]
110    fn py_isin(&self) -> Option<&str> {
111        match self.isin {
112            Some(isin) => Some(isin.as_str()),
113            None => None,
114        }
115    }
116
117    #[getter]
118    #[pyo3(name = "quote_currency")] // TODO: Currency property standardization
119    fn py_quote_currency(&self) -> Currency {
120        self.currency
121    }
122
123    #[getter]
124    #[pyo3(name = "price_precision")]
125    fn py_price_precision(&self) -> u8 {
126        self.price_precision
127    }
128
129    #[getter]
130    #[pyo3(name = "size_precision")]
131    fn py_size_precision(&self) -> u8 {
132        0
133    }
134
135    #[getter]
136    #[pyo3(name = "price_increment")]
137    fn py_price_increment(&self) -> Price {
138        self.price_increment
139    }
140
141    #[getter]
142    #[pyo3(name = "size_increment")]
143    fn py_size_increment(&self) -> Quantity {
144        Quantity::from(1)
145    }
146
147    #[getter]
148    #[pyo3(name = "lot_size")]
149    fn py_lot_size(&self) -> Option<Quantity> {
150        self.lot_size
151    }
152
153    #[getter]
154    #[pyo3(name = "max_quantity")]
155    fn py_max_quantity(&self) -> Option<Quantity> {
156        self.max_quantity
157    }
158
159    #[getter]
160    #[pyo3(name = "min_quantity")]
161    fn py_min_quantity(&self) -> Option<Quantity> {
162        self.min_quantity
163    }
164
165    #[getter]
166    #[pyo3(name = "max_price")]
167    fn py_max_price(&self) -> Option<Price> {
168        self.max_price
169    }
170
171    #[getter]
172    #[pyo3(name = "min_price")]
173    fn py_min_price(&self) -> Option<Price> {
174        self.min_price
175    }
176
177    #[getter]
178    #[pyo3(name = "ts_event")]
179    fn py_ts_event(&self) -> u64 {
180        self.ts_event.as_u64()
181    }
182
183    #[getter]
184    #[pyo3(name = "ts_init")]
185    fn py_ts_init(&self) -> u64 {
186        self.ts_init.as_u64()
187    }
188
189    #[getter]
190    #[pyo3(name = "info")]
191    fn py_info(&self, py: Python<'_>) -> PyResult<PyObject> {
192        Ok(PyDict::new(py).into())
193    }
194
195    #[staticmethod]
196    #[pyo3(name = "from_dict")]
197    fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
198        from_dict_pyo3(py, values)
199    }
200
201    #[pyo3(name = "to_dict")]
202    fn py_to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
203        let dict = PyDict::new(py);
204        dict.set_item("type", stringify!(Equity))?;
205        dict.set_item("id", self.id.to_string())?;
206        dict.set_item("raw_symbol", self.raw_symbol.to_string())?;
207        dict.set_item("currency", self.currency.code.to_string())?;
208        dict.set_item("price_precision", self.price_precision)?;
209        dict.set_item("price_increment", self.price_increment.to_string())?;
210        dict.set_item("ts_event", self.ts_event.as_u64())?;
211        dict.set_item("ts_init", self.ts_init.as_u64())?;
212        dict.set_item("info", PyDict::new(py))?;
213        dict.set_item("maker_fee", self.maker_fee.to_string())?;
214        dict.set_item("taker_fee", self.taker_fee.to_string())?;
215        dict.set_item("margin_init", self.margin_init.to_string())?;
216        dict.set_item("margin_maint", self.margin_maint.to_string())?;
217        match &self.isin {
218            Some(value) => dict.set_item("isin", value.to_string())?,
219            None => dict.set_item("isin", py.None())?,
220        }
221        match self.lot_size {
222            Some(value) => dict.set_item("lot_size", value.to_string())?,
223            None => dict.set_item("lot_size", py.None())?,
224        }
225        match self.max_quantity {
226            Some(value) => dict.set_item("max_quantity", value.to_string())?,
227            None => dict.set_item("max_quantity", py.None())?,
228        }
229        match self.min_quantity {
230            Some(value) => dict.set_item("min_quantity", value.to_string())?,
231            None => dict.set_item("min_quantity", py.None())?,
232        }
233        match self.max_price {
234            Some(value) => dict.set_item("max_price", value.to_string())?,
235            None => dict.set_item("max_price", py.None())?,
236        }
237        match self.min_price {
238            Some(value) => dict.set_item("min_price", value.to_string())?,
239            None => dict.set_item("min_price", py.None())?,
240        }
241        Ok(dict.into())
242    }
243}