nautilus_model/python/account/
cash.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};
22
23use crate::{
24 accounts::{Account, CashAccount},
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 CashAccount {
36 #[new]
38 #[pyo3(signature = (event, calculate_account_state, allow_borrowing = false))]
39 #[must_use]
40 pub fn py_new(
41 event: AccountState,
42 calculate_account_state: bool,
43 allow_borrowing: bool,
44 ) -> Self {
45 Self::new(event, calculate_account_state, allow_borrowing)
46 }
47
48 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
49 match op {
50 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
51 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
52 _ => py.NotImplemented(),
53 }
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) -> IndexMap<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) -> IndexMap<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) -> IndexMap<Currency, Money> {
145 self.balances_locked()
146 }
147
148 #[pyo3(name = "balance")]
149 #[pyo3(signature = (currency=None))]
150 fn py_balance(&self, currency: Option<Currency>) -> Option<AccountBalance> {
151 Account::balance(self, currency).copied()
152 }
153
154 #[pyo3(name = "balances")]
155 fn py_balances(&self) -> IndexMap<Currency, AccountBalance> {
156 Account::balances(self)
157 }
158
159 #[pyo3(name = "starting_balances")]
160 fn py_starting_balances(&self) -> IndexMap<Currency, Money> {
161 Account::starting_balances(self)
162 }
163
164 #[pyo3(name = "currencies")]
165 fn py_currencies(&self) -> Vec<Currency> {
166 Account::currencies(self)
167 }
168
169 #[pyo3(name = "is_cash_account")]
170 fn py_is_cash_account(&self) -> bool {
171 Account::is_cash_account(self)
172 }
173
174 #[pyo3(name = "is_margin_account")]
175 fn py_is_margin_account(&self) -> bool {
176 Account::is_margin_account(self)
177 }
178
179 #[pyo3(name = "purge_account_events")]
180 fn py_purge_account_events(&mut self, ts_now: u64, lookback_secs: u64) {
181 Account::purge_account_events(self, UnixNanos::from(ts_now), lookback_secs);
182 }
183
184 #[pyo3(name = "apply")]
185 fn py_apply(&mut self, event: AccountState) -> PyResult<()> {
186 self.apply(event).map_err(to_pyruntime_err)
187 }
188
189 #[pyo3(name = "calculate_balance_locked")]
190 #[pyo3(signature = (instrument, side, quantity, price, use_quote_for_inverse=None))]
191 fn py_calculate_balance_locked(
192 &mut self,
193 instrument: Py<PyAny>,
194 side: OrderSide,
195 quantity: Quantity,
196 price: Price,
197 use_quote_for_inverse: Option<bool>,
198 py: Python,
199 ) -> PyResult<Money> {
200 let instrument = pyobject_to_instrument_any(py, instrument)?;
201 self.calculate_balance_locked(&instrument, side, quantity, price, use_quote_for_inverse)
202 .map_err(to_pyvalue_err)
203 }
204
205 #[pyo3(name = "calculate_commission")]
206 #[pyo3(signature = (instrument, last_qty, last_px, liquidity_side, use_quote_for_inverse=None))]
207 fn py_calculate_commission(
208 &self,
209 instrument: Py<PyAny>,
210 last_qty: Quantity,
211 last_px: Price,
212 liquidity_side: LiquiditySide,
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 self.calculate_commission(
221 &instrument,
222 last_qty,
223 last_px,
224 liquidity_side,
225 use_quote_for_inverse,
226 )
227 .map_err(to_pyvalue_err)
228 }
229
230 #[pyo3(name = "calculate_pnls")]
231 #[pyo3(signature = (instrument, fill, position=None))]
232 fn py_calculate_pnls(
233 &self,
234 instrument: Py<PyAny>,
235 fill: OrderFilled,
236 position: Option<Position>,
237 py: Python,
238 ) -> PyResult<Vec<Money>> {
239 let instrument = pyobject_to_instrument_any(py, instrument)?;
240 self.calculate_pnls(&instrument, &fill, position)
241 .map_err(to_pyvalue_err)
242 }
243
244 #[pyo3(name = "to_dict")]
245 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
246 let dict = PyDict::new(py);
247 dict.set_item("calculate_account_state", self.calculate_account_state)?;
248 let events_list: PyResult<Vec<Py<PyAny>>> =
249 self.events.iter().map(|item| item.py_to_dict(py)).collect();
250 dict.set_item("events", events_list.unwrap())?;
251 Ok(dict.into())
252 }
253}