Conversation
regime_conditional_performance and RegimeResult were ported with the
overfitting statistics but never reachable: exported from the domain
package and called by nothing, with no tests.
Wire them through the layers:
- AnalyzeRegimes use case in validation/application, converting
EquityCurve/pandas/array input via the existing returns bridge
- AlgoSystem.analyze_regimes() and an `algosystem regimes` CLI command
- RegimeResult.to_metrics(), keyed by ValidationMetricKey
The statistics degrade quietly on thin input: a series no longer than
vol_window collapses to a single "all" regime, and any bucket under five
observations scores 0.0, so a data shortage reads like a finding
("Sharpe 0.0, regime-dependent"). AnalyzeRegimes rejects input it cannot
describe instead. Regimes are assigned positionally, so dated series are
checked for matching indices rather than matching length alone; the CLI
restricts strategy and benchmark to the dates they share.
Add REGIME_COUNTS, WORST_REGIME_NAME and WORKS_IN_ALL_REGIMES to
ValidationMetricKey so to_metrics() is lossless — the counts are what
distinguish an empty bucket from a genuine loss.
The ported numerics are unchanged.
The demo builds two strategies calibrated to the same overall Sharpe and shows that only the regime breakdown separates them: one holds its edge across all volatility buckets, the other earns everything while calm and gives it back once volatility arrives. It also exercises both guardrails (short series, mismatched dates). The notes record what was already in the tree before this work, the design decisions behind the new use case, the defects found in review, and the known follow-ups.
SivanRP
requested review from
colerottenberg,
dominickdupuy,
jrile018,
patrickbott,
raohemdutt and
ry2009
as code owners
September 22, 2026 13:11
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Public input coercion still leaks raw conversion errors and accepts boolean annualization factors.
Review effort: Lite
Findings: None
What changed in this PR
Exposes regime-conditional performance analysis through the validation domain, application, API, CLI, documentation, and tests.
Changes:
- Adds
AnalyzeRegimeswith input validation, date alignment, and sufficiency checks. - Exposes regime metrics through public APIs and the
regimesCLI command. - Adds documentation, examples, fixtures, and comprehensive tests.
| File | Change |
|---|---|
tests/validation/domain/test_robustness_regimes.py |
Domain regime-statistics tests |
tests/validation/conftest.py |
Shared regime fixtures |
tests/validation/application/test_analyze_regimes.py |
Use-case tests |
tests/validation/application/test_analyze_regimes_api.py |
API tests |
tests/test_cli.py |
CLI coverage |
tests/test_cli_integration.py |
CLI integration tests |
REGIME_DETECTION_HANDOFF.md |
Implementation handoff notes |
README.md |
Regime CLI usage |
examples/regime_detection_demo.py |
Regime-analysis example |
docs/VALIDATION_GUIDE.md |
Validation documentation |
docs/CLI_GUIDE.md |
CLI documentation |
docs/API_GUIDE.md |
API documentation |
CHANGELOG.md |
Feature changelog entry |
algosystem/validation/domain/validation_metric.py |
Canonical regime metric keys |
algosystem/validation/domain/statistics/robustness.py |
Lossless metrics export |
algosystem/validation/application/analyze_regimes.py |
Regime-analysis use case |
algosystem/validation/application/__init__.py |
Application export |
algosystem/validation/__init__.py |
Validation exports |
algosystem/interfaces/cli/main.py |
regimes CLI command |
algosystem/interfaces/api.py |
Public API method |
algosystem/__init__.py |
Top-level result export |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This branch has not been deployed
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.
What this does
regime_conditional_performance()andRegimeResultcame in with the overfitting port in #24 and have never been reachable — exported fromvalidation/domain/__init__.py, called by nothing, no tests, absent from the guides. This wires them through the layers so the "does this strategy only work in calm markets?" check is actually usable.No new statistics. Riley's numerics are untouched:
robustness.pyis +26 lines, 0 deletions, and the only addition is ato_metrics()accessor.Changes
Layer | Change -- | -- validation/domain/statistics/robustness.py | RegimeResult.to_metrics() — lossless export keyed by ValidationMetricKey validation/domain/validation_metric.py | REGIME_COUNTS, WORST_REGIME_NAME, WORKS_IN_ALL_REGIMES so every computed field has a canonical name (R4/V3) validation/application/analyze_regimes.py (new) | AnalyzeRegimes use case — input coercion, parameter validation, alignment and sufficiency guards interfaces/api.py | AlgoSystem.analyze_regimes() interfaces/cli/main.py | algosystem regimes command docs | New sections in VALIDATION_GUIDE, API_GUIDE, CLI_GUIDE, READMEOpen to Review
The domain statistics degrade quietly, which makes a data shortage read like a finding. Two guards in the use case, both of which I'd want a second opinion on:
Positional alignment. Regimes are assigned by position, so a strategy dated 2020 paired with a benchmark dated 2010 silently classified against the wrong observations. Checking length alone is not enough.
_require_matching_dates()now compares the converted returns indices when both inputs carry aDatetimeIndex; undated arrays still fall back to the length check. The CLI restricts strategy and benchmark to the dates they share.Insufficient data. A bucket under five observations scores
0.0, and a series no longer thanvol_windowcollapses to a single"all"regime. A 30-row CSV therefore printed "every regime Sharpe 0.0, REGIME-DEPENDENT" with exit 0 for a strategy whose true Sharpe was ~93._require_classifiable()rejects input it cannot describe rather than emitting that. The guard is necessary but not sufficient — percentile thresholds can still leave one bucket thin — which is whyregime_countsis exposed into_metrics(): it distinguishes an empty bucket from a genuine loss.Design decisions
No
facade.pyentry point.validation/facade.pyexists to compose infrastructure —detect_overfittingbuilds aPassRunnerand resolves strategies fromvalidation.infrastructure.strategies.AnalyzeRegimescomposes nothing and takes no constructor dependency, so a facade wrapper would be a pure pass-through.The CLI calls the use case directly rather than
AlgoSystem.AlgoSystem.__init__eagerly builds a quantstats calculator that regime analysis never uses.validate-strategiesandbenchmarksalready import their dependencies directly, so there is precedent, but the underlying eagerness is a pre-existing issue worth its own ticket (see follow-ups).Testing
58 new test functions across domain, application, facade and CLI.
The classification test measures realized volatility inside each bucket rather than bucket sizes — terciles are a third each by construction, so asserting sizes passes on IID noise with no regime structure at all. It now separates 8.01x on a regime fixture vs 0.97x on noise.
Local run: 301 passed, 22 failed, 1 error. All 22 failures and the collection error reproduce identically on
main— verified in a clean worktree. They arequantstatsmissing from my environment plus one sandboxparquet_cachefailure. No regressions.black --check,isort --checkand the domain purity test pass. I could not runrufforimport-linterlocally (not installed), so CI is the authoritative check on the import contracts — please don't merge on my local results alone.