atelier_data 0.0.15

Data Artifacts and I/O for the atelier-rs engine
use chrono::{DateTime, SecondsFormat};
use rust_decimal::Decimal;
use std::time::{SystemTime, UNIX_EPOCH};

pub fn current_timestamp_ms() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_millis() as u64)
        .unwrap_or(0)
}

pub fn format_ts(ts: u64) -> String {
    let timestamp_nanos: i64 = ts as i64;
    let dt = DateTime::from_timestamp_nanos(timestamp_nanos);
    dt.to_rfc3339_opts(SecondsFormat::Nanos, true)
}

pub fn decimal_to_f64(d: Decimal) -> f64 {
    use std::str::FromStr;
    f64::from_str(&d.to_string()).unwrap_or(0.0)
}

/// Normalize a trade/liquidation side string to lowercase `"buy"` or `"sell"`.
///
/// Handles exchange-specific formats:
/// - Bybit: `"Buy"` / `"Sell"`
/// - Coinbase: `"BUY"` / `"SELL"` (trades), `"bid"` / `"offer"` (L2)
/// - Kraken: `"buy"` / `"sell"` (already normalized)
#[inline]
pub fn normalize_side(raw: &str) -> String {
    match raw {
        "Buy" | "BUY" | "buy" | "bid" => "buy".to_string(),
        "Sell" | "SELL" | "sell" | "offer" => "sell".to_string(),
        other => other.to_lowercase(),
    }
}

/// Normalize a trading pair symbol to lowercase, separator-free format.
///
/// Handles exchange-specific formats:
/// - Bybit: `"BTCUSDT"` → `"btcusdt"`
/// - Coinbase: `"BTC-USD"` → `"btcusd"`
/// - Kraken: `"BTC/USD"` → `"btcusd"`
#[inline]
pub fn normalize_symbol(raw: &str) -> String {
    raw.replace(['-', '/', '_'], "").to_lowercase()
}