Skip to content

Decision-support: Profile/IPS planning layer + walk-forward validation#3

Merged
manan-tech merged 22 commits into
mainfrom
feature/planning-ips-keystone
Jun 2, 2026
Merged

Decision-support: Profile/IPS planning layer + walk-forward validation#3
manan-tech merged 22 commits into
mainfrom
feature/planning-ips-keystone

Conversation

@manan-tech

Copy link
Copy Markdown
Owner

Summary

Two stacked features that evolve quant_reporter toward 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)

  • CFA-grounded Profile (risk-tolerance presets + return objective + TTLU constraints), build_profile, combine_risk_tolerance.
  • apply_constraints (Profile → optimizer bounds/constraints) and check_suitabilitySuitabilityReport.
  • recommend()/recommend_weights() accept an optional profile= 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 in to_text/to_dict/to_html.
  • Implemented by extracting a shared _rolling_oos_sharpe core from run_rolling_windows (behavior-preserving) + a walk_forward_recommendation helper. The existing validation report is unchanged.

Both built through the brainstorm → spec → plan → subagent-execution → review flow; specs/plans under docs/superpowers/.

Test Plan

  • Full suite green: 396 passed, coverage 85.96% (gate 80%), ruff clean.
  • Regression gate test/test_validation_unlock.py passes verbatim (proves the run_rolling_windows refactor is behavior-identical).
  • Original validation report regenerated end-to-end with its OOS table intact.
  • Backward compatible: profile=None / validate=False reproduce prior behavior.

🤖 Generated with Claude Code

manan-tech and others added 22 commits June 2, 2026 10:30
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>
@manan-tech manan-tech merged commit 88b0253 into main Jun 2, 2026
4 checks passed
@manan-tech manan-tech deleted the feature/planning-ips-keystone branch June 2, 2026 12:02
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