refactor(surfer): reconcile live funding deploy onto cross-sectional construction

The cockpit ran TWO funding tracks: 'funding' (absolute 5bp-hurdle, long-only, faithful
port of crypto_funding_paper.py) and 'xsfunding' (cross-sectional long-top/short-bottom,
delta-neutral). Investigation (regime-switch backtest) showed the absolute-hurdle variant
sits idle ~60% of days and was flat through 2022, while the cross-sectional construction
harvests funding DISPERSION, trades continuously, and made +71% in the 2022 backtest
(LUNA/FTX deleveraging dispersion). The two were a confusing parallel where the conservative
one masqueraded as the dormant crown jewel.

Retire the absolute 'funding' track (asset funding_nav, registry entry, job membership) and
promote 'xsfunding' as the canonical funding deploy (relabelled 'Crypto funding harvest
(cross-sectional, delta-neutral)'). FundingCarryStrategy stays in paper_strategies.py
(tested, on the shelf) — only the live deploy changes. Tests updated (12 assets now);
full suite 499 passed, mypy clean on changed lines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-20 10:37:27 +02:00
parent 92f1d0b40e
commit 17fa0d0394
5 changed files with 18 additions and 23 deletions

View File

@@ -183,15 +183,11 @@ def gd_nav(context: AssetExecutionContext) -> dict: # type: ignore[type-arg]
)
@asset
def funding_nav(context: AssetExecutionContext) -> dict: # type: ignore[type-arg]
"""Paper-forward crypto funding harvest (Binance public futures API, no key)."""
from fxhnt.adapters.data.binance_funding import BinanceFundingClient
from fxhnt.application.paper_strategies import FundingCarryStrategy
return _run_paper_tracker(
context, "funding_nav", lambda: FundingCarryStrategy(BinanceFundingClient()), "funding_paper_state"
)
# NOTE: the absolute-hurdle `funding_nav` track was RETIRED 2026-06-20 — its 5bp absolute long-only
# hurdle sits idle ~60% of the time (flat in 2022) and was a misleading parallel to the cross-sectional
# `xsfunding_nav`, which IS the deployed funding edge (harvests dispersion, always active, +71% in the
# 2022 backtest). The faithful-port FundingCarryStrategy stays in paper_strategies.py (tested) for the
# record; the live funding deploy is now exclusively cross-sectional. See xsfunding_nav below.
@asset
@@ -258,7 +254,7 @@ def xsfunding_nav(context: AssetExecutionContext) -> dict: # type: ignore[type-
)
@asset(deps=[combined_forward_nav, sixtyforty_nav, multistrat_nav, gd_nav, funding_nav, poc_nav,
@asset(deps=[combined_forward_nav, sixtyforty_nav, multistrat_nav, gd_nav, poc_nav,
eqfactor_long_nav, eqfactor_tilt_nav, xsfunding_nav])
def cockpit_forward(context: AssetExecutionContext) -> None:
"""Normalize tracker state files and upsert rows + summary into the operational cockpit DB."""

View File

@@ -11,7 +11,6 @@ from fxhnt.adapters.orchestration.assets import (
eqfactor_long_nav,
eqfactor_scores,
eqfactor_tilt_nav,
funding_nav,
futures_bars,
gd_nav,
multistrat_nav,
@@ -24,7 +23,7 @@ combined_book_job = define_asset_job(
name="combined_book_forward_job",
selection=[
crypto_bars, futures_bars, combined_forward_nav,
sixtyforty_nav, multistrat_nav, gd_nav, funding_nav, poc_nav,
sixtyforty_nav, multistrat_nav, gd_nav, poc_nav,
eqfactor_scores, eqfactor_long_nav, eqfactor_tilt_nav,
xsfunding_nav,
cockpit_forward,
@@ -42,7 +41,7 @@ daily_combined_book_schedule = ScheduleDefinition(
defs = Definitions(
assets=[
crypto_bars, futures_bars, combined_forward_nav,
sixtyforty_nav, multistrat_nav, gd_nav, funding_nav, poc_nav,
sixtyforty_nav, multistrat_nav, gd_nav, poc_nav,
eqfactor_scores, eqfactor_long_nav, eqfactor_tilt_nav,
xsfunding_nav,
cockpit_forward,

View File

@@ -10,11 +10,9 @@ STRATEGY_REGISTRY: dict[str, dict] = {
"venue": "binance+glbx", "state_file": "fxhnt_combined_forward",
"gate_spec": {"min_days": 20, "min_total_return": 0.0, "min_sharpe": 0.5},
},
"funding": {
"display_name": "Crypto funding harvest (delta-neutral)", "sleeve": "carry",
"venue": "binance", "state_file": "funding_paper_state",
"gate_spec": {"min_days": 28, "min_total_return": 0.0, "min_sharpe": 0.0},
},
# NOTE: the absolute-hurdle "funding" track was RETIRED 2026-06-20 (idle ~60% of days, flat 2022);
# the canonical funding deploy is the cross-sectional "xsfunding" below (harvests dispersion, always
# active, +71% in the 2022 backtest). The faithful-port harness stays in paper_strategies.py.
"multistrat": {
"display_name": "Multi-strategy book", "sleeve": "multi",
"venue": "glbx", "state_file": "multistrat_state",
@@ -46,7 +44,7 @@ STRATEGY_REGISTRY: dict[str, dict] = {
"gate_spec": {"min_days": 60, "min_total_return": 0.0, "min_sharpe": 0.3},
},
"xsfunding": {
"display_name": "Crypto funding cross-sectional", "sleeve": "crypto-funding",
"display_name": "Crypto funding harvest (cross-sectional, delta-neutral)", "sleeve": "crypto-funding",
"venue": "binance-perp", "state_file": "xsfunding_state",
"gate_spec": {"min_days": 60, "min_total_return": 0.0, "min_sharpe": 0.5},
},

View File

@@ -41,7 +41,7 @@ def test_fleet_joins_registry_to_summary() -> None:
def test_fleet_includes_registry_entries_without_data() -> None:
svc = DashboardService(_seeded())
fleet = svc.fleet()
funding = next(f for f in fleet if f.strategy_id == "funding")
funding = next(f for f in fleet if f.strategy_id == "xsfunding")
assert funding.days == 0 and funding.gate_status == "WAIT" # no data yet -> shown, not errored

View File

@@ -12,10 +12,12 @@ def test_definitions_load_with_assets_and_schedule() -> None:
asset_keys = {k.to_user_string() for k in repo.assets_defs_by_key}
assert {"crypto_bars", "futures_bars", "combined_forward_nav", "cockpit_forward"} <= asset_keys
# B1: the paper-track assets are wired into the graph alongside the B0 four
# (crossvenue retired — falsified 3x as a max-min statistical artifact; only the 5 remain)
paper = {"sixtyforty_nav", "multistrat_nav", "gd_nav", "funding_nav", "poc_nav"}
# (crossvenue retired — falsified 3x as a max-min statistical artifact;
# funding_nav retired 2026-06-20 — absolute-hurdle variant idle ~60%, superseded by xsfunding)
paper = {"sixtyforty_nav", "multistrat_nav", "gd_nav", "poc_nav"}
assert paper <= asset_keys, f"missing paper-track assets: {paper - asset_keys}"
assert "crossvenue_nav" not in asset_keys, "crossvenue_nav must be retired (falsified 3x)"
assert "funding_nav" not in asset_keys, "funding_nav retired — superseded by cross-sectional xsfunding"
# B2: the equity-factor forward sleeves are wired into the graph alongside the B1 paper tracks
# (eqfactor_ls retired per B3b — no edge in backtest; only long + tilt forward-track now)
eqfactor = {"eqfactor_long_nav", "eqfactor_tilt_nav"}
@@ -25,7 +27,7 @@ def test_definitions_load_with_assets_and_schedule() -> None:
assert "xsfunding_nav" in asset_keys, f"missing xsfunding_nav asset: {asset_keys}"
# B2.1: the shared scores asset is wired in (fetched once, consumed by the sleeves) — 13 total
assert "eqfactor_scores" in asset_keys, f"missing eqfactor_scores asset: {asset_keys}"
assert len(asset_keys) == 13, f"expected 13 assets, got {len(asset_keys)}: {asset_keys}"
assert len(asset_keys) == 12, f"expected 12 assets, got {len(asset_keys)}: {asset_keys}"
# --- schedule present with the right cron ---
# defs.schedules is a list[ScheduleDefinition] (or None when empty)