Decision-support: Profile/IPS planning layer + walk-forward validation#3
Merged
Conversation
First build in the decision-support direction: a CFA L1-grounded Profile dataclass + pure functions (build_profile, apply_constraints, check_suitability) threaded into the existing recommend()/recommend_weights() via an optional profile= param. Closes the latent seam where recommend() limits only drove alerts, not the optimizer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Six TDD tasks: Profile+presets, apply_constraints, check_suitability, recommend_weights wiring, recommend wiring + suitability rendering, exports. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e combine Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… check
Remove pre-emptive numpy/build_constraints/max_drawdown imports that
caused 3 F401 ruff failures (they belong in Task 2/3 commits where they
are first used). Drop the sector_caps sum-to-1 validation: sector caps
are per-sector upper bounds, not a partition; a profile like
{Tech: 0.3, Fin: 0.3} is valid because uncapped assets are freely
investable. Remove the companion test that baked in the wrong behavior.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…/constraints Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… raw n apply_constraints line 106 used `n * cap < invest_budget` which passed when excluded assets would make the effective bound sum infeasible. E.g. moderate profile (cap=0.40) with 1 of 3 assets excluded: 3*0.40=1.20 passes the old guard but the effective sum is 2*0.40=0.80 < 1.0, producing a silent infeasible SLSQP problem at runtime. Fix computes n_investable = n - len(excluded & set(cols)) and gates on n_investable*cap. Adds two new tests: exclusion-only and exclusion+liquidity_floor combinations. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mmendation Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hed sector The single aggregate check stored limit=max(all sector caps), which meant observed > limit was numerically false even when the check was failing (e.g. Tech breached at 0.8 with cap 0.3 showed limit=0.9 from Fin's cap). Any downstream consumer of to_dict()/to_text() doing `observed > limit` got the wrong answer, directly contradicting passed=False. Fix: replace the single aggregated sector_caps check with one SuitabilityCheck per breached sector (name=sector_caps_<sector>), each carrying its own sector's cap as .limit. This guarantees observed > limit holds for every failing check and makes the per-sector breach visible. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The variable was set before the sector-caps loop and toggled inside the breach branch but never read after the loop exits. Remove both the initialisation and the assignment to eliminate the dead code. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nds/constraints) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, attaches suitability Closes the seam where recommend() limits drove only alerts: target weights now respect the profile's constraints. Backward compatible (profile=None unchanged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements the missing basic HTML suitability section required by spec
Section 3 / line 150 (_suitability_html helper + Suitability Assessment
entry in build_recommendation_section). Adds
test_recommendation_to_html_renders_suitability verifying both the
profile path (SUITABLE/NOT SUITABLE label) and the no-profile path
("not assessed" fallback). Ruff clean; 385 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… partition Removes the incorrect sector_caps-sum validation rule from the spec to match the implementation (commit b8ac536). Per-sector caps are upper bounds; uncapped sectors stay investable, so caps need not sum to >= 1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ion 2) Extends the existing run_rolling_windows engine (DRY core extraction + optional objective/profile) and wires recommend(validate=True) -> RecommendationValidation. Existing report + test_validation_unlock.py untouched (regression gate). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…validation Four TDD tasks: extract _rolling_oos_sharpe core (regression-safe), optional profile/objective Recommended column, RecommendationValidation + recommend(validate=), rendering + exports. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ng_windows Behavior-preserving: run_rolling_windows becomes a thin wrapper over a shared strategy-driven core. Existing test_validation_unlock.py is the regression gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-forward column Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reuses the shared _rolling_oos_sharpe core + calculate_overfitting_score to attach a degradation-based RecommendationValidation. Backward compatible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the spec gap (validation was rendered in to_text/to_dict but not to_html). Adds a "Walk-Forward Validation" section alongside Suitability. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nore advisory_out/ Documents the planning/IPS layer + walk-forward validation under [Unreleased]; gitignores generated advisory reports (advisory_out/) alongside research/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Summary
Two stacked features that evolve
quant_reportertoward an opinionated decision-support advisor, built as reusable library primitives (dataclasses + pure functions). PyPI publish is deferred (planned next week).1. Profile / IPS planning keystone (
planning.py)Profile(risk-tolerance presets + return objective + TTLU constraints),build_profile,combine_risk_tolerance.apply_constraints(Profile → optimizer bounds/constraints) andcheck_suitability→SuitabilityReport.recommend()/recommend_weights()accept an optionalprofile=that constrains the optimizer and sets alert thresholds — closes a seam where the limits previously drove only alerts, not the recommended weights.2. Walk-forward validation of recommendations
recommend(validate=True)→RecommendationValidation(OOS vs in-sample Sharpe, degradation, holds-up/fragile/inconclusive verdict, current portfolio as OOS baseline); rendered into_text/to_dict/to_html._rolling_oos_sharpecore fromrun_rolling_windows(behavior-preserving) + awalk_forward_recommendationhelper. The existing validation report is unchanged.Both built through the brainstorm → spec → plan → subagent-execution → review flow; specs/plans under
docs/superpowers/.Test Plan
ruffclean.test/test_validation_unlock.pypasses verbatim (proves therun_rolling_windowsrefactor is behavior-identical).profile=None/validate=Falsereproduce prior behavior.🤖 Generated with Claude Code