fix(sp21): Return v3.1 — drop short-rollout guard for volume bars (atomic)
Single-file fix to `compute_epoch_financials`: remove the `if n_returns_f >= bars_per_year` short-rollout fallback that left v2 semantics in place for sub-year rollouts. For Foxhunt's volume bars, `bars_per_day ≈ 34_496` → `bars_per_year ≈ 8.69M`, while a training epoch produces `n_returns ≈ 4.10M`. The guard fired on every production epoch, so the v3 CAGR fix was a no-op. Diagnosis chain: - v3 commit (2937da889) merged the n_returns >= bars_per_year guard - Smoke v4 (commit62b5a50e8, workflow train-frv8x) epoch 1 showed Return=+2.963e2% — bit-identical to v1's pre-fix output - Hypothesis 1 (cache poisoning): ruled out — ensure-binary log shows "Cache MISS: compiling binaries for 62b5a50e8" and ml crate was recompiled fresh - Hypothesis 2 (different commit): ruled out — workflow params confirm commit-sha =62b5a50e8= current HEAD - Hypothesis 3 (bars_per_year mismatch): confirmed — v4 log emits "Bars per day (from data): 34496" which makes bars_per_year > n_returns and triggers the v2 fallback inside the v3 branch Fix: unconditional CAGR. The log-space clamp [-23, +20] bounds the display in all edge cases (tests with tiny n_returns extrapolate aggressively; the clamp caps at exp(20) - 1 ≈ +4.85e8%). Expected v5 epoch 1 Return: ~+1.770e3% (was +2.963e2% under v4). The new value is the *actual annualized* projection: 1377% over a 0.47-year rollout. Overfit cycles cap at +4.85e8% (was +e19%). Tests: - cargo test -p ml --lib financials → 7/7 - Sign-only assertions in test cases (all > 0.0) — no regressions Files changed: - crates/ml/src/trainers/dqn/financials.rs: 1 conditional removed, comment block updated with v3 → v3.1 history - docs/dqn-wire-up-audit.md: diagnosis + fix entry for 2026-05-11 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -80,13 +80,15 @@ pub(crate) fn compute_epoch_financials(
|
||||
// bars): annualized = exp(45.93 × 19_656 / 4_096_000) − 1 ≈ 24.7% —
|
||||
// a reasonable, interpretable number.
|
||||
//
|
||||
// History (do not regress to either earlier form):
|
||||
// History (do not regress to any earlier form):
|
||||
// v1 (pre-2026-04): mean_per_bar × bars_per_year (arithmetic
|
||||
// annualization) — produced -51,234% on negative per-bar means.
|
||||
// v2 (2026-04..2026-05-11): log_growth.exp() − 1 (total compounded,
|
||||
// no annualization) — produced +e19% on long rollouts.
|
||||
// v3 (this commit, 2026-05-11): annualized compounded — bounded by
|
||||
// construction and operationally meaningful.
|
||||
// v3 (2026-05-11): annualized compounded for n_returns >= bars_per_year,
|
||||
// fallback to v2 below it — but the fallback subsumed all production
|
||||
// runs on volume bars (bars_per_year ≈ 8.7M > n_returns ≈ 4.1M).
|
||||
// v3.1 (2026-05-11): unconditional CAGR — the clamp handles edge cases.
|
||||
//
|
||||
// Lower bound: -100% (log_growth.exp() − 1 ≥ -1 when log_growth ≥
|
||||
// log(1e-10) ≈ -23 per the per-bar clamp). Upper bound: practically
|
||||
@@ -112,20 +114,25 @@ pub(crate) fn compute_epoch_financials(
|
||||
// annualization factor exceeds 1, which would extrapolate a
|
||||
// brief positive run to absurd annualized magnitudes (a 5-bar
|
||||
// test with 1.5% gain would annualize to e8% on 1-min bars).
|
||||
// For sub-year rollouts, report TOTAL compounded growth
|
||||
// instead — same `log_growth.exp() − 1` as the v2 formula,
|
||||
// bounded by the per-bar floor to ±100% per the inflation
|
||||
// audit's WR=46% honest meter discipline.
|
||||
// **v3.1 (2026-05-11)**: dropped the `n_returns >= bars_per_year`
|
||||
// short-rollout guard. The guard fell back to raw `log_growth`
|
||||
// (= v2 formula) whenever the rollout was shorter than a trading
|
||||
// year. For Foxhunt's volume bars the data extracts
|
||||
// `bars_per_day ≈ 34_496` → `bars_per_year ≈ 8.7M`, but a single
|
||||
// training epoch produces `n_returns ≈ 4.1M` step returns. Result:
|
||||
// every production epoch took the v2 branch and the v3 fix was a
|
||||
// no-op — smoke v4 epoch 1 (commit 62b5a50e8) reproduced v1's
|
||||
// `Return=+2.963e2%` exactly.
|
||||
//
|
||||
// Unconditional CAGR is the correct invariant: "what annualized
|
||||
// return is this policy producing?" works for any rollout length.
|
||||
// Tests (very small n_returns) get extrapolated (a 5-bar +1.5%
|
||||
// test extrapolates to e8% nominally) — the log-space clamp
|
||||
// `[-23, +20]` bounds the display magnitude, and the test cases
|
||||
// below assert ordering / sign, not exact magnitudes.
|
||||
let n_returns_f = n_returns as f64;
|
||||
if log_growth.is_finite() && n_returns_f > 0.0 && bars_per_year > 0.0 {
|
||||
let log_scaled = if n_returns_f >= bars_per_year {
|
||||
// Production rollout (typically ~4M bars vs ~20K bars/year)
|
||||
// — annualize for CAGR semantics.
|
||||
log_growth * bars_per_year / n_returns_f
|
||||
} else {
|
||||
// Short rollout (tests, warmup) — report total compounded.
|
||||
log_growth
|
||||
};
|
||||
let log_scaled = log_growth * bars_per_year / n_returns_f;
|
||||
// Sanity bounds: lower at -23 (≈ -100% by construction
|
||||
// from per-bar `(1+r).max(1e-10)` floor), upper at +20
|
||||
// (≈ exp(20) − 1 ≈ +4.85e8% — beyond this magnitude the
|
||||
|
||||
Reference in New Issue
Block a user