use std::process::ExitCode;
use sharpebench_core::{rank, AgentSubmission, CompositeScore, ScoreConfig};
#[cfg(feature = "self-update")]
mod update;
fn main() -> ExitCode {
let raw: Vec<String> = std::env::args().collect();
let json = raw.iter().any(|a| a == "--json");
let args: Vec<String> = raw.into_iter().filter(|a| a != "--json").collect();
let subcommand = args.get(1).map(String::as_str);
#[cfg(feature = "self-update")]
update::notify_if_outdated(json, subcommand);
match subcommand {
Some("run") => run_demo(&args, json),
Some("score") => match args.get(2) {
Some(path) => run_score(path, json),
None => {
eprintln!("usage: sharpebench score <submissions.json> [--json]");
ExitCode::from(2)
}
},
Some("commit") => run_commit(&args),
Some("stress") => run_stress(json),
Some("audit") => run_audit(json),
Some("realism") => run_realism(&args, json),
Some("sign") => run_sign(&args, json),
Some("verify") => run_verify(&args, json),
Some("capture") => run_capture(&args, json),
Some("verify-trajectory") => run_verify_trajectory(&args, json),
Some("audit-briefing") => run_audit_briefing(&args, json),
Some("canary") => run_canary(&args, json),
Some("score-allocation") => run_score_allocation(&args, json),
Some("greeks") => run_greeks(&args, json),
Some("check") => run_check(&args, json),
Some("regime") => run_regime(&args, json),
Some("self-update" | "update") => run_self_update(),
Some("--help") | Some("-h") | None => {
help();
ExitCode::SUCCESS
}
Some(other) => {
eprintln!("unknown command: {other}\nrun `sharpebench --help`");
ExitCode::from(2)
}
}
}
fn run_self_update() -> ExitCode {
#[cfg(feature = "self-update")]
{
update::run_self_update()
}
#[cfg(not(feature = "self-update"))]
{
eprintln!(
"this build has self-update disabled.\n\
upgrade with `cargo install sharpebench`, re-download the binary from\n\
https://github.com/general-liquidity/sharpebench/releases/latest, or\n\
rebuild with `cargo install sharpebench --features self-update`."
);
ExitCode::from(2)
}
}
fn run_audit_briefing(args: &[String], json: bool) -> ExitCode {
let Some(path) = args.get(2) else {
eprintln!("usage: sharpebench audit-briefing <briefing.json> [--json]");
return ExitCode::from(2);
};
let data = match std::fs::read_to_string(path) {
Ok(d) => d,
Err(e) => {
eprintln!("error: cannot read {path}: {e}");
return ExitCode::FAILURE;
}
};
let briefing: sharpebench_core::Briefing = match serde_json::from_str(&data) {
Ok(b) => b,
Err(e) => {
eprintln!("error: invalid briefing JSON: {e}");
return ExitCode::FAILURE;
}
};
let audit =
sharpebench_core::audit_briefing(&briefing, &sharpebench_core::BriefingPolicy::default());
if json {
emit_json(&audit);
} else if audit.balanced {
println!("BALANCED — no input-side salience bias detected");
} else {
println!("BIASED — {} violation(s):", audit.violations.len());
for v in &audit.violations {
println!(" - {v:?}");
}
}
if audit.balanced {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}
fn run_canary(args: &[String], json: bool) -> ExitCode {
let Some(seed) = args.get(2) else {
eprintln!("usage: sharpebench canary <seed> [--json]");
return ExitCode::from(2);
};
let canary = sharpebench_attest::make_canary(seed.as_bytes());
if json {
emit_json(&canary);
} else {
println!("canary id: {}", canary.id);
println!("canary token: {}", canary.token);
println!("\nEmbed the marker in the scenario artifact; if a model ever emits the token, the held-out set leaked into its training corpus.");
}
ExitCode::SUCCESS
}
fn run_score_allocation(args: &[String], json: bool) -> ExitCode {
let Some(path) = args.get(2) else {
eprintln!("usage: sharpebench score-allocation <allocation.json> [--json]");
return ExitCode::from(2);
};
let data = match std::fs::read_to_string(path) {
Ok(d) => d,
Err(e) => {
eprintln!("error: cannot read {path}: {e}");
return ExitCode::FAILURE;
}
};
let traj: sharpebench_core::AllocationTrajectory = match serde_json::from_str(&data) {
Ok(t) => t,
Err(e) => {
eprintln!("error: invalid allocation JSON: {e}");
return ExitCode::FAILURE;
}
};
let report =
sharpebench_core::score_allocation(&traj, &sharpebench_core::AllocationPolicy::default());
if json {
emit_json(&report);
} else {
println!(
"allocation: valid={} total_turnover={:.4} mean_turnover={:.4}",
report.valid, report.total_turnover, report.mean_turnover
);
for v in &report.weight_violations {
println!(" - {v:?}");
}
}
if report.valid {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}
fn run_greeks(args: &[String], json: bool) -> ExitCode {
if args.len() < 8 {
eprintln!(
"usage: sharpebench greeks <spot> <strike> <t_years> <rate> <vol> <call|put> [--json]"
);
return ExitCode::from(2);
}
let nums: Result<Vec<f64>, _> = args[2..7].iter().map(|s| s.parse::<f64>()).collect();
let Ok(n) = nums else {
eprintln!("error: spot/strike/t/rate/vol must be numbers");
return ExitCode::from(2);
};
let is_call = match args[7].as_str() {
"call" => true,
"put" => false,
other => {
eprintln!("error: expected call|put, got {other}");
return ExitCode::from(2);
}
};
let (spot, strike, t, r, vol) = (n[0], n[1], n[2], n[3], n[4]);
let price = sharpebench_core::bs_price(spot, strike, t, r, vol, is_call);
let greeks = sharpebench_core::bs_greeks(spot, strike, t, r, vol, is_call);
let risk =
sharpebench_core::classify_greeks_risk(&greeks, &sharpebench_core::GreeksPolicy::default());
if json {
emit_json(&serde_json::json!({ "price": price, "greeks": greeks, "risk": risk }));
} else {
println!("price {price:.4}");
println!(
"delta {:.4} gamma {:.4} theta {:.4} vega {:.4} rho {:.4}",
greeks.delta, greeks.gamma, greeks.theta, greeks.vega, greeks.rho
);
println!(
"tail-risk: short_gamma={} unbounded_tail={} short_vega={}",
risk.naked_short_gamma, risk.unbounded_tail, risk.short_vega
);
}
ExitCode::SUCCESS
}
fn run_check(args: &[String], json: bool) -> ExitCode {
use sharpebench_edge::{is_my_sharpe_real, HonestyConfig, Verdict};
let Some(path) = args.get(2).filter(|p| !p.starts_with('-')) else {
eprintln!("usage: sharpebench check <returns.csv> --trials N [--col NAME] [--confidence C] [--json]");
return ExitCode::from(2);
};
let Some(trials_str) = flag_value(args, "--trials") else {
eprintln!("error: --trials N is required (the number of strategies/configs tried before keeping this one). n_trials=1 is usually a lie.");
return ExitCode::from(2);
};
let Ok(n_trials) = trials_str.parse::<u32>() else {
eprintln!("error: --trials must be a positive integer, got `{trials_str}`");
return ExitCode::from(2);
};
let confidence = match flag_value(args, "--confidence") {
Some(c) => match c.parse::<f64>() {
Ok(v) if (0.0..1.0).contains(&v) => v,
_ => {
eprintln!("error: --confidence must be in (0, 1), got `{c}`");
return ExitCode::from(2);
}
},
None => 0.95,
};
let col = flag_value(args, "--col");
let text = match std::fs::read_to_string(path) {
Ok(t) => t,
Err(e) => {
eprintln!("error: cannot read {path}: {e}");
return ExitCode::FAILURE;
}
};
let returns = match read_returns_column(&text, col) {
Ok(r) => r,
Err(e) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
};
if returns.len() < 2 {
eprintln!("error: need at least 2 returns, got {}", returns.len());
return ExitCode::FAILURE;
}
let cfg = HonestyConfig {
n_trials,
confidence,
..HonestyConfig::default()
};
let v = is_my_sharpe_real(&returns, &cfg);
if json {
emit_json(&v);
} else {
let tag = match v.verdict {
Verdict::Pass => "PASS",
Verdict::Borderline => "BORDERLINE",
Verdict::Fail => "FAIL",
};
println!("Sharpe : {:.4} ({} obs)", v.sharpe, v.n_obs);
println!(
"Deflated : {:.4} (n_trials={})",
v.deflated_sharpe, v.n_trials
);
println!("Haircut : {:.4}", v.haircut);
let mintrl = if v.min_track_record_len.is_finite() {
format!("{:.0} periods", v.min_track_record_len)
} else {
"unreachable (Sharpe ≤ benchmark)".to_string()
};
println!("MinTRL : {mintrl}");
println!("Verdict : {tag}");
println!("\n{}", v.explanation);
}
match v.verdict {
Verdict::Pass => ExitCode::SUCCESS,
_ => ExitCode::FAILURE,
}
}
fn run_regime(args: &[String], json: bool) -> ExitCode {
let (Some(path_a), Some(path_b), Some(path_r)) = (args.get(2), args.get(3), args.get(4)) else {
eprintln!(
"usage: sharpebench regime <returns_a.csv> <returns_b.csv> <regimes.csv> [--col NAME] [--json]"
);
return ExitCode::from(2);
};
let col = flag_value(args, "--col");
let read = |path: &str| -> Result<String, ExitCode> {
std::fs::read_to_string(path).map_err(|e| {
eprintln!("error: cannot read {path}: {e}");
ExitCode::FAILURE
})
};
let (text_a, text_b, text_r) = match (read(path_a), read(path_b), read(path_r)) {
(Ok(a), Ok(b), Ok(r)) => (a, b, r),
(Err(c), _, _) | (_, Err(c), _) | (_, _, Err(c)) => return c,
};
let (a, b) = match (
read_returns_column(&text_a, col),
read_returns_column(&text_b, col),
) {
(Ok(a), Ok(b)) => (a, b),
(Err(e), _) | (_, Err(e)) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
};
let labels = read_label_column(&text_r, col);
if a.is_empty() || b.is_empty() || labels.is_empty() {
eprintln!(
"error: need at least one aligned period, got a={} b={} regimes={}",
a.len(),
b.len(),
labels.len()
);
return ExitCode::FAILURE;
}
if a.len() != b.len() || a.len() != labels.len() {
eprintln!(
"warning: lengths differ (a={} b={} regimes={}); comparing the first {} periods",
a.len(),
b.len(),
labels.len(),
a.len().min(b.len()).min(labels.len())
);
}
let regimes: Vec<&str> = labels.iter().map(String::as_str).collect();
let report = sharpebench_core::compare_by_regime(
&a,
&b,
®imes,
sharpebench_core::RegimeCompareOpts::default(),
);
if json {
emit_json(&report);
} else {
println!(
"Pooled mean gap (A-B): {:+.6} sign={:+}",
report.pooled_mean_gap, report.pooled_edge_sign
);
println!(
"{:<14} {:>5} {:>9} {:>9} {:>10} {:>10} {:>7} {:>5} counted",
"regime", "n", "zero_a", "zero_b", "mean_gap", "cont_gap", "ks", "edge"
);
for r in &report.regimes {
println!(
"{:<14} {:>5} {:>9.3} {:>9.3} {:>+10.6} {:>+10.6} {:>7.3} {:>+5} {}",
r.regime,
r.n_periods,
r.a.zero_mass,
r.b.zero_mass,
r.mean_gap,
r.cont_mean_gap,
r.ks_statistic,
r.edge_sign,
if r.counted { "yes" } else { "no" }
);
}
println!(
"Edge dispersion across counted regimes: {:.6}",
report.edge_dispersion
);
if report.pooled_hides_reversal {
println!(
"REVERSAL: the pooled sign is contradicted in {}",
report.reversal_regimes.join(", ")
);
} else {
println!("No sign reversal among counted regimes.");
}
}
ExitCode::SUCCESS
}
fn read_label_column(text: &str, col: Option<&str>) -> Vec<String> {
let mut lines = text.lines().filter(|l| !l.trim().is_empty());
let Some(first) = lines.next() else {
return Vec::new();
};
let header: Vec<&str> = first.split(',').map(str::trim).collect();
let (col_idx, skip_first) = match col {
Some(name) => match header.iter().position(|h| *h == name) {
Some(idx) => (idx, true),
None => (0, false),
},
None => {
let skip = header
.first()
.map(|c| c.eq_ignore_ascii_case("regime") || c.eq_ignore_ascii_case("label"))
== Some(true);
(0, skip)
}
};
let body = if skip_first { Vec::new() } else { vec![first] };
body.into_iter()
.chain(lines)
.filter_map(|line| line.split(',').nth(col_idx).map(str::trim))
.filter(|cell| !cell.is_empty())
.map(str::to_string)
.collect()
}
fn read_returns_column(text: &str, col: Option<&str>) -> Result<Vec<f64>, String> {
let mut lines = text.lines().filter(|l| !l.trim().is_empty());
let Some(first) = lines.next() else {
return Err("empty file".to_string());
};
let header: Vec<&str> = first.split(',').map(str::trim).collect();
let (col_idx, skip_first) = match col {
Some(name) => {
let idx = header
.iter()
.position(|h| *h == name)
.ok_or_else(|| format!("column `{name}` not found in header"))?;
(idx, true)
}
None => {
let skip = header.first().map(|c| c.parse::<f64>().is_err()) == Some(true);
(0, skip)
}
};
let mut out = Vec::new();
let body = if skip_first { Vec::new() } else { vec![first] };
for line in body.into_iter().chain(lines) {
let cell = line
.split(',')
.nth(col_idx)
.map(str::trim)
.unwrap_or_default();
if cell.is_empty() {
continue;
}
let v = cell
.parse::<f64>()
.map_err(|_| format!("non-numeric value `{cell}` in returns column"))?;
out.push(v);
}
Ok(out)
}
fn emit_json<T: serde::Serialize>(value: &T) {
match serde_json::to_string_pretty(value) {
Ok(j) => println!("{j}"),
Err(e) => eprintln!("error: serializing output: {e}"),
}
}
fn flag_value<'a>(args: &'a [String], flag: &str) -> Option<&'a str> {
args.iter()
.position(|a| a == flag)
.and_then(|i| args.get(i + 1))
.map(String::as_str)
}
fn resolve_key(spec: &str) -> std::io::Result<Vec<u8>> {
if let Some(var) = spec.strip_prefix("env:") {
std::env::var(var)
.map(String::into_bytes)
.map_err(|_| std::io::Error::other(format!("env var {var} is not set")))
} else if let Some(path) = spec.strip_prefix("file:") {
Ok(std::fs::read_to_string(path)?
.trim_end()
.as_bytes()
.to_vec())
} else {
Ok(spec.as_bytes().to_vec())
}
}
fn help() {
println!("sharpebench — luck-robust benchmark for AI trading agents\n");
println!("USAGE:");
println!(
" sharpebench run [--data <csv>] [--http <addr>|--cmd \"<prog>\"] run agents and rank"
);
println!(" --data: a frozen CSV (else synthetic) · --http/--cmd: add YOUR agent");
println!(" --checkpoint <path>: resumable external-agent sweep (crash-tolerant)");
println!(" --periods-per-year N: bars per year of the dataset (default 252; 1h crypto 8760, 4h 2190, 1d crypto 365, 1w 52)");
println!(
" sharpebench score <submissions.json> rank a JSON field of pre-computed submissions"
);
println!(
" sharpebench commit <agent> <window> <digest> <salt> forward-attestation pre-registration"
);
println!(" sharpebench stress run the adversarial stress suite (masked)");
println!(" sharpebench audit self-audit: prove the scorer resists gaming");
println!(" sharpebench realism [--data <csv>] prove a dataset behaves like a market (Cont's stylized facts)");
println!(
" sharpebench sign <subs.json> <key> <out.json> [--ed25519 <secret>] score + sign a board to a file"
);
println!(" --ed25519: also embed a publicly verifiable Ed25519 chain + its verifying key");
println!(" sharpebench verify <board.json> <key> verify a signed board's HMAC chain (needs the secret)");
println!(" sharpebench verify <board.json> --pubkey <hex> verify the Ed25519 chain with the public key only");
println!(" sharpebench verify <board.json> --public verify the Ed25519 chain with the key embedded in the board");
println!(
" sharpebench capture <agent> <out.json> [--data <csv>] capture an agent's raw-decision trajectory artifact"
);
println!(
" sharpebench verify-trajectory <traj.json> [--data <csv>] replay a trajectory → recompute its score from raw decisions"
);
println!(" sharpebench audit-briefing <briefing.json> audit a shared briefing for input-side salience bias");
println!(" sharpebench canary <seed> derive a do-not-train contamination tripwire token");
println!(
" sharpebench score-allocation <alloc.json> score a weight-vector trajectory (validity + turnover)"
);
println!(
" sharpebench greeks <spot> <strike> <t> <r> <vol> <call|put> Black-Scholes price + Greeks + tail-risk"
);
println!(
" sharpebench check <returns.csv> --trials N [--col NAME] [--confidence C] is this Sharpe real? (deflated/MinTRL)"
);
println!(
" sharpebench regime <a.csv> <b.csv> <regimes.csv> [--col NAME] compare two return series within each regime (labels are an input)"
);
println!(" sharpebench self-update update the binary in place (--features self-update builds)");
println!("\n<key>, <secret> and --pubkey accept a literal, or env:NAME / file:PATH to keep secrets out of process listings.");
println!("HMAC <key> holders can both verify and forge; an Ed25519 verifying key can only verify, so publish it.");
println!("\nGlobal flags:");
println!(" --json emit machine-readable JSON instead of a human table (for agents / CI)");
}
const PUBLIC_CHAIN_FIELD: &str = "public_chain";
fn run_sign(args: &[String], json: bool) -> ExitCode {
if args.len() < 5 {
eprintln!(
"usage: sharpebench sign <submissions.json> <key> <out.json> [--ed25519 <secret>] [--json]"
);
return ExitCode::from(2);
}
let data = match std::fs::read_to_string(&args[2]) {
Ok(d) => d,
Err(e) => {
eprintln!("error: cannot read {}: {e}", args[2]);
return ExitCode::FAILURE;
}
};
let subs: Vec<AgentSubmission> = match serde_json::from_str(&data) {
Ok(s) => s,
Err(e) => {
eprintln!("error: invalid submissions JSON: {e}");
return ExitCode::FAILURE;
}
};
let key = match resolve_key(&args[3]) {
Ok(k) => k,
Err(e) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
};
let pb = sharpebench_leaderboard::publish(&rank(&subs, &ScoreConfig::default()), &key);
let Some(secret_spec) = flag_value(args, "--ed25519") else {
return match sharpebench_leaderboard::save(&pb, &args[4]) {
Ok(()) => {
if json {
emit_json(&serde_json::json!({
"signed": true,
"entries": pb.chain.len(),
"path": args[4],
}));
} else {
println!("signed board ({} entries) -> {}", pb.chain.len(), args[4]);
}
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("error: {e}");
ExitCode::FAILURE
}
};
};
let signing_key = match resolve_key(secret_spec) {
Ok(s) => sharpebench_attest::SigningKey::derive(&s),
Err(e) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
};
let payloads: Vec<String> = pb.chain.iter().map(|r| r.payload.clone()).collect();
let public = sharpebench_attest::publish_public_chain(&payloads, &signing_key);
let verifying_key = public.verifying_key.clone();
let mut doc = serde_json::to_value(&pb).unwrap_or_default();
doc[PUBLIC_CHAIN_FIELD] = serde_json::to_value(&public).unwrap_or_default();
let written = serde_json::to_string_pretty(&doc)
.map_err(std::io::Error::other)
.and_then(|s| std::fs::write(&args[4], s));
match written {
Ok(()) => {
if json {
emit_json(&serde_json::json!({
"signed": true,
"entries": pb.chain.len(),
"path": args[4],
"scheme": sharpebench_attest::ED25519_SCHEME,
"verifying_key": verifying_key,
}));
} else {
println!(
"signed board ({} entries, HMAC + Ed25519) -> {}",
pb.chain.len(),
args[4]
);
println!("verifying key (publish this): {verifying_key}");
}
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("error: {e}");
ExitCode::FAILURE
}
}
}
fn run_verify_public(path: &str, pubkey_spec: Option<&str>, json: bool) -> ExitCode {
let doc: serde_json::Value = match std::fs::read_to_string(path)
.map_err(std::io::Error::other)
.and_then(|s| serde_json::from_str(&s).map_err(std::io::Error::other))
{
Ok(v) => v,
Err(e) => {
eprintln!("error: cannot load {path}: {e}");
return ExitCode::FAILURE;
}
};
let public: sharpebench_attest::PublicChain =
match serde_json::from_value(doc[PUBLIC_CHAIN_FIELD].clone()) {
Ok(p) => p,
Err(_) => {
eprintln!(
"error: {path} carries no `{PUBLIC_CHAIN_FIELD}` (sign it with --ed25519 first)"
);
return ExitCode::FAILURE;
}
};
let pinned = match pubkey_spec {
Some(spec) => match resolve_key(spec) {
Ok(bytes) => {
let hex = String::from_utf8_lossy(&bytes).trim().to_string();
match sharpebench_attest::VerifyingKey::from_hex(&hex) {
Some(vk) => Some(vk),
None => {
eprintln!("error: --pubkey is not a valid 64-hex-char Ed25519 key");
return ExitCode::FAILURE;
}
}
}
Err(e) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
},
None => None,
};
let ok = match &pinned {
Some(vk) => sharpebench_attest::verify_public_chain_with(&public, vk),
None => sharpebench_attest::verify_public_chain(&public),
};
if json {
emit_json(&serde_json::json!({
"ok": ok,
"entries": public.chain.len(),
"scheme": public.scheme,
"verifying_key": public.verifying_key,
"pinned": pinned.is_some(),
}));
} else if ok {
println!(
"OK — {} entries, Ed25519 chain valid under {} key {}",
public.chain.len(),
if pinned.is_some() {
"pinned"
} else {
"embedded"
},
public.verifying_key
);
} else {
eprintln!("FAIL — Ed25519 chain invalid (tampered, or the key does not match)");
}
if ok {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}
fn run_verify(args: &[String], json: bool) -> ExitCode {
let public_mode =
args.iter().any(|a| a == "--public") || flag_value(args, "--pubkey").is_some();
if args.len() < 4 || (!public_mode && args[3].starts_with("--")) {
eprintln!(
"usage: sharpebench verify <board.json> <key> | --pubkey <hex> | --public [--json]"
);
return ExitCode::from(2);
}
if public_mode {
return run_verify_public(&args[2], flag_value(args, "--pubkey"), json);
}
let pb = match sharpebench_leaderboard::load(&args[2]) {
Ok(b) => b,
Err(e) => {
eprintln!("error: cannot load {}: {e}", args[2]);
return ExitCode::FAILURE;
}
};
let key = match resolve_key(&args[3]) {
Ok(k) => k,
Err(e) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
};
let ok = sharpebench_leaderboard::verify_board(&pb.chain, &key);
if json {
emit_json(&serde_json::json!({ "ok": ok, "entries": pb.chain.len() }));
} else if ok {
println!("OK — {} entries, signature chain valid", pb.chain.len());
} else {
eprintln!("FAIL — signature chain invalid (tampered or wrong key)");
}
if ok {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}
fn run_audit(json: bool) -> ExitCode {
let report = sharpebench_core::run_self_audit();
if json {
emit_json(&report);
} else {
println!("SharpeBench — benchmark self-audit (does the scorer resist gaming?)\n");
for c in &report.cases {
println!(
"[{}] {:<26} {}",
if c.defended { "DEFENDED" } else { " GAMED " },
c.name,
c.detail
);
}
if report.all_defended {
println!(
"\nAll {} attacks demoted. The benchmark holds.",
report.cases.len()
);
} else {
eprintln!("\nFAIL — an attack was not demoted; a gate has regressed.");
}
}
if report.all_defended {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}
fn run_realism(args: &[String], json: bool) -> ExitCode {
use sharpebench_sim::Dataset;
let (data, src) = match flag_value(args, "--data") {
Some(path) => match Dataset::from_csv_file(path) {
Ok(d) => (d, path.to_string()),
Err(e) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
},
None => (
Dataset::synthetic(8, 180, 20_260_621),
"synthetic".to_string(),
),
};
let mut returns: Vec<f64> = Vec::new();
for series in data.closes.values() {
for w in series.windows(2) {
if w[0] != 0.0 {
returns.push(w[1] / w[0] - 1.0);
}
}
}
if returns.len() < 40 {
eprintln!(
"error: not enough returns to assess realism ({} < 40)",
returns.len()
);
return ExitCode::FAILURE;
}
let v = sharpebench_core::validate_dataset(&returns);
let r = &v.report;
if json {
emit_json(&serde_json::json!({
"source": src,
"n_returns": returns.len(),
"realistic": v.realistic,
"failures": v.failures.iter().map(|f| format!("{f:?}")).collect::<Vec<_>>(),
"report": {
"excess_kurtosis": r.excess_kurtosis,
"abs_return_autocorr": r.abs_return_autocorr,
"vol_clustering_acf": r.vol_clustering_acf,
"gain_loss_skew": r.gain_loss_skew,
"aggregational_gaussianity": r.aggregational_gaussianity,
"zumbach_asymmetry": r.zumbach_asymmetry,
},
}));
} else {
println!(
"SharpeBench — dataset realism proof ({src}, {} returns)\n",
returns.len()
);
println!(
" excess kurtosis (fat tails) : {:+.3}",
r.excess_kurtosis
);
println!(
" |return| autocorr (clustering) : {:+.3}",
r.abs_return_autocorr
);
println!(
" squared-return ACF : {:+.3}",
r.vol_clustering_acf
);
println!(
" skew (gain/loss asymmetry) : {:+.3}",
r.gain_loss_skew
);
println!(
" kurtosis drop under aggregation : {:+.3}",
r.aggregational_gaussianity
);
println!(
" Zumbach time-reversal asymmetry : {:+.4}",
r.zumbach_asymmetry
);
if v.realistic {
println!("\nREALISTIC — the dataset exhibits every gated stylized fact.");
} else {
println!("\nUNREALISTIC — missing: {:?}", v.failures);
}
}
if v.realistic {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}
fn run_stress(json: bool) -> ExitCode {
use sharpebench_sim::{Agent, BuyAndHold, CostModel, Dataset, Momentum, Window};
let seeds: Vec<u64> = (0..6).collect();
let costs = CostModel::default();
if !json {
println!("SharpeBench — adversarial stress suite (contamination-masked, costs on)\n");
}
let mut scenarios: Vec<serde_json::Value> = Vec::new();
for (name, data) in Dataset::stress_suite(20_260_621) {
let masked = data.masked();
let windows = [Window {
start: 20,
end: masked.len(),
}];
let bh = sharpebench_harness::run_agent(
"buy-and-hold",
&masked,
&windows,
&seeds,
costs,
|| Box::new(BuyAndHold) as Box<dyn Agent>,
);
let mo =
sharpebench_harness::run_agent("momentum", &masked, &windows, &seeds, costs, || {
Box::new(Momentum::default()) as Box<dyn Agent>
});
let board = rank(&[bh, mo], &ScoreConfig::default());
if json {
scenarios.push(serde_json::json!({ "scenario": name, "board": board }));
} else {
println!("# scenario: {name}");
print_board(&board);
println!();
}
}
if json {
emit_json(&scenarios);
}
ExitCode::SUCCESS
}
fn run_commit(args: &[String]) -> ExitCode {
if args.len() < 6 {
eprintln!("usage: sharpebench commit <agent_id> <target_window> <artifact_digest> <salt>");
return ExitCode::from(2);
}
let c = sharpebench_attest::make_commitment(&args[2], &args[3], &args[4], &args[5]);
match serde_json::to_string_pretty(&c) {
Ok(j) => {
println!("{j}");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("error: {e}");
ExitCode::FAILURE
}
}
}
fn report_transport_failures(label: &str, failures: &sharpebench_harness::FailureLog, json: bool) {
if failures.is_empty() {
return;
}
if !json {
eprintln!(
"note: {} transport failure(s) surfaced for {label} ({} runtime, {} agent-fault); \
affected runs were not scored as holds",
failures.records.len(),
failures.runtime_failures(),
failures.agent_faults(),
);
}
}
fn run_demo(args: &[String], json: bool) -> ExitCode {
use sharpebench_sim::{
Agent, BuyAndHold, CostModel, Dataset, ExternalAgent, HttpAgent, Momentum, Window,
};
let (data, windows) = match flag_value(args, "--data") {
Some(path) => match Dataset::from_csv_file(path) {
Ok(d) => {
let n = d.len();
if n < 40 {
eprintln!("error: dataset too short ({n} rows); need at least 40");
return ExitCode::FAILURE;
}
let warm = (n / 10).clamp(10, 30);
let mid = (warm + n) / 2;
let w = vec![
Window {
start: warm,
end: mid,
},
Window { start: mid, end: n },
];
(d, w)
}
Err(e) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
},
None => (
Dataset::synthetic(8, 180, 20_260_621),
vec![
Window {
start: 20,
end: 100,
},
Window {
start: 100,
end: 180,
},
],
),
};
let periods_per_year = match flag_value(args, "--periods-per-year") {
Some(raw) => match raw.parse::<f64>() {
Ok(p) if p.is_finite() && p > 0.0 => p,
_ => {
eprintln!("error: --periods-per-year must be a positive number, got `{raw}`");
return ExitCode::from(2);
}
},
None => ScoreConfig::default().periods_per_year,
};
let cfg = ScoreConfig::for_periods_per_year(periods_per_year);
let seeds: Vec<u64> = (0..8).collect();
let costs = CostModel::default();
let bh = sharpebench_harness::run_agent("buy-and-hold", &data, &windows, &seeds, costs, || {
Box::new(BuyAndHold) as Box<dyn Agent>
});
let mo = sharpebench_harness::run_agent("momentum", &data, &windows, &seeds, costs, || {
Box::new(Momentum::default()) as Box<dyn Agent>
});
let mut field = vec![bh, mo];
field.extend(sharpebench_harness::luck_floor(
&data, &windows, &seeds, costs, 3,
));
const EXTERNAL_MAX_RETRIES: u32 = 2;
let checkpoint = flag_value(args, "--checkpoint").map(std::path::PathBuf::from);
if let Some(addr) = flag_value(args, "--http") {
let addr = addr.to_string();
let label = format!("http:{addr}");
let res = if let Some(ckpt) = &checkpoint {
match sharpebench_harness::run_resumable_sweep(
ckpt,
&label,
&windows,
&seeds,
EXTERNAL_MAX_RETRIES,
|wi, seed| {
let mut agent = HttpAgent::new(addr.clone());
sharpebench_harness::run_external_backtest(
&data,
&mut agent,
windows[wi],
seed,
costs,
)
},
) {
Ok(r) => r,
Err(e) => {
eprintln!("error: checkpoint sweep failed: {e}");
return ExitCode::FAILURE;
}
}
} else {
sharpebench_harness::run_external_agent(
&label,
&data,
&windows,
&seeds,
costs,
EXTERNAL_MAX_RETRIES,
|| Some(HttpAgent::new(addr.clone())),
)
};
report_transport_failures(&label, &res.failures, json);
field.insert(0, res.submission);
} else if let Some(cmd) = flag_value(args, "--cmd") {
let parts: Vec<String> = cmd.split_whitespace().map(String::from).collect();
let Some((prog, rest)) = parts.split_first() else {
eprintln!("error: --cmd needs a program to run");
return ExitCode::from(2);
};
let prog = prog.clone();
let rest = rest.to_vec();
let rest_refs: Vec<&str> = rest.iter().map(String::as_str).collect();
if ExternalAgent::spawn(&prog, &rest_refs).is_err() {
eprintln!("error: cannot spawn agent `{cmd}`");
return ExitCode::FAILURE;
}
let label = format!("cmd:{prog}");
let res = if let Some(ckpt) = &checkpoint {
match sharpebench_harness::run_resumable_sweep(
ckpt,
&label,
&windows,
&seeds,
EXTERNAL_MAX_RETRIES,
|wi, seed| {
let rest_refs: Vec<&str> = rest.iter().map(String::as_str).collect();
match ExternalAgent::spawn(&prog, &rest_refs) {
Ok(mut a) => sharpebench_harness::run_external_backtest(
&data,
&mut a,
windows[wi],
seed,
costs,
),
Err(_) => Err(sharpebench_harness::FailureKind::SpawnError),
}
},
) {
Ok(r) => r,
Err(e) => {
eprintln!("error: checkpoint sweep failed: {e}");
return ExitCode::FAILURE;
}
}
} else {
sharpebench_harness::run_external_agent(
&label,
&data,
&windows,
&seeds,
costs,
EXTERNAL_MAX_RETRIES,
|| {
let rest_refs: Vec<&str> = rest.iter().map(String::as_str).collect();
ExternalAgent::spawn(&prog, &rest_refs).ok()
},
)
};
report_transport_failures(&label, &res.failures, json);
field.insert(0, res.submission);
}
if !json {
let src = flag_value(args, "--data").unwrap_or("synthetic");
println!(
"SharpeBench — run on {src} ({} symbols, {} windows × {} seeds, {periods_per_year} periods/year, costs on; incl. luck floor)\n",
data.symbols().len(),
windows.len(),
seeds.len()
);
}
emit_board(&rank(&field, &cfg), json);
ExitCode::SUCCESS
}
fn resolve_dataset(
args: &[String],
) -> Result<(sharpebench_sim::Dataset, Vec<sharpebench_sim::Window>), String> {
use sharpebench_sim::{Dataset, Window};
match flag_value(args, "--data") {
Some(path) => {
let d = Dataset::from_csv_file(path)?;
let n = d.len();
if n < 40 {
return Err(format!("dataset too short ({n} rows); need at least 40"));
}
let warm = (n / 10).clamp(10, 30);
let mid = (warm + n) / 2;
let w = vec![
Window {
start: warm,
end: mid,
},
Window { start: mid, end: n },
];
Ok((d, w))
}
None => Ok((
Dataset::synthetic(8, 180, 20_260_621),
vec![
Window {
start: 20,
end: 100,
},
Window {
start: 100,
end: 180,
},
],
)),
}
}
fn run_capture(args: &[String], json: bool) -> ExitCode {
use sharpebench_sim::{Agent, BuyAndHold, CostModel, Momentum};
if args.len() < 4 {
eprintln!(
"usage: sharpebench capture <buy-and-hold|momentum> <out.json> [--data <csv>] [--json]"
);
return ExitCode::from(2);
}
let agent_id = args[2].as_str();
let out = &args[3];
let (data, windows) = match resolve_dataset(args) {
Ok(dw) => dw,
Err(e) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
};
let seeds: Vec<u64> = (0..8).collect();
let costs = CostModel::default();
let make: Box<dyn Fn() -> Box<dyn Agent>> = match agent_id {
"buy-and-hold" => Box::new(|| Box::new(BuyAndHold) as Box<dyn Agent>),
"momentum" => Box::new(|| Box::new(Momentum::default()) as Box<dyn Agent>),
other => {
eprintln!("error: unknown agent `{other}` (use buy-and-hold or momentum)");
return ExitCode::from(2);
}
};
let (_sub, traj) =
sharpebench_harness::run_agent_capture(agent_id, &data, &windows, &seeds, costs, || make());
let payload = match serde_json::to_string_pretty(&traj) {
Ok(p) => p,
Err(e) => {
eprintln!("error: serializing trajectory: {e}");
return ExitCode::FAILURE;
}
};
if let Err(e) = std::fs::write(out, payload) {
eprintln!("error: cannot write {out}: {e}");
return ExitCode::FAILURE;
}
if json {
emit_json(&serde_json::json!({
"captured": true,
"agent_id": agent_id,
"runs": traj.runs.len(),
"path": out,
}));
} else {
println!(
"captured trajectory for `{agent_id}` ({} runs) -> {out}",
traj.runs.len()
);
}
ExitCode::SUCCESS
}
fn run_verify_trajectory(args: &[String], json: bool) -> ExitCode {
use sharpebench_protocol::AgentTrajectory;
use sharpebench_sim::CostModel;
if args.len() < 3 {
eprintln!("usage: sharpebench verify-trajectory <trajectory.json> [--data <csv>] [--json]");
return ExitCode::from(2);
}
let text = match std::fs::read_to_string(&args[2]) {
Ok(t) => t,
Err(e) => {
eprintln!("error: cannot read {}: {e}", args[2]);
return ExitCode::FAILURE;
}
};
let traj: AgentTrajectory = match serde_json::from_str(&text) {
Ok(t) => t,
Err(e) => {
eprintln!("error: invalid trajectory JSON: {e}");
return ExitCode::FAILURE;
}
};
let (data, _windows) = match resolve_dataset(args) {
Ok(dw) => dw,
Err(e) => {
eprintln!("error: {e}");
return ExitCode::FAILURE;
}
};
let result = sharpebench_harness::verify_trajectory(
&data,
&traj,
CostModel::default(),
&ScoreConfig::default(),
);
if json {
emit_json(&result);
} else {
println!(
"verified `{}` by replay — {} decisions across {} runs",
result.agent_id, result.decisions_replayed, result.runs_replayed
);
println!(" deflated Sharpe : {:.4}", result.score.deflated_sharpe);
println!(" raw mean return : {:.5}", result.score.raw_mean_return);
println!(" rank-eligible : {}", yn(result.score.rank_eligible));
println!("\n{}", result.verification_explanation);
}
ExitCode::SUCCESS
}
fn run_score(path: &str, json: bool) -> ExitCode {
let data = match std::fs::read_to_string(path) {
Ok(d) => d,
Err(e) => {
eprintln!("error: cannot read {path}: {e}");
return ExitCode::FAILURE;
}
};
let subs: Vec<AgentSubmission> = match serde_json::from_str(&data) {
Ok(s) => s,
Err(e) => {
eprintln!("error: invalid submissions JSON: {e}");
return ExitCode::FAILURE;
}
};
emit_board(&rank(&subs, &ScoreConfig::default()), json);
ExitCode::SUCCESS
}
fn emit_board(board: &[CompositeScore], json: bool) {
if json {
emit_json(&board);
} else {
print_board(board);
}
}
fn print_board(board: &[CompositeScore]) {
println!(
"{:<4} {:<18} {:>9} {:>8} {:>7} {:>6} {:>9} {:>10}",
"#", "agent", "DSR", "PSR", "pass^k", "proc", "boot_p", "raw_ret"
);
println!("{}", "-".repeat(80));
for (i, s) in board.iter().enumerate() {
let pos = if s.rank_eligible {
format!("{}", i + 1)
} else {
"—".to_string()
};
println!(
"{:<4} {:<18} {:>9.4} {:>8.4} {:>7} {:>6} {:>9.4} {:>10.5}",
pos,
truncate(&s.agent_id, 18),
s.deflated_sharpe,
s.psr,
yn(s.passed_k),
yn(s.process_ok),
s.bootstrap_p,
s.raw_mean_return,
);
}
println!(
"\n{} eligible of {} submitted. Rank key = deflated Sharpe; raw return never ranks.",
board.iter().filter(|s| s.rank_eligible).count(),
board.len()
);
}
fn yn(b: bool) -> &'static str {
if b {
"yes"
} else {
"NO"
}
}
fn truncate(s: &str, n: usize) -> String {
if s.len() <= n {
s.to_string()
} else {
format!("{}…", &s[..n - 1])
}
}