percli 0.2.1

Offline CLI simulator for the Percolator risk engine
use percli_core::scenario::Scenario;
use anyhow::{Context, Result};

pub fn run(path: &str) -> Result<()> {
    let content = std::fs::read_to_string(path)
        .with_context(|| format!("failed to read scenario file: {path}"))?;

    let scenario: Scenario =
        toml::from_str(&content).with_context(|| format!("failed to parse scenario: {path}"))?;

    println!("Scenario: {}", scenario.meta.name);
    if !scenario.meta.description.is_empty() {
        println!("  {}", scenario.meta.description);
    }
    println!();

    println!("Parameters:");
    println!(
        "  warmup_period_slots:      {}",
        scenario.params.warmup_period_slots
    );
    println!(
        "  maintenance_margin_bps:   {}",
        scenario.params.maintenance_margin_bps
    );
    println!(
        "  initial_margin_bps:       {}",
        scenario.params.initial_margin_bps
    );
    println!(
        "  trading_fee_bps:          {}",
        scenario.params.trading_fee_bps
    );
    println!();

    println!("Market:");
    println!(
        "  initial_oracle_price:     {}",
        scenario.market.initial_oracle_price
    );
    println!(
        "  initial_slot:             {}",
        scenario.market.initial_slot
    );
    println!();

    println!("Steps ({} total):", scenario.steps.len());
    for (i, step) in scenario.steps.iter().enumerate() {
        let desc = match step {
            percli_core::scenario::Step::Deposit {
                account, amount, ..
            } => {
                format!("deposit {account} {amount}")
            }
            percli_core::scenario::Step::Withdraw {
                account, amount, ..
            } => {
                format!("withdraw {account} {amount}")
            }
            percli_core::scenario::Step::Trade {
                long,
                short,
                size,
                price,
                ..
            } => {
                format!("trade {long} long / {short} short x {size} @ {price}")
            }
            percli_core::scenario::Step::Crank {
                oracle_price, slot, ..
            } => {
                format!("crank oracle={oracle_price} slot={slot}")
            }
            percli_core::scenario::Step::Liquidate { account, .. } => {
                format!("liquidate {account}")
            }
            percli_core::scenario::Step::Settle { account, .. } => {
                format!("settle {account}")
            }
            percli_core::scenario::Step::SetOracle { oracle_price, .. } => {
                format!("set_oracle {oracle_price}")
            }
            percli_core::scenario::Step::SetSlot { slot, .. } => {
                format!("set_slot {slot}")
            }
            percli_core::scenario::Step::SetFundingRate { rate, .. } => {
                format!("set_funding_rate {rate}")
            }
            percli_core::scenario::Step::Query { metric, .. } => {
                format!("query {metric:?}")
            }
            percli_core::scenario::Step::Assert { condition, .. } => {
                format!("assert {condition:?}")
            }
        };
        println!("  {:>2}. {desc}", i + 1);
    }

    println!();
    println!("Scenario is valid.");

    Ok(())
}