atelier_data 0.0.15

Data Artifacts and I/O for the atelier-rs engine
//! Multi-source market snapshot aggregation.
//!
//! A [`MarketSnapshot`] joins orderbook state, trades, liquidations, funding
//! rates, and open interest at a single grid-aligned timestamp. This is the
//! canonical input to multi-source feature computation.

use crate::{
    funding::FundingRate, liquidations::Liquidation, open_interest::OpenInterest,
    orderbooks::Orderbook, trades::Trade,
};

/// A point-in-time view of the market across all data sources.
///
/// Produced by the [`MarketSynchronizer`](crate::synchronizers::MarketSynchronizer) at each grid period. Orderbook,
/// funding rate, and open interest are *state-based* (latest value carried
/// forward). Trades and liquidations are *event-based* (aggregated within
/// the period).
#[derive(Debug, Clone)]
pub struct MarketSnapshot {
    /// Grid-aligned timestamp in nanoseconds.
    pub ts_ns: u64,

    /// Most recent orderbook snapshot at this timestamp. `None` if no
    /// orderbook data has been received yet.
    pub orderbook: Option<Orderbook>,

    /// All trades that occurred during this grid period.
    pub trades: Vec<Trade>,

    /// All liquidations that occurred during this grid period.
    pub liquidations: Vec<Liquidation>,

    /// Most recent funding rate. `None` if no ticker data received yet.
    pub funding_rate: Vec<FundingRate>,

    /// Most recent open interest. `None` if no ticker data received yet.
    pub open_interest: Vec<OpenInterest>,
}

impl MarketSnapshot {
    /// Create an empty snapshot at the given timestamp.
    pub fn empty(ts_ns: u64) -> Self {
        Self {
            ts_ns,
            orderbook: None,
            trades: Vec::new(),
            liquidations: Vec::new(),
            funding_rate: Vec::new(),
            open_interest: Vec::new(),
        }
    }

    /// Whether any data source has been populated.
    pub fn has_data(&self) -> bool {
        self.orderbook.is_some()
            || !self.trades.is_empty()
            || !self.liquidations.is_empty()
            || !self.funding_rate.is_empty()
            || !self.open_interest.is_empty()
    }

    /// Total trade volume (sum of amounts) in this period.
    pub fn trade_volume(&self) -> f64 {
        self.trades.iter().map(|t| t.amount).sum()
    }

    /// Total trade count in this period.
    pub fn trade_count(&self) -> usize {
        self.trades.len()
    }

    /// Total liquidation notional (price * amount) in this period.
    pub fn liquidation_notional(&self) -> f64 {
        self.liquidations.iter().map(|l| l.price * l.amount).sum()
    }

}