Skip to content

Per-leg deleveraging for split-allocation books (D-028)#65

Merged
HouseofTyrell merged 2 commits into
mainfrom
claude/indexing-calculator-deleveraging-aw5ee2
Jul 1, 2026
Merged

Per-leg deleveraging for split-allocation books (D-028)#65
HouseofTyrell merged 2 commits into
mainfrom
claude/indexing-calculator-deleveraging-aw5ee2

Conversation

@HouseofTyrell

@HouseofTyrell HouseofTyrell commented Jun 30, 2026

Copy link
Copy Markdown
Owner

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

  • SplitAllocation gains optional coreDeleverage / overlayDeleverage plans. The top-level deleveragePlan stays single-strategy-only (still ignored when split is on).
  • deleverage.ts: extracted 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. 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 against taxSavings, per D-016/D-017). Per-leg financing follows each leg's glide.

Surface area

  • ResultsTable (both orientations) and Excel export light up in split mode with no schema change on the aggregate columns — data-driven on extensionFraction < 1, and the book-level outputs are now the per-leg sums.
  • Per-leg breakout columns (second commit): YearResult.coreDeleverageGain / overlayDeleverageGain (defined only in split mode, summing to deleverageGainRealized) surface as conditional "Core Unwind" / "Overlay Unwind" columns in both table orientations + Excel, each with a popupContent.ts entry. 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.
  • CSV round-trips splitAllocation.coreDeleverage.* / .overlayDeleverage.* via a shared serializer/parser.
  • Workspace rail: in split mode the single plan editor becomes two per-leg target selectors; the two "plan ignored" warning chips are removed (a softer note remains only when a stray top-level plan is set with split on).
  • Sensitivity grid unchanged (still excluded, D-013/D-016 — cells stay comparable).

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:run419 passed (was 408)
  • npm run build — passes (tsc + vite)
  • npm run lint — 0 errors
  • Browser-verified (Playwright): per-leg rail editors render in split mode; enabling the overlay plan moved the headline $2,686,020 → $1,993,074; the expanded Year-by-Year Deleverage group shows Core Unwind $418,966 + Overlay Unwind $586,552 = $1,005,518 ≈ Unwind Gain $1,005,517; no console errors.

🤖 Generated with Claude Code

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
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 30, 2026

Copy link
Copy Markdown

Deploying enhanceddirectindexingcalc with  Cloudflare Pages  Cloudflare Pages

Latest commit: cb1887f
Status: ✅  Deploy successful!
Preview URL: https://0dd19ac0.enhanceddirectindexingcalc.pages.dev
Branch Preview URL: https://claude-indexing-calculator-d.enhanceddirectindexingcalc.pages.dev

View logs

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
@HouseofTyrell HouseofTyrell merged commit 894d43d into main Jul 1, 2026
2 checks passed
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.

2 participants