Skip to main content

nautilus_model/events/portfolio/
snapshot.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
16use std::fmt::Display;
17
18use nautilus_core::{UUID4, UnixNanos};
19use serde::{Deserialize, Serialize};
20
21use crate::{
22    enums::AccountType,
23    identifiers::AccountId,
24    types::{AccountBalance, Currency, MarginBalance, Money},
25};
26
27/// Represents a point-in-time snapshot of portfolio state for a single account,
28/// emitted periodically while the account holds open positions.
29///
30/// Unlike [`AccountState`](crate::events::AccountState), which fires only on
31/// balance or margin changes, `PortfolioSnapshot` carries a continuous
32/// mark-to-market view by folding open-position valuations into the totals.
33/// Totals span every venue the account holds positions on, so multi-venue
34/// accounts (e.g., a prime broker routing across exchanges) produce a single
35/// account-wide snapshot rather than per-venue slices.
36#[repr(C)]
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[cfg_attr(
39    feature = "python",
40    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
41)]
42#[cfg_attr(
43    feature = "python",
44    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
45)]
46pub struct PortfolioSnapshot {
47    /// The account ID this snapshot belongs to.
48    pub account_id: AccountId,
49    /// The type of the account (e.g., cash, margin).
50    pub account_type: AccountType,
51    /// The base currency for the account, if applicable.
52    pub base_currency: Option<Currency>,
53    /// The per-currency account balances at snapshot time.
54    pub balances: Vec<AccountBalance>,
55    /// The per-instrument margin balances at snapshot time (margin accounts only).
56    pub margins: Vec<MarginBalance>,
57    /// The per-currency unrealized PnL across all open positions at snapshot time.
58    pub unrealized_pnls: Vec<Money>,
59    /// The per-currency realized PnL accumulated for positions opened in this session.
60    pub realized_pnls: Vec<Money>,
61    /// The per-currency total equity (mark-to-market).
62    ///
63    /// For cash accounts: `balance.total + Σ mark_value(open positions)` in the same currency.
64    /// For margin accounts: `balance.total + Σ unrealized_pnl(open positions)` in the same currency.
65    pub total_equity: Vec<Money>,
66    /// The unique identifier for the event.
67    pub event_id: UUID4,
68    /// UNIX timestamp (nanoseconds) when the event occurred.
69    pub ts_event: UnixNanos,
70    /// UNIX timestamp (nanoseconds) when the event was initialized.
71    pub ts_init: UnixNanos,
72}
73
74impl PortfolioSnapshot {
75    /// Creates a new [`PortfolioSnapshot`] instance.
76    #[expect(clippy::too_many_arguments)]
77    #[must_use]
78    pub fn new(
79        account_id: AccountId,
80        account_type: AccountType,
81        base_currency: Option<Currency>,
82        balances: Vec<AccountBalance>,
83        margins: Vec<MarginBalance>,
84        unrealized_pnls: Vec<Money>,
85        realized_pnls: Vec<Money>,
86        total_equity: Vec<Money>,
87        event_id: UUID4,
88        ts_event: UnixNanos,
89        ts_init: UnixNanos,
90    ) -> Self {
91        Self {
92            account_id,
93            account_type,
94            base_currency,
95            balances,
96            margins,
97            unrealized_pnls,
98            realized_pnls,
99            total_equity,
100            event_id,
101            ts_event,
102            ts_init,
103        }
104    }
105}
106
107impl Display for PortfolioSnapshot {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        write!(
110            f,
111            "{}(account_id={}, account_type={}, total_equity=[{}], unrealized_pnls=[{}], realized_pnls=[{}], event_id={})",
112            stringify!(PortfolioSnapshot),
113            self.account_id,
114            self.account_type,
115            self.total_equity
116                .iter()
117                .map(|m| format!("{m}"))
118                .collect::<Vec<_>>()
119                .join(", "),
120            self.unrealized_pnls
121                .iter()
122                .map(|m| format!("{m}"))
123                .collect::<Vec<_>>()
124                .join(", "),
125            self.realized_pnls
126                .iter()
127                .map(|m| format!("{m}"))
128                .collect::<Vec<_>>()
129                .join(", "),
130            self.event_id,
131        )
132    }
133}
134
135impl PartialEq for PortfolioSnapshot {
136    fn eq(&self, other: &Self) -> bool {
137        self.account_id == other.account_id && self.event_id == other.event_id
138    }
139}