test(vrp): port archived-hide coverage to synthetic entry + honest test narratives (review fixes)

This commit is contained in:
2026-07-21 00:53:51 +00:00
parent c7a40b70ec
commit baf6ddbe9e
3 changed files with 55 additions and 24 deletions

View File

@@ -9,7 +9,7 @@ module does NO new compute, only comparison, plus a read of the strategy-tagged
cost-to-trade inputs (median slippage, total fees).
`has_exec` is the gate: when the `<sid>_exec` track has no persisted forward_summary row yet (a real account
that hasn't run, or an exec twin that's suspended — e.g. `vrp_exec` today), there is nothing to reconcile
that hasn't run, or an exec twin that's suspended pending a fund decision), there is nothing to reconcile
against — every other field defaults to zero rather than computing on unavailable data."""
from __future__ import annotations

View File

@@ -9,6 +9,7 @@ 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
from fxhnt.registry import STRATEGY_REGISTRY
def _seeded() -> ForwardNavRepo:
@@ -92,3 +93,31 @@ def test_fleet_no_backtest_renders_blank() -> None:
assert unlock.bt_as_of == ""
def test_archived_sleeves_hidden_everywhere(monkeypatch) -> None:
# A SYNTHETIC archived registry entry (`STRATEGY_REGISTRY` `archived: True` — the pattern
# `test_paper_books_web.py` uses for its synthetic `archivedstrat`/`newstrat`, in place of the deleted
# vrp-based test) must be excluded from every cockpit surface `ForwardNavRepo.registry()` feeds:
# `DashboardService.fleet()` and `.detail()`. Monkeypatch BEFORE `migrate()` so `_seed_registry()` (which
# seeds the DB registry table FROM `STRATEGY_REGISTRY` and prunes anything not present there) picks up
# the synthetic entry.
monkeypatch.setitem(STRATEGY_REGISTRY, "archivedstrat",
{"archived": True, "display_name": "Options income (put spreads)",
"sleeve": "equity-vol", "gate_spec": {}})
repo = _seeded() # seeds registry AFTER the monkeypatch above, so archivedstrat lands in the DB row set
svc = DashboardService(repo)
# registry() itself: the archived row never comes back, a non-archived row (unlock) does.
reg_ids = {r.strategy_id for r in repo.registry()}
assert "archivedstrat" not in reg_ids
assert "unlock" in reg_ids
# fleet(): archived sleeve excluded; a live sleeve still shows.
fleet = svc.fleet()
assert all(f.strategy_id != "archivedstrat" for f in fleet)
assert any(f.strategy_id == "unlock" for f in fleet)
# detail(): direct hit on the archived id returns None (404 at the route layer); a live id still resolves.
assert svc.detail("archivedstrat") is None
assert svc.detail("unlock") is not None

View File

@@ -4,20 +4,24 @@ from the PRECOMPUTED `sim_curve_ret` table (never recomputed on the request path
measured-cost path's shape (curve + metrics + Live forward), with plain-language pill labels (never the raw
registry id "multistrat").
NOTE: vrp was ARCHIVED/FALSIFIED (shelved 2026-07-15) — its `STRATEGY_REGISTRY` entry carries
`archived: True`, so it is EXCLUDED from the Backtest book pills (`_SIM_BOOKS`) and a `?book=vrp` request
falls back to the default book. The recompute-replay machinery itself still supports vrp (code + OPRA data
kept); this test file now asserts vrp is HIDDEN from the sim UI (see the archived-book tests below).
NOTE: vrp has been FULLY REMOVED from the codebase (shelved 2026-07-15, code+data deleted in the
`chore/remove-vrp-code` cleanup) — there is no longer a `STRATEGY_REGISTRY` entry, `_SIM_BOOKS` pill, or
recompute-replay support for it at all. This test file uses a synthetic non-existent book id
(`no-such-book`) as an honest stand-in to probe the SAME fallback behavior an archived/unknown book must
still get: never selectable, never rendered, always a graceful fallback to the default book (see the
archived-book tests below).
Asserts:
* /paper/sim shows the multistrat pill labelled in plain language, not the raw id, and NO vrp pill;
* /paper/sim shows the multistrat pill labelled in plain language, not the raw id, and NO stray pill for an
unknown book;
* /paper/sim/run?book=multistrat renders a real curve + metrics from the precomputed sim_curve_ret series;
* capital scales the curve linearly (same contract as the Bybit measured path);
* the request path never recomputes the replay (a tripwire on ForwardStrategy.advance would fire if it did —
proven implicitly: the seeded repo carries no build_strategy at all, so a recompute is structurally
impossible; the curve still renders from sim_curve_ret alone);
* the Live forward section renders for multistrat (its own forward track), not just bybit_4edge;
* an archived book (vrp) is not selectable — the request falls back to the default book, never rendering vrp;
* an unknown/archived book id is not selectable — the request falls back to the default book, never
rendering that id;
* absent precompute -> the graceful 'precomputing' note (never a 500, never the Bybit-specific caption).
NO network — in-memory SQLite repos, seeded directly.
"""
@@ -47,7 +51,7 @@ def _seed_ibkr_curve(repo: PaperRepo, book: str, *, start: dt.date = dt.date(202
repo.upsert_sim_curve_ret(book, d, ret, at=at)
def _seeded_paper_repo(*, books: tuple[str, ...] = ("multistrat", "vrp")) -> PaperRepo:
def _seeded_paper_repo(*, books: tuple[str, ...] = ("multistrat", "no-such-book")) -> PaperRepo:
repo = PaperRepo("sqlite://")
repo.migrate()
for b in books:
@@ -89,16 +93,14 @@ def _sim_data(html: str) -> dict:
return json.loads(m.group(1))
def test_sim_page_shows_multistrat_pill_and_hides_archived_vrp() -> None:
def test_sim_page_shows_multistrat_pill_and_hides_unknown_book() -> None:
c = _client(_seeded_paper_repo())
html = c.get("/paper/sim").text
assert "ETF Portfolio" in html
# vrp is ARCHIVED — neither its pill label nor its raw id may appear anywhere on the Backtest page.
assert "Options income (put spreads)" not in html
assert "Equity VRP" not in html
assert 'data-book="vrp"' not in html and "book=vrp" not in html
# a book id absent from `_SIM_BOOKS` (unknown or archived) must never get a pill on the Backtest page.
assert 'data-book="no-such-book"' not in html and "book=no-such-book" not in html
# the raw registry id must never leak as pill TEXT (it is fine as a query-string value).
assert ">multistrat<" not in html and ">vrp<" not in html
assert ">multistrat<" not in html and ">no-such-book<" not in html
def test_multistrat_book_is_selectable_and_lazy_loads() -> None:
@@ -121,14 +123,14 @@ def test_multistrat_run_renders_curve_and_metrics_from_precomputed_sim_curve_ret
assert 80000 < data["equity"][0] < 120000 # curve starts near the configured capital
def test_archived_vrp_run_falls_back_and_never_renders_vrp() -> None:
"""An archived book (vrp) is not in `_SIM_BOOKS`, so `/paper/sim/run?book=vrp` falls back to the default
book (bybit_4edge) — it renders that book's view (or the graceful pending note), never vrp's curve/label."""
def test_unknown_book_run_falls_back_and_never_renders_it() -> None:
"""A book id absent from `_SIM_BOOKS` (unknown or archived) makes `/paper/sim/run?book=no-such-book`
fall back to the default book (bybit_4edge) — it renders that book's view (or the graceful pending
note), never the unknown id's curve/label."""
c = _client(_seeded_paper_repo())
r = c.get("/paper/sim/run", params={"book": "vrp", "capital": 100000})
r = c.get("/paper/sim/run", params={"book": "no-such-book", "capital": 100000})
assert r.status_code == 200 # graceful fallback, never a 500
assert "Options income (put spreads)" not in r.text and "Equity VRP" not in r.text
assert 'data-book="vrp"' not in r.text # the effective book is the default, not vrp
assert 'data-book="no-such-book"' not in r.text # the effective book is the default, not the unknown id
def test_ibkr_capital_scales_the_curve_linearly() -> None:
@@ -180,12 +182,12 @@ def test_ibkr_missing_precompute_shows_pending_note_not_bybit_caption() -> None:
assert 'data-cost-mode="flat"' not in r.text
def test_sim_book_switch_covers_visible_books_and_excludes_archived_vrp() -> None:
def test_sim_book_switch_covers_visible_books_and_excludes_unknown_book() -> None:
c = _client(_seeded_paper_repo())
# the three VISIBLE books each render their own page...
for book in ("bybit_4edge", "bybit_4edge_levered", "multistrat"):
r = c.get("/paper/sim", params={"book": book})
assert r.status_code == 200 and f'data-book="{book}"' in r.text
# ...but the ARCHIVED vrp is not selectable: it falls back to the default book (200, never vrp).
r = c.get("/paper/sim", params={"book": "vrp"})
assert r.status_code == 200 and 'data-book="vrp"' not in r.text
# ...but an unknown/archived book id is not selectable: it falls back to the default book (200, never it).
r = c.get("/paper/sim", params={"book": "no-such-book"})
assert r.status_code == 200 and 'data-book="no-such-book"' not in r.text