1use indexmap::IndexMap;
17use nautilus_core::{
18 UnixNanos,
19 python::{IntoPyObjectNautilusExt, to_pyruntime_err, to_pyvalue_err},
20};
21use pyo3::{IntoPyObjectExt, basic::CompareOp, prelude::*, types::PyDict};
22use rust_decimal::Decimal;
23
24use crate::{
25 accounts::{Account, MarginAccount},
26 enums::{AccountType, LiquiditySide, OrderSide},
27 events::{AccountState, OrderFilled},
28 identifiers::{AccountId, InstrumentId},
29 instruments::InstrumentAny,
30 position::Position,
31 python::instruments::pyobject_to_instrument_any,
32 types::{AccountBalance, Currency, MarginBalance, Money, Price, Quantity},
33};
34
35#[pymethods]
36#[pyo3_stub_gen::derive::gen_stub_pymethods]
37impl MarginAccount {
38 #[new]
40 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 #[getter]
53 fn id(&self) -> AccountId {
54 self.id
55 }
56
57 #[getter]
58 #[pyo3(name = "account_type")]
59 fn py_account_type(&self) -> AccountType {
60 self.account_type
61 }
62
63 #[getter]
64 #[pyo3(name = "base_currency")]
65 fn py_base_currency(&self) -> Option<Currency> {
66 self.base_currency
67 }
68
69 #[getter]
70 fn default_leverage(&self) -> Decimal {
71 self.default_leverage
72 }
73
74 #[getter]
75 #[pyo3(name = "calculate_account_state")]
76 fn py_calculate_account_state(&self) -> bool {
77 self.calculate_account_state
78 }
79
80 #[getter]
81 #[pyo3(name = "last_event")]
82 fn py_last_event(&self) -> Option<AccountState> {
83 Account::last_event(self)
84 }
85
86 #[getter]
87 #[pyo3(name = "event_count")]
88 fn py_event_count(&self) -> usize {
89 Account::event_count(self)
90 }
91
92 #[getter]
93 #[pyo3(name = "events")]
94 fn py_events(&self) -> Vec<AccountState> {
95 Account::events(self)
96 }
97
98 #[pyo3(name = "balance_total")]
99 #[pyo3(signature = (currency=None))]
100 fn py_balance_total(&self, currency: Option<Currency>) -> Option<Money> {
101 Account::balance_total(self, currency)
102 }
103
104 #[pyo3(name = "balances_total")]
105 fn py_balances_total(&self) -> IndexMap<Currency, Money> {
106 Account::balances_total(self)
107 }
108
109 #[pyo3(name = "balance_free")]
110 #[pyo3(signature = (currency=None))]
111 fn py_balance_free(&self, currency: Option<Currency>) -> Option<Money> {
112 Account::balance_free(self, currency)
113 }
114
115 #[pyo3(name = "balances_free")]
116 fn py_balances_free(&self) -> IndexMap<Currency, Money> {
117 Account::balances_free(self)
118 }
119
120 #[pyo3(name = "balance_locked")]
121 #[pyo3(signature = (currency=None))]
122 fn py_balance_locked(&self, currency: Option<Currency>) -> Option<Money> {
123 Account::balance_locked(self, currency)
124 }
125
126 #[pyo3(name = "balances_locked")]
127 fn py_balances_locked(&self) -> IndexMap<Currency, Money> {
128 Account::balances_locked(self)
129 }
130
131 #[pyo3(name = "balance")]
132 #[pyo3(signature = (currency=None))]
133 fn py_balance(&self, currency: Option<Currency>) -> Option<AccountBalance> {
134 Account::balance(self, currency).copied()
135 }
136
137 #[pyo3(name = "balances")]
138 fn py_balances(&self) -> IndexMap<Currency, AccountBalance> {
139 Account::balances(self)
140 }
141
142 #[pyo3(name = "starting_balances")]
143 fn py_starting_balances(&self) -> IndexMap<Currency, Money> {
144 Account::starting_balances(self)
145 }
146
147 #[pyo3(name = "currencies")]
148 fn py_currencies(&self) -> Vec<Currency> {
149 Account::currencies(self)
150 }
151
152 #[pyo3(name = "is_cash_account")]
153 fn py_is_cash_account(&self) -> bool {
154 Account::is_cash_account(self)
155 }
156
157 #[pyo3(name = "is_margin_account")]
158 fn py_is_margin_account(&self) -> bool {
159 Account::is_margin_account(self)
160 }
161
162 #[pyo3(name = "apply")]
163 fn py_apply(&mut self, event: AccountState) -> PyResult<()> {
164 Account::apply(self, event).map_err(to_pyruntime_err)
165 }
166
167 #[pyo3(name = "purge_account_events")]
168 fn py_purge_account_events(&mut self, ts_now: u64, lookback_secs: u64) {
169 Account::purge_account_events(self, UnixNanos::from(ts_now), lookback_secs);
170 }
171
172 #[pyo3(name = "calculate_balance_locked")]
173 #[pyo3(signature = (instrument, side, quantity, price, use_quote_for_inverse=None))]
174 fn py_calculate_balance_locked(
175 &mut self,
176 instrument: Py<PyAny>,
177 side: OrderSide,
178 quantity: Quantity,
179 price: Price,
180 use_quote_for_inverse: Option<bool>,
181 py: Python,
182 ) -> PyResult<Money> {
183 let instrument = pyobject_to_instrument_any(py, instrument)?;
184 Account::calculate_balance_locked(
185 self,
186 &instrument,
187 side,
188 quantity,
189 price,
190 use_quote_for_inverse,
191 )
192 .map_err(to_pyvalue_err)
193 }
194
195 #[pyo3(name = "calculate_commission")]
196 #[pyo3(signature = (instrument, last_qty, last_px, liquidity_side, use_quote_for_inverse=None))]
197 fn py_calculate_commission(
198 &self,
199 instrument: Py<PyAny>,
200 last_qty: Quantity,
201 last_px: Price,
202 liquidity_side: LiquiditySide,
203 use_quote_for_inverse: Option<bool>,
204 py: Python,
205 ) -> PyResult<Money> {
206 if liquidity_side == LiquiditySide::NoLiquiditySide {
207 return Err(to_pyvalue_err("Invalid liquidity side"));
208 }
209 let instrument = pyobject_to_instrument_any(py, instrument)?;
210 Account::calculate_commission(
211 self,
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 Account::calculate_pnls(self, &instrument, fill, position).map_err(to_pyvalue_err)
232 }
233
234 fn __repr__(&self) -> String {
235 format!(
236 "{}(id={}, type={}, base={})",
237 stringify!(MarginAccount),
238 self.id,
239 self.account_type,
240 self.base_currency.map_or_else(
241 || "None".to_string(),
242 |base_currency| format!("{}", base_currency.code)
243 ),
244 )
245 }
246
247 #[pyo3(name = "set_default_leverage")]
249 fn py_set_default_leverage(&mut self, default_leverage: Decimal) {
250 self.set_default_leverage(default_leverage);
251 }
252
253 #[pyo3(name = "leverages")]
254 fn py_leverages(&self, py: Python) -> PyResult<Py<PyAny>> {
255 let leverages = PyDict::new(py);
256 for (key, &value) in &self.leverages {
257 leverages.set_item(key.into_py_any(py)?, value)?;
258 }
259 leverages.into_py_any(py)
260 }
261
262 #[pyo3(name = "leverage")]
263 fn py_leverage(&self, instrument_id: &InstrumentId) -> Decimal {
264 self.get_leverage(instrument_id)
265 }
266
267 #[pyo3(name = "set_leverage")]
269 fn py_set_leverage(&mut self, instrument_id: InstrumentId, leverage: Decimal) {
270 self.set_leverage(instrument_id, leverage);
271 }
272
273 #[pyo3(name = "is_unleveraged")]
274 fn py_is_unleveraged(&self, instrument_id: InstrumentId) -> bool {
275 self.is_unleveraged(instrument_id)
276 }
277
278 #[pyo3(name = "margin")]
280 fn py_margin(&self, instrument_id: InstrumentId) -> Option<MarginBalance> {
281 self.margin(&instrument_id)
282 }
283
284 #[pyo3(name = "margins")]
285 fn py_margins(&self) -> IndexMap<InstrumentId, MarginBalance> {
286 self.margins.clone()
287 }
288
289 #[pyo3(name = "initial_margins")]
290 fn py_initial_margins(&self) -> IndexMap<InstrumentId, Money> {
291 self.initial_margins()
292 }
293
294 #[pyo3(name = "maintenance_margins")]
295 fn py_maintenance_margins(&self) -> IndexMap<InstrumentId, Money> {
296 self.maintenance_margins()
297 }
298
299 #[pyo3(name = "account_margin")]
301 fn py_account_margin(&self, currency: Currency) -> Option<MarginBalance> {
302 self.account_margin(¤cy)
303 }
304
305 #[pyo3(name = "account_margins")]
306 fn py_account_margins(&self) -> IndexMap<Currency, MarginBalance> {
307 self.account_margins.clone()
308 }
309
310 #[pyo3(name = "account_initial_margin")]
312 fn py_account_initial_margin(&self, currency: Currency) -> Option<Money> {
313 self.account_initial_margin(¤cy)
314 }
315
316 #[pyo3(name = "account_initial_margins")]
318 fn py_account_initial_margins(&self) -> IndexMap<Currency, Money> {
319 self.account_initial_margins()
320 }
321
322 #[pyo3(name = "account_maintenance_margin")]
324 fn py_account_maintenance_margin(&self, currency: Currency) -> Option<Money> {
325 self.account_maintenance_margin(¤cy)
326 }
327
328 #[pyo3(name = "account_maintenance_margins")]
330 fn py_account_maintenance_margins(&self) -> IndexMap<Currency, Money> {
331 self.account_maintenance_margins()
332 }
333
334 #[pyo3(name = "total_initial_margin")]
337 fn py_total_initial_margin(&self, currency: Currency) -> Money {
338 self.total_initial_margin(currency)
339 }
340
341 #[pyo3(name = "total_maintenance_margin")]
344 fn py_total_maintenance_margin(&self, currency: Currency) -> Money {
345 self.total_maintenance_margin(currency)
346 }
347
348 #[pyo3(name = "update_initial_margin")]
350 fn py_update_initial_margin(&mut self, instrument_id: InstrumentId, initial_margin: Money) {
351 self.update_initial_margin(instrument_id, initial_margin);
352 }
353
354 #[pyo3(name = "initial_margin")]
356 fn py_initial_margin(&self, instrument_id: InstrumentId) -> Option<Money> {
357 self.margin(&instrument_id).map(|margin| margin.initial)
358 }
359
360 #[pyo3(name = "update_maintenance_margin")]
362 fn py_update_maintenance_margin(
363 &mut self,
364 instrument_id: InstrumentId,
365 maintenance_margin: Money,
366 ) {
367 self.update_maintenance_margin(instrument_id, maintenance_margin);
368 }
369
370 #[pyo3(name = "maintenance_margin")]
372 fn py_maintenance_margin(&self, instrument_id: InstrumentId) -> Option<Money> {
373 self.margin(&instrument_id).map(|margin| margin.maintenance)
374 }
375
376 #[pyo3(name = "calculate_initial_margin")]
377 #[pyo3(signature = (instrument, quantity, price, use_quote_for_inverse=None))]
378 pub fn py_calculate_initial_margin(
387 &mut self,
388 instrument: Py<PyAny>,
389 quantity: Quantity,
390 price: Price,
391 use_quote_for_inverse: Option<bool>,
392 py: Python,
393 ) -> PyResult<Money> {
394 let instrument_type = pyobject_to_instrument_any(py, instrument)?;
395 match instrument_type {
396 InstrumentAny::Betting(inst) => self
397 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
398 .map_err(to_pyvalue_err),
399 InstrumentAny::BinaryOption(inst) => self
400 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
401 .map_err(to_pyvalue_err),
402 InstrumentAny::Cfd(inst) => self
403 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
404 .map_err(to_pyvalue_err),
405 InstrumentAny::Commodity(inst) => self
406 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
407 .map_err(to_pyvalue_err),
408 InstrumentAny::CryptoFuture(inst) => self
409 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
410 .map_err(to_pyvalue_err),
411 InstrumentAny::CryptoFuturesSpread(inst) => self
412 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
413 .map_err(to_pyvalue_err),
414 InstrumentAny::CryptoOption(inst) => self
415 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
416 .map_err(to_pyvalue_err),
417 InstrumentAny::CryptoOptionSpread(inst) => self
418 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
419 .map_err(to_pyvalue_err),
420 InstrumentAny::CryptoPerpetual(inst) => self
421 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
422 .map_err(to_pyvalue_err),
423 InstrumentAny::CurrencyPair(inst) => self
424 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
425 .map_err(to_pyvalue_err),
426 InstrumentAny::Equity(inst) => self
427 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
428 .map_err(to_pyvalue_err),
429 InstrumentAny::FuturesContract(inst) => self
430 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
431 .map_err(to_pyvalue_err),
432 InstrumentAny::FuturesSpread(inst) => self
433 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
434 .map_err(to_pyvalue_err),
435 InstrumentAny::IndexInstrument(inst) => self
436 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
437 .map_err(to_pyvalue_err),
438 InstrumentAny::OptionContract(inst) => self
439 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
440 .map_err(to_pyvalue_err),
441 InstrumentAny::OptionSpread(inst) => self
442 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
443 .map_err(to_pyvalue_err),
444 InstrumentAny::PerpetualContract(inst) => self
445 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
446 .map_err(to_pyvalue_err),
447 InstrumentAny::TokenizedAsset(inst) => self
448 .calculate_initial_margin(&inst, quantity, price, use_quote_for_inverse)
449 .map_err(to_pyvalue_err),
450 }
451 }
452
453 #[pyo3(name = "calculate_maintenance_margin")]
461 #[pyo3(signature = (instrument, quantity, price, use_quote_for_inverse=None))]
462 pub fn py_calculate_maintenance_margin(
463 &mut self,
464 instrument: Py<PyAny>,
465 quantity: Quantity,
466 price: Price,
467 use_quote_for_inverse: Option<bool>,
468 py: Python,
469 ) -> PyResult<Money> {
470 let instrument_type = pyobject_to_instrument_any(py, instrument)?;
471 match instrument_type {
472 InstrumentAny::Betting(inst) => self
473 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
474 .map_err(to_pyvalue_err),
475 InstrumentAny::BinaryOption(inst) => self
476 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
477 .map_err(to_pyvalue_err),
478 InstrumentAny::Cfd(inst) => self
479 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
480 .map_err(to_pyvalue_err),
481 InstrumentAny::Commodity(inst) => self
482 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
483 .map_err(to_pyvalue_err),
484 InstrumentAny::CryptoFuture(inst) => self
485 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
486 .map_err(to_pyvalue_err),
487 InstrumentAny::CryptoFuturesSpread(inst) => self
488 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
489 .map_err(to_pyvalue_err),
490 InstrumentAny::CryptoOption(inst) => self
491 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
492 .map_err(to_pyvalue_err),
493 InstrumentAny::CryptoOptionSpread(inst) => self
494 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
495 .map_err(to_pyvalue_err),
496 InstrumentAny::CryptoPerpetual(inst) => self
497 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
498 .map_err(to_pyvalue_err),
499 InstrumentAny::CurrencyPair(inst) => self
500 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
501 .map_err(to_pyvalue_err),
502 InstrumentAny::Equity(inst) => self
503 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
504 .map_err(to_pyvalue_err),
505 InstrumentAny::FuturesContract(inst) => self
506 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
507 .map_err(to_pyvalue_err),
508 InstrumentAny::FuturesSpread(inst) => self
509 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
510 .map_err(to_pyvalue_err),
511 InstrumentAny::IndexInstrument(inst) => self
512 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
513 .map_err(to_pyvalue_err),
514 InstrumentAny::OptionContract(inst) => self
515 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
516 .map_err(to_pyvalue_err),
517 InstrumentAny::OptionSpread(inst) => self
518 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
519 .map_err(to_pyvalue_err),
520 InstrumentAny::PerpetualContract(inst) => self
521 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
522 .map_err(to_pyvalue_err),
523 InstrumentAny::TokenizedAsset(inst) => self
524 .calculate_maintenance_margin(&inst, quantity, price, use_quote_for_inverse)
525 .map_err(to_pyvalue_err),
526 }
527 }
528
529 #[pyo3(name = "to_dict")]
530 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
531 let dict = PyDict::new(py);
532 dict.set_item("calculate_account_state", self.calculate_account_state)?;
533 let events_list: PyResult<Vec<Py<PyAny>>> =
534 self.events.iter().map(|item| item.py_to_dict(py)).collect();
535 dict.set_item("events", events_list?)?;
536 Ok(dict.into())
537 }
538}