nautilus_model/python/account/
mod.rs1pub mod betting;
17pub mod cash;
18pub mod margin;
19pub mod margin_model;
20pub mod transformer;
21pub mod wallet;
22
23use nautilus_core::python::to_pyvalue_err;
24use pyo3::{Py, PyAny, PyResult, Python, conversion::IntoPyObjectExt};
25
26use crate::{
27 accounts::{AccountAny, BettingAccount, CashAccount, MarginAccount, WalletAccount},
28 enums::AccountType,
29 types::Currency,
30};
31
32#[expect(clippy::needless_pass_by_value)]
41pub fn pyobject_to_account_any(py: Python, account: Py<PyAny>) -> PyResult<AccountAny> {
42 let account_type = account
43 .getattr(py, "account_type")?
44 .extract::<AccountType>(py)?;
45 if account_type == AccountType::Margin {
46 let margin = account.extract::<MarginAccount>(py)?;
47 Ok(AccountAny::Margin(margin))
48 } else if account_type == AccountType::Cash {
49 let cash = account.extract::<CashAccount>(py)?;
50 Ok(AccountAny::Cash(cash))
51 } else if account_type == AccountType::Betting {
52 let betting = account.extract::<BettingAccount>(py)?;
53 Ok(AccountAny::Betting(betting))
54 } else if account_type == AccountType::Wallet {
55 let wallet = account.extract::<WalletAccount>(py)?;
56 Ok(AccountAny::Wallet(wallet))
57 } else {
58 Err(to_pyvalue_err("Unsupported account type"))
59 }
60}
61
62pub fn account_any_to_pyobject(py: Python, account: AccountAny) -> PyResult<Py<PyAny>> {
68 match account {
69 AccountAny::Margin(account) => account.into_py_any(py),
70 AccountAny::Cash(account) => account.into_py_any(py),
71 AccountAny::Betting(account) => account.into_py_any(py),
72 AccountAny::Wallet(account) => account.into_py_any(py),
73 }
74}
75
76fn resolve_balance_currency(
77 currency: Option<Currency>,
78 base_currency: Option<Currency>,
79) -> PyResult<Currency> {
80 currency.or(base_currency).ok_or_else(|| {
81 to_pyvalue_err("`currency` must be specified for an account with no base currency")
82 })
83}
84
85#[cfg(test)]
86mod tests {
87 use pyo3::Python;
88 use rstest::rstest;
89
90 use super::resolve_balance_currency;
91 use crate::types::Currency;
92
93 #[rstest]
94 #[case(Some(Currency::USD()), None, Currency::USD())]
95 #[case(None, Some(Currency::EUR()), Currency::EUR())]
96 #[case(Some(Currency::USD()), Some(Currency::EUR()), Currency::USD())]
97 fn test_resolve_balance_currency(
98 #[case] currency: Option<Currency>,
99 #[case] base_currency: Option<Currency>,
100 #[case] expected: Currency,
101 ) {
102 let resolved = resolve_balance_currency(currency, base_currency).unwrap();
103
104 assert_eq!(resolved, expected);
105 }
106
107 #[rstest]
108 fn test_resolve_balance_currency_rejects_missing_currency() {
109 Python::initialize();
110 Python::attach(|_| {
111 let error = resolve_balance_currency(None, None).unwrap_err();
112
113 assert_eq!(
114 error.to_string(),
115 "ValueError: `currency` must be specified for an account with no base currency"
116 );
117 });
118 }
119}