paft_aggregates/snapshot.rs
1//! Instant-in-time market snapshot model.
2//!
3//! [`Snapshot`] is a strictly snapshot-shaped view of an instrument: identity,
4//! the current session's prices, and the timestamp the snapshot was taken at.
5//! Fundamentals, analyst coverage, and ESG fields are intentionally excluded
6//! and live in `paft-fundamentals`.
7
8use chrono::{DateTime, Utc};
9#[cfg(feature = "dataframe")]
10use df_derive_macros::ToDataFrame;
11use paft_domain::{Instrument, MarketState};
12use paft_money::{Currency, PriceAmount, QuantityAmount};
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16#[cfg_attr(feature = "dataframe", derive(ToDataFrame))]
17/// Strictly instant-in-time snapshot for an instrument.
18///
19/// All fields except `instrument` and `currency` are optional to accommodate
20/// partially populated data from upstream sources.
21///
22/// Generic over a provider metadata payload `M`, which is flattened into the
23/// serialized representation. Use the [`Snapshot`] alias for the
24/// standard shape (no extra metadata).
25///
26/// **Collision warning:** provider metadata is flattened into the same object
27/// as paft fields. Metadata field names must not collide with paft field
28/// names; prefer provider-specific prefixes when in doubt.
29pub struct GenericSnapshot<M = ()> {
30 /// Primary instrument as provided by the data source.
31 #[cfg_attr(feature = "dataframe", df_derive(as_string))]
32 pub instrument: Instrument,
33 /// Human-friendly instrument name.
34 pub name: Option<String>,
35 /// Current market session state (for example: Pre, Regular, Post).
36 #[cfg_attr(feature = "dataframe", df_derive(as_str))]
37 pub market_state: Option<MarketState>,
38 /// Timestamp (UTC) when this snapshot was taken.
39 #[serde(default, with = "chrono::serde::ts_milliseconds_option")]
40 pub as_of: Option<DateTime<Utc>>,
41 /// Currency shared by every price amount in this snapshot.
42 #[cfg_attr(feature = "dataframe", df_derive(as_str))]
43 pub currency: Currency,
44 /// Most recent traded/quoted price.
45 pub last: Option<PriceAmount>,
46 /// Previous session's official close price.
47 pub previous_close: Option<PriceAmount>,
48 /// Opening price for the current session.
49 pub open: Option<PriceAmount>,
50 /// Highest traded price observed during the current session.
51 pub day_high: Option<PriceAmount>,
52 /// Lowest traded price observed during the current session.
53 pub day_low: Option<PriceAmount>,
54 /// Today's trading volume in the provider's stated quantity unit.
55 pub volume: Option<QuantityAmount>,
56 /// Provider-specific payload, flattened into the serialized form.
57 #[serde(flatten, default = "Default::default")]
58 pub provider: M,
59}
60
61impl<M: Default> GenericSnapshot<M> {
62 /// Build a snapshot for the given instrument with all optional fields
63 /// unset. `provider` is initialised via `M::default()`.
64 #[must_use]
65 pub fn new(instrument: Instrument, currency: Currency) -> Self {
66 Self {
67 instrument,
68 name: None,
69 market_state: None,
70 as_of: None,
71 currency,
72 last: None,
73 previous_close: None,
74 open: None,
75 day_high: None,
76 day_low: None,
77 volume: None,
78 provider: M::default(),
79 }
80 }
81}
82
83/// Standard `Snapshot` with no extra provider metadata.
84pub type Snapshot = GenericSnapshot<()>;