From 2c94c65953eea56d0076fd2f2c3e27de48f4d8a2 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Thu, 18 Jun 2026 18:48:41 +0200 Subject: [PATCH] feat(b3b): retire eqfactor_ls forward track (no edge per backtest); keep long/tilt + shared scores Co-Authored-By: Claude Opus 4.8 (1M context) --- src/fxhnt/adapters/orchestration/assets.py | 13 +------------ src/fxhnt/adapters/orchestration/definitions.py | 5 ++--- src/fxhnt/application/equity_factor_strategy.py | 11 ----------- src/fxhnt/registry.py | 5 ----- tests/integration/test_dashboard_service.py | 7 ++++--- tests/integration/test_equity_factor_strategy.py | 11 ----------- tests/integration/test_orchestration_definitions.py | 12 +++++++----- 7 files changed, 14 insertions(+), 50 deletions(-) diff --git a/src/fxhnt/adapters/orchestration/assets.py b/src/fxhnt/adapters/orchestration/assets.py index e25ca31..d0229cd 100644 --- a/src/fxhnt/adapters/orchestration/assets.py +++ b/src/fxhnt/adapters/orchestration/assets.py @@ -242,17 +242,6 @@ def eqfactor_long_nav(context: AssetExecutionContext, eqfactor_scores: dict) -> return {"forward_days": st.forward_days, "last_date": st.last_date} -@asset -def eqfactor_ls_nav(context: AssetExecutionContext, eqfactor_scores: dict) -> dict: # type: ignore[type-arg] - """Paper-forward market-neutral long-short equity-factor sleeve over the shared precomputed scores.""" - from fxhnt.application.equity_factor_strategy import EquityFactorLS - from fxhnt.application.forward_tracker import ForwardTracker - - st = ForwardTracker(EquityFactorLS(eqfactor_scores), f"{_data_dir()}/eqfactor_ls_state.json").step() - context.log.info(f"eqfactor_ls_nav: {st.forward_days}d through {st.last_date}") - return {"forward_days": st.forward_days, "last_date": st.last_date} - - @asset def eqfactor_tilt_nav(context: AssetExecutionContext, eqfactor_scores: dict) -> dict: # type: ignore[type-arg] """Paper-forward long-only rank-weighted (tilt) equity-factor sleeve over the shared precomputed scores.""" @@ -265,7 +254,7 @@ def eqfactor_tilt_nav(context: AssetExecutionContext, eqfactor_scores: dict) -> @asset(deps=[combined_forward_nav, sixtyforty_nav, multistrat_nav, gd_nav, funding_nav, crossvenue_nav, poc_nav, - eqfactor_long_nav, eqfactor_ls_nav, eqfactor_tilt_nav]) + eqfactor_long_nav, eqfactor_tilt_nav]) def cockpit_forward(context: AssetExecutionContext) -> None: """Normalize tracker state files and upsert rows + summary into the operational cockpit DB.""" from fxhnt.application.forward_ingest import ingest_forward_state diff --git a/src/fxhnt/adapters/orchestration/definitions.py b/src/fxhnt/adapters/orchestration/definitions.py index bd3b01e..1fbfff3 100644 --- a/src/fxhnt/adapters/orchestration/definitions.py +++ b/src/fxhnt/adapters/orchestration/definitions.py @@ -10,7 +10,6 @@ from fxhnt.adapters.orchestration.assets import ( crossvenue_nav, crypto_bars, eqfactor_long_nav, - eqfactor_ls_nav, eqfactor_scores, eqfactor_tilt_nav, funding_nav, @@ -26,7 +25,7 @@ combined_book_job = define_asset_job( selection=[ crypto_bars, futures_bars, combined_forward_nav, sixtyforty_nav, multistrat_nav, gd_nav, funding_nav, crossvenue_nav, poc_nav, - eqfactor_scores, eqfactor_long_nav, eqfactor_ls_nav, eqfactor_tilt_nav, + eqfactor_scores, eqfactor_long_nav, eqfactor_tilt_nav, cockpit_forward, ], ) @@ -43,7 +42,7 @@ defs = Definitions( assets=[ crypto_bars, futures_bars, combined_forward_nav, sixtyforty_nav, multistrat_nav, gd_nav, funding_nav, crossvenue_nav, poc_nav, - eqfactor_scores, eqfactor_long_nav, eqfactor_ls_nav, eqfactor_tilt_nav, + eqfactor_scores, eqfactor_long_nav, eqfactor_tilt_nav, cockpit_forward, ], jobs=[combined_book_job], diff --git a/src/fxhnt/application/equity_factor_strategy.py b/src/fxhnt/application/equity_factor_strategy.py index 4354418..6d6d674 100644 --- a/src/fxhnt/application/equity_factor_strategy.py +++ b/src/fxhnt/application/equity_factor_strategy.py @@ -151,17 +151,6 @@ class EquityFactorLong: return self._impl.advance(last_date, extra) -class EquityFactorLS: - """Market-neutral long-short equity-factor sleeve (top quintile long, bottom quintile short, borrow cost).""" - - def __init__(self, scores: dict[str, Any], borrow_annual: float = 0.01, - clock: Callable[[], str] = _today_iso) -> None: - self._impl = _EquityFactorStrategy("ls", scores, borrow_annual=borrow_annual, clock=clock) - - def advance(self, last_date: str | None, extra: dict[str, Any]) -> tuple[list[tuple[str, float]], dict[str, Any]]: - return self._impl.advance(last_date, extra) - - class EquityFactorTilt: """Long-only rank-weighted (tilt) equity-factor sleeve over a shared precomputed `scores` dict.""" diff --git a/src/fxhnt/registry.py b/src/fxhnt/registry.py index 4a74f2f..85c25b0 100644 --- a/src/fxhnt/registry.py +++ b/src/fxhnt/registry.py @@ -45,11 +45,6 @@ STRATEGY_REGISTRY: dict[str, dict] = { "venue": "us-equity", "state_file": "eqfactor_long_state", "gate_spec": {"min_days": 60, "min_total_return": 0.0, "min_sharpe": 0.3}, }, - "eqfactor_ls": { - "display_name": "Equity factor — long-short", "sleeve": "equity-factor", - "venue": "us-equity", "state_file": "eqfactor_ls_state", - "gate_spec": {"min_days": 60, "min_total_return": 0.0, "min_sharpe": 0.3}, - }, "eqfactor_tilt": { "display_name": "Equity factor — long-tilt", "sleeve": "equity-factor", "venue": "us-equity", "state_file": "eqfactor_tilt_state", diff --git a/tests/integration/test_dashboard_service.py b/tests/integration/test_dashboard_service.py index 7ff786a..8ea4d5c 100644 --- a/tests/integration/test_dashboard_service.py +++ b/tests/integration/test_dashboard_service.py @@ -77,9 +77,10 @@ def test_fleet_surfaces_backtest_verdict() -> None: assert abs(el.bt_maxdd - (-0.22)) < 1e-9 assert el.bt_as_of == "2026-05-30" - ls = next(f for f in fleet if f.strategy_id == "eqfactor_ls") - assert ls.bt_status == "FAIL" - assert abs(ls.bt_sharpe - (-0.05)) < 1e-9 + # eqfactor_ls is retired from the registry (B3b — no edge); even though a backtest summary row + # still exists in the DB (the backtest reports all three constructions), the fleet is registry-driven + # so the retired LS track is no longer surfaced. + assert all(f.strategy_id != "eqfactor_ls" for f in fleet) def test_fleet_no_backtest_renders_blank() -> None: diff --git a/tests/integration/test_equity_factor_strategy.py b/tests/integration/test_equity_factor_strategy.py index 587ebbc..82ac3da 100644 --- a/tests/integration/test_equity_factor_strategy.py +++ b/tests/integration/test_equity_factor_strategy.py @@ -17,7 +17,6 @@ import pytest from fxhnt.adapters.persistence.state_reader import ForwardStateReader from fxhnt.application.equity_factor_strategy import ( EquityFactorLong, - EquityFactorLS, EquityFactorTilt, _momentum_12_1, _realized_vol, @@ -221,16 +220,6 @@ def test_equity_factor_long_service_round_trips(tmp_path) -> None: assert all(w >= 0.0 for w in prev_targets.values()) # long-only -def test_equity_factor_ls_service_round_trips(tmp_path) -> None: - _, loaded0, loaded1, summary, rows, prev_targets = _drive_two_steps( - EquityFactorLS, "eqfactor_ls", tmp_path, ls=True) - assert summary.days == 1 and len(rows) == 1 - assert "positions" in loaded1["extra"] and "last_rebal" in loaded1["extra"] - # ls targets net ~0 / gross ~2 - assert sum(prev_targets.values()) == pytest.approx(0.0) - assert sum(abs(w) for w in prev_targets.values()) == pytest.approx(2.0) - - def test_equity_factor_tilt_service_round_trips(tmp_path) -> None: _, loaded0, loaded1, summary, rows, prev_targets = _drive_two_steps( EquityFactorTilt, "eqfactor_tilt", tmp_path, ls=False) diff --git a/tests/integration/test_orchestration_definitions.py b/tests/integration/test_orchestration_definitions.py index 4d2ba93..7ca5b81 100644 --- a/tests/integration/test_orchestration_definitions.py +++ b/tests/integration/test_orchestration_definitions.py @@ -14,12 +14,14 @@ def test_definitions_load_with_assets_and_schedule() -> None: # B1: the six paper-track assets are wired into the graph alongside the B0 four paper = {"sixtyforty_nav", "multistrat_nav", "gd_nav", "funding_nav", "crossvenue_nav", "poc_nav"} assert paper <= asset_keys, f"missing paper-track assets: {paper - asset_keys}" - # B2: the three equity-factor assets are wired into the graph alongside the B1 six - eqfactor = {"eqfactor_long_nav", "eqfactor_ls_nav", "eqfactor_tilt_nav"} + # B2: the equity-factor forward sleeves are wired into the graph alongside the B1 six + # (eqfactor_ls retired per B3b — no edge in backtest; only long + tilt forward-track now) + eqfactor = {"eqfactor_long_nav", "eqfactor_tilt_nav"} assert eqfactor <= asset_keys, f"missing equity-factor assets: {eqfactor - asset_keys}" - # B2.1: the shared scores asset is wired in (fetched once, consumed by the three sleeves) — 14 total + assert "eqfactor_ls_nav" not in asset_keys, "eqfactor_ls_nav must be retired (B3b)" + # 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) == 14, f"expected 14 assets, got {len(asset_keys)}: {asset_keys}" + assert len(asset_keys) == 13, f"expected 13 assets, got {len(asset_keys)}: {asset_keys}" # --- schedule present with the right cron --- # defs.schedules is a list[ScheduleDefinition] (or None when empty) @@ -41,7 +43,7 @@ def test_definitions_load_with_assets_and_schedule() -> None: assert expected_upstream <= upstream, f"cockpit_forward missing upstream: {expected_upstream - upstream}" # --- each equity-factor nav sleeve depends on the shared eqfactor_scores asset (fetched once) --- - for nav in ("eqfactor_long_nav", "eqfactor_ls_nav", "eqfactor_tilt_nav"): + for nav in ("eqfactor_long_nav", "eqfactor_tilt_nav"): nav_def = repo.assets_defs_by_key[AssetKey(nav)] nav_up = {k.to_user_string() for k in nav_def.asset_deps[AssetKey(nav)]} assert "eqfactor_scores" in nav_up, f"{nav} missing eqfactor_scores upstream dep: {nav_up}"