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