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