nautilus_model/python/account/
cash.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::{basic::CompareOp, prelude::*, types::PyDict};
18
19use crate::{
20    accounts::{Account, CashAccount},
21    enums::{AccountType, LiquiditySide, OrderSide},
22    events::{AccountState, OrderFilled},
23    identifiers::AccountId,
24    position::Position,
25    python::instruments::pyobject_to_instrument_any,
26    types::{Currency, Money, Price, Quantity},
27};
28
29#[pymethods]
30impl CashAccount {
31    #[new]
32    #[pyo3(signature = (event, calculate_account_state, allow_borrowing = false))]
33    pub fn py_new(
34        event: AccountState,
35        calculate_account_state: bool,
36        allow_borrowing: bool,
37    ) -> Self {
38        Self::new(event, calculate_account_state, allow_borrowing)
39    }
40
41    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
42        match op {
43            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
44            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
45            _ => py.NotImplemented(),
46        }
47    }
48
49    #[getter]
50    fn id(&self) -> AccountId {
51        self.id
52    }
53
54    #[getter]
55    fn allow_borrowing(&self) -> bool {
56        self.allow_borrowing
57    }
58
59    fn __repr__(&self) -> String {
60        format!(
61            "{}(id={}, type={}, base={})",
62            stringify!(CashAccount),
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    #[getter]
73    #[pyo3(name = "id")]
74    fn py_id(&self) -> AccountId {
75        self.id
76    }
77
78    #[getter]
79    #[pyo3(name = "account_type")]
80    fn py_account_type(&self) -> AccountType {
81        self.account_type
82    }
83
84    #[getter]
85    #[pyo3(name = "base_currency")]
86    fn py_base_currency(&self) -> Option<Currency> {
87        self.base_currency
88    }
89
90    #[getter]
91    #[pyo3(name = "last_event")]
92    fn py_last_event(&self) -> Option<AccountState> {
93        self.last_event()
94    }
95
96    #[getter]
97    #[pyo3(name = "event_count")]
98    fn py_event_count(&self) -> usize {
99        self.event_count()
100    }
101
102    #[getter]
103    #[pyo3(name = "events")]
104    fn py_events(&self) -> Vec<AccountState> {
105        self.events()
106    }
107
108    #[getter]
109    #[pyo3(name = "calculate_account_state")]
110    fn py_calculate_account_state(&self) -> bool {
111        self.calculate_account_state
112    }
113
114    #[pyo3(name = "balance_total")]
115    #[pyo3(signature = (currency=None))]
116    fn py_balance_total(&self, currency: Option<Currency>) -> Option<Money> {
117        self.balance_total(currency)
118    }
119
120    #[pyo3(name = "balances_total")]
121    fn py_balances_total(&self) -> std::collections::HashMap<Currency, Money> {
122        self.balances_total().into_iter().collect()
123    }
124
125    #[pyo3(name = "balance_free")]
126    #[pyo3(signature = (currency=None))]
127    fn py_balance_free(&self, currency: Option<Currency>) -> Option<Money> {
128        self.balance_free(currency)
129    }
130
131    #[pyo3(name = "balances_free")]
132    fn py_balances_free(&self) -> std::collections::HashMap<Currency, Money> {
133        self.balances_free().into_iter().collect()
134    }
135
136    #[pyo3(name = "balance_locked")]
137    #[pyo3(signature = (currency=None))]
138    fn py_balance_locked(&self, currency: Option<Currency>) -> Option<Money> {
139        self.balance_locked(currency)
140    }
141    #[pyo3(name = "balances_locked")]
142    fn py_balances_locked(&self) -> std::collections::HashMap<Currency, Money> {
143        self.balances_locked().into_iter().collect()
144    }
145
146    #[pyo3(name = "apply")]
147    fn py_apply(&mut self, event: AccountState) {
148        self.apply(event);
149    }
150
151    #[pyo3(name = "calculate_balance_locked")]
152    #[pyo3(signature = (instrument, side, quantity, price, use_quote_for_inverse=None))]
153    fn py_calculate_balance_locked(
154        &mut self,
155        instrument: Py<PyAny>,
156        side: OrderSide,
157        quantity: Quantity,
158        price: Price,
159        use_quote_for_inverse: Option<bool>,
160        py: Python,
161    ) -> PyResult<Money> {
162        let instrument = pyobject_to_instrument_any(py, instrument)?;
163        self.calculate_balance_locked(instrument, side, quantity, price, use_quote_for_inverse)
164            .map_err(to_pyvalue_err)
165    }
166
167    #[pyo3(name = "calculate_commission")]
168    #[pyo3(signature = (instrument, last_qty, last_px, liquidity_side, use_quote_for_inverse=None))]
169    fn py_calculate_commission(
170        &self,
171        instrument: Py<PyAny>,
172        last_qty: Quantity,
173        last_px: Price,
174        liquidity_side: LiquiditySide,
175        use_quote_for_inverse: Option<bool>,
176        py: Python,
177    ) -> PyResult<Money> {
178        if liquidity_side == LiquiditySide::NoLiquiditySide {
179            return Err(to_pyvalue_err("Invalid liquidity side"));
180        }
181        let instrument = pyobject_to_instrument_any(py, instrument)?;
182        self.calculate_commission(
183            instrument,
184            last_qty,
185            last_px,
186            liquidity_side,
187            use_quote_for_inverse,
188        )
189        .map_err(to_pyvalue_err)
190    }
191
192    #[pyo3(name = "calculate_pnls")]
193    #[pyo3(signature = (instrument, fill, position=None))]
194    fn py_calculate_pnls(
195        &self,
196        instrument: Py<PyAny>,
197        fill: OrderFilled,
198        position: Option<Position>,
199        py: Python,
200    ) -> PyResult<Vec<Money>> {
201        let instrument = pyobject_to_instrument_any(py, instrument)?;
202        self.calculate_pnls(instrument, fill, position)
203            .map_err(to_pyvalue_err)
204    }
205
206    #[pyo3(name = "to_dict")]
207    fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
208        let dict = PyDict::new(py);
209        dict.set_item("calculate_account_state", self.calculate_account_state)?;
210        let events_list: PyResult<Vec<Py<PyAny>>> =
211            self.events.iter().map(|item| item.py_to_dict(py)).collect();
212        dict.set_item("events", events_list.unwrap())?;
213        Ok(dict.into())
214    }
215}