atelier_data 0.0.15

Data Artifacts and I/O for the atelier-rs engine
//! Funding rate data structures and I/O.
//!
//! Funding rates are periodic payments between long and short position holders
//! in perpetual futures markets. A positive rate means longs pay shorts; negative
//! means shorts pay longs.

pub mod io;

/// A single funding rate observation.
#[derive(Debug, Clone)]
pub struct FundingRate {
    /// Timestamp when this rate was observed (Unix ms).
    pub funding_rate_ts: u64,
    /// Trading pair symbol (e.g. "BTCUSDT").
    pub symbol: String,
    /// The funding rate as a decimal (e.g. 0.0001 = 1 bps).
    pub funding_rate: f64,
    /// Next funding settlement timestamp (Unix ms). 0 if unknown.
    pub next_funding_ts: u64,
    /// Exchange name.
    pub exchange: String,
}

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

/// Builder for constructing a [`FundingRate`] with validated fields.
///
/// Required fields: `funding_rate_ts`, `symbol`, `funding_rate`,
/// `exchange`.  `next_funding_ts` defaults to `0` when omitted.
#[derive(Debug, Clone)]
pub struct FundingRateBuilder {
    funding_rate_ts: Option<u64>,
    symbol: Option<String>,
    funding_rate: Option<f64>,
    next_funding_ts: Option<u64>,
    exchange: Option<String>,
}

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

impl FundingRateBuilder {
    /// Create an empty builder with all fields set to `None`.
    pub fn new() -> Self {
        FundingRateBuilder {
            funding_rate_ts: None,
            symbol: None,
            funding_rate: None,
            next_funding_ts: None,
            exchange: None,
        }
    }

    /// Set the observation timestamp (Unix ms).
    pub fn funding_rate_ts(mut self, funding_rate_ts: u64) -> Self {
        self.funding_rate_ts = Some(funding_rate_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 funding rate as a decimal (e.g. `0.0001` = 1 bps).
    pub fn funding_rate(mut self, rate: f64) -> Self {
        self.funding_rate = Some(rate);
        self
    }

    /// Set the next funding settlement timestamp (Unix ms, optional, defaults to `0`).
    pub fn next_funding_ts(mut self, ts: u64) -> Self {
        self.next_funding_ts = Some(ts);
        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 a [`FundingRate`].
    ///
    /// # Errors
    ///
    /// Returns `Err(String)` if any required field is missing.
    /// `next_funding_ts` defaults to `0` when not set.
    pub fn build(self) -> Result<FundingRate, String> {
        let funding_rate_ts = self.funding_rate_ts.ok_or("Missing ts")?;
        let symbol = self.symbol.ok_or("Missing symbol")?;
        let funding_rate = self.funding_rate.ok_or("Missing funding_rate")?;
        let next_funding_ts = self.next_funding_ts.unwrap_or(0);
        let exchange = self.exchange.ok_or("Missing exchange")?;

        Ok(FundingRate {
            funding_rate_ts,
            symbol,
            funding_rate,
            next_funding_ts,
            exchange,
        })
    }
}