Skip to main content

nautilus_model/accounts/
any.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Enum wrapper providing a type-erased view over the various concrete [`Account`] implementations.
17//!
18//! The `AccountAny` enum is primarily used when heterogeneous account types need to be stored in a
19//! single collection (e.g. `Vec<AccountAny>`).  Each variant simply embeds one of the concrete
20//! account structs defined in this module.
21
22use ahash::AHashMap;
23use enum_dispatch::enum_dispatch;
24use serde::{Deserialize, Serialize};
25
26use crate::{
27    accounts::{Account, CashAccount, MarginAccount},
28    enums::{AccountType, LiquiditySide},
29    events::{AccountState, OrderFilled},
30    identifiers::AccountId,
31    instruments::InstrumentAny,
32    position::Position,
33    types::{AccountBalance, Currency, Money, Price, Quantity},
34};
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37#[enum_dispatch(Account)]
38pub enum AccountAny {
39    Margin(MarginAccount),
40    Cash(CashAccount),
41}
42
43impl AccountAny {
44    #[must_use]
45    pub fn id(&self) -> AccountId {
46        match self {
47            Self::Margin(margin) => margin.id,
48            Self::Cash(cash) => cash.id,
49        }
50    }
51
52    pub fn last_event(&self) -> Option<AccountState> {
53        match self {
54            Self::Margin(margin) => margin.last_event(),
55            Self::Cash(cash) => cash.last_event(),
56        }
57    }
58
59    pub fn events(&self) -> Vec<AccountState> {
60        match self {
61            Self::Margin(margin) => margin.events(),
62            Self::Cash(cash) => cash.events(),
63        }
64    }
65
66    /// Applies an account state event to update the account.
67    ///
68    /// # Errors
69    ///
70    /// Returns an error if the account state cannot be applied (e.g., negative balance
71    /// when borrowing is not allowed for a cash account).
72    pub fn apply(&mut self, event: AccountState) -> anyhow::Result<()> {
73        match self {
74            Self::Margin(margin) => margin.apply(event),
75            Self::Cash(cash) => cash.apply(event),
76        }
77    }
78
79    pub fn balances(&self) -> AHashMap<Currency, AccountBalance> {
80        match self {
81            Self::Margin(margin) => margin.balances(),
82            Self::Cash(cash) => cash.balances(),
83        }
84    }
85
86    pub fn balances_locked(&self) -> AHashMap<Currency, Money> {
87        match self {
88            Self::Margin(margin) => margin.balances_locked(),
89            Self::Cash(cash) => cash.balances_locked(),
90        }
91    }
92
93    pub fn base_currency(&self) -> Option<Currency> {
94        match self {
95            Self::Margin(margin) => margin.base_currency(),
96            Self::Cash(cash) => cash.base_currency(),
97        }
98    }
99
100    /// # Errors
101    ///
102    /// Returns an error if `events` is empty.
103    ///
104    /// # Panics
105    ///
106    /// Panics if `events` is empty when unwrapping the first element.
107    pub fn from_events(events: Vec<AccountState>) -> anyhow::Result<Self> {
108        if events.is_empty() {
109            anyhow::bail!("No order events provided to create `AccountAny`");
110        }
111
112        let init_event = events.first().unwrap();
113        let mut account = Self::from(init_event.clone());
114        for event in events.iter().skip(1) {
115            account.apply(event.clone())?;
116        }
117        Ok(account)
118    }
119
120    /// # Errors
121    ///
122    /// Returns an error if calculating P&Ls fails for the underlying account.
123    pub fn calculate_pnls(
124        &self,
125        instrument: InstrumentAny,
126        fill: OrderFilled,
127        position: Option<Position>,
128    ) -> anyhow::Result<Vec<Money>> {
129        match self {
130            Self::Margin(margin) => margin.calculate_pnls(instrument, fill, position),
131            Self::Cash(cash) => cash.calculate_pnls(instrument, fill, position),
132        }
133    }
134
135    /// # Errors
136    ///
137    /// Returns an error if calculating commission fails for the underlying account.
138    pub fn calculate_commission(
139        &self,
140        instrument: InstrumentAny,
141        last_qty: Quantity,
142        last_px: Price,
143        liquidity_side: LiquiditySide,
144        use_quote_for_inverse: Option<bool>,
145    ) -> anyhow::Result<Money> {
146        match self {
147            Self::Margin(margin) => margin.calculate_commission(
148                instrument,
149                last_qty,
150                last_px,
151                liquidity_side,
152                use_quote_for_inverse,
153            ),
154            Self::Cash(cash) => cash.calculate_commission(
155                instrument,
156                last_qty,
157                last_px,
158                liquidity_side,
159                use_quote_for_inverse,
160            ),
161        }
162    }
163
164    pub fn balance(&self, currency: Option<Currency>) -> Option<&AccountBalance> {
165        match self {
166            Self::Margin(margin) => margin.balance(currency),
167            Self::Cash(cash) => cash.balance(currency),
168        }
169    }
170}
171
172impl AccountAny {
173    /// Creates an `AccountAny` from an `AccountState`, returning an error for unsupported types.
174    ///
175    /// # Errors
176    ///
177    /// Returns an error if the account type is `Betting` or `Wallet` (unsupported in Rust).
178    pub fn try_from_state(event: AccountState) -> Result<Self, &'static str> {
179        match event.account_type {
180            AccountType::Margin => Ok(Self::Margin(MarginAccount::new(event, false))),
181            AccountType::Cash => Ok(Self::Cash(CashAccount::new(event, false, false))),
182            AccountType::Betting => Err("Betting accounts are not yet supported in Rust, \
183                use Python for betting workflows"),
184            AccountType::Wallet => Err("Wallet accounts are not yet implemented in Rust"),
185        }
186    }
187}
188
189impl From<AccountState> for AccountAny {
190    /// Creates an `AccountAny` from an `AccountState`.
191    ///
192    /// # Panics
193    ///
194    /// Panics if the account type is `Betting` or `Wallet` (unsupported in Rust).
195    /// Use [`AccountAny::try_from_state`] for fallible conversion.
196    fn from(event: AccountState) -> Self {
197        Self::try_from_state(event).expect("Unsupported account type")
198    }
199}
200
201impl PartialEq for AccountAny {
202    fn eq(&self, other: &Self) -> bool {
203        self.id() == other.id()
204    }
205}