nautilus_model/accounts/
any.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.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 std::collections::HashMap;
23
24use enum_dispatch::enum_dispatch;
25use serde::{Deserialize, Serialize};
26
27use crate::{
28    accounts::{Account, CashAccount, MarginAccount},
29    enums::{AccountType, LiquiditySide},
30    events::{AccountState, OrderFilled},
31    identifiers::AccountId,
32    instruments::InstrumentAny,
33    position::Position,
34    types::{AccountBalance, Currency, Money, Price, Quantity},
35};
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[enum_dispatch(Account)]
39pub enum AccountAny {
40    Margin(MarginAccount),
41    Cash(CashAccount),
42}
43
44impl AccountAny {
45    #[must_use]
46    pub fn id(&self) -> AccountId {
47        match self {
48            AccountAny::Margin(margin) => margin.id,
49            AccountAny::Cash(cash) => cash.id,
50        }
51    }
52
53    pub fn last_event(&self) -> Option<AccountState> {
54        match self {
55            AccountAny::Margin(margin) => margin.last_event(),
56            AccountAny::Cash(cash) => cash.last_event(),
57        }
58    }
59
60    pub fn events(&self) -> Vec<AccountState> {
61        match self {
62            AccountAny::Margin(margin) => margin.events(),
63            AccountAny::Cash(cash) => cash.events(),
64        }
65    }
66
67    pub fn apply(&mut self, event: AccountState) {
68        match self {
69            AccountAny::Margin(margin) => margin.apply(event),
70            AccountAny::Cash(cash) => cash.apply(event),
71        }
72    }
73
74    pub fn balances(&self) -> HashMap<Currency, AccountBalance> {
75        match self {
76            AccountAny::Margin(margin) => margin.balances(),
77            AccountAny::Cash(cash) => cash.balances(),
78        }
79    }
80
81    pub fn balances_locked(&self) -> HashMap<Currency, Money> {
82        match self {
83            AccountAny::Margin(margin) => margin.balances_locked(),
84            AccountAny::Cash(cash) => cash.balances_locked(),
85        }
86    }
87
88    pub fn base_currency(&self) -> Option<Currency> {
89        match self {
90            AccountAny::Margin(margin) => margin.base_currency(),
91            AccountAny::Cash(cash) => cash.base_currency(),
92        }
93    }
94
95    /// # Errors
96    ///
97    /// Returns an error if `events` is empty.
98    ///
99    /// # Panics
100    ///
101    /// Panics if `events` is empty when unwrapping the first element.
102    pub fn from_events(events: Vec<AccountState>) -> anyhow::Result<Self> {
103        if events.is_empty() {
104            anyhow::bail!("No order events provided to create `AccountAny`");
105        }
106
107        let init_event = events.first().unwrap();
108        let mut account = Self::from(init_event.clone());
109        for event in events.iter().skip(1) {
110            account.apply(event.clone());
111        }
112        Ok(account)
113    }
114
115    /// # Errors
116    ///
117    /// Returns an error if calculating P&Ls fails for the underlying account.
118    pub fn calculate_pnls(
119        &self,
120        instrument: InstrumentAny,
121        fill: OrderFilled,
122        position: Option<Position>,
123    ) -> anyhow::Result<Vec<Money>> {
124        match self {
125            AccountAny::Margin(margin) => margin.calculate_pnls(instrument, fill, position),
126            AccountAny::Cash(cash) => cash.calculate_pnls(instrument, fill, position),
127        }
128    }
129
130    /// # Errors
131    ///
132    /// Returns an error if calculating commission fails for the underlying account.
133    pub fn calculate_commission(
134        &self,
135        instrument: InstrumentAny,
136        last_qty: Quantity,
137        last_px: Price,
138        liquidity_side: LiquiditySide,
139        use_quote_for_inverse: Option<bool>,
140    ) -> anyhow::Result<Money> {
141        match self {
142            AccountAny::Margin(margin) => margin.calculate_commission(
143                instrument,
144                last_qty,
145                last_px,
146                liquidity_side,
147                use_quote_for_inverse,
148            ),
149            AccountAny::Cash(cash) => cash.calculate_commission(
150                instrument,
151                last_qty,
152                last_px,
153                liquidity_side,
154                use_quote_for_inverse,
155            ),
156        }
157    }
158
159    pub fn balance(&self, currency: Option<Currency>) -> Option<&AccountBalance> {
160        match self {
161            AccountAny::Margin(margin) => margin.balance(currency),
162            AccountAny::Cash(cash) => cash.balance(currency),
163        }
164    }
165}
166
167impl From<AccountState> for AccountAny {
168    fn from(event: AccountState) -> Self {
169        match event.account_type {
170            AccountType::Margin => AccountAny::Margin(MarginAccount::new(event, false)),
171            AccountType::Cash => AccountAny::Cash(CashAccount::new(event, false)),
172            AccountType::Betting => todo!("Betting account not implemented"),
173        }
174    }
175}
176
177impl PartialEq for AccountAny {
178    fn eq(&self, other: &Self) -> bool {
179        self.id() == other.id()
180    }
181}