# xsfunding Paper-Forward Track Implementation Plan

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

**Goal:** Wire the validated `executable` cross-sectional funding construction as a live daily paper-forward cockpit track (`xsfunding`) that experiences the REAL net-of-everything return (funding + basis + the tail the backtest had to guard out), with its backtest verdict surfaced.

**Architecture:** A live-booking `ForwardStrategy` (mirrors the existing eqfactor/funding tracks) that each day fetches live universe + trailing funding + perp & spot prices, books the prior delta-neutral book's realized return using the SAME `cross_sectional_funding` domain math the backtest validated, rebalances `executable` weights, persists state in `extra`. Registered in `STRATEGY_REGISTRY` + a Dagster asset in the daily combined-book job + cockpit, backtest verdict in `backtest_summary`.

**Tech Stack:** Python 3.12, `BinanceFundingClient` + Binance spot/perp tickers, the `cross_sectional_funding` domain, `ForwardTracker`/`ForwardStrategy`, Dagster, cockpit pg. pytest, strict mypy.

**House rules:** TDD, NO live network in tests (fake clients), strict mypy (no NEW errors), commit per task (do NOT push until the deploy task), trailer `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`.

---

## Reuse contract (READ these first)
- `src/fxhnt/application/forward_tracker.py` — `ForwardStrategy` Protocol `advance(last_date: str|None, extra: dict) -> tuple[list[tuple[str,float]], dict]`; `ForwardTracker(strategy, state_file).step()`. Study the LIVE-BOOKING pattern (positions+marks in `extra`).
- `src/fxhnt/application/paper_strategies.py` — the existing `FundingCarryStrategy` live track (the closest analog: live crypto booking). Mirror its shape.
- `src/fxhnt/application/equity_factor_strategy.py` — `EquityFactorLong.advance` live-booking (positions/prices/last_rebal in extra, monthly rebal, book_return on prior book). Mirror this structure.
- `src/fxhnt/adapters/data/binance_funding.py` — `BinanceFundingClient`: `universe() -> {sym: quoteVolume}`, `scan(prev) -> (liq, tf30, last24)` (liq=liquid universe vol, tf30=trailing-30d-mean funding, last24=today's funding). 
- `src/fxhnt/domain/cross_sectional_funding.py` — `funding_score`-style scoring + `construction_weights(scores, "executable", quantile, borrowable)` + the signed-carry concept.
- `src/fxhnt/registry.py`, `src/fxhnt/adapters/orchestration/assets.py` (eqfactor_long_nav, cockpit_forward), `definitions.py` (combined_book_job + Definitions) — the wiring pattern (see the eqfactor entries).
- `src/fxhnt/application/backtest_ingest.py` + `adapters/persistence/forward_nav.py` `upsert_backtest_summary` — the verdict-surface path.

---

## File Structure
- Create `src/fxhnt/adapters/data/binance_xsfunding_live.py` — `BinanceXsFundingLive`: per-coin live `{sym: (trailing_funding, today_funding, perp_price, spot_price, qvol)}` for the universe.
- Create `src/fxhnt/application/xsfunding_strategy.py` — `XsFundingForward` live-booking ForwardStrategy (executable construction, delta-neutral funding+basis book).
- Modify `src/fxhnt/registry.py` — add `xsfunding` entry.
- Modify `src/fxhnt/adapters/orchestration/assets.py` + `definitions.py` — `xsfunding_nav` asset in the daily job + Definitions + cockpit_forward dep.
- Tests: `tests/integration/test_xsfunding_strategy.py`, `tests/integration/test_binance_xsfunding_live.py`.

---

## Task 1: live data source

**Files:** Create `src/fxhnt/adapters/data/binance_xsfunding_live.py`; Test `tests/integration/test_binance_xsfunding_live.py`.

- [ ] **Step 1** — READ `binance_funding.py` to confirm `BinanceFundingClient.scan`/`universe` signatures + how it does `_get`. The live source COMPOSES it for universe+funding, and adds perp+spot current prices via two bulk ticker calls: perp `fapi/v1/ticker/price` (all perps), spot `api/v3/ticker/price` (all spot). 

- [ ] **Step 2** — failing test `tests/integration/test_binance_xsfunding_live.py` (inject fakes; NO live net):
```python
from fxhnt.adapters.data.binance_xsfunding_live import BinanceXsFundingLive


class _FakeFunding:
    def scan(self, prev):
        liq = {"AAAUSDT": 5e6, "BBBUSDT": 3e6}
        tf30 = {"AAAUSDT": 0.001, "BBBUSDT": 0.0003}
        last24 = {"AAAUSDT": 0.0012, "BBBUSDT": 0.0004}
        return liq, tf30, last24


def test_live_source_merges_funding_and_prices(monkeypatch):
    src = BinanceXsFundingLive(funding=_FakeFunding())
    monkeypatch.setattr(src, "_perp_prices", lambda: {"AAAUSDT": 100.0, "BBBUSDT": 10.0})
    monkeypatch.setattr(src, "_spot_prices", lambda: {"AAAUSDT": 99.5, "BBBUSDT": 10.1})
    snap = src.snapshot(prev={})
    assert snap["AAAUSDT"] == (0.001, 0.0012, 100.0, 99.5, 5e6)   # (tf, today_funding, perp, spot, qvol)
    assert snap["BBBUSDT"][2] == 10.0 and snap["BBBUSDT"][3] == 10.1
```

- [ ] **Step 3** — run → FAIL.

- [ ] **Step 4** — implement `src/fxhnt/adapters/data/binance_xsfunding_live.py`:
```python
"""Live per-coin snapshot for the cross-sectional funding paper track: trailing funding,
today's funding, perp price, spot price, quote-volume — for the liquid universe."""
from __future__ import annotations

import json
import urllib.request
from typing import Any

from fxhnt.adapters.data.binance_funding import BinanceFundingClient

_FAPI = "https://fapi.binance.com"
_SPOT = "https://api.binance.com"


class BinanceXsFundingLive:
    def __init__(self, funding: Any | None = None) -> None:
        self._f = funding if funding is not None else BinanceFundingClient()

    def _get(self, url: str) -> Any:
        with urllib.request.urlopen(url, timeout=30) as r:
            return json.loads(r.read())

    def _perp_prices(self) -> dict[str, float]:
        return {x["symbol"]: float(x["price"]) for x in self._get(f"{_FAPI}/fapi/v1/ticker/price")}

    def _spot_prices(self) -> dict[str, float]:
        return {x["symbol"]: float(x["price"]) for x in self._get(f"{_SPOT}/api/v3/ticker/price")}

    def snapshot(self, prev: dict[str, float]) -> dict[str, tuple[float, float, float, float, float]]:
        """{sym: (trailing_funding, today_funding, perp_price, spot_price, qvol)} over the liquid
        universe (+ any prev-held coin still scannable). Coins lacking a spot price are omitted
        (funding-only handling happens upstream if needed)."""
        liq, tf30, last24 = self._f.scan(prev)
        perp, spot = self._perp_prices(), self._spot_prices()
        out: dict[str, tuple[float, float, float, float, float]] = {}
        for s in liq:
            if s in perp and s in spot and perp[s] > 0 and spot[s] > 0:
                out[s] = (tf30.get(s, 0.0), last24.get(s, 0.0), perp[s], spot[s], liq[s])
        return out
```

- [ ] **Step 5** — run → PASS; mypy → no errors; full suite → PASS.

- [ ] **Step 6** — commit:
```bash
git add src/fxhnt/adapters/data/binance_xsfunding_live.py tests/integration/test_binance_xsfunding_live.py
git commit -m "feat(xsfunding): live per-coin snapshot (funding + perp + spot + qvol)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 2: XsFundingForward live-booking strategy

**Files:** Create `src/fxhnt/application/xsfunding_strategy.py`; Test `tests/integration/test_xsfunding_strategy.py`.

- [ ] **Step 1** — READ `EquityFactorLong.advance` + `ForwardTracker` to mirror the live-booking contract exactly (extra carries positions/marks/last_date; advance books prior book then rebalances; returns `(rows, extra)` where rows = `[(date, realized_return)]`).

- [ ] **Step 2** — failing test `tests/integration/test_xsfunding_strategy.py`:
```python
from fxhnt.application.xsfunding_strategy import XsFundingForward


class _FakeLive:
    def __init__(self, seq): self._seq = seq; self._i = -1
    def snapshot(self, prev):
        self._i += 1
        return self._seq[self._i]


def _snap(funding):   # 5 coins, perp=spot=100 (no basis move on day 1), given trailing funding
    return {s: (f, f, 100.0, 100.0, 5e6) for s, f in funding.items()}


def test_first_advance_inception_no_booking():
    live = _FakeLive([_snap({"A": 0.002, "B": 0.001, "C": 0.0, "D": -0.001, "E": -0.002})])
    strat = XsFundingForward(live, quantile=0.4, clock=lambda: "2026-06-19")
    rows, extra = strat.advance(None, {})
    assert rows == []                          # inception: no prior book to book
    assert extra["positions"]                  # executable weights set (top-carry long)


def test_second_advance_books_delta_neutral_return():
    day1 = _snap({"A": 0.002, "B": 0.001, "C": 0.0, "D": -0.001, "E": -0.002})
    # day2: A's perp falls to 99 (short-perp gains) and spot flat -> basis P&L positive; funding accrues
    day2 = {"A": (0.002, 0.002, 99.0, 100.0, 5e6), "B": (0.001, 0.001, 100.0, 100.0, 5e6),
            "C": (0.0, 0.0, 100.0, 100.0, 5e6), "D": (-0.001, -0.001, 100.0, 100.0, 5e6),
            "E": (-0.002, -0.002, 100.0, 100.0, 5e6)}
    live = _FakeLive([day1, day2])
    strat = XsFundingForward(live, quantile=0.4, clock=lambda: "2026-06-20")
    _, extra = strat.advance(None, {})         # inception (day1)
    rows, extra = strat.advance("2026-06-19", extra)
    assert len(rows) == 1 and rows[0][0] == "2026-06-20"
    assert rows[0][1] > 0.0                     # A long-carry: short-perp gain (99/100) + funding
```

- [ ] **Step 3** — run → FAIL.

- [ ] **Step 4** — implement `src/fxhnt/application/xsfunding_strategy.py`. Use the cross-sectional scoring (robust_z of trailing funding across the snapshot coins) + `construction_weights(scores, "executable", quantile, borrowable)` (borrowable = high-qvol coins) + book the prior book's delta-neutral return `Σ w·[(spot/spot_prev−1) − (perp/perp_prev−1) + today_funding] − turnover·cost`. State (`extra`): `{positions: {sym: w}, perp: {sym: px}, spot: {sym: px}, last_date}`. On inception (no prior positions) return `[]`. Reuse `robust_z` from equity_factor (or `funding_score` adapted to the snapshot). Daily rebalance. cost = a flat `cost_bps` (default 8bp) on turnover (live track doesn't need the liquidity-slip model — keep simple + documented).
```python
"""Live-booking cross-sectional funding-dispersion paper track (executable construction).
Books the REAL delta-neutral return (funding + spot-perp basis) forward each day — so the
paper record experiences the basis tail the backtest had to guard out. Mirrors the
equity-factor live tracks' ForwardStrategy contract."""
from __future__ import annotations

from typing import Any, Callable

from fxhnt.domain.strategies.equity_factor import robust_z
from fxhnt.domain.cross_sectional_funding import construction_weights


def _today() -> str:
    import datetime as dt
    return dt.date.today().isoformat()


class XsFundingForward:
    def __init__(self, live: Any, *, quantile: float = 0.2, cost_bps: float = 8.0,
                 borrowable_qvol: float = 2e7, clock: Callable[[], str] = _today) -> None:
        self._live, self._q, self._cost = live, quantile, cost_bps / 1e4
        self._bq, self._clock = borrowable_qvol, clock

    def advance(self, last_date: str | None, extra: dict[str, Any]) -> tuple[list[tuple[str, float]], dict[str, Any]]:
        today = self._clock()
        prev_pos: dict[str, float] = extra.get("positions", {})
        prev_perp: dict[str, float] = extra.get("perp", {})
        prev_spot: dict[str, float] = extra.get("spot", {})
        snap = self._live.snapshot(prev_pos)            # {sym: (tf, today_funding, perp, spot, qvol)}
        syms = sorted(snap)
        # book prior book's realized delta-neutral return
        rows: list[tuple[str, float]] = []
        if prev_pos:
            realized = 0.0
            for s, w in prev_pos.items():
                if s in snap and s in prev_perp and s in prev_spot and prev_perp[s] > 0 and prev_spot[s] > 0:
                    _, tf_today, perp, spot, _ = snap[s]
                    spot_ret = spot / prev_spot[s] - 1.0
                    perp_ret = perp / prev_perp[s] - 1.0
                    realized += w * ((spot_ret - perp_ret) + tf_today)
            # turnover cost vs the NEW book computed below — compute weights first
            # (we charge cost on the rebalance into the new book)
            ...
        # new executable weights from trailing funding cross-section
        tf = [snap[s][0] for s in syms]
        scores = {syms[i]: z for i, z in enumerate(robust_z(tf))}
        borrowable = {s for s in syms if snap[s][4] >= self._bq}
        new_w = construction_weights(scores, "executable", quantile=self._q, borrowable=borrowable)
        if prev_pos:
            turnover = sum(abs(new_w.get(s, 0.0) - prev_pos.get(s, 0.0)) for s in set(new_w) | set(prev_pos))
            realized -= turnover * self._cost / 2.0
            rows.append((today, realized))
        extra = {"positions": new_w,
                 "perp": {s: snap[s][2] for s in new_w},
                 "spot": {s: snap[s][3] for s in new_w},
                 "last_date": today}
        return rows, extra
```
(Clean up the `...` placeholder — fold the turnover cost after weights are computed, as shown in the bottom block; the top `if prev_pos:` block computes only the carry+basis realized, then after weights the turnover cost is subtracted and the row appended. Restructure so it's linear and the two unit tests pass.)

- [ ] **Step 5** — run → PASS; mypy → no errors; full suite → PASS.

- [ ] **Step 6** — commit:
```bash
git add src/fxhnt/application/xsfunding_strategy.py tests/integration/test_xsfunding_strategy.py
git commit -m "feat(xsfunding): live-booking forward strategy (delta-neutral funding+basis, executable)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 3: registry + Dagster wiring + verdict surface

**Files:** Modify `src/fxhnt/registry.py`, `src/fxhnt/adapters/orchestration/assets.py`, `definitions.py`; Test `tests/integration/test_orchestration_definitions.py` (update count + xsfunding present).

- [ ] **Step 1** — add to `STRATEGY_REGISTRY` (mirror an eqfactor entry):
```python
    "xsfunding": {
        "display_name": "Crypto funding — cross-sectional", "sleeve": "crypto-funding",
        "venue": "binance-perp", "state_file": "xsfunding_state",
        "gate_spec": {"min_days": 60, "min_total_return": 0.0, "min_sharpe": 0.5},
    },
```

- [ ] **Step 2** — add a Dagster asset `xsfunding_nav` in `assets.py` (mirror `eqfactor_long_nav`): construct `XsFundingForward(BinanceXsFundingLive())` wrapped in `ForwardTracker(..., f"{_data_dir()}/xsfunding_state.json").step()`, log forward_days. Add `xsfunding_nav` to `cockpit_forward`'s deps. Register in `definitions.py` (import + combined_book_job selection + Definitions assets list). Update `test_orchestration_definitions.py` (asset count +1, assert `xsfunding_nav` present).

- [ ] **Step 3** — run the orchestration test + full suite → PASS; mypy → no new errors; confirm `defs` loads (`python -c "from fxhnt.adapters.orchestration.definitions import defs"`).

- [ ] **Step 4** — commit:
```bash
git add src/fxhnt/registry.py src/fxhnt/adapters/orchestration/assets.py src/fxhnt/adapters/orchestration/definitions.py tests/integration/test_orchestration_definitions.py
git commit -m "feat(xsfunding): register xsfunding paper track + Dagster asset in daily book

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 4: deploy + surface verdict (controller)
- [ ] Push fxhnt master; rebuild cockpit image via Argo `fxhnt-cockpit` at HEAD; wait for Succeeded.
- [ ] Persist the xsfunding backtest verdict (executable, net-of-basis) to `backtest_summary` keyed `xsfunding` — extend `backtest_ingest._CONSTRUCTION_TO_SID` mapping or run a one-off upsert from the basis report's `executable@5e6` cell (sharpe ~6.3, but record with the honest caveat). Simplest: a tiny one-off that reads `/tmp/funding_basis_report.json` executable cell → `upsert_backtest_summary(BacktestSummary(strategy_id="xsfunding", ...))` against the cockpit pg (in-cluster Job or local+port-forward). 
- [ ] Verify: cockpit fleet shows `xsfunding` (gate=WAIT initially); the daily job materializes `xsfunding_nav`; backtest verdict visible. Trigger one manual materialization to seed inception.

## Final review (after Task 3) + honest note
Final review over the whole diff; full suite + mypy. The paper track will accumulate the REAL net-of-everything record (incl. the basis tail the backtest guarded out + execution reality) — gate=WAIT until weeks of forward data. Do NOT oversell the backtest Sharpe in the cockpit; the surfaced verdict carries the funding-research-consistent ~3-5 deployable expectation.
