Skip to content

fix(backtest): report walk-forward OOS over the test window only (BUG-21) - #20

Merged
sijie-Z merged 2 commits into
mainfrom
fix/walkforward-oos-window
Sep 13, 2026
Merged

sijie-Z merged 2 commits into
mainfrom
fix/walkforward-oos-window

Conversation

@sijie-Z

@sijie-Z sijie-Z commented Sep 13, 2026

Copy link
Copy Markdown
Owner

P0. Third of the four research-integrity defects.

What was wrong

The engine is handed train_start - 252 .. test_end so its first rebalance has a warm-up — that part is right. But result["daily_returns"] covers that whole range and was appended as the fold's OOS in full:

oos_ret = result["daily_returns"]
oos_returns_list.append(oos_ret)
m = all_metrics(oos_ret, oos_bench)

Measured on 900 dates × 30 assets (train 252, test 63):

fold 0 oos_days
before 315
after 63

So a 63-day test window was reported over 315 days, 252 of which were in-sample. Every fold's metrics, the concatenated OOS series, the aggregate metrics and the stability analysis were all computed over train + test.

This is a reporting-window defect, not signal leakage

The fold signal is recomputed from train-only ICs, and that part is correct — the audit agent and I verified it independently.

Recording this as a violation of NO_LOOKAHEAD_CONTRACT 第四条 would have been wrong: that clause governs signal construction, and signal construction was never the problem. The distinction matters, because calling it "walk-forward has lookahead" would both overstate it and hide where the real defect is.

Also fixed, one line away

fold_details["oos_days"] was len(fold_signal) — the signal length, i.e. train + test — rather than the length of the OOS returns actually being reported. Same confusion, different field.

Tests — both checked against the old implementation and both failing there

  • test_each_fold_reports_only_its_test_window
  • test_concatenated_oos_is_not_the_whole_sample

The second one exists because a per-fold check alone would not catch a regression that kept the per-fold slice but concatenated the unsliced series.

1320 passed, 1 skipped.

Impact

Every walk-forward report produced so far — main.py walkforward, the Strategy Gates input, and the front-end WalkForward panel — reported OOS metrics that were mostly in-sample. Those results need re-running and should be marked affected rather than deleted.

🤖 Generated with Claude Code

Builder and others added 2 commits September 13, 2026 12:24
…-21)

The engine is handed `train_start - 252 .. test_end` so its first rebalance has
a warm-up, and `result["daily_returns"]` covers that whole range. It was
appended as the fold's OOS in full:

    oos_ret = result["daily_returns"]
    oos_returns_list.append(oos_ret)
    m = all_metrics(oos_ret, oos_bench)

So a fold with a 63-day test window reported 315 days of "OOS" -- 252 of which
were in-sample. Every fold's metrics, the concatenated OOS series, the
aggregate metrics and the stability analysis were all computed over train+test.

Measured on 900 dates x 30 assets (train 252, test 63):

    before   fold 0 oos_days = 315
    after    fold 0 oos_days =  63   (and 63 for every full fold; the last
                                      partial window reports 18, correctly)

**This is a reporting-window defect, not signal leakage.** The fold signal is
recomputed from train-only ICs and that part is correct -- both the audit agent
and I verified it independently. Recording it as a contract violation of 第四条
would have overstated it and hidden where the real defect is. The docstring of
NO_LOOKAHEAD_CONTRACT 第四条 is about signal construction, and signal
construction was never the problem.

Also fixes the same confusion one line away: `fold_details["oos_days"]` was
`len(fold_signal)` -- the signal length, i.e. train+test -- rather than the
length of the OOS returns actually being reported.

Two regression tests added, both checked against the old implementation and
both failing there:
  - test_each_fold_reports_only_its_test_window
  - test_concatenated_oos_is_not_the_whole_sample

1320 passed, 1 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: ac41c6c2-9d79-457d-87dc-7c6c00447f61


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sijie-Z
sijie-Z merged commit 837425b into main Sep 13, 2026
9 checks passed
@sijie-Z
sijie-Z deleted the fix/walkforward-oos-window branch September 13, 2026 04:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant