Skip to main content

nautilus_model/python/account/
wallet.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 indexmap::IndexMap;
17use nautilus_core::{
18    UnixNanos,
19    python::{IntoPyObjectNautilusExt, to_pyruntime_err, to_pyvalue_err},
20};
21use pyo3::{basic::CompareOp, prelude::*, types::PyDict};
22
23use crate::{
24    accounts::{Account, WalletAccount},
25    enums::{AccountType, LiquiditySide, OrderSide},
26    events::{AccountState, OrderFilled},
27    identifiers::AccountId,
28    position::Position,
29    python::instruments::pyobject_to_instrument_any,
30    types::{AccountBalance, Currency, Money, Price, Quantity},
31};
32
33#[pymethods]
34#[pyo3_stub_gen::derive::gen_stub_pymethods]
35impl WalletAccount {
36    /// Creates a new `WalletAccount` instance.
37    #[new]
38    #[pyo3(signature = (event, calculate_account_state))]
39    pub fn py_new(event: AccountState, calculate_account_state: bool) -> PyResult<Self> {
40        Self::new_checked(event, calculate_account_state).map_err(to_pyvalue_err)
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    fn __repr__(&self) -> String {
52        format!(
53            "{}(id={}, type={}, base={})",
54            stringify!(WalletAccount),
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) -> IndexMap<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) -> IndexMap<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
134    #[pyo3(name = "balances_locked")]
135    fn py_balances_locked(&self) -> IndexMap<Currency, Money> {
136        self.balances_locked()
137    }
138
139    #[pyo3(name = "balance")]
140    #[pyo3(signature = (currency=None))]
141    fn py_balance(&self, currency: Option<Currency>) -> Option<AccountBalance> {
142        Account::balance(self, currency).copied()
143    }
144
145    #[pyo3(name = "balances")]
146    fn py_balances(&self) -> IndexMap<Currency, AccountBalance> {
147        Account::balances(self)
148    }
149
150    #[pyo3(name = "starting_balances")]
151    fn py_starting_balances(&self) -> IndexMap<Currency, Money> {
152        Account::starting_balances(self)
153    }
154
155    #[pyo3(name = "currencies")]
156    fn py_currencies(&self) -> Vec<Currency> {
157        Account::currencies(self)
158    }
159
160    #[pyo3(name = "is_cash_account")]
161    fn py_is_cash_account(&self) -> bool {
162        Account::is_cash_account(self)
163    }
164
165    #[pyo3(name = "is_margin_account")]
166    fn py_is_margin_account(&self) -> bool {
167        Account::is_margin_account(self)
168    }
169
170    #[pyo3(name = "purge_account_events")]
171    fn py_purge_account_events(&mut self, ts_now: u64, lookback_secs: u64) {
172        Account::purge_account_events(self, UnixNanos::from(ts_now), lookback_secs);
173    }
174
175    #[pyo3(name = "apply")]
176    fn py_apply(&mut self, event: AccountState) -> PyResult<()> {
177        self.apply(event).map_err(to_pyruntime_err)
178    }
179
180    #[pyo3(name = "calculate_balance_locked")]
181    #[pyo3(signature = (instrument, side, quantity, price, use_quote_for_inverse=None))]
182    fn py_calculate_balance_locked(
183        &mut self,
184        instrument: Py<PyAny>,
185        side: OrderSide,
186        quantity: Quantity,
187        price: Price,
188        use_quote_for_inverse: Option<bool>,
189        py: Python,
190    ) -> PyResult<Money> {
191        let instrument = pyobject_to_instrument_any(py, instrument)?;
192        self.calculate_balance_locked(&instrument, side, quantity, price, use_quote_for_inverse)
193            .map_err(to_pyvalue_err)
194    }
195
196    #[pyo3(name = "calculate_commission")]
197    #[pyo3(signature = (instrument, last_qty, last_px, liquidity_side, use_quote_for_inverse=None))]
198    fn py_calculate_commission(
199        &self,
200        instrument: Py<PyAny>,
201        last_qty: Quantity,
202        last_px: Price,
203        liquidity_side: LiquiditySide,
204        use_quote_for_inverse: Option<bool>,
205        py: Python,
206    ) -> PyResult<Money> {
207        if liquidity_side == LiquiditySide::NoLiquiditySide {
208            return Err(to_pyvalue_err("Invalid liquidity side"));
209        }
210        let instrument = pyobject_to_instrument_any(py, instrument)?;
211        self.calculate_commission(
212            &instrument,
213            last_qty,
214            last_px,
215            liquidity_side,
216            use_quote_for_inverse,
217        )
218        .map_err(to_pyvalue_err)
219    }
220
221    #[pyo3(name = "calculate_pnls")]
222    #[pyo3(signature = (instrument, fill, position=None))]
223    fn py_calculate_pnls(
224        &self,
225        instrument: Py<PyAny>,
226        fill: &OrderFilled,
227        position: Option<Position>,
228        py: Python,
229    ) -> PyResult<Vec<Money>> {
230        let instrument = pyobject_to_instrument_any(py, instrument)?;
231        self.calculate_pnls(&instrument, fill, position)
232            .map_err(to_pyvalue_err)
233    }
234
235    #[pyo3(name = "to_dict")]
236    fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
237        let dict = PyDict::new(py);
238        dict.set_item("type", "WalletAccount")?;
239        dict.set_item("calculate_account_state", self.calculate_account_state)?;
240        let events_list: PyResult<Vec<Py<PyAny>>> =
241            self.events.iter().map(|item| item.py_to_dict(py)).collect();
242        dict.set_item("events", events_list?)?;
243        Ok(dict.into())
244    }
245}