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