nautilus_model/events/portfolio/
snapshot.rs1use std::fmt::Display;
17
18use nautilus_core::{UUID4, UnixNanos};
19use serde::{Deserialize, Serialize};
20
21use crate::{
22 enums::AccountType,
23 identifiers::{AccountId, InstrumentId},
24 types::{AccountBalance, Currency, MarginBalance, Money},
25};
26
27#[repr(C)]
39#[derive(Debug, Clone, Serialize, Deserialize)]
40#[cfg_attr(
41 feature = "python",
42 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
43)]
44#[cfg_attr(
45 feature = "python",
46 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
47)]
48pub struct PortfolioSnapshot {
49 pub account_id: AccountId,
51 pub account_type: AccountType,
53 pub base_currency: Option<Currency>,
55 pub balances: Vec<AccountBalance>,
57 pub margins: Vec<MarginBalance>,
59 pub unrealized_pnls: Vec<Money>,
61 pub realized_pnls: Vec<Money>,
63 pub total_equity: Vec<Money>,
68 #[serde(default)]
70 pub base_currency_equity: Option<Money>,
71 #[serde(default)]
73 pub is_stale: bool,
74 #[serde(default)]
76 pub stale_instruments: Vec<InstrumentId>,
77 #[serde(default)]
79 pub stale_currencies: Vec<Currency>,
80 #[serde(default)]
82 pub unpriced_instruments: Vec<InstrumentId>,
83 pub event_id: UUID4,
85 pub ts_event: UnixNanos,
87 pub ts_init: UnixNanos,
89}
90
91impl PortfolioSnapshot {
92 #[expect(clippy::too_many_arguments)]
94 #[must_use]
95 pub fn new(
96 account_id: AccountId,
97 account_type: AccountType,
98 base_currency: Option<Currency>,
99 balances: Vec<AccountBalance>,
100 margins: Vec<MarginBalance>,
101 unrealized_pnls: Vec<Money>,
102 realized_pnls: Vec<Money>,
103 total_equity: Vec<Money>,
104 base_currency_equity: Option<Money>,
105 is_stale: bool,
106 stale_instruments: Vec<InstrumentId>,
107 stale_currencies: Vec<Currency>,
108 unpriced_instruments: Vec<InstrumentId>,
109 event_id: UUID4,
110 ts_event: UnixNanos,
111 ts_init: UnixNanos,
112 ) -> Self {
113 Self {
114 account_id,
115 account_type,
116 base_currency,
117 balances,
118 margins,
119 unrealized_pnls,
120 realized_pnls,
121 total_equity,
122 base_currency_equity,
123 is_stale,
124 stale_instruments,
125 stale_currencies,
126 unpriced_instruments,
127 event_id,
128 ts_event,
129 ts_init,
130 }
131 }
132}
133
134impl Display for PortfolioSnapshot {
135 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 write!(
137 f,
138 "{}(account_id={}, account_type={}, total_equity=[{}], unrealized_pnls=[{}], realized_pnls=[{}], event_id={})",
139 stringify!(PortfolioSnapshot),
140 self.account_id,
141 self.account_type,
142 self.total_equity
143 .iter()
144 .map(|m| format!("{m}"))
145 .collect::<Vec<_>>()
146 .join(", "),
147 self.unrealized_pnls
148 .iter()
149 .map(|m| format!("{m}"))
150 .collect::<Vec<_>>()
151 .join(", "),
152 self.realized_pnls
153 .iter()
154 .map(|m| format!("{m}"))
155 .collect::<Vec<_>>()
156 .join(", "),
157 self.event_id,
158 )
159 }
160}
161
162impl PartialEq for PortfolioSnapshot {
163 fn eq(&self, other: &Self) -> bool {
164 self.account_id == other.account_id && self.event_id == other.event_id
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use rstest::rstest;
171
172 use super::*;
173
174 #[rstest]
175 fn test_deserialize_legacy_snapshot_defaults_valuation_metadata() {
176 let snapshot = PortfolioSnapshot::new(
177 AccountId::new("SIM-001"),
178 AccountType::Cash,
179 Some(Currency::USD()),
180 Vec::new(),
181 Vec::new(),
182 Vec::new(),
183 Vec::new(),
184 vec![Money::from("100.00 USD")],
185 Some(Money::from("100.00 USD")),
186 true,
187 vec![InstrumentId::from("AUDUSD.SIM")],
188 vec![Currency::AUD()],
189 vec![InstrumentId::from("GBPUSD.SIM")],
190 UUID4::new(),
191 UnixNanos::from(1),
192 UnixNanos::from(2),
193 );
194 let mut value = serde_json::to_value(snapshot).unwrap();
195 let object = value.as_object_mut().unwrap();
196 object.remove("base_currency_equity");
197 object.remove("is_stale");
198 object.remove("stale_instruments");
199 object.remove("stale_currencies");
200 object.remove("unpriced_instruments");
201
202 let decoded: PortfolioSnapshot = serde_json::from_value(value).unwrap();
203
204 assert_eq!(decoded.base_currency_equity, None);
205 assert!(!decoded.is_stale);
206 assert!(decoded.stale_instruments.is_empty());
207 assert!(decoded.stale_currencies.is_empty());
208 assert!(decoded.unpriced_instruments.is_empty());
209 }
210}