Per-leg deleveraging for split-allocation books (D-028)#65
Merged
HouseofTyrell merged 2 commits intoJul 1, 2026
Conversation
Split allocation and deleveraging were mutually exclusive (D-016/D-026): when both were on, the deleverage plan was dropped with a "plan ignored" warning. This lets each split leg (core + overlay) carry and glide its own deleverage plan independently, per the owner decision logged as D-028. Engine: - SplitAllocation gains optional coreDeleverage/overlayDeleverage plans; the top-level deleveragePlan stays single-strategy-only (still ignored when split is on). - deleverage.ts: extract resolveLegDeleveragePlan(plan, source) and buildDeleverageSchedule(resolvedPlan, ...) so a leg can be scheduled from its own strategy; the single-strategy path is bit-identical. - core.ts: new combined split+deleverage branch that collateral-weight- blends each leg's per-year rate/financing/extension and runs a SEPARATE embedded-gain pool per leg (book pre-existing gain assigned to the overlay leg first, capped at its market value, remainder to core), so a concentrated overlay's unwind cost is attributed to the right sleeve. Unwind gains stay endogenous (netted with strategy flows, charged against taxSavings). Per-leg financing follows each leg's glide. Surface: - Book-level deleverage outputs are the per-leg sums, so the existing ResultsTable "Deleverage" column group (both orientations) and Excel export light up in split mode with no schema change (data-driven on extensionFraction < 1). - CSV round-trips splitAllocation.coreDeleverage.*/overlayDeleverage.* via a shared serializer/parser. - Workspace rail: the single plan editor becomes two per-leg target selectors in split mode; the "plan ignored" chips are removed (a softer note remains only for a stray top-level plan with split on). - Sensitivity grid unchanged (still excluded, D-013/D-016). Tests: split equivalence (two identical legs == single strategy), overlay-only collateral-weighted extension, independent per-leg schedules, per-leg pools, endogenous-netting invariant, financing glide, CSV round-trip. Suite 408 -> 417. Browser-verified end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1ZREPdGEiNqhSRGsKw1bF
Deploying enhanceddirectindexingcalc with
|
| Latest commit: |
cb1887f
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://0dd19ac0.enhanceddirectindexingcalc.pages.dev |
| Branch Preview URL: | https://claude-indexing-calculator-d.enhanceddirectindexingcalc.pages.dev |
Follow-up to the per-leg deleverage build: surface each split leg's realized unwind gain as its own column, so an advisor can see how much of a year's unwind gain came from the core vs overlay sleeve. - YearResult gains coreDeleverageGain / overlayDeleverageGain (defined only in split mode; they sum to deleverageGainRealized by construction). - core.ts populates them from the per-leg unwind totals (long + short cover) already computed for the per-leg embedded-gain pools. - ResultsTable renders "Core Unwind" / "Overlay Unwind" as conditional columns in BOTH orientations, gated on split mode; popupContent.ts gains matching entries. Excel export mirrors the two columns. - Fixed a pre-existing latent bug found here: the table "Start" row emitted only 3 placeholder cells for the 5-column expanded Deleverage group, so it rendered ragged when the group was expanded (now 5). Tests: per-leg gains sum to the book gain and attribute to the right leg/year; fields stay undefined in single-strategy mode. Suite 417 -> 419. Browser-verified: both columns render with correct values summing to the book Unwind Gain, no console errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1ZREPdGEiNqhSRGsKw1bF
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 & why
Split allocation and deleveraging were mutually exclusive (D-016/D-026): with both enabled, the deleverage plan was dropped and the UI showed a "plan ignored" warning. You wanted to run a deleveraging scenario on a split book, so this builds the feature out.
Per the decision logged as D-028 (owner-approved scope: per-leg plans, engine depth = full combined branch with per-leg embedded-gain pools), each split leg — core and overlay — can now carry and glide its own deleverage plan independently.
Engine
SplitAllocationgains optionalcoreDeleverage/overlayDeleverageplans. The top-leveldeleveragePlanstays single-strategy-only (still ignored when split is on).deleverage.ts: extractedresolveLegDeleveragePlan(plan, source)andbuildDeleverageSchedule(resolvedPlan, …)so a leg can be scheduled from its own strategy. The single-strategy path is bit-identical.core.ts: new combined split+deleverage branch that collateral-weight-blends each leg's per-year rate / financing / extension, and runs a separate embedded-gain pool per leg. The book's pre-existing gain is assigned to the overlay leg first (the appreciated-stock sleeve), capped at its market value, remainder to core — so a concentrated overlay's unwind cost is attributed to the right sleeve. Unwind gains stay endogenous (netted with strategy flows, charged againsttaxSavings, per D-016/D-017). Per-leg financing follows each leg's glide.Surface area
extensionFraction < 1, and the book-level outputs are now the per-leg sums.YearResult.coreDeleverageGain/overlayDeleverageGain(defined only in split mode, summing todeleverageGainRealized) surface as conditional "Core Unwind" / "Overlay Unwind" columns in both table orientations + Excel, each with apopupContent.tsentry. Also fixed a pre-existing latent bug found here: the table "Start" row emitted 3 placeholder cells for the 5-column expanded Deleverage group (now 5), which rendered ragged when expanded.splitAllocation.coreDeleverage.*/.overlayDeleverage.*via a shared serializer/parser.Tests & verification
New engine + CSV tests: split equivalence (two identical legs == single-strategy deleverage), overlay-only collateral-weighted extension, independent per-leg schedules, per-leg pools (overlay basis raises the overlay's unwind gain), endogenous-netting invariant, financing-glide, per-leg CSV round-trip, and per-leg attribution (core+overlay sum to the book gain; undefined in single mode). Updated the WorkspaceTab test to assert the rail offers per-leg editors instead of the conflict warning.
npm run test:run— 419 passed (was 408)npm run build— passes (tsc + vite)npm run lint— 0 errors🤖 Generated with Claude Code