The first pass only filtered the Overview fleet(); the Backtest tab still showed
vrp as "Options income (put spreads)" + a "Live forward — Equity VRP" section,
and /strategy/vrp still rendered. Fix at the SOURCE so every registry-driven path
inherits the hide, then patch the pages that bypass the source.
- forward_nav.ForwardNavRepo.registry(): SINGLE authoritative filter — exclude
rows whose STRATEGY_REGISTRY entry is `archived: True`. Every caller (fleet,
overview gate specs, detail()'s reg lookup) now drops vrp/vrp_exec automatically.
DB rows stay seeded (code + OPRA data kept) — only hidden from the UI.
- dashboard_service.detail(): explicit archived guard -> returns None so
/strategy/vrp 404s (belt-and-suspenders over the registry() filter).
- dashboard_service: _active_ibkr_account_sids() helper excludes archived sids;
the IBKR account per-strategy breakdown + recent-fills roll-up iterate it, so
vrp never renders a row/label on the account page.
- app.py: _SIM_BOOKS excludes archived books -> no vrp Backtest pill, and a
?book=vrp request falls back to the default book (folded "Live forward" section
can't surface vrp either). _SIM_BOOK_LABELS derives from the filtered list.
- fleet() keeps its archived-check as defense-in-depth (registry() is now the
authoritative filter).
Render paths fixed: Overview, Backtest (/paper/sim + /paper/sim/run pills,
selector, folded Live-forward), /strategy/<sid> detail, IBKR account per-strategy
breakdown. Verified end-to-end: no "vrp"/"Equity VRP"/"Options income" on any page,
detail("vrp"/"vrp_exec")=None, /strategy/vrp=404, raw DB rows still seeded.
Tests: registry()/detail()/sim-book-list/IBKR-breakdown all assert vrp ABSENT;
test_cockpit_vrp_render flipped to assert 404; test_ibkr_sim_web asserts vrp
excluded from pills + falls back; account-view/detail-breakdown tests use a
synthetic non-archived `newstrat` for the dimmed/scale rows.
uv run pytest -q: 2006 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
130 lines
6.1 KiB
Python
130 lines
6.1 KiB
Python
"""DashboardService turns DB rows into view-models: a fleet overview (registry joined to summary) and a
|
|
per-strategy detail (summary + nav history)."""
|
|
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
|
|
from fxhnt.adapters.persistence.forward_nav import ForwardNavRepo
|
|
from fxhnt.application.dashboard_service import DashboardService
|
|
from fxhnt.application.forward_models import BacktestSummary
|
|
from fxhnt.application.forward_models import ForwardNavRow as Row
|
|
from fxhnt.application.forward_models import ForwardSummary
|
|
|
|
|
|
def _seeded() -> ForwardNavRepo:
|
|
repo = ForwardNavRepo("sqlite://")
|
|
repo.migrate()
|
|
at = dt.datetime(2026, 6, 14, 23, 30)
|
|
repo.upsert_rows([Row("unlock", "2026-06-05", 0.01, 1.01),
|
|
Row("unlock", "2026-06-06", 0.0099, 1.0201)], at)
|
|
repo.upsert_summary(ForwardSummary("unlock", "2026-06-06", 2, 1.0201, 0.0201, 1.2, -0.01),
|
|
"WAIT", "2/20 forward days", at)
|
|
# a surviving track (xsfunding) carries a backtest verdict; a RETIRED one (eqfactor_long) has a
|
|
# leftover backtest-summary DB row but must NOT surface (fleet is registry-driven).
|
|
repo.upsert_backtest_summary(BacktestSummary(
|
|
"xsfunding", "2026-05-30", cagr=0.12, ann_vol=0.18, sharpe=0.85, max_drawdown=-0.22,
|
|
passed=True, dsr=0.61, is_sharpe=0.9, oos_sharpe=0.7, pvalue=0.03), at)
|
|
repo.upsert_backtest_summary(BacktestSummary(
|
|
"eqfactor_long", "2026-05-30", cagr=-0.01, ann_vol=0.11, sharpe=-0.05, max_drawdown=-0.31,
|
|
passed=False, dsr=-0.2, is_sharpe=0.1, oos_sharpe=-0.3, pvalue=0.6), at)
|
|
return repo
|
|
|
|
|
|
def test_fleet_joins_registry_to_summary() -> None:
|
|
svc = DashboardService(_seeded())
|
|
fleet = svc.fleet()
|
|
unlock = next(f for f in fleet if f.strategy_id == "unlock")
|
|
assert unlock.display_name.startswith("Token-unlock dilution shorts")
|
|
assert unlock.days == 2 and unlock.gate_status == "WAIT"
|
|
assert round(unlock.total_return_pct, 2) == 2.01
|
|
|
|
|
|
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 == "xsfunding")
|
|
assert funding.days == 0 and funding.gate_status == "WAIT" # no data yet -> shown, not errored
|
|
|
|
|
|
def test_detail_returns_summary_and_history() -> None:
|
|
svc = DashboardService(_seeded())
|
|
detail = svc.detail("unlock")
|
|
assert detail is not None
|
|
assert detail.display_name.startswith("Token-unlock dilution shorts")
|
|
assert [p.date for p in detail.history] == ["2026-06-05", "2026-06-06"]
|
|
|
|
|
|
def test_detail_includes_raw_period_aggregations() -> None:
|
|
svc = DashboardService(_seeded())
|
|
detail = svc.detail("unlock")
|
|
assert set(detail.periods) == {"daily", "weekly", "monthly"}
|
|
assert [p.label for p in detail.periods["daily"]] == ["2026-06-05", "2026-06-06"]
|
|
assert abs(detail.periods["daily"][0].ret - 0.01) < 1e-9 # raw, not normalized
|
|
assert all(p.label.startswith("2026") for p in detail.periods["monthly"])
|
|
|
|
|
|
def test_detail_unknown_strategy_is_none() -> None:
|
|
svc = DashboardService(_seeded())
|
|
assert svc.detail("does-not-exist") is None
|
|
|
|
|
|
def test_fleet_surfaces_backtest_verdict() -> None:
|
|
fleet = DashboardService(_seeded()).fleet()
|
|
el = next(f for f in fleet if f.strategy_id == "xsfunding")
|
|
assert el.bt_status == "PASS"
|
|
assert abs(el.bt_sharpe - 0.85) < 1e-9
|
|
assert abs(el.bt_dsr - 0.61) < 1e-9
|
|
assert abs(el.bt_cagr - 0.12) < 1e-9
|
|
assert abs(el.bt_maxdd - (-0.22)) < 1e-9
|
|
assert el.bt_as_of == "2026-05-30"
|
|
|
|
# eqfactor_long is retired from the registry (unproven); even though a backtest-summary row still
|
|
# exists in the DB, the fleet is registry-driven so the retired track is no longer surfaced.
|
|
assert all(f.strategy_id != "eqfactor_long" for f in fleet)
|
|
|
|
|
|
def test_fleet_no_backtest_renders_blank() -> None:
|
|
fleet = DashboardService(_seeded()).fleet()
|
|
unlock = next(f for f in fleet if f.strategy_id == "unlock")
|
|
assert unlock.bt_status == ""
|
|
assert unlock.bt_sharpe is None and unlock.bt_dsr is None
|
|
assert unlock.bt_cagr is None and unlock.bt_maxdd is None
|
|
assert unlock.bt_as_of == ""
|
|
|
|
|
|
def test_archived_sleeves_hidden_everywhere() -> None:
|
|
"""The `archived: True` entries (vrp/vrp_exec — FALSIFIED/shelved 2026-07-15) are KEPT in the Python
|
|
registry (code + OPRA data preserved, entry not deleted) AND still seeded into the DB registry table by
|
|
`_seed_registry` — but `ForwardNavRepo.registry()` (the SINGLE authoritative filter) excludes them, so
|
|
every registry-driven render path drops them: the fleet Overview, per-strategy gate specs, and
|
|
`detail()`. Proven here at the repo, fleet, and detail levels."""
|
|
from fxhnt.adapters.persistence.cockpit_models import StrategyRegistryRow
|
|
from fxhnt.registry import STRATEGY_REGISTRY
|
|
from sqlalchemy import select as _select
|
|
from sqlalchemy.orm import Session as _Session
|
|
|
|
# entries KEPT in the Python registry, flagged archived (not deleted)
|
|
assert STRATEGY_REGISTRY["vrp"].get("archived") is True
|
|
assert STRATEGY_REGISTRY["vrp_exec"].get("archived") is True
|
|
|
|
repo = _seeded()
|
|
# the DB row IS still seeded (code + data kept) — a raw table read finds it...
|
|
with _Session(repo._engine) as s:
|
|
raw_ids = {r.strategy_id for r in s.scalars(_select(StrategyRegistryRow))}
|
|
assert {"vrp", "vrp_exec"} <= raw_ids, "archived rows must still be seeded in the DB (not pruned)"
|
|
# ...but the cockpit-facing registry() EXCLUDES them (the authoritative hide).
|
|
reg_ids = {r.strategy_id for r in repo.registry()}
|
|
assert "vrp" not in reg_ids and "vrp_exec" not in reg_ids
|
|
assert "unlock" in reg_ids # a non-archived track is still visible (the filter is targeted)
|
|
|
|
# fleet Overview drops them; a non-archived track still renders
|
|
fleet_ids = {f.strategy_id for f in DashboardService(repo).fleet()}
|
|
assert "vrp" not in fleet_ids and "vrp_exec" not in fleet_ids
|
|
assert "unlock" in fleet_ids
|
|
|
|
# a direct /strategy/vrp hit returns None (route 404s), but a live track still resolves
|
|
svc = DashboardService(repo)
|
|
assert svc.detail("vrp") is None
|
|
assert svc.detail("vrp_exec") is None
|
|
assert svc.detail("unlock") is not None
|