use chrono::{DateTime, Duration, TimeZone, Utc};
use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
use zero_operator_state::classifier::Classifier;
use zero_operator_state::events::{Event, EventKind, Outcome, Source};
use zero_operator_state::friction::RiskContext;
fn anchor() -> DateTime<Utc> {
Utc.with_ymd_and_hms(2026, 4, 21, 18, 0, 0).unwrap()
}
fn synth_events(n: usize, anchor: DateTime<Utc>) -> Vec<Event> {
let mut out = Vec::with_capacity(n);
for i in 0..n {
let ts =
anchor - Duration::milliseconds(i64::try_from(i).expect("bench load fits i64") * 1_000);
let symbol = match i % 4 {
0 => "BTC",
1 => "ETH",
2 => "SOL",
_ => "AVAX",
}
.to_string();
let kind = match i % 20 {
0..=11 => EventKind::DecisionMade {
symbol,
source: Source::Manual,
},
12 => EventKind::TradeClosed {
symbol,
outcome: Outcome::Win,
pnl_r: 1.2,
conviction: Some(7),
},
13 => EventKind::TradeClosed {
symbol,
outcome: Outcome::Loss,
pnl_r: -0.8,
conviction: Some(5),
},
14 => EventKind::TradeClosed {
symbol,
outcome: Outcome::Scratch,
pnl_r: 0.0,
conviction: Some(6),
},
15 => EventKind::VerdictShown,
16 => EventKind::VerdictOverridden,
17 => EventKind::Idle { since_ms: 30_000 },
18 => EventKind::Resumed,
_ => EventKind::BreakStarted {
planned_ms: Some(5 * 60 * 1_000),
},
};
out.push(Event::new(ts, kind));
}
out.reverse();
out
}
fn build_classifier(n: usize) -> Classifier {
let now = anchor();
let mut c = Classifier::new();
c.push(Event::new(
now - Duration::hours(6),
EventKind::SessionStarted,
));
for ev in synth_events(n, now) {
c.push(ev);
}
c
}
fn bench_classifier_tick(c: &mut Criterion) {
let mut group = c.benchmark_group("classifier/tick");
group.sample_size(50);
let now = anchor();
for &n in &[32_usize, 512, 4096] {
let classifier = build_classifier(n);
group.bench_with_input(BenchmarkId::from_parameter(n), &classifier, |b, cls| {
b.iter(|| {
let snap = cls.classify(black_box(now));
black_box(snap);
});
});
}
group.finish();
}
fn bench_classifier_tick_with_risk(c: &mut Criterion) {
let mut group = c.benchmark_group("classifier/tick_with_risk");
group.sample_size(50);
let now = anchor();
let classifier = build_classifier(1024);
let risk = RiskContext {
guardrail_proximity_pct: Some(0.5),
halted: false,
};
group.bench_function(BenchmarkId::from_parameter(1024_usize), |b| {
b.iter(|| {
let snap = classifier.classify_with_risk(black_box(now), black_box(risk));
black_box(snap);
});
});
group.finish();
}
criterion_group!(
benches,
bench_classifier_tick,
bench_classifier_tick_with_risk
);
criterion_main!(benches);