Repository navigation
fix(backtest): report walk-forward OOS over the test window only (BUG-21) - #20
Merged
Merged
Conversation
…-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>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 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. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
P0. Third of the four research-integrity defects.
What was wrong
The engine is handed
train_start - 252 .. test_endso its first rebalance has a warm-up — that part is right. Butresult["daily_returns"]covers that whole range and was appended as the fold's OOS in full:Measured on 900 dates × 30 assets (train 252, test 63):
oos_daysSo 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"]waslen(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_windowtest_concatenated_oos_is_not_the_whole_sampleThe 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 markedaffectedrather than deleted.🤖 Generated with Claude Code