Files
foxhunt/scripts/validation/check_all_tiers.py
jgrusewski 0d373da490 plan5(task4): tiered-exit validation script suite (tier1/2/3 checks)
Creates scripts/validation/ with per-tier exit checks consuming the
aggregate JSON from scripts/aggregate-multi-seed-metrics.py (P5T1B):

  check_tier1.py — convergence (std/mean ≤ 0.15 on val_sharpe /
    avg_q_value / train_loss; avg_q_value max ≤ 500 fold-1 explosion
    guard; placeholders for Q-saturation + hot-path-DtoH per spec).
  check_tier2.py — behavioural (val_trades_per_bar ≥ 0.005,
    val_active_frac > 0.2, dir argmax entropy > 0.8·log4 with
    val_dir_entropy primary + val_dir_dist_* fallback).
  check_tier3.py — profitability (val_sharpe_annualised > 1.0 with
    val_sharpe per-bar fallback, val_win_rate ≥ 0.52 gated on
    >500 trades, val_profit_factor mean ≥ 1.1 AND cross-seed std < 0.3).
  check_all_tiers.py — subprocess wrapper, exits 0 only if all pass.

Stdlib-only (statistics / argparse / json / subprocess) — no new deps.
Defensive missing-metric handling: each check FAILs with an explanatory
message when its required aggregate key is absent rather than silently
passing, so missing HEALTH_DIAG metrics are surfaced loudly.

Test harness scripts/validation/tests/test_tier_checks.sh exercises
good + bad fixtures across all four scripts and against the wrapper.

Audit row added to docs/dqn-wire-up-audit.md documenting the suite +
the deferred metrics list (val_trades_per_bar, val_active_frac,
val_dir_entropy/_dist_*, val_sharpe_annualised, val_win_rate,
val_profit_factor, val_trade_count) that HEALTH_DIAG must emit before
tiers 2/3 can ever PASS on real data — tracked for Plan 5 Task 5
pre-flight wire-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 12:33:28 +02:00

73 lines
1.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Top-level wrapper: run check_tier{1,2,3}.py in sequence (Plan 5 Task 4).
Subprocess-each strategy chosen over in-process import so that:
- per-tier checks remain runnable standalone (single source of truth);
- per-tier exit codes propagate naturally;
- tier scripts can evolve independently without touching this wrapper.
Exits 0 only if all three tier checks PASS. Each tier's own PASS/FAIL
lines stream through to stdout so the top-level summary is auditable.
"""
from __future__ import annotations
import argparse
import os
import subprocess
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
TIER_SCRIPTS = (
"check_tier1.py",
"check_tier2.py",
"check_tier3.py",
)
def main():
ap = argparse.ArgumentParser(
description="Run all three tiered exit checks (Plan 5 Task 4).",
)
ap.add_argument(
"metrics_json",
help="Aggregated metrics JSON (output of aggregate-multi-seed-metrics.py).",
)
ap.add_argument(
"--warmup-end",
type=int,
default=15,
help="Epoch at which warmup ends; forwarded to each tier check.",
)
args = ap.parse_args()
overall_ok = True
for script in TIER_SCRIPTS:
path = os.path.join(HERE, script)
print(f"=== {script} ===")
result = subprocess.run(
[
sys.executable,
path,
args.metrics_json,
"--warmup-end",
str(args.warmup_end),
],
check=False,
)
if result.returncode != 0:
overall_ok = False
print(f"--- {script}: FAIL (exit {result.returncode}) ---")
else:
print(f"--- {script}: PASS ---")
if overall_ok:
print("ALL TIERS PASS")
sys.exit(0)
print("ONE OR MORE TIERS FAILED")
sys.exit(1)
if __name__ == "__main__":
main()