Skip to content

fix: preserve gaps in multi-strategy reports - #561

Open
JohnDReynolds wants to merge 1 commit into
ranaroussi:mainfrom
JohnDReynolds:fix/pr-2/isolate-multistrategy-report-gaps
Open

JohnDReynolds wants to merge 1 commit into
ranaroussi:mainfrom
JohnDReynolds:fix/pr-2/isolate-multistrategy-report-gaps

Conversation

@JohnDReynolds

@JohnDReynolds JohnDReynolds commented Sep 29, 2026 •

Copy link
Copy Markdown

Summary

Fix report preprocessing so a missing observation in one strategy does not remove or replace valid observations from another strategy.

Previously, report-level row deletion could discard an entire date when any strategy column contained a gap, while zero-filling could treat missing observations as real zero returns. This produced incorrect cumulative returns, volatility, and date metadata in multi-strategy reports.

Behavior

Each strategy is now evaluated from its own observed returns instead of a row-wise intersection of every strategy column.

The change:

  • preserves per-column gaps through HTML, basic, full, metrics, and plot report paths;
  • stops replacing missing report observations with zero returns;
  • forwards match_dates consistently from basic() and full() to plots();
  • reports a Series start date from its first observed value; and
  • operates on copies where report normalization modifies index or label metadata, preserving caller-owned inputs.

Complete-data and single-Series report behavior remains unchanged.

Regression evidence

  • Fail before: In the retained six-day regression, Strategy A's observed returns are 1%, 50%, -2%, 3%, and 1%. They independently compound to (1.01 × 1.50 × 0.98 × 1.03 × 1.01) − 1 = 0.54453341, displayed as 0.54. Accepted report preprocessing instead displayed 0.02 when Strategy A appeared beside another strategy with gaps. Zero-filling also reduced annualized volatility from the direct result of 3.51 to 3.21, and a leading gap caused the report to show 2024-01-01 instead of the first observation on 2024-01-06.
  • Independent expectation: For report-preprocessing behavior, each column in a combined strategy table must match that same strategy evaluated alone. Cumulative return and volatility are checked independently with stats.comp() and stats.volatility().
  • Pass after: 10 focused cases pass across basic and full modes, both match_dates settings, gapped benchmarks, leading/interior/all-strategy gaps, caller-input preservation, report delegation, and structurally parsed HTML output. Full-table comparisons exclude the existing Smart Sharpe and Smart Sortino rows because their metric-specific cross-column behavior is not changed here.

Verification

  • 10 focused regression tests passed.
  • 145 report, regression, and plot tests passed.
  • 235 full-suite tests passed with 72 inherited dependency deprecation warnings.
  • Changed-file and full Ruff lint passed.
  • Ruff formatting and git diff --check passed.
  • Wrapper diagnostics passed.
  • Generated HTML output was parsed structurally.
  • No public signature, template, dependency, documentation, or build behavior changed.

Scope

Changed files:

  • quantstats/reports.py
  • tests/test_reports.py
  • tests/test_regressions.py

Out of scope:

  • Smart Sharpe and Smart Sortino metric-level penalty isolation
  • Nullable extension-dtype handling in stats.ulcer_index()
  • Redesigning match_dates
  • Unrelated report formatting or cleanup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant