nautilus_model/python/types/
currency.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::str::FromStr;
17
18use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyruntime_err, to_pyvalue_err};
19use pyo3::{
20    IntoPyObjectExt,
21    prelude::*,
22    pyclass::CompareOp,
23    types::{PyInt, PyString, PyTuple},
24};
25use ustr::Ustr;
26
27use crate::{enums::CurrencyType, types::Currency};
28
29#[pymethods]
30impl Currency {
31    #[new]
32    fn py_new(
33        code: &str,
34        precision: u8,
35        iso4217: u16,
36        name: &str,
37        currency_type: CurrencyType,
38    ) -> PyResult<Self> {
39        Self::new_checked(code, precision, iso4217, name, currency_type).map_err(to_pyvalue_err)
40    }
41
42    fn __setstate__(&mut self, state: &Bound<'_, PyAny>) -> PyResult<()> {
43        let py_tuple: &Bound<'_, PyTuple> = state.downcast::<PyTuple>()?;
44        self.code = Ustr::from(
45            py_tuple
46                .get_item(0)?
47                .downcast::<PyString>()?
48                .extract::<&str>()?,
49        );
50        self.precision = py_tuple.get_item(1)?.downcast::<PyInt>()?.extract::<u8>()?;
51        self.iso4217 = py_tuple
52            .get_item(2)?
53            .downcast::<PyInt>()?
54            .extract::<u16>()?;
55        self.name = Ustr::from(
56            py_tuple
57                .get_item(3)?
58                .downcast::<PyString>()?
59                .extract::<&str>()?,
60        );
61        self.currency_type = CurrencyType::from_str(
62            py_tuple
63                .get_item(4)?
64                .downcast::<PyString>()?
65                .extract::<&str>()?,
66        )
67        .map_err(to_pyvalue_err)?;
68        Ok(())
69    }
70
71    fn __getstate__(&self, py: Python) -> PyResult<PyObject> {
72        (
73            self.code.to_string(),
74            self.precision,
75            self.iso4217,
76            self.name.to_string(),
77            self.currency_type.to_string(),
78        )
79            .into_py_any(py)
80    }
81
82    fn __reduce__(&self, py: Python) -> PyResult<PyObject> {
83        let safe_constructor = py.get_type::<Self>().getattr("_safe_constructor")?;
84        let state = self.__getstate__(py)?;
85        (safe_constructor, PyTuple::empty(py), state).into_py_any(py)
86    }
87
88    #[staticmethod]
89    fn _safe_constructor() -> PyResult<Self> {
90        Ok(Self::AUD()) // Safe default
91    }
92
93    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
94        match op {
95            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
96            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
97            _ => py.NotImplemented(),
98        }
99    }
100
101    fn __hash__(&self) -> isize {
102        self.code.precomputed_hash() as isize
103    }
104
105    fn __repr__(&self) -> String {
106        format!("{self:?}")
107    }
108
109    fn __str__(&self) -> &'static str {
110        self.code.as_str()
111    }
112
113    #[getter]
114    #[pyo3(name = "code")]
115    fn py_code(&self) -> &'static str {
116        self.code.as_str()
117    }
118
119    #[getter]
120    #[pyo3(name = "precision")]
121    fn py_precision(&self) -> u8 {
122        self.precision
123    }
124
125    #[getter]
126    #[pyo3(name = "iso4217")]
127    fn py_iso4217(&self) -> u16 {
128        self.iso4217
129    }
130
131    #[getter]
132    #[pyo3(name = "name")]
133    fn py_name(&self) -> &'static str {
134        self.name.as_str()
135    }
136
137    #[getter]
138    #[pyo3(name = "currency_type")]
139    fn py_currency_type(&self) -> CurrencyType {
140        self.currency_type
141    }
142
143    #[staticmethod]
144    #[pyo3(name = "is_fiat")]
145    fn py_is_fiat(code: &str) -> PyResult<bool> {
146        Self::is_fiat(code).map_err(to_pyvalue_err)
147    }
148
149    #[staticmethod]
150    #[pyo3(name = "is_crypto")]
151    fn py_is_crypto(code: &str) -> PyResult<bool> {
152        Self::is_crypto(code).map_err(to_pyvalue_err)
153    }
154
155    #[staticmethod]
156    #[pyo3(name = "is_commodity_backed")]
157    fn py_is_commodidity_backed(code: &str) -> PyResult<bool> {
158        Self::is_commodity_backed(code).map_err(to_pyvalue_err)
159    }
160
161    #[staticmethod]
162    #[pyo3(name = "from_str")]
163    #[pyo3(signature = (value, strict = false))]
164    fn py_from_str(value: &str, strict: bool) -> PyResult<Self> {
165        match Self::from_str(value) {
166            Ok(currency) => Ok(currency),
167            Err(e) => {
168                if strict {
169                    Err(to_pyvalue_err(e))
170                } else {
171                    Self::new_checked(value, 8, 0, value, CurrencyType::Crypto)
172                        .map_err(to_pyvalue_err)
173                }
174            }
175        }
176    }
177
178    #[staticmethod]
179    #[pyo3(name = "register")]
180    #[pyo3(signature = (currency, overwrite = false))]
181    fn py_register(currency: Self, overwrite: bool) -> PyResult<()> {
182        Self::register(currency, overwrite).map_err(to_pyruntime_err)
183    }
184}