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}