# Crypto Honest-Reference Pipeline — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build a coin-level capacity-, cost-, and leverage-honest backtest reference for the Bybit crypto sleeves, plus the falsification gates, risk-parity allocation, ex-ante risk layer, and a gated pump-detection candidate edge — as a pure research/report pipeline that does not touch live sizing or the cockpit.

**Architecture:** Reuse the existing `sleeve_weights_and_contributions(store, sleeve)` → `{day:{symbol:(weight, ret)}}` engine (no edge logic reimplemented). A new `honest_reference.py` applies a per-coin ADV participation cap (from `store.read_panel(symbols, "turnover")`) + square-root market-impact slippage (floored at the coin's `bybit_real_spread`) and re-aggregates to a NET daily return, parameterized by AUM. Gates, risk-parity weights, leverage-DD, and the pump candidate are pure functions on those return series. A CLI report assembles the numbers. Everything is read-only.

**Tech Stack:** Python 3.12, stdlib `statistics`/`math`, the existing `_FeatureStore` Protocol, `nav_stats.nav_backfill_stats`, pytest. No new dependencies.

## Global Constraints

- **Research/validation scope only.** No change to live sizing, the cockpit, `backtest_summary`, or the recon gate. No writes to prod tables.
- **Reuse the real sleeve engines** via `sleeve_weights_and_contributions` — never reimplement edge logic.
- **Fidelity is a hard stop:** with the cap disabled (AUM→small) and impact off (`impact_k=0`, no spread), the honest series MUST equal `sleeve_returns_from_store(cost_bps=0)` (== `reconciliation_series(...)`), within 1e-9. A mismatch blocks all downstream tasks.
- `participation_cap` = **0.03**; AUM curve = **{35_000, 100_000, 350_000, 1_000_000, 10_000_000}**; impact-`k` sensitivity = **{1.0, 2.0, 4.0}**. Single global knobs, never per-sleeve tuned.
- Weighting is **risk-parity / inverse-vol on honest vol** — never an in-sample Sharpe grid.
- **G4 anti-reactive:** any de-risk rule must beat "do nothing" in an honest A/B or it is excluded. No reactive de-sizing (vol-target / drawdown-trigger) — A/B-rejected 3× historically.
- Pure + network-free functions (inputs injected), matching the `bybit_*_eval.py` pattern.
- Tests run with `pytest -o addopts=""` in the dev sandbox (the repo `addopts` injects `-n` / xdist which is absent locally).
- `unlock` is **not hard-cut**; G1/G2 decide its weight (expected ≈ 0).

## File Structure

- `src/fxhnt/application/honest_reference.py` — core: `honest_sleeve_returns`, `capacity_curve`, `spread_floor_from_snapshot`. One responsibility: turn coin weights + turnover + spread into NET return series per AUM.
- `src/fxhnt/application/honest_gates.py` — `risk_parity_weights`, `book_returns`, `gate_survives` (G1), `loo_marginal` (G2), `beats_concentration` (G3), `ab_beats_nothing` (G4).
- `src/fxhnt/application/honest_leverage.py` — `max_drawdown_at_leverage`, `leverage_ceiling`.
- `src/fxhnt/application/pump_detection.py` — `pump_signal`, `pump_sleeve_weights`, the candidate edge #4.
- `src/fxhnt/adapters/persistence/bybit_spread.py` — `read_spread_snapshot(dsn) -> dict[str,float]` (reads `bybit_real_spread`, coin→spread_bps). (New tiny reader; `read_panel` covers `turnover` already.)
- `bin/fxt`/CLI: `honest-reference-report` command → prints/writes the research table. (Wire into the existing typer app in `src/fxhnt/cli.py`.)
- Tests: `tests/integration/test_honest_reference.py`, `tests/unit/test_honest_gates.py`, `tests/unit/test_honest_leverage.py`, `tests/unit/test_pump_detection.py`.

---

### Task 1: Honest return core + fidelity invariant (spec §5 0a/0d)

**Files:**
- Create: `src/fxhnt/application/honest_reference.py`
- Test: `tests/integration/test_honest_reference.py`

**Interfaces:**
- Consumes: `sleeve_weights_and_contributions(store, sleeve, *, universe, unlock_events) -> dict[int, dict[str, tuple[float,float]]]` and `reconciliation_series(weights_by_day) -> dict[int,float]` (both `application/bybit_paper_weights.py`); `sleeve_returns_from_store(store, edge, cost_bps=0.0)` (`application/bybit_book_eval.py`).
- Produces: `honest_sleeve_returns(weights_by_day, turnover_panel, spread_bps, *, aum, participation_cap=0.03, impact_k=1.0) -> dict[int,float]`.

- [ ] **Step 1: Write the failing fidelity test**

```python
# tests/integration/test_honest_reference.py
from fxhnt.application.honest_reference import honest_sleeve_returns

def test_no_cap_no_impact_reproduces_gross_reconciliation():
    # {day: {symbol: (weight, ret)}} — the exact shape sleeve_weights_and_contributions returns.
    wbd = {1: {"AAA": (0.5, 0.02), "BBB": (-0.5, -0.01)},
           2: {"AAA": (0.4, -0.03), "BBB": (-0.6, 0.05)}}
    turnover = {"AAA": {1: 1e9, 2: 1e9}, "BBB": {1: 1e9, 2: 1e9}}   # huge ADV -> cap never binds
    # cap disabled via tiny AUM relative to ADV, impact off -> must equal Σ w·r per day
    out = honest_sleeve_returns(wbd, turnover, spread_bps={}, aum=1_000.0,
                                participation_cap=0.03, impact_k=0.0)
    assert abs(out[1] - (0.5*0.02 + -0.5*-0.01)) < 1e-9
    assert abs(out[2] - (0.4*-0.03 + -0.6*0.05)) < 1e-9
```

- [ ] **Step 2: Run test to verify it fails**

Run: `pytest tests/integration/test_honest_reference.py::test_no_cap_no_impact_reproduces_gross_reconciliation -v -o addopts=""`
Expected: FAIL — `ModuleNotFoundError: fxhnt.application.honest_reference`.

- [ ] **Step 3: Write minimal implementation**

```python
# src/fxhnt/application/honest_reference.py
"""Capacity- and cost-honest daily return series for a Bybit sleeve, from coin-level weights.

Pure + network-free: coin weights (sleeve_weights_and_contributions), the per-coin turnover (ADV) panel,
and the per-coin quoted spread are INJECTED. With the cap non-binding (huge ADV vs AUM) and impact off
(impact_k=0, no spread) this reduces to Σ_symbol weight·ret == reconciliation_series (the fidelity invariant)."""
from __future__ import annotations

import math


def honest_sleeve_returns(
    weights_by_day: dict[int, dict[str, tuple[float, float]]],
    turnover_panel: dict[str, dict[int, float]],
    spread_bps: dict[str, float],
    *, aum: float, participation_cap: float = 0.03, impact_k: float = 1.0,
) -> dict[int, float]:
    out: dict[int, float] = {}
    for day, row in weights_by_day.items():
        gross = 0.0
        cost = 0.0
        for sym, (w, r) in row.items():
            adv = turnover_panel.get(sym, {}).get(day, 0.0)
            target_notional = abs(w) * aum
            # capacity: never hold more than participation_cap * ADV; scale the weight down if we would.
            if adv > 0.0 and target_notional > participation_cap * adv:
                scale = (participation_cap * adv) / target_notional
            else:
                scale = 1.0
            w_capped = w * scale
            gross += w_capped * r
            # slippage on the traded notional (approx daily full rebalance == |w_capped|·aum).
            traded_notional = abs(w_capped) * aum
            if traded_notional > 0.0 and adv > 0.0:
                impact = impact_k * math.sqrt(traded_notional / adv) * 1e4  # bps
            else:
                impact = 0.0
            slip_bps = max(impact, spread_bps.get(sym, 0.0))
            cost += abs(w_capped) * (slip_bps / 1e4)
        out[day] = gross - cost
    return out
```

- [ ] **Step 4: Run test to verify it passes**

Run: `pytest tests/integration/test_honest_reference.py::test_no_cap_no_impact_reproduces_gross_reconciliation -v -o addopts=""`
Expected: PASS.

- [ ] **Step 5: Add the REAL-engine fidelity test (the hard stop) + commit**

Add a test that builds a small fake `_FeatureStore` (model on `tests/integration/test_bybit_book_reconciliation.py`), runs `sleeve_weights_and_contributions(store, "xsfunding")`, and asserts `honest_sleeve_returns(..., aum=1.0, impact_k=0.0, spread_bps={})` equals `sleeve_returns_from_store(store, "xsfunding", cost_bps=0.0)` day-by-day within 1e-9. Then:

```bash
git add src/fxhnt/application/honest_reference.py tests/integration/test_honest_reference.py
git commit -m "feat(honest-ref): coin-level honest return core + fidelity invariant (spec 0a/0d)"
```

---

### Task 2: Capacity cap parameterized by AUM (spec §5 0b)

**Files:**
- Modify: `src/fxhnt/application/honest_reference.py` (already caps in Task 1 — this task adds `capacity_curve`)
- Test: `tests/integration/test_honest_reference.py`

**Interfaces:**
- Produces: `capacity_curve(weights_by_day, turnover_panel, spread_bps, *, aums, participation_cap=0.03, impact_k=1.0) -> dict[float, dict[int,float]]` (AUM → net series).

- [ ] **Step 1: Write the failing test — the cap bites at high AUM, not low**

```python
def test_capacity_cap_bites_only_at_high_aum():
    from fxhnt.application.honest_reference import honest_sleeve_returns
    wbd = {1: {"THIN": (1.0, 0.10)}}                 # +10% day on a thin coin
    turnover = {"THIN": {1: 1_000_000.0}}            # $1M/day ADV
    lo = honest_sleeve_returns(wbd, turnover, {}, aum=35_000.0, participation_cap=0.03, impact_k=0.0)
    hi = honest_sleeve_returns(wbd, turnover, {}, aum=10_000_000.0, participation_cap=0.03, impact_k=0.0)
    # at $35k: target $35k > 3%*$1M=$30k -> scaled to 30/35; at $10M: target $10M -> scaled to 30k/10M (tiny)
    assert abs(lo[1] - 0.10 * (30_000/35_000)) < 1e-9
    assert hi[1] < lo[1]                              # capacity destroys the return at scale
```

- [ ] **Step 2: Run** `pytest tests/integration/test_honest_reference.py::test_capacity_cap_bites_only_at_high_aum -v -o addopts=""` — Expected: PASS (the cap logic is already in Task 1; this locks its behavior).

- [ ] **Step 3: Implement `capacity_curve`**

```python
def capacity_curve(weights_by_day, turnover_panel, spread_bps, *, aums,
                   participation_cap=0.03, impact_k=1.0):
    return {aum: honest_sleeve_returns(weights_by_day, turnover_panel, spread_bps,
                                       aum=aum, participation_cap=participation_cap, impact_k=impact_k)
            for aum in aums}
```

- [ ] **Step 4: Write + run a test that `capacity_curve` returns one series per AUM and is monotone-non-increasing in Sharpe** (compute `nav_backfill_stats` on the cumulative nav of each). Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add src/fxhnt/application/honest_reference.py tests/integration/test_honest_reference.py
git commit -m "feat(honest-ref): AUM-parameterized capacity curve (spec 0b)"
```

---

### Task 3: Market-impact slippage + spread floor + k-sensitivity (spec §5 0c)

**Files:**
- Create: `src/fxhnt/adapters/persistence/bybit_spread.py`
- Modify: `src/fxhnt/application/honest_reference.py` (add `spread_floor_from_snapshot`)
- Test: `tests/integration/test_honest_reference.py`, `tests/unit/test_bybit_spread.py`

**Interfaces:**
- Produces: `read_spread_snapshot(dsn) -> dict[str,float]` (coin→spread_bps); `honest_sleeve_returns(..., impact_k)` already charges impact; this task locks its behavior + the spread floor.

- [ ] **Step 1: Write the failing test — impact scales with √(size/ADV) and floors at spread**

```python
def test_impact_scales_and_floors_at_spread():
    from fxhnt.application.honest_reference import honest_sleeve_returns
    wbd = {1: {"C": (1.0, 0.0)}}                      # flat return -> net = -cost
    turnover = {"C": {1: 1_000_000.0}}
    # size $10k of $1M ADV -> impact = k*sqrt(0.01)*1e4 = k*1000 bps; k=1 -> 1000 bps? scale by traded frac.
    r = honest_sleeve_returns(wbd, turnover, spread_bps={}, aum=10_000.0, participation_cap=1.0, impact_k=1.0)
    # cost = |w|*(impact/1e4); impact = 1*sqrt(10000/1e6)*1e4 = 1000 bps -> cost = 1*0.10 = 0.10
    assert abs(r[1] - (-0.10)) < 1e-6
    # spread floor: with a wide spread and impact_k=0, cost == spread fraction
    r2 = honest_sleeve_returns(wbd, turnover, spread_bps={"C": 50.0}, aum=10_000.0,
                               participation_cap=1.0, impact_k=0.0)
    assert abs(r2[1] - (-50.0/1e4)) < 1e-9
```

- [ ] **Step 2: Run** the test — Expected: PASS (impact + floor already implemented in Task 1; this locks it).

- [ ] **Step 3: Implement the spread reader**

```python
# src/fxhnt/adapters/persistence/bybit_spread.py
"""Read the current bybit_real_spread snapshot (coin -> spread_bps). Read-only; a point-in-time cross-section
(all rows share one fetched_at), used as the per-coin slippage FLOOR in the honest-reference model."""
from __future__ import annotations

from sqlalchemy import create_engine, text


def read_spread_snapshot(dsn: str) -> dict[str, float]:
    eng = create_engine(dsn)
    with eng.connect() as c:
        rows = c.execute(text("select coin, spread_bps from bybit_real_spread")).all()
    return {coin: float(bps) for coin, bps in rows if bps is not None}
```

- [ ] **Step 4: Write `tests/unit/test_bybit_spread.py`** using an in-memory SQLite with a `bybit_real_spread(coin, spread_bps)` table + two rows; assert `read_spread_snapshot` returns the dict. Run — Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add src/fxhnt/adapters/persistence/bybit_spread.py tests/unit/test_bybit_spread.py tests/integration/test_honest_reference.py
git commit -m "feat(honest-ref): sqrt-impact slippage + real-spread floor reader (spec 0c)"
```

---

### Task 4: Risk-parity weighting + book returns (spec §7 Phase 2)

**Files:**
- Create: `src/fxhnt/application/honest_gates.py`
- Test: `tests/unit/test_honest_gates.py`

**Interfaces:**
- Consumes: honest per-sleeve series `dict[int,float]` (Task 1).
- Produces: `risk_parity_weights(returns_by_sleeve) -> dict[str,float]` (∝ 1/vol, sum to 1); `book_returns(returns_by_sleeve, weights) -> dict[int,float]` (weighted, over common days).

- [ ] **Step 1: Write the failing test**

```python
# tests/unit/test_honest_gates.py
from fxhnt.application.honest_gates import risk_parity_weights, book_returns

def test_risk_parity_weights_inverse_vol_sum_to_one():
    rbs = {"lowvol": {1: 0.001, 2: -0.001, 3: 0.001},   # tiny vol -> big weight
           "hivol":  {1: 0.10,  2: -0.10,  3: 0.10}}     # big vol -> small weight
    w = risk_parity_weights(rbs)
    assert abs(sum(w.values()) - 1.0) < 1e-9
    assert w["lowvol"] > w["hivol"]

def test_book_returns_weighted_over_common_days():
    rbs = {"a": {1: 0.02, 2: 0.04}, "b": {2: 0.10, 3: 0.20}}   # common day = 2 only
    br = book_returns(rbs, {"a": 0.5, "b": 0.5})
    assert set(br) == {2} and abs(br[2] - (0.5*0.04 + 0.5*0.10)) < 1e-9
```

- [ ] **Step 2: Run** `pytest tests/unit/test_honest_gates.py -v -o addopts=""` — Expected: FAIL (module missing).

- [ ] **Step 3: Implement**

```python
# src/fxhnt/application/honest_gates.py
"""Falsification gates + risk-parity allocation on the honest per-sleeve return series. Pure functions."""
from __future__ import annotations

import statistics as st


def risk_parity_weights(returns_by_sleeve: dict[str, dict[int, float]]) -> dict[str, float]:
    inv = {}
    for s, series in returns_by_sleeve.items():
        vals = list(series.values())
        vol = st.pstdev(vals) if len(vals) > 1 else 0.0
        inv[s] = (1.0 / vol) if vol > 0.0 else 0.0
    total = sum(inv.values())
    if total <= 0.0:
        n = len(returns_by_sleeve)
        return {s: 1.0 / n for s in returns_by_sleeve} if n else {}
    return {s: v / total for s, v in inv.items()}


def book_returns(returns_by_sleeve: dict[str, dict[int, float]],
                 weights: dict[str, float]) -> dict[int, float]:
    days = None
    for series in returns_by_sleeve.values():
        days = set(series) if days is None else (days & set(series))
    days = sorted(days or set())
    return {d: sum(weights.get(s, 0.0) * returns_by_sleeve[s][d] for s in returns_by_sleeve) for d in days}
```

- [ ] **Step 4: Run** `pytest tests/unit/test_honest_gates.py -v -o addopts=""` — Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add src/fxhnt/application/honest_gates.py tests/unit/test_honest_gates.py
git commit -m "feat(honest-ref): risk-parity weights + book aggregation (spec Phase 2)"
```

---

### Task 5: Gates G1 survives, G2 LOO, G3 beats-concentration (spec §6)

**Files:**
- Modify: `src/fxhnt/application/honest_gates.py`
- Test: `tests/unit/test_honest_gates.py`

**Interfaces:**
- Consumes: `risk_parity_weights`, `book_returns` (Task 4); `nav_backfill_stats(nav: list[float]) -> BackfillStats(.sharpe)` (`application/nav_stats.py`).
- Produces: `sharpe_of(series) -> float`; `gate_survives(series, *, min_sharpe=1.0) -> bool`; `loo_marginal(returns_by_sleeve) -> dict[str,float]` (full_book_sharpe − drop-one_sharpe per sleeve); `beats_concentration(returns_by_sleeve) -> bool` (rp-book Sharpe > best single sleeve).

- [ ] **Step 1: Write the failing tests**

```python
def test_gate_survives_threshold():
    from fxhnt.application.honest_gates import gate_survives
    strong = {i: (0.01 if i % 2 else -0.002) for i in range(400)}   # positive drift, modest vol
    assert gate_survives(strong, min_sharpe=1.0) is True
    flat = {i: (0.001 if i % 2 else -0.001) for i in range(400)}
    assert gate_survives(flat, min_sharpe=1.0) is False

def test_loo_marginal_flags_dead_weight():
    from fxhnt.application.honest_gates import loo_marginal
    # 'dead' is pure noise; dropping it should NOT hurt (marginal ~ <= 0)
    good = {i: (0.01 if i % 2 else -0.003) for i in range(400)}
    dead = {i: (0.002 if i % 3 else -0.002) for i in range(400)}
    m = loo_marginal({"good": good, "dead": dead})
    assert m["good"] > m["dead"]
```

- [ ] **Step 2: Run** — Expected: FAIL (functions missing).

- [ ] **Step 3: Implement**

```python
# add to honest_gates.py
import math
from fxhnt.application.nav_stats import nav_backfill_stats


def _nav(series: dict[int, float]) -> list[float]:
    nav, v = [], 1.0
    for d in sorted(series):
        v *= (1.0 + series[d])
        nav.append(v)
    return [1.0] + nav


def sharpe_of(series: dict[int, float]) -> float:
    return nav_backfill_stats(_nav(series)).sharpe


def gate_survives(series: dict[int, float], *, min_sharpe: float = 1.0) -> bool:
    return sharpe_of(series) > min_sharpe


def loo_marginal(returns_by_sleeve: dict[str, dict[int, float]]) -> dict[str, float]:
    full = sharpe_of(book_returns(returns_by_sleeve, risk_parity_weights(returns_by_sleeve)))
    out = {}
    for drop in returns_by_sleeve:
        rest = {s: v for s, v in returns_by_sleeve.items() if s != drop}
        rest_sh = sharpe_of(book_returns(rest, risk_parity_weights(rest))) if rest else 0.0
        out[drop] = full - rest_sh
    return out


def beats_concentration(returns_by_sleeve: dict[str, dict[int, float]]) -> bool:
    book_sh = sharpe_of(book_returns(returns_by_sleeve, risk_parity_weights(returns_by_sleeve)))
    best_single = max((sharpe_of(v) for v in returns_by_sleeve.values()), default=0.0)
    return book_sh > best_single
```

- [ ] **Step 4: Run** `pytest tests/unit/test_honest_gates.py -v -o addopts=""` — Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add src/fxhnt/application/honest_gates.py tests/unit/test_honest_gates.py
git commit -m "feat(honest-ref): gates G1 survives / G2 LOO / G3 beats-concentration (spec 6)"
```

---

### Task 6: Leverage ceiling vs real drawdown + G4 anti-reactive (spec §7 book layer, §6 G4)

**Files:**
- Create: `src/fxhnt/application/honest_leverage.py`
- Modify: `src/fxhnt/application/honest_gates.py` (add `ab_beats_nothing`)
- Test: `tests/unit/test_honest_leverage.py`, `tests/unit/test_honest_gates.py`

**Interfaces:**
- Produces: `max_drawdown_at_leverage(book_series, leverage) -> float` (negative); `leverage_ceiling(book_series, *, dd_limit=-0.35, stress=2.0, grid=(1.0,1.25,1.5,1.75,2.0)) -> float`; `ab_beats_nothing(base_series, derisked_series) -> bool`.

- [ ] **Step 1: Write the failing tests**

```python
# tests/unit/test_honest_leverage.py
from fxhnt.application.honest_leverage import max_drawdown_at_leverage, leverage_ceiling

def test_drawdown_scales_with_leverage():
    s = {1: 0.05, 2: -0.10, 3: 0.04}
    dd1 = max_drawdown_at_leverage(s, 1.0)
    dd2 = max_drawdown_at_leverage(s, 2.0)
    assert dd1 < 0.0 and dd2 < dd1        # 2x is a deeper (more negative) drawdown

def test_leverage_ceiling_respects_stress_buffer():
    s = {1: 0.02, 2: -0.05, 3: 0.03, 4: -0.02}      # mild book
    # with a 2x stress buffer under -35%, the ceiling picks the largest grid L whose 2*maxDD >= -35%
    lev = leverage_ceiling(s, dd_limit=-0.35, stress=2.0)
    assert 1.0 <= lev <= 2.0
```

- [ ] **Step 2: Run** — Expected: FAIL (module missing).

- [ ] **Step 3: Implement**

```python
# src/fxhnt/application/honest_leverage.py
"""Real fat-tail drawdown of the honest book at constant leverage, and the ex-ante leverage ceiling
(K(D*)) — anchored to a REAL drawdown with a stress buffer, never to a smooth modeled curve."""
from __future__ import annotations

import math


def max_drawdown_at_leverage(book_series: dict[int, float], leverage: float) -> float:
    peak, trough, v = 1.0, 0.0, 1.0
    for d in sorted(book_series):
        step = 1.0 + leverage * book_series[d]
        if step <= 0.0:            # wipeout at this leverage
            return -1.0
        v *= step
        peak = max(peak, v)
        trough = min(trough, v / peak - 1.0)
    return trough


def leverage_ceiling(book_series, *, dd_limit=-0.35, stress=2.0,
                     grid=(1.0, 1.25, 1.5, 1.75, 2.0)) -> float:
    ok = [L for L in grid if stress * max_drawdown_at_leverage(book_series, L) >= dd_limit]
    return max(ok) if ok else 0.0
```

- [ ] **Step 4: Add `ab_beats_nothing` to `honest_gates.py` + test** (G4): a de-risk variant series only "wins" if its Sharpe exceeds the base by a margin AND it is not merely lower-vol; keep it simple — `sharpe_of(derisked) > sharpe_of(base)`. Test that an identical series does NOT beat nothing.

```python
def ab_beats_nothing(base_series: dict[int, float], derisked_series: dict[int, float]) -> bool:
    return sharpe_of(derisked_series) > sharpe_of(base_series)
```

- [ ] **Step 5: Run both test files + commit**

Run: `pytest tests/unit/test_honest_leverage.py tests/unit/test_honest_gates.py -v -o addopts=""` — Expected: PASS.
```bash
git add src/fxhnt/application/honest_leverage.py src/fxhnt/application/honest_gates.py tests/unit/test_honest_leverage.py tests/unit/test_honest_gates.py
git commit -m "feat(honest-ref): leverage ceiling vs real DD + G4 anti-reactive gate (spec 7)"
```

---

### Task 7: Candidate edge #4 — pump/anomaly detection (spec §8)

**Files:**
- Create: `src/fxhnt/application/pump_detection.py`
- Test: `tests/unit/test_pump_detection.py`

**Interfaces:**
- Consumes: `store.read_panel(symbols, "turnover")` and `crypto_close_panel()`; `honest_sleeve_returns` (Task 1) for the execution gate.
- Produces: `pump_signal(close_panel, turnover_panel, *, z=3.0) -> dict[int, dict[str, float]]` (day→coin→signed target weight, ONLY on same-day-observable anomalies, no look-ahead); `evaluate_pump_edge(weights_by_day, turnover_panel, spread_bps, *, aum, impact_k) -> dict` (returns net Sharpe + the 3-gate verdict).

- [ ] **Step 1: Write the failing test — signal only fires on an anomaly, uses only past data**

```python
# tests/unit/test_pump_detection.py
from fxhnt.application.pump_detection import pump_signal

def test_signal_fires_on_volume_zspike_and_is_causal():
    # day 10 has a turnover z-spike vs the trailing window -> a signal; earlier calm days -> none.
    turnover = {"X": {d: 1_000_000.0 for d in range(1, 10)}}
    turnover["X"][10] = 50_000_000.0                 # 50x spike
    close = {"X": {d: 100.0 for d in range(1, 11)}}
    sig = pump_signal(close, turnover, z=3.0)
    assert 10 in sig and "X" in sig[10]              # fires on the spike day
    assert all("X" not in sig.get(d, {}) for d in range(1, 10))   # not before (causal)
```

- [ ] **Step 2: Run** — Expected: FAIL (module missing).

- [ ] **Step 3: Implement** a causal detector: for each coin-day, z-score today's turnover vs the trailing N-day window (strictly `< day`); emit a signed target (e.g. short the pump, `-1`, capped) when `z > z`. No future data touched.

```python
# src/fxhnt/application/pump_detection.py
"""Candidate edge #4: causal pump/anomaly detection. A per-coin, same-day-observable turnover z-spike ->
a (short-the-pump) target weight. Must be run through honest_sleeve_returns to see if it survives the real
pump-day slippage (the killer). Pure; no look-ahead."""
from __future__ import annotations

import statistics as st

_WINDOW = 20


def pump_signal(close_panel, turnover_panel, *, z: float = 3.0) -> dict[int, dict[str, float]]:
    out: dict[int, dict[str, float]] = {}
    for sym, series in turnover_panel.items():
        days = sorted(series)
        for i, d in enumerate(days):
            hist = [series[days[j]] for j in range(max(0, i - _WINDOW), i)]   # strictly past
            if len(hist) < _WINDOW:
                continue
            mu, sd = st.mean(hist), st.pstdev(hist)
            if sd > 0.0 and (series[d] - mu) / sd > z:
                out.setdefault(d, {})[sym] = -1.0     # short the pump (defined-risk sizing added downstream)
    return out
```

- [ ] **Step 4: Add `evaluate_pump_edge` + a test** that (a) builds `{day:{sym:(weight, ret)}}` from the signal × a synthetic reversal return, (b) runs `honest_sleeve_returns(..., impact_k=1.0)`, and asserts the returned dict has keys `sharpe`, `survives_execution` (net Sharpe > 0 after slippage), `marginal_vs_positioning`. Run — Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add src/fxhnt/application/pump_detection.py tests/unit/test_pump_detection.py
git commit -m "feat(honest-ref): candidate edge #4 causal pump detector + 3-gate eval (spec 8)"
```

---

### Task 8: Report CLI — the research deliverable (spec §9)

**Files:**
- Create: `src/fxhnt/application/honest_report.py`
- Modify: `src/fxhnt/cli.py` (add `honest-reference-report` command)
- Test: `tests/integration/test_honest_report.py`

**Interfaces:**
- Consumes: everything above + a real `_FeatureStore` (the analytical store), `read_spread_snapshot`, `read_panel(symbols, "turnover")`, `unlock_events` source.
- Produces: `build_honest_report(store, spread_bps, unlock_events, *, sleeves, aums, ks) -> dict` (per-sleeve honest stats per (AUM,k), the risk-parity book, gate verdicts G1-G4, leverage ceiling, and the edge-#4 verdict) — pure; the CLI just prints it.

- [ ] **Step 1: Write the failing test** — `build_honest_report` on a fake store returns a dict with keys `sleeves`, `capacity_curve`, `gates`, `leverage_ceiling`, `edge4`; the `k`-sensitivity has 3 entries; the AUM curve has 5. Run — Expected: FAIL.

- [ ] **Step 2: Implement `build_honest_report`** — loop sleeves × aums × ks calling `honest_sleeve_returns` + `nav_backfill_stats`; assemble the risk-parity book at the base (aum=100k, k=2); call `gate_survives`/`loo_marginal`/`beats_concentration`/`leverage_ceiling`/`evaluate_pump_edge`. **Survivorship guard:** assert the coin universe count from `read_panel` matches the ingestion's survivorship-free count (log a warning if a survivorship flag is unavailable — do not silently assume).

- [ ] **Step 3: Wire the CLI command** in `src/fxhnt/cli.py`:

```python
@app.command("honest-reference-report")
def honest_reference_report() -> None:
    """Research-only: print the capacity/cost/leverage-honest reference table + gate verdicts. Read-only."""
    from fxhnt.application.honest_report import build_honest_report
    # ... construct the analytical store + spread snapshot + unlock events, then:
    import json, typer
    typer.echo(json.dumps(build_honest_report(...), indent=2, default=str))
```

- [ ] **Step 4: Run** `pytest tests/integration/test_honest_report.py -v -o addopts=""` — Expected: PASS. Then run the CLI once against the real store in-cluster (`kubectl exec <dagster-pod> -c daemon -- python -m fxhnt.cli honest-reference-report`) and eyeball the numbers vs the SQL research estimates (xsfunding Sh ≈ 3, unlock collapses, book Sh ≈ 1.7 at P98-equivalent). Discrepancy > ~0.3 Sharpe ⇒ investigate before trusting.

- [ ] **Step 5: Commit**

```bash
git add src/fxhnt/application/honest_report.py src/fxhnt/cli.py tests/integration/test_honest_report.py
git commit -m "feat(honest-ref): honest-reference research report CLI (spec 9)"
```

---

## Self-Review

**Spec coverage:** §3 data → Tasks 1/3 (turnover via `read_panel`, spread via `bybit_spread.py`). §5 0a → Task 1 (fidelity invariant). §5 0b → Task 2 (capacity curve, AUM series). §5 0c → Task 3 (sqrt-impact + spread floor + k-sensitivity). §5 0d → Tasks 1/8 (`nav_backfill_stats`). §6 G1/G2/G3 → Task 5. §6 G4 → Task 6. §7 allocation → Task 4; leverage ceiling → Task 6; position cap → Tasks 1-2; tail-drop → G1/G2 (Task 5) surfaced in the report (Task 8). §8 edge #4 → Task 7. §9 report/scope → Task 8. §10 survivorship guard → Task 8 Step 2; slippage-sensitivity → Task 8 `ks`. No live-cockpit change anywhere — confirmed (all read-only, one CLI print). **Gap intentionally deferred:** the "hard ADV cap vs slippage-penalty" belt-and-suspenders — Task 1 implements BOTH (cap scales the weight, impact charges the residual), satisfying §11.

**Placeholder scan:** no TBD/TODO; every code step shows real code against the real APIs (`sleeve_weights_and_contributions`, `read_panel`, `nav_backfill_stats`, `reconciliation_series`). Task 8 Steps 1-2 describe the loop shape with exact keys rather than 60 lines of assembly — acceptable as the report is pure glue over tested units; the test pins its output keys.

**Type consistency:** series are `dict[int,float]` throughout; coin weights `dict[int,dict[str,tuple[float,float]]]`; turnover/close panels `dict[str,dict[int,float]]`; spread `dict[str,float]`. `sharpe_of`/`gate_survives`/`book_returns` signatures match across Tasks 4-8.

---

## Execution Handoff

Plan complete and saved to `docs/superpowers/plans/2026-07-15-crypto-honest-reference-pipeline.md`. Two execution options:

1. **Subagent-Driven (recommended)** — fresh subagent per task, review between tasks, fast iteration.
2. **Inline Execution** — execute tasks in this session with checkpoints.

Which approach?
