nautilus_model/python/types/
balance.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_pyvalue_err};
19use pyo3::{basic::CompareOp, prelude::*, types::PyDict};
20
21use crate::{
22    identifiers::InstrumentId,
23    types::{AccountBalance, Currency, MarginBalance, Money},
24};
25
26#[pymethods]
27impl AccountBalance {
28    #[new]
29    fn py_new(total: Money, locked: Money, free: Money) -> PyResult<Self> {
30        Self::new_checked(total, locked, free).map_err(to_pyvalue_err)
31    }
32
33    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
34        match op {
35            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
36            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
37            _ => py.NotImplemented(),
38        }
39    }
40
41    fn __repr__(&self) -> String {
42        format!("{self:?}")
43    }
44
45    fn __str__(&self) -> String {
46        self.to_string()
47    }
48
49    /// Constructs an [`AccountBalance`] from a Python dict.
50    ///
51    /// # Errors
52    ///
53    /// Returns a `PyErr` if parsing or conversion fails.
54    ///
55    /// # Panics
56    ///
57    /// Panics if parsing numeric values (`unwrap()`) fails due to invalid format.
58    #[staticmethod]
59    #[pyo3(name = "from_dict")]
60    pub fn py_from_dict(values: &Bound<'_, PyDict>) -> PyResult<Self> {
61        let dict = values.as_ref();
62        let currency: String = dict.get_item("currency")?.extract()?;
63        let total_str: String = dict.get_item("total")?.extract()?;
64        let total: f64 = total_str.parse::<f64>().unwrap();
65        let free_str: String = dict.get_item("free")?.extract()?;
66        let free: f64 = free_str.parse::<f64>().unwrap();
67        let locked_str: String = dict.get_item("locked")?.extract()?;
68        let locked: f64 = locked_str.parse::<f64>().unwrap();
69        let currency = Currency::from_str(currency.as_str()).map_err(to_pyvalue_err)?;
70        Self::new_checked(
71            Money::new(total, currency),
72            Money::new(locked, currency),
73            Money::new(free, currency),
74        )
75        .map_err(to_pyvalue_err)
76    }
77
78    /// Converts this [`AccountBalance`] into a Python dict.
79    ///
80    /// # Errors
81    ///
82    /// Returns a `PyErr` if serialization fails.
83    #[pyo3(name = "to_dict")]
84    pub fn py_to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
85        let dict = PyDict::new(py);
86        dict.set_item("type", stringify!(AccountBalance))?;
87        dict.set_item(
88            "total",
89            format!(
90                "{:.*}",
91                self.total.currency.precision as usize,
92                self.total.as_f64()
93            ),
94        )?;
95        dict.set_item(
96            "locked",
97            format!(
98                "{:.*}",
99                self.locked.currency.precision as usize,
100                self.locked.as_f64()
101            ),
102        )?;
103        dict.set_item(
104            "free",
105            format!(
106                "{:.*}",
107                self.free.currency.precision as usize,
108                self.free.as_f64()
109            ),
110        )?;
111        dict.set_item("currency", self.currency.code.to_string())?;
112        Ok(dict.into())
113    }
114}
115
116#[pymethods]
117impl MarginBalance {
118    #[new]
119    fn py_new(initial: Money, maintenance: Money, instrument: InstrumentId) -> Self {
120        Self::new(initial, maintenance, instrument)
121    }
122    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
123        match op {
124            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
125            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
126            _ => py.NotImplemented(),
127        }
128    }
129
130    fn __repr__(&self) -> String {
131        format!("{self:?}")
132    }
133
134    fn __str__(&self) -> String {
135        self.to_string()
136    }
137
138    /// Constructs a [`MarginBalance`] from a Python dict.
139    ///
140    /// # Errors
141    ///
142    /// Returns a `PyErr` if parsing or conversion fails.
143    ///
144    /// # Panics
145    ///
146    /// Panics if parsing numeric values (`unwrap()`) fails due to invalid format.
147    #[staticmethod]
148    #[pyo3(name = "from_dict")]
149    pub fn py_from_dict(values: &Bound<'_, PyDict>) -> PyResult<Self> {
150        let dict = values.as_ref();
151        let currency: String = dict.get_item("currency")?.extract()?;
152        let initial_str: String = dict.get_item("initial")?.extract()?;
153        let initial: f64 = initial_str.parse::<f64>().unwrap();
154        let maintenance_str: String = dict.get_item("maintenance")?.extract()?;
155        let maintenance: f64 = maintenance_str.parse::<f64>().unwrap();
156        let instrument_id_str: String = dict.get_item("instrument_id")?.extract()?;
157        let currency = Currency::from_str(currency.as_str()).map_err(to_pyvalue_err)?;
158        let account_balance = Self::new(
159            Money::new(initial, currency),
160            Money::new(maintenance, currency),
161            InstrumentId::from(instrument_id_str.as_str()),
162        );
163        Ok(account_balance)
164    }
165
166    /// Converts this [`MarginBalance`] into a Python dict.
167    ///
168    /// # Errors
169    ///
170    /// Returns a `PyErr` if serialization fails.
171    ///
172    /// # Panics
173    ///
174    /// Panics if parsing numeric values (`unwrap()`) fails due to invalid format.
175    #[pyo3(name = "to_dict")]
176    pub fn py_to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
177        let dict = PyDict::new(py);
178        dict.set_item("type", stringify!(MarginBalance))?;
179        dict.set_item(
180            "initial",
181            format!(
182                "{:.*}",
183                self.initial.currency.precision as usize,
184                self.initial.as_f64()
185            ),
186        )?;
187        dict.set_item(
188            "maintenance",
189            format!(
190                "{:.*}",
191                self.maintenance.currency.precision as usize,
192                self.maintenance.as_f64()
193            ),
194        )?;
195        dict.set_item("currency", self.currency.code.to_string())?;
196        dict.set_item("instrument_id", self.instrument_id.to_string())?;
197        Ok(dict.into())
198    }
199}