# Paper book: equal-weight replayable sleeves + wire unlock to warehouse — Design (Phase 2F-A3b)

**Date:** 2026-06-21
**Status:** Design (approved)
**Repo:** fxhnt
**Follows:** 2F MVP + 2F-2 snapshot + A3 replay. Fixes the flat/empty paper book.

## Motivation

Diagnosis: `/paper` + `/paper/replay` render but the book is **empty/flat** — `paper_positions` has 0 rows
across all 365 backfilled dates. Two root causes:
1. **Sleeve weights are `{}`.** The backfill + nightly snapshot size positions by `current_sleeve_weights`
   (the live `book_allocator`), but the allocator weights a DIFFERENT edge set (crypto-momentum /
   futures-trend), not the replayable paper sleeves (ts-trend / stablecoin / unlock). So every replayable
   sleeve gets weight 0 → notional 0 → no positions. (Verified: ts-trend `current_weights` returns 70 names
   on the latest date, but `_paper_backfill_sleeve_weights()` returns `{}`.)
2. **unlock builds from the absent `crypto_pit_dir`** (`/root/.fxhnt/crypto_pit`, not in-cluster) for its
   vol panel → it raises and is skipped. The warehouse (197 USDT symbols × 365 closes) holds those vols.

## Decisions (from brainstorming)

- **Equal-weight the active replayable sleeves**: per date, `sleeve_weight = 1/N` where N = the sleeves
  that produced a non-empty `current_weights(as_of)` that day. Simple, transparent, no chicken-and-egg.
  (Inverse-vol/fixed weights are possible refinements — out of scope.)
- **Shared logic**: the equal-weight sizing must be used by BOTH the backfill AND the nightly
  `paper_book_snapshot` asset, so the live `/paper` view and the replay agree and neither is flat.
- **unlock** built from the **warehouse** vol panel (same `{sym:{epoch_day:close}}` the ts-trend backfill
  uses, augmented to the (close, qvol, funding) tuple the unlock runner needs where available) +
  `UnlockCalendarLive` (3,185 events incl. historical cliffs covering the window). NOT `crypto_pit_dir`.

## Scope

1. **Equal-weight sizing** (`application/paper_book.py` or `paper_snapshot.py`): a helper
   `equal_weights(symbol_weights_by_sleeve) -> {sleeve: 1/N}` over the non-empty sleeves; used by
   `backfill_paper_book` (per date) and the nightly snapshot path. Drop the dependency on
   `current_sleeve_weights`/`_paper_backfill_sleeve_weights` for the paper book (the allocator covers a
   different set). `backfill_paper_book` computes `sleeve_weights` per date from the day's active sleeves.
2. **unlock construction** (`cli.py _paper_backfill_sleeves` + the snapshot's `_live_sleeves`): build the
   unlock sleeve's vol panel from the warehouse (`crypto_close_panel`, adapted to what `UnlockShortRunner`
   needs) + `UnlockCalendarLive(<data_dir>/unlock_calendar.json)`. Remove the `crypto_pit_dir` dependency.
   If qvol/funding aren't in the warehouse, pass closes with neutral qvol/funding so the vol-based weights
   still compute (document the approximation), or skip names lacking the needed fields — never crash.
3. Keep stablecoin as-is (BinanceSpotHistory; it legitimately produces positions only on depeg days — rare
   but real). ts-trend already works. xsfunding excluded.

## Data flow

Backfill / nightly snapshot → each replayable sleeve `current_weights(as_of)` → keep the non-empty ones →
`sleeve_weight = 1/N` each → `persist_paper_book(... sleeve_weights, symbol_weights_by_sleeve, prices ...)`
→ positions sized at `(1/N)·$100k·w / close`. `/paper` (live) + `/paper/replay` (history) both populate.

## Risks / safety

- **Read-only/additive**: no change to sleeve `run()`/`advance()` hot paths, `combine_book`, or the gate
  record. Only the paper sizing + unlock loader wiring change.
- **stablecoin MTM**: its stable-pair symbols aren't in the warehouse close panel used for MTM → those
  positions mark at entry (~0 PnL, correct for pegs). Documented.
- **unlock vol approximation**: if the warehouse lacks qvol/funding, the unlock weights use a documented
  fallback; never crash (per-name skip).
- **Consistency**: equal-weight shared by backfill + nightly snapshot so live + replay agree.

## Out of scope
Inverse-vol / fixed sleeve weights; B alerts panel; back-computing xsfunding; the `_live_sleeves`↔backfill
duplication (the proper shared-composition refactor — noted as tech debt).

## Acceptance criteria
- A shared equal-weight helper sizes the active replayable sleeves (1/N); unit-tested.
- `backfill_paper_book` + the nightly snapshot use it (no more `{}` sleeve weights); positions persist.
- unlock builds from the warehouse panel + calendar (no `crypto_pit_dir`); produces positions in pre-cliff
  windows; tested with a fake warehouse + historical-cliff calendar.
- Re-run backfill → `paper_positions` non-empty across many dates; `/paper/replay` equity curve is NON-flat
  (ts-trend + unlock contribute; stablecoin on depeg days); verified post-deploy.
- Nightly snapshot populates the live `/paper` book too (not flat).
