Skip to main content

nautilus_model/python/types/
currency.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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::{correctness_error_to_pyvalue_err, to_pyruntime_err, to_pyvalue_err};
19use pyo3::{IntoPyObjectExt, prelude::*};
20
21use crate::{enums::CurrencyType, python::currency_lookup_error_to_pyvalue_err, types::Currency};
22
23#[pymethods]
24#[pyo3_stub_gen::derive::gen_stub_pymethods]
25impl Currency {
26    /// Represents a medium of exchange in a specified denomination with a fixed decimal precision.
27    ///
28    /// Handles up to `FIXED_PRECISION` decimals of precision.
29    #[new]
30    fn py_new(
31        code: &str,
32        precision: u8,
33        iso4217: u16,
34        name: &str,
35        currency_type: CurrencyType,
36    ) -> PyResult<Self> {
37        Self::new_checked(code, precision, iso4217, name, currency_type)
38            .map_err(correctness_error_to_pyvalue_err)
39    }
40
41    fn __reduce__(&self, py: Python) -> PyResult<Py<PyAny>> {
42        let unpickle = py.get_type::<Self>().getattr("_unpickle")?;
43        let args = (
44            self.code.to_string(),
45            self.precision,
46            self.iso4217,
47            self.name.to_string(),
48            self.currency_type.to_string(),
49        )
50            .into_py_any(py)?;
51        (unpickle, args).into_py_any(py)
52    }
53
54    #[staticmethod]
55    fn _unpickle(
56        code: &str,
57        precision: u8,
58        iso4217: u16,
59        name: &str,
60        currency_type_str: &str,
61    ) -> PyResult<Self> {
62        let currency_type = CurrencyType::from_str(currency_type_str).map_err(to_pyvalue_err)?;
63        Self::new_checked(code, precision, iso4217, name, currency_type)
64            .map_err(correctness_error_to_pyvalue_err)
65    }
66
67    fn __repr__(&self) -> String {
68        format!("{self:?}")
69    }
70
71    fn __str__(&self) -> &'static str {
72        self.code.as_str()
73    }
74
75    #[getter]
76    #[pyo3(name = "code")]
77    fn py_code(&self) -> &'static str {
78        self.code.as_str()
79    }
80
81    #[getter]
82    #[pyo3(name = "precision")]
83    fn py_precision(&self) -> u8 {
84        self.precision
85    }
86
87    #[getter]
88    #[pyo3(name = "iso4217")]
89    fn py_iso4217(&self) -> u16 {
90        self.iso4217
91    }
92
93    #[getter]
94    #[pyo3(name = "name")]
95    fn py_name(&self) -> &'static str {
96        self.name.as_str()
97    }
98
99    #[getter]
100    #[pyo3(name = "currency_type")]
101    fn py_currency_type(&self) -> CurrencyType {
102        self.currency_type
103    }
104
105    /// Checks if the currency identified by the given `code` is a fiat currency.
106    ///
107    /// # Errors
108    ///
109    /// Returns an error if a currency with the given `code` does not exist.
110    #[staticmethod]
111    #[pyo3(name = "is_fiat")]
112    fn py_is_fiat(code: &str) -> PyResult<bool> {
113        Self::is_fiat(code).map_err(currency_lookup_error_to_pyvalue_err)
114    }
115
116    /// Checks if the currency identified by the given `code` is a cryptocurrency.
117    ///
118    /// # Errors
119    ///
120    /// Returns an error if a currency with the given `code` does not exist.
121    #[staticmethod]
122    #[pyo3(name = "is_crypto")]
123    fn py_is_crypto(code: &str) -> PyResult<bool> {
124        Self::is_crypto(code).map_err(currency_lookup_error_to_pyvalue_err)
125    }
126
127    #[staticmethod]
128    #[pyo3(name = "is_commodity_backed")]
129    fn py_is_commodidity_backed(code: &str) -> PyResult<bool> {
130        Self::is_commodity_backed(code).map_err(currency_lookup_error_to_pyvalue_err)
131    }
132
133    #[staticmethod]
134    #[pyo3(name = "from_str")]
135    #[pyo3(signature = (value, strict = false))]
136    fn py_from_str(value: &str, strict: bool) -> PyResult<Self> {
137        match Self::from_str(value) {
138            Ok(currency) => Ok(currency),
139            Err(e) => {
140                if strict {
141                    Err(currency_lookup_error_to_pyvalue_err(e))
142                } else {
143                    Self::new_checked(value, 8, 0, value, CurrencyType::Crypto)
144                        .map_err(correctness_error_to_pyvalue_err)
145                }
146            }
147        }
148    }
149
150    /// Register the given `currency` in the internal currency map.
151    ///
152    /// - If `overwrite` is `true`, any existing currency will be replaced.
153    /// - If `overwrite` is `false` and the currency already exists, the operation is a no-op.
154    ///
155    /// This function does not return an error.
156    #[staticmethod]
157    #[pyo3(name = "register")]
158    #[pyo3(signature = (currency, overwrite = false))]
159    fn py_register(currency: Self, overwrite: bool) -> PyResult<()> {
160        Self::register(currency, overwrite).map_err(to_pyruntime_err)
161    }
162}