nautilus_model/python/account/
wallet.rs1use indexmap::IndexMap;
17use nautilus_core::{
18 UnixNanos,
19 python::{IntoPyObjectNautilusExt, to_pyruntime_err, to_pyvalue_err},
20};
21use pyo3::{basic::CompareOp, prelude::*, types::PyDict};
22use rust_decimal::Decimal;
23
24use crate::{
25 accounts::{Account, WalletAccount},
26 enums::{AccountType, LiquiditySide, OrderSide},
27 events::{AccountState, OrderFilled},
28 fees::MakerTakerFeeRates,
29 identifiers::AccountId,
30 position::Position,
31 python::{account::resolve_balance_currency, instruments::pyobject_to_instrument_any},
32 types::{AccountBalance, Currency, Money, Price, Quantity},
33};
34
35#[pymethods]
36#[pyo3_stub_gen::derive::gen_stub_pymethods]
37impl WalletAccount {
38 #[new]
40 #[pyo3(signature = (event, calculate_account_state))]
41 pub fn py_new(event: AccountState, calculate_account_state: bool) -> PyResult<Self> {
42 Self::new_checked(event, calculate_account_state).map_err(to_pyvalue_err)
43 }
44
45 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
46 match op {
47 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
48 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
49 _ => py.NotImplemented(),
50 }
51 }
52
53 fn __repr__(&self) -> String {
54 format!(
55 "{}(id={}, type={}, base={})",
56 stringify!(WalletAccount),
57 self.id,
58 self.account_type,
59 self.base_currency.map_or_else(
60 || "None".to_string(),
61 |base_currency| format!("{}", base_currency.code)
62 ),
63 )
64 }
65
66 #[getter]
67 #[pyo3(name = "id")]
68 fn py_id(&self) -> AccountId {
69 self.id
70 }
71
72 #[getter]
73 #[pyo3(name = "account_type")]
74 fn py_account_type(&self) -> AccountType {
75 self.account_type
76 }
77
78 #[getter]
79 #[pyo3(name = "base_currency")]
80 fn py_base_currency(&self) -> Option<Currency> {
81 self.base_currency
82 }
83
84 #[getter]
85 #[pyo3(name = "last_event")]
86 fn py_last_event(&self) -> Option<AccountState> {
87 self.last_event()
88 }
89
90 #[getter]
91 #[pyo3(name = "event_count")]
92 fn py_event_count(&self) -> usize {
93 self.event_count()
94 }
95
96 #[getter]
97 #[pyo3(name = "events")]
98 fn py_events(&self) -> Vec<AccountState> {
99 self.events()
100 }
101
102 #[getter]
103 #[pyo3(name = "calculate_account_state")]
104 fn py_calculate_account_state(&self) -> bool {
105 self.calculate_account_state
106 }
107
108 #[pyo3(name = "balance_total")]
109 #[pyo3(signature = (currency=None))]
110 fn py_balance_total(&self, currency: Option<Currency>) -> PyResult<Option<Money>> {
111 let currency = resolve_balance_currency(currency, self.base_currency)?;
112 Ok(self.balance_total(Some(currency)))
113 }
114
115 #[pyo3(name = "balances_total")]
116 fn py_balances_total(&self) -> IndexMap<Currency, Money> {
117 self.balances_total()
118 }
119
120 #[pyo3(name = "balance_free")]
121 #[pyo3(signature = (currency=None))]
122 fn py_balance_free(&self, currency: Option<Currency>) -> PyResult<Option<Money>> {
123 let currency = resolve_balance_currency(currency, self.base_currency)?;
124 Ok(self.balance_free(Some(currency)))
125 }
126
127 #[pyo3(name = "balances_free")]
128 fn py_balances_free(&self) -> IndexMap<Currency, Money> {
129 self.balances_free()
130 }
131
132 #[pyo3(name = "balance_locked")]
133 #[pyo3(signature = (currency=None))]
134 fn py_balance_locked(&self, currency: Option<Currency>) -> PyResult<Option<Money>> {
135 let currency = resolve_balance_currency(currency, self.base_currency)?;
136 Ok(self.balance_locked(Some(currency)))
137 }
138
139 #[pyo3(name = "balances_locked")]
140 fn py_balances_locked(&self) -> IndexMap<Currency, Money> {
141 self.balances_locked()
142 }
143
144 #[pyo3(name = "balance")]
145 #[pyo3(signature = (currency=None))]
146 fn py_balance(&self, currency: Option<Currency>) -> PyResult<Option<AccountBalance>> {
147 let currency = resolve_balance_currency(currency, self.base_currency)?;
148 Ok(Account::balance(self, Some(currency)).copied())
149 }
150
151 #[pyo3(name = "balances")]
152 fn py_balances(&self) -> IndexMap<Currency, AccountBalance> {
153 Account::balances(self)
154 }
155
156 #[pyo3(name = "starting_balances")]
157 fn py_starting_balances(&self) -> IndexMap<Currency, Money> {
158 Account::starting_balances(self)
159 }
160
161 #[pyo3(name = "currencies")]
162 fn py_currencies(&self) -> Vec<Currency> {
163 Account::currencies(self)
164 }
165
166 #[pyo3(name = "is_cash_account")]
167 fn py_is_cash_account(&self) -> bool {
168 Account::is_cash_account(self)
169 }
170
171 #[pyo3(name = "is_margin_account")]
172 fn py_is_margin_account(&self) -> bool {
173 Account::is_margin_account(self)
174 }
175
176 #[pyo3(name = "purge_account_events")]
177 fn py_purge_account_events(&mut self, ts_now: u64, lookback_secs: u64) {
178 Account::purge_account_events(self, UnixNanos::from(ts_now), lookback_secs);
179 }
180
181 #[pyo3(name = "apply")]
182 fn py_apply(&mut self, event: AccountState) -> PyResult<()> {
183 self.apply(event).map_err(to_pyruntime_err)
184 }
185
186 #[pyo3(name = "calculate_balance_locked")]
187 #[pyo3(signature = (instrument, side, quantity, price, use_quote_for_inverse=None))]
188 fn py_calculate_balance_locked(
189 &self,
190 instrument: Py<PyAny>,
191 side: OrderSide,
192 quantity: Quantity,
193 price: Price,
194 use_quote_for_inverse: Option<bool>,
195 py: Python,
196 ) -> PyResult<Money> {
197 let instrument = pyobject_to_instrument_any(py, instrument)?;
198 self.calculate_balance_locked(&instrument, side, quantity, price, use_quote_for_inverse)
199 .map_err(to_pyvalue_err)
200 }
201
202 #[pyo3(name = "calculate_commission")]
203 #[pyo3(signature = (instrument, last_qty, last_px, liquidity_side, maker_rate, taker_rate, use_quote_for_inverse=None))]
204 #[expect(clippy::too_many_arguments)]
205 fn py_calculate_commission(
206 &self,
207 instrument: Py<PyAny>,
208 last_qty: Quantity,
209 last_px: Price,
210 liquidity_side: LiquiditySide,
211 maker_rate: Decimal,
212 taker_rate: Decimal,
213 use_quote_for_inverse: Option<bool>,
214 py: Python,
215 ) -> PyResult<Money> {
216 if liquidity_side == LiquiditySide::NoLiquiditySide {
217 return Err(to_pyvalue_err("Invalid liquidity side"));
218 }
219 let instrument = pyobject_to_instrument_any(py, instrument)?;
220 let fee_rates = MakerTakerFeeRates::new(maker_rate, taker_rate);
221 self.calculate_commission(
222 &instrument,
223 last_qty,
224 last_px,
225 liquidity_side,
226 fee_rates,
227 use_quote_for_inverse,
228 )
229 .map_err(to_pyvalue_err)
230 }
231
232 #[pyo3(name = "calculate_pnls")]
233 #[pyo3(signature = (instrument, fill, position=None))]
234 fn py_calculate_pnls(
235 &self,
236 instrument: Py<PyAny>,
237 fill: &OrderFilled,
238 position: Option<Position>,
239 py: Python,
240 ) -> PyResult<Vec<Money>> {
241 let instrument = pyobject_to_instrument_any(py, instrument)?;
242 self.calculate_pnls(&instrument, fill, position)
243 .map_err(to_pyvalue_err)
244 }
245
246 #[pyo3(name = "to_dict")]
247 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
248 let dict = PyDict::new(py);
249 dict.set_item("type", "WalletAccount")?;
250 dict.set_item("calculate_account_state", self.calculate_account_state)?;
251 let events_list: PyResult<Vec<Py<PyAny>>> =
252 self.events.iter().map(|item| item.py_to_dict(py)).collect();
253 dict.set_item("events", events_list?)?;
254 Ok(dict.into())
255 }
256}