nautilus_model/python/account/
margin.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 nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
17use pyo3::{IntoPyObjectExt, basic::CompareOp, prelude::*, types::PyDict};
18
19use crate::{
20    accounts::MarginAccount,
21    events::AccountState,
22    identifiers::{AccountId, InstrumentId},
23    instruments::InstrumentAny,
24    python::instruments::pyobject_to_instrument_any,
25    types::{Money, Price, Quantity},
26};
27
28#[pymethods]
29impl MarginAccount {
30    #[new]
31    fn py_new(event: AccountState, calculate_account_state: bool) -> Self {
32        Self::new(event, calculate_account_state)
33    }
34
35    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
36        match op {
37            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
38            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
39            _ => py.NotImplemented(),
40        }
41    }
42
43    #[getter]
44    fn id(&self) -> AccountId {
45        self.id
46    }
47
48    #[getter]
49    fn default_leverage(&self) -> f64 {
50        self.default_leverage
51    }
52
53    #[getter]
54    #[pyo3(name = "calculate_account_state")]
55    fn py_calculate_account_state(&self) -> bool {
56        self.calculate_account_state
57    }
58
59    fn __repr__(&self) -> String {
60        format!(
61            "{}(id={}, type={}, base={})",
62            stringify!(MarginAccount),
63            self.id,
64            self.account_type,
65            self.base_currency.map_or_else(
66                || "None".to_string(),
67                |base_currency| format!("{}", base_currency.code)
68            ),
69        )
70    }
71
72    #[pyo3(name = "set_default_leverage")]
73    fn py_set_default_leverage(&mut self, default_leverage: f64) -> PyResult<()> {
74        self.set_default_leverage(default_leverage);
75        Ok(())
76    }
77
78    #[pyo3(name = "leverages")]
79    fn py_leverages(&self, py: Python) -> PyResult<PyObject> {
80        let leverages = PyDict::new(py);
81        for (key, &value) in &self.leverages {
82            leverages
83                .set_item(key.into_py_any_unwrap(py), value)
84                .unwrap();
85        }
86        leverages.into_py_any(py)
87    }
88
89    #[pyo3(name = "leverage")]
90    fn py_leverage(&self, instrument_id: &InstrumentId) -> PyResult<f64> {
91        Ok(self.get_leverage(instrument_id))
92    }
93
94    #[pyo3(name = "set_leverage")]
95    fn py_set_leverage(&mut self, instrument_id: InstrumentId, leverage: f64) -> PyResult<()> {
96        self.set_leverage(instrument_id, leverage);
97        Ok(())
98    }
99
100    #[pyo3(name = "is_unleveraged")]
101    fn py_is_unleveraged(&self, instrument_id: InstrumentId) -> PyResult<bool> {
102        Ok(self.is_unleveraged(instrument_id))
103    }
104
105    #[pyo3(name = "initial_margins")]
106    fn py_initial_margins(&self, py: Python) -> PyResult<PyObject> {
107        let initial_margins = PyDict::new(py);
108        for (key, &value) in &self.initial_margins() {
109            initial_margins
110                .set_item(key.into_py_any_unwrap(py), value.into_py_any_unwrap(py))
111                .unwrap();
112        }
113        initial_margins.into_py_any(py)
114    }
115
116    #[pyo3(name = "maintenance_margins")]
117    fn py_maintenance_margins(&self, py: Python) -> PyResult<PyObject> {
118        let maintenance_margins = PyDict::new(py);
119        for (key, &value) in &self.maintenance_margins() {
120            maintenance_margins
121                .set_item(key.into_py_any_unwrap(py), value.into_py_any_unwrap(py))
122                .unwrap();
123        }
124        maintenance_margins.into_py_any(py)
125    }
126
127    #[pyo3(name = "update_initial_margin")]
128    fn py_update_initial_margin(
129        &mut self,
130        instrument_id: InstrumentId,
131        initial_margin: Money,
132    ) -> PyResult<()> {
133        self.update_initial_margin(instrument_id, initial_margin);
134        Ok(())
135    }
136
137    #[pyo3(name = "initial_margin")]
138    fn py_initial_margin(&self, instrument_id: InstrumentId) -> PyResult<Money> {
139        Ok(self.initial_margin(instrument_id))
140    }
141
142    #[pyo3(name = "update_maintenance_margin")]
143    fn py_update_maintenance_margin(
144        &mut self,
145        instrument_id: InstrumentId,
146        maintenance_margin: Money,
147    ) -> PyResult<()> {
148        self.update_maintenance_margin(instrument_id, maintenance_margin);
149        Ok(())
150    }
151
152    #[pyo3(name = "maintenance_margin")]
153    fn py_maintenance_margin(&self, instrument_id: InstrumentId) -> PyResult<Money> {
154        Ok(self.maintenance_margin(instrument_id))
155    }
156
157    #[pyo3(name = "calculate_initial_margin")]
158    #[pyo3(signature = (instrument, quantity, price, use_quote_for_inverse=None))]
159    pub fn py_calculate_initial_margin(
160        &mut self,
161        instrument: PyObject,
162        quantity: Quantity,
163        price: Price,
164        use_quote_for_inverse: Option<bool>,
165        py: Python,
166    ) -> PyResult<Money> {
167        let instrument_type = pyobject_to_instrument_any(py, instrument)?;
168        match instrument_type {
169            InstrumentAny::CryptoFuture(inst) => {
170                Ok(self.calculate_initial_margin(inst, quantity, price, use_quote_for_inverse))
171            }
172            InstrumentAny::CryptoPerpetual(inst) => {
173                Ok(self.calculate_initial_margin(inst, quantity, price, use_quote_for_inverse))
174            }
175            InstrumentAny::CurrencyPair(inst) => {
176                Ok(self.calculate_initial_margin(inst, quantity, price, use_quote_for_inverse))
177            }
178            InstrumentAny::Equity(inst) => {
179                Ok(self.calculate_initial_margin(inst, quantity, price, use_quote_for_inverse))
180            }
181            InstrumentAny::FuturesContract(inst) => {
182                Ok(self.calculate_initial_margin(inst, quantity, price, use_quote_for_inverse))
183            }
184            InstrumentAny::OptionContract(inst) => {
185                Ok(self.calculate_initial_margin(inst, quantity, price, use_quote_for_inverse))
186            }
187            _ => Err(to_pyvalue_err("Unsupported instrument type")),
188        }
189    }
190
191    #[pyo3(name = "calculate_maintenance_margin")]
192    #[pyo3(signature = (instrument, quantity, price, use_quote_for_inverse=None))]
193    pub fn py_calculate_maintenance_margin(
194        &mut self,
195        instrument: PyObject,
196        quantity: Quantity,
197        price: Price,
198        use_quote_for_inverse: Option<bool>,
199        py: Python,
200    ) -> PyResult<Money> {
201        let instrument_type = pyobject_to_instrument_any(py, instrument)?;
202        match instrument_type {
203            InstrumentAny::CryptoFuture(inst) => {
204                Ok(self.calculate_maintenance_margin(inst, quantity, price, use_quote_for_inverse))
205            }
206            InstrumentAny::CryptoPerpetual(inst) => {
207                Ok(self.calculate_maintenance_margin(inst, quantity, price, use_quote_for_inverse))
208            }
209            InstrumentAny::CurrencyPair(inst) => {
210                Ok(self.calculate_maintenance_margin(inst, quantity, price, use_quote_for_inverse))
211            }
212            InstrumentAny::Equity(inst) => {
213                Ok(self.calculate_maintenance_margin(inst, quantity, price, use_quote_for_inverse))
214            }
215            InstrumentAny::FuturesContract(inst) => {
216                Ok(self.calculate_maintenance_margin(inst, quantity, price, use_quote_for_inverse))
217            }
218            InstrumentAny::OptionContract(inst) => {
219                Ok(self.calculate_maintenance_margin(inst, quantity, price, use_quote_for_inverse))
220            }
221            _ => Err(to_pyvalue_err("Unsupported instrument type")),
222        }
223    }
224
225    #[pyo3(name = "to_dict")]
226    fn py_to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
227        let dict = PyDict::new(py);
228        dict.set_item("calculate_account_state", self.calculate_account_state)?;
229        let events_list: PyResult<Vec<PyObject>> =
230            self.events.iter().map(|item| item.py_to_dict(py)).collect();
231        dict.set_item("events", events_list.unwrap())?;
232        Ok(dict.into())
233    }
234}