atelier_data 0.0.15

Data Artifacts and I/O for the atelier-rs engine
//! Open interest data structures and I/O.
//!
//! Open interest represents the total number of outstanding derivative contracts
//! (futures/options) that have not been settled. Changes in OI signal new money
//! entering or exiting the market.

pub mod io;

/// A single open interest observation.
#[derive(Debug, Clone)]
pub struct OpenInterest {
    /// Timestamp when this value was observed (Unix ms).
    pub open_interest_ts: u64,
    /// Trading pair symbol (e.g. "BTCUSDT").
    pub symbol: String,
    /// Open interest in contract units.
    pub open_interest: f64,
    /// Open interest in quote currency (e.g. USD value).
    pub open_interest_value: f64,
    /// Exchange name.
    pub exchange: String,
}

impl OpenInterest {
    /// Create a new [`OpenInterestBuilder`].
    pub fn builder() -> OpenInterestBuilder {
        OpenInterestBuilder::new()
    }
}

/// Builder for constructing an [`OpenInterest`] with validated fields.
///
/// Required fields: `open_interest_ts`, `symbol`, `open_interest`,
/// `exchange`.  `open_interest_value` defaults to `0.0` when omitted.
#[derive(Debug, Clone)]
pub struct OpenInterestBuilder {
    open_interest_ts: Option<u64>,
    symbol: Option<String>,
    open_interest: Option<f64>,
    open_interest_value: Option<f64>,
    exchange: Option<String>,
}

impl Default for OpenInterestBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl OpenInterestBuilder {
    /// Create an empty builder with all fields set to `None`.
    pub fn new() -> Self {
        OpenInterestBuilder {
            open_interest_ts: None,
            symbol: None,
            open_interest: None,
            open_interest_value: None,
            exchange: None,
        }
    }

    /// Set the observation timestamp (Unix ms).
    pub fn open_interest_ts(mut self, open_interest_ts: u64) -> Self {
        self.open_interest_ts = Some(open_interest_ts);
        self
    }

    /// Set the trading pair symbol (e.g. `"BTCUSDT"`).
    pub fn symbol(mut self, symbol: String) -> Self {
        self.symbol = Some(symbol);
        self
    }

    /// Set the open interest in contract units.
    pub fn open_interest(mut self, oi: f64) -> Self {
        self.open_interest = Some(oi);
        self
    }

    /// Set the open interest in quote-currency value (optional, defaults to `0.0`).
    pub fn open_interest_value(mut self, value: f64) -> Self {
        self.open_interest_value = Some(value);
        self
    }

    /// Set the exchange name (e.g. `"bybit"`).
    pub fn exchange(mut self, exchange: String) -> Self {
        self.exchange = Some(exchange);
        self
    }

    /// Consume the builder and produce an [`OpenInterest`].
    ///
    /// # Errors
    ///
    /// Returns `Err(String)` if any required field is missing.
    /// `open_interest_value` defaults to `0.0` when not set.
    pub fn build(self) -> Result<OpenInterest, String> {
        let open_interest_ts = self.open_interest_ts.ok_or("Missing ts")?;
        let symbol = self.symbol.ok_or("Missing symbol")?;
        let open_interest = self.open_interest.ok_or("Missing open_interest")?;
        let open_interest_value = self.open_interest_value.unwrap_or(0.0);
        let exchange = self.exchange.ok_or("Missing exchange")?;

        Ok(OpenInterest {
            open_interest_ts,
            symbol,
            open_interest,
            open_interest_value,
            exchange,
        })
    }
}