sandbox-quant 1.0.7

Exchange-truth trading core for Binance Spot and Futures
Documentation
use crate::recorder_app::runtime::RecorderStatus;

pub fn render_live_recorder_status(header: &str, status: &RecorderStatus) -> String {
    let mut lines = vec![
        header.to_string(),
        format!("mode={}", status.mode.as_str()),
        format!("state={}", status.state.as_str()),
        format!("desired_running={}", status.state.is_running()),
        format!("process_alive={}", status.worker_alive),
        format!("worker_alive={}", status.worker_alive),
        format!("status_stale={}", status.heartbeat_age_sec > 5),
        format!("heartbeat_age_sec={}", status.heartbeat_age_sec),
        "pid=in-process".to_string(),
        format!("binary_version={}", env!("CARGO_PKG_VERSION")),
        format!("storage_backend={}", status.storage_backend),
        format!("storage_target={}", status.storage_target),
        format!("db_path={}", status.db_path.display()),
        format!(
            "schema_version={}",
            status
                .metrics
                .schema_version
                .clone()
                .unwrap_or_else(|| "missing".to_string())
        ),
        format!(
            "started_at={}",
            status
                .started_at
                .map(|value| value.to_rfc3339())
                .unwrap_or_else(|| "n/a".to_string())
        ),
        format!("updated_at={}", status.updated_at.to_rfc3339()),
        format!("manual_symbols={}", status.manual_symbols.len()),
        format!("strategy_symbols={}", status.strategy_symbols.len()),
        format!("watched_symbols={}", status.watched_symbols.len()),
        format!("liquidation_events={}", status.metrics.liquidation_events),
        format!("book_ticker_events={}", status.metrics.book_ticker_events),
        format!("agg_trade_events={}", status.metrics.agg_trade_events),
        format!(
            "derived_kline_1s_bars={}",
            status.metrics.derived_kline_1s_bars
        ),
        format!(
            "last_liquidation_event_time={}",
            status
                .metrics
                .last_liquidation_event_time
                .clone()
                .unwrap_or_else(|| "n/a".to_string())
        ),
        format!(
            "last_book_ticker_event_time={}",
            status
                .metrics
                .last_book_ticker_event_time
                .clone()
                .unwrap_or_else(|| "n/a".to_string())
        ),
        format!(
            "last_agg_trade_event_time={}",
            status
                .metrics
                .last_agg_trade_event_time
                .clone()
                .unwrap_or_else(|| "n/a".to_string())
        ),
        format!(
            "top_liquidation_symbols={}",
            join_symbols(&status.metrics.top_liquidation_symbols)
        ),
        format!(
            "top_book_ticker_symbols={}",
            join_symbols(&status.metrics.top_book_ticker_symbols)
        ),
        format!(
            "top_agg_trade_symbols={}",
            join_symbols(&status.metrics.top_agg_trade_symbols)
        ),
    ];
    if let Some(error) = &status.last_error {
        lines.push(format!("last_error={error}"));
    }
    lines.join("\n")
}

fn join_symbols(symbols: &[String]) -> String {
    if symbols.is_empty() {
        "none".to_string()
    } else {
        symbols.join(", ")
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::app::bootstrap::BinanceMode;
    use crate::dataset::types::RecorderMetrics;
    use crate::recorder_app::runtime::{RecorderState, RecorderStatus};
    use std::path::PathBuf;

    #[test]
    fn render_live_recorder_status_includes_schema_version() {
        let status = RecorderStatus {
            mode: BinanceMode::Demo,
            state: RecorderState::Stopped,
            db_path: PathBuf::from("var/market-v2-demo.duckdb"),
            storage_backend: "duckdb".to_string(),
            storage_target: "var/market-v2-demo.duckdb".to_string(),
            started_at: None,
            updated_at: chrono::Utc::now(),
            manual_symbols: Vec::new(),
            strategy_symbols: Vec::new(),
            watched_symbols: Vec::new(),
            worker_alive: false,
            heartbeat_age_sec: 0,
            last_error: None,
            metrics: RecorderMetrics {
                schema_version: Some("1".to_string()),
                ..RecorderMetrics::default()
            },
        };

        let rendered = render_live_recorder_status("record stopped", &status);
        assert!(rendered.contains("schema_version=1"), "{rendered}");
    }
}