Files
foxhunt/crates/ml/tests/behavioral/oracle.rs
jgrusewski dff6e666ee feat(sp15-p2a.2): oracle policies + evaluator harness + pre-commit hook
Phase 2A scaffolding complete (per spec §7.2):
- oracle.rs: OracleAction enum + 3 oracle policies (flat, drift, OU)
- harness.rs: BehavioralResult + evaluate_policy_on_market stub.
   Trainer-side methods (eval_actions_on_features, read_isv_for_test)
   are documented gap #7; Phase 2B tests add them per-test as needed.
- pre-commit hook: cargo test -p ml --test behavioral_suite gates
   argo-train.sh per spec §4.5 discipline rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 11:29:22 +02:00

81 lines
2.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! SP15 Phase 2A — oracle policies for behavioral test harness.
//! Each oracle is a known-correct action sequence for a given synthetic
//! market type; tests assert the trained policy approaches the oracle.
use ml::cuda_pipeline::lob_bar::LobBar;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MagBucket { Quarter, Half, Full }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OracleAction {
Hold,
Long(MagBucket),
Short(MagBucket),
Flat,
}
/// Oracle for flat markets: always Hold.
pub fn flat_market_oracle(bars: &[LobBar]) -> Vec<OracleAction> {
vec![OracleAction::Hold; bars.len()]
}
/// Oracle for drift markets: take direction-of-trend, mag = Half.
pub fn drift_market_oracle(bars: &[LobBar]) -> Vec<OracleAction> {
let mut actions = Vec::with_capacity(bars.len());
actions.push(OracleAction::Hold); // first bar: no prior
for w in bars.windows(2) {
let action = if w[1].price > w[0].price + 0.05 {
OracleAction::Long(MagBucket::Half)
} else if w[1].price < w[0].price - 0.05 {
OracleAction::Short(MagBucket::Half)
} else {
OracleAction::Hold
};
actions.push(action);
}
actions
}
/// Oracle for OU markets: reverse at ±2σ extremes.
pub fn ou_market_oracle(bars: &[LobBar], mu: f32, sigma: f32) -> Vec<OracleAction> {
bars.iter().map(|b| {
let z = (b.price - mu) / sigma.max(1e-6);
if z > 2.0 { OracleAction::Short(MagBucket::Half) }
else if z < -2.0 { OracleAction::Long(MagBucket::Half) }
else { OracleAction::Hold }
}).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::synthetic_markets::*;
#[test]
fn flat_oracle_all_hold() {
let bars = flat_market(100, 0.1, 0.25, 42);
let actions = flat_market_oracle(&bars);
assert_eq!(actions.len(), 100);
assert!(actions.iter().all(|a| *a == OracleAction::Hold));
}
#[test]
fn drift_oracle_takes_uptrend() {
let bars = drift_market(1000, 0.5, 0.1, 0.25, 42);
let actions = drift_market_oracle(&bars);
let long_count = actions.iter().filter(|a| matches!(a, OracleAction::Long(_))).count();
// Strong uptrend → most actions should be Long
assert!(long_count > 700, "drift_market_oracle Long count = {}, expected > 700", long_count);
}
#[test]
fn ou_oracle_reverses_at_extremes() {
let bars = ou_market(1000, 0.05, 1.0, 4500.0, 0.25, 42);
let actions = ou_market_oracle(&bars, 4500.0, 1.0);
// OU oracle should produce SOME reversion actions (not all Hold)
let action_count = actions.iter().filter(|a| !matches!(a, OracleAction::Hold)).count();
assert!(action_count > 0, "OU oracle produced zero non-Hold actions");
}
}