# Cockpit Levered Track Views Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development or executing-plans. Steps use `- [ ]`.

**Goal:** Surface the levered shadow book in the cockpit Backtest (a measured levered curve, 4th book pill) and Replay (naive↔levered track toggle), so the two tracks can be compared visually. Observe-only.

**Architecture:** Thread a `leverage_shape` through the per-coin measured-cost combine (`per_coin_book_returns` → `_bybit_measured_net` → `precompute_single_book_measured`) so the levered book gets a MEASURED curve (never flat); add it to the sim book switch. Add a `track` param to the Replay reading the levered `forward_nav` position-less. Reuse `_LEVERED_SLEEVE_SHAPE`/`MAX_BOOK_GROSS`.

**Tech Stack:** Python 3.13, FastAPI + Jinja2 + htmx, pytest, playwright (local browser verify).

## Global Constraints
- Naive paths BYTE-IDENTICAL: `leverage_shape=None` and `track=naive` produce exactly the prior output.
- Backtest is MEASURED cost only — never a flat/cost-blind levered curve (mirage foot-gun).
- Shape `{crypto_tstrend 1.0, unlock 1.5, xsfunding 5.0, positioning 2.0}`, book-gross ≤ 1.5 (`_LEVERED_SLEEVE_SHAPE`/`MAX_BOOK_GROSS`).
- MANDATORY local browser verification before "done" (feedback_verify_cockpit_locally_not_prod); read the rendered numbers, screenshot naive vs levered.
- Never `git add -A`; commit end `Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>`.

---

### Task 1: `per_coin_book_returns` accepts a gross-capped `leverage_shape`

**Files:** Modify `src/fxhnt/application/bybit_liquidity_sweep.py`; Test `tests/integration/test_bybit_liquidity_sweep.py` (or the file that tests `per_coin_book_returns`).

**Interfaces:** Produces `per_coin_book_returns(store, *, universe, sleeves=None, capital=100000.0, ..., leverage_shape: dict[str,float] | None = None, max_gross: float = 1.5)`; `leverage_shape=None` → naive (unchanged).

- [ ] **Step 1: failing test** — with a uniform shape the per-coin book equals naive; with a carry-weighted shape the summed sleeve factor is gross-capped ≤ max_gross and carry's contribution scales up.

```python
def test_per_coin_book_returns_uniform_shape_equals_naive():
    # seed a store with >=2 sleeves; naive vs leverage_shape={all:1.0} must give identical net_series
    ...
    naive = per_coin_book_returns(store, universe=None, capital=100000.0, taker_fee_bps=5.5)
    lev1  = per_coin_book_returns(store, universe=None, capital=100000.0, taker_fee_bps=5.5,
                                  leverage_shape={s: 1.0 for s in _DEFAULT_BYBIT_SLEEVES})
    assert lev1["net_series"] == naive["net_series"]
```

- [ ] **Step 2: run → fail** (`unexpected keyword argument 'leverage_shape'`).

- [ ] **Step 3: implement.** Add the two params. Just before the "2) combine eq-wt" loop compute the per-sleeve factor (gross-capped), then use it:

```python
    if leverage_shape is None:
        factor = {s: 1.0 / n_sleeves for s in per_sleeve}
    else:
        factor = {s: leverage_shape.get(s, 1.0) / n_sleeves for s in per_sleeve}
        gross = sum(abs(v) for v in factor.values())
        if gross > max_gross and gross > 0:
            scale = max_gross / gross
            factor = {s: v * scale for s, v in factor.items()}
```

and in the combine loop change `for by_day in per_sleeve.values():` to `for sleeve, by_day in per_sleeve.items():` and replace `w / n_sleeves` with `factor[sleeve] * w` in BOTH the `wrow[coin]` and `crow[coin]` updates (`crow[coin] += factor[sleeve] * w * r`).

- [ ] **Step 4: run → pass** (uniform-shape==naive; carry-weighted differs + gross-capped).
- [ ] **Step 5: commit** `feat(measured): per_coin_book_returns gross-capped leverage_shape`.

---

### Task 2: `_bybit_measured_net` + `precompute_single_book_measured` compute the levered measured curve

**Files:** Modify `src/fxhnt/application/compare_measured_cost.py`; Test its test file.

**Interfaces:** `_bybit_measured_net(..., leverage_shape=None, max_gross=1.5)` passes through to `per_coin_book_returns`. `precompute_single_book_measured` additionally caches `bybit_4edge_levered` using `_LEVERED_SLEEVE_SHAPE`.

- [ ] **Step 1: failing test** — `_bybit_measured_net` with `_LEVERED_SLEEVE_SHAPE` yields a curve whose metrics differ from naive (higher Sharpe, carry-weighted) at the SAME measured cost model; and `precompute_single_book_measured` writes a `bybit_4edge_levered` cache entry readable by `read_cached_single_measured`.

- [ ] **Step 2: run → fail.**
- [ ] **Step 3: implement.** Thread `leverage_shape`/`max_gross` from `_bybit_measured_net` into `per_coin_book_returns`. In `precompute_single_book_measured`, after the naive `bybit_4edge` single-book precompute, compute the same for the levered book (`leverage_shape=_LEVERED_SLEEVE_SHAPE` imported from `bybit_forward_track`) and persist under cache key `bybit_4edge_levered`.
- [ ] **Step 4: run → pass.**
- [ ] **Step 5: commit** `feat(measured): precompute the bybit_4edge_levered measured curve`.

---

### Task 3: Backtest sim — add the levered book pill

**Files:** Modify `src/fxhnt/adapters/web/app.py` (`_SIM_BOOKS`, `_sim_returns`) + `templates/sim.html` (pill label); Test `tests/integration/test_bybit_sim_web.py` / `test_paper_sim_web.py`.

- [ ] **Step 1: failing test** — `_SIM_BOOKS` includes `bybit_4edge_levered`; `/paper/sim?book=bybit_4edge_levered` renders 200 with the levered pill active; the lazy `/paper/sim/run?book=bybit_4edge_levered` renders the measured curve (or the graceful pending note if the cache is absent) — never a 500, never a flat slider.

- [ ] **Step 2: run → fail.**
- [ ] **Step 3: implement.** Add `"bybit_4edge_levered"` to `_SIM_BOOKS`; make `_sim_returns("bybit_4edge_levered")` read `bybit_sleeve_returns()` (same sleeves; the levered curve comes from the measured cache, not this raw table). Add the 4th pill to `sim.html`'s book switch with label "Bybit 4-edge levered · shadow". `read_cached_single_measured` already resolves any book key.
- [ ] **Step 4: run → pass.**
- [ ] **Step 5: commit** `feat(cockpit): levered book pill in the Backtest`.

---

### Task 4: Replay — naive↔levered track toggle

**Files:** Modify `src/fxhnt/adapters/web/app.py` (`paper_replay`, `paper_replay_at`) + `templates/replay.html`; Test `tests/integration/test_bybit_sim_web.py` or the replay test.

**Interfaces:** `paper_replay(request, venue="bybit", track="naive")`; `track="levered"` reads `bybit_4edge_levered` forward_nav position-less.

- [ ] **Step 1: failing test** — `/paper/replay?track=levered` renders 200 showing the levered forward_nav curve and the "return-defined — no scrubbable positions" note (no positions); `/paper/replay` (default) and `?track=naive` are byte-identical to before; the toggle appears in the HTML; leak-clean.

- [ ] **Step 2: run → fail.**
- [ ] **Step 3: implement.** Add `track` param. `track=="levered"` → read the `bybit_4edge_levered` forward track via the DashboardService `detail("bybit_4edge_levered")` (its `history` NAV points) → build the sparkline + a position-less context (reuse the carry note). `track=="naive"` → the current venue nav_history path unchanged. Add a naive↔levered toggle to `replay.html` (relative-URL pills, like the venue toggle). `paper_replay_at` ignores positions when `track=levered`.
- [ ] **Step 4: run → pass.**
- [ ] **Step 5: commit** `feat(cockpit): naive<->levered track toggle in Replay`.

---

### Task 5: Local browser verification + deploy

**Files:** none (verification + deploy).

- [ ] **Step 1** Run the cockpit locally against a seeded/prod-mirrored DB per reference_fxhnt_local_cockpit_dev_loop.
- [ ] **Step 2** Open `/paper/sim?book=bybit_4edge_levered` in playwright; confirm the levered pill is active and a curve (or the pending note) renders; screenshot naive vs levered; READ the rendered Sharpe/CAGR numbers and confirm they match the levered backtest_summary (Sh~3.25, cagr~46%) when the measured cache is present.
- [ ] **Step 3** Open `/paper/replay?track=levered`; confirm the levered forward_nav curve renders position-less with the note; toggle back to naive; screenshot both.
- [ ] **Step 4** Full suite green: `uv run pytest tests/ -q`.
- [ ] **Step 5** Deploy: commit → push master → build → dagster rollout; re-run the `compare-measured-precompute` in-cluster Job so the levered measured cache populates. Verify on prod the levered pill renders a measured curve.

## Self-Review
- Spec coverage: Component 1 (measured levered curve) = Tasks 1-3; Component 2 (replay toggle) = Task 4; browser guardrail + deploy = Task 5. Naive-byte-identical + measured-not-flat constraints are the Task 1/3 tests. ✓
- Placeholders: none — combine-code change is spelled out; test intents concrete.
- Type consistency: `leverage_shape: dict[str,float] | None` + `max_gross: float` threaded identically through per_coin_book_returns → _bybit_measured_net → precompute; `track: str` in the two replay routes. ✓
