Skip to content

Fix backtest rigor and safety gaps (lookback guard, metrics inf, breakout look-ahead) - #18

Merged
OnePunchMonk merged 1 commit into
mainfrom
fix/backtest-rigor-and-guards
Aug 22, 2026
Merged

Fix backtest rigor and safety gaps (lookback guard, metrics inf, breakout look-ahead)#18
OnePunchMonk merged 1 commit into
mainfrom
fix/backtest-rigor-and-guards

Conversation

@OnePunchMonk

Copy link
Copy Markdown
Owner

Summary

Fixes found during a code review of the backtest engine, feature guards, and agent loop:

  • lookback_guard was advisory-only, despite the README's anti-look-ahead-bias claim. enforce_lookback only logged a warning and was applied nowhere; WarmupEnforcer's raised InsufficientWarmupError was caught and logged in runner.py rather than blocking the backtest. Now the decorator raises and is applied to RSI/ATR, and a warmup failure excludes that asset from the run instead of silently proceeding.
  • BreakoutStrategy compared today's close against a rolling high/low that included today's own bar. Now uses shift(1) so the breakout level reflects genuinely prior extremes.
  • Calmar/Sortino returned float("inf") on near-zero drawdown/downside, which is a landmine for any future ranking/sorting by these metrics. Now clipped to a finite MAX_RATIO sentinel.
  • bootstrap_sharpe used a naive IID resample, destroying autocorrelation — understates uncertainty for the momentum/trend strategies this repo mostly trades. Switched to a moving-block bootstrap.
  • max_retries was only honored by the LangChain planner path; raw Gemini/OpenAI planners ignored it. Added a shared retry helper used by both, plus tighter API-key validation (rejects malformed/whitespace keys, not just known placeholder strings).
  • Reflect step fed nothing back into hypothesize. Each iteration re-prompted the LLM with the same context, not what already underperformed this run. agent_graph now accumulates all_results across iterations and passes them into the proposal prompt.
  • Fixed a docstring claiming square-root market impact when the implementation is flat linear bps.

Test plan

  • pytest -q — all 63 existing tests pass unchanged
  • ruff check on all touched files — clean

- lookback_guard: enforce_lookback now raises InsufficientWarmupError
  instead of only logging, and is actually applied to RSI/ATR feature
  functions (previously dead code, applied nowhere)
- runner: warmup check failures now block the affected asset's backtest
  instead of being caught and logged as a warning
- runner: fix docstring claiming square-root market impact when the
  implementation is flat linear bps
- strategies: BreakoutStrategy now excludes the current bar from its
  rolling high/low so breakout levels reflect genuine prior extremes
- metrics: Calmar/Sortino return a finite MAX_RATIO sentinel instead of
  inf on near-zero drawdown/downside, so they're safe to sort/rank/persist
- metrics: bootstrap_sharpe uses a moving-block bootstrap instead of an
  IID resample, preserving autocorrelation for trend/momentum strategies
- base_planner: max_retries is now honored by the raw Gemini and OpenAI
  planner paths (previously only the LangChain path used it), and API
  key validation rejects malformed/whitespace keys, not just known
  placeholders
- proposal_generator/agent_graph: reflect/hypothesize loop now feeds
  prior-iteration results back into the LLM prompt so retries reason
  about what already underperformed within the same run

All 63 existing tests pass unchanged.
@OnePunchMonk
OnePunchMonk merged commit 0d6ff53 into main Aug 22, 2026
4 of 7 checks passed
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