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) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}"
|
||||
|
||||
Reference in New Issue
Block a user