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