nautilus_model/python/account/
betting.rs1use indexmap::IndexMap;
17use nautilus_core::{
18 UnixNanos,
19 python::{
20 IntoPyObjectNautilusExt, correctness_error_to_pyvalue_err, to_pyruntime_err, to_pyvalue_err,
21 },
22};
23use pyo3::{basic::CompareOp, prelude::*, types::PyDict};
24use rust_decimal::Decimal;
25
26use crate::{
27 accounts::{Account, BettingAccount},
28 enums::{AccountType, LiquiditySide, OrderSide},
29 events::{AccountState, OrderFilled},
30 fees::MakerTakerFeeRates,
31 identifiers::AccountId,
32 position::Position,
33 python::{account::resolve_balance_currency, instruments::pyobject_to_instrument_any},
34 types::{AccountBalance, Currency, Money, Price, Quantity},
35};
36
37#[pymethods]
38#[pyo3_stub_gen::derive::gen_stub_pymethods]
39impl BettingAccount {
40 #[new]
42 #[pyo3(signature = (event, calculate_account_state))]
43 #[must_use]
44 pub fn py_new(event: AccountState, calculate_account_state: bool) -> Self {
45 Self::new(event, calculate_account_state)
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 fn __repr__(&self) -> String {
57 format!(
58 "{}(id={}, type={}, base={})",
59 stringify!(BettingAccount),
60 self.id,
61 self.account_type,
62 self.base_currency.map_or_else(
63 || "None".to_string(),
64 |base_currency| format!("{}", base_currency.code)
65 ),
66 )
67 }
68
69 #[getter]
70 #[pyo3(name = "id")]
71 fn py_id(&self) -> AccountId {
72 self.id
73 }
74
75 #[getter]
76 #[pyo3(name = "account_type")]
77 fn py_account_type(&self) -> AccountType {
78 self.account_type
79 }
80
81 #[getter]
82 #[pyo3(name = "base_currency")]
83 fn py_base_currency(&self) -> Option<Currency> {
84 self.base_currency
85 }
86
87 #[getter]
88 #[pyo3(name = "last_event")]
89 fn py_last_event(&self) -> Option<AccountState> {
90 self.last_event()
91 }
92
93 #[getter]
94 #[pyo3(name = "event_count")]
95 fn py_event_count(&self) -> usize {
96 self.event_count()
97 }
98
99 #[getter]
100 #[pyo3(name = "events")]
101 fn py_events(&self) -> Vec<AccountState> {
102 self.events()
103 }
104
105 #[getter]
106 #[pyo3(name = "calculate_account_state")]
107 fn py_calculate_account_state(&self) -> bool {
108 self.calculate_account_state
109 }
110
111 #[pyo3(name = "balance_total")]
112 #[pyo3(signature = (currency=None))]
113 fn py_balance_total(&self, currency: Option<Currency>) -> PyResult<Option<Money>> {
114 let currency = resolve_balance_currency(currency, self.base_currency)?;
115 Ok(self.balance_total(Some(currency)))
116 }
117
118 #[pyo3(name = "balances_total")]
119 fn py_balances_total(&self) -> IndexMap<Currency, Money> {
120 self.balances_total()
121 }
122
123 #[pyo3(name = "balance_free")]
124 #[pyo3(signature = (currency=None))]
125 fn py_balance_free(&self, currency: Option<Currency>) -> PyResult<Option<Money>> {
126 let currency = resolve_balance_currency(currency, self.base_currency)?;
127 Ok(self.balance_free(Some(currency)))
128 }
129
130 #[pyo3(name = "balances_free")]
131 fn py_balances_free(&self) -> IndexMap<Currency, Money> {
132 self.balances_free()
133 }
134
135 #[pyo3(name = "balance_locked")]
136 #[pyo3(signature = (currency=None))]
137 fn py_balance_locked(&self, currency: Option<Currency>) -> PyResult<Option<Money>> {
138 let currency = resolve_balance_currency(currency, self.base_currency)?;
139 Ok(self.balance_locked(Some(currency)))
140 }
141
142 #[pyo3(name = "balances_locked")]
143 fn py_balances_locked(&self) -> IndexMap<Currency, Money> {
144 self.balances_locked()
145 }
146
147 #[pyo3(name = "balance")]
148 #[pyo3(signature = (currency=None))]
149 fn py_balance(&self, currency: Option<Currency>) -> PyResult<Option<AccountBalance>> {
150 let currency = resolve_balance_currency(currency, self.base_currency)?;
151 Ok(Account::balance(self, Some(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 &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, maker_rate, taker_rate, use_quote_for_inverse=None))]
207 #[expect(clippy::too_many_arguments)]
208 fn py_calculate_commission(
209 &self,
210 instrument: Py<PyAny>,
211 last_qty: Quantity,
212 last_px: Price,
213 liquidity_side: LiquiditySide,
214 maker_rate: Decimal,
215 taker_rate: Decimal,
216 use_quote_for_inverse: Option<bool>,
217 py: Python,
218 ) -> PyResult<Money> {
219 if liquidity_side == LiquiditySide::NoLiquiditySide {
220 return Err(to_pyvalue_err("Invalid liquidity side"));
221 }
222 let instrument = pyobject_to_instrument_any(py, instrument)?;
223 let fee_rates = MakerTakerFeeRates::new(maker_rate, taker_rate);
224 self.calculate_commission(
225 &instrument,
226 last_qty,
227 last_px,
228 liquidity_side,
229 fee_rates,
230 use_quote_for_inverse,
231 )
232 .map_err(to_pyvalue_err)
233 }
234
235 #[pyo3(name = "calculate_pnls")]
236 #[pyo3(signature = (instrument, fill, position=None))]
237 fn py_calculate_pnls(
238 &self,
239 instrument: Py<PyAny>,
240 fill: &OrderFilled,
241 position: Option<Position>,
242 py: Python,
243 ) -> PyResult<Vec<Money>> {
244 let instrument = pyobject_to_instrument_any(py, instrument)?;
245 self.calculate_pnls(&instrument, fill, position)
246 .map_err(to_pyvalue_err)
247 }
248
249 #[pyo3(name = "balance_impact")]
258 fn py_balance_impact(
259 &self,
260 instrument: Py<PyAny>,
261 quantity: Quantity,
262 price: Price,
263 order_side: OrderSide,
264 py: Python,
265 ) -> PyResult<Money> {
266 let instrument = pyobject_to_instrument_any(py, instrument)?;
267 self.balance_impact(&instrument, quantity, price, order_side)
268 .map_err(correctness_error_to_pyvalue_err)
269 }
270
271 #[pyo3(name = "to_dict")]
272 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
273 let dict = PyDict::new(py);
274 dict.set_item("type", "BettingAccount")?;
275 dict.set_item("calculate_account_state", self.calculate_account_state)?;
276 let events_list: PyResult<Vec<Py<PyAny>>> =
277 self.events.iter().map(|item| item.py_to_dict(py)).collect();
278 dict.set_item("events", events_list?)?;
279 Ok(dict.into())
280 }
281}