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