portfolio_balancing: trace the risk-return frontier with solver shadow prices#83
Draft
chriscoey wants to merge 2 commits into
Draft
portfolio_balancing: trace the risk-return frontier with solver shadow prices#83chriscoey wants to merge 2 commits into
chriscoey wants to merge 2 commits into
Conversation
…w prices Extend the prescriptive stage to use solver sensitivity information (constraint duals) to trace the bi-objective risk vs return efficient frontier efficiently. - Read the return-floor constraint's dual via solve(sensitivity=True) on HiGHS. By the envelope theorem that dual is the frontier's local slope d(variance)/d(return), so each solve yields a Pareto point and its slope with no finite differencing. - Add three frontier drivers compared at equal solve budget: grid (blind even spacing), adaptive (step sized by the current shadow price), and dichotomic (split the interval with the largest chord-vs-tangent gap, sampling at the tangent crossover). The dual-guided drivers approximate the frontier far more tightly than blind spacing. - Materialize the chosen frontier as a FrontierPoint Concept with integrity constraints requiring that neither return nor risk decreases along it, a relational statement of Pareto-efficiency (non-decreasing with a small relative tolerance so near-equal crisis-scenario points do not spuriously trip it). - Switch the QP from Ipopt to HiGHS (HiGHS returns the sensitivity duals the frontier search relies on); bump relationalai to 1.9.0. - Rewrite the README for the new flow: shadow-price sign convention, the three-driver comparison, the max chord-gap quality metric, and refreshed expected output. Co-Authored-By: Claude Opus 4.8 <[email protected]>
… docs A solve can report OPTIMAL yet return no primal rows (e.g. an unstable or timed-out reasoner handing back a hollow result). Treat that as a non-result in solve_epsilon so the frontier drivers fall back through the same path as infeasible, instead of KeyError-ing on the missing "scenario" column in _extract_allocations. Expand the solve_at SystemExit message to name this case. Also clarify surrounding comments and docs: the module-scope rules-in-loop suppression, POSITION_LIMIT feasibility note, the LOCALLY_SOLVED back-end note, the pair_gap tangent-crossover docstring, the frontier_adaptive anchors-only guard, and the README's Anchor 2 per-scenario output and infeasibility note. Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
The docs preview for this pull request has been deployed to Vercel!
|
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.
Why
The
portfolio_balancingtemplate walks the risk/return efficient frontier. It previously sampled the frontier blindly — evenly spaced return targets, each an independent solve, with no signal about where the curve actually bends. Now that the prescriptive solver returns sensitivity information, the dual of each return-floor constraint is the local slope of the frontier (envelope theorem:shadow_price = d(min variance) / d(return target)). That turns frontier tracing from guesswork into a guided search, and gives the template a concrete, teachable use of solver duals.What changed
Stage 3 now solves with
sensitivity=Trueand uses the per-scenario shadow prices to trace the frontier three ways, compared at the same solve budget:The run prints an approximation-quality table (max chord-gap per driver — dichotomic is tightest at equal budget), a shadow-price-vs-secant table showing each dual matches the finite-difference slope, and the existing base-vs-crisis stress test (per-scenario frontier with exact dual marginals and the knee point).
Supporting changes:
1.9.0(sensitivity support), matching the other sensitivity templates.How to validate
Run
python portfolio_balancing.pyagainst a prescriptive-capable engine (sensitivity support required). The README's expected-output block matches a fresh run end-to-end:557.93> adaptive415.17> dichotomic202.30I ran this end-to-end on a current engine; output is numerically stable across runs.