docs(vrp): scrub stale VRP comment/docstring references across cockpit + shared infra

This commit is contained in:
2026-07-21 00:10:27 +00:00
parent 92b0871050
commit c7a40b70ec
16 changed files with 66 additions and 63 deletions

View File

@@ -290,7 +290,7 @@ def _persist_track_backtest_ref(context: AssetExecutionContext, name: str, build
"""Persist the reconciliation-gate backtest reference for an individual forward track, routed through the
ONE `backtest_ref` dispatcher (fxhnt.application.backtest_refs) so this inline nightly path, the
`backtest-refs --all` CLI (report-kind), and the B3 runner all share the same dispatch. For
`recompute-replay` strategies (multistrat/vrp/crypto_tstrend/stablecoin/unlock) this is bit-identical to
`recompute-replay` strategies (multistrat/crypto_tstrend/stablecoin/unlock) this is bit-identical to
the prior inline behaviour: `build_strategy().advance(None, {})` = the full inception series at the EXACT
basis the forward books (e.g. crypto_tstrend's forward is 15%-book-vol-targeted, not the raw TrendRunner
sleeve). For a `sleeve` strategy it would resolve a feature store instead, since `sleeve_returns_from_store`
@@ -302,9 +302,9 @@ def _persist_track_backtest_ref(context: AssetExecutionContext, name: str, build
BEST-EFFORT: a ref-persist failure logs and returns — it must NEVER fail the forward step (the ref is a
gate input, not the track itself). Without it the gate WITHHOLDS PASS (no ref to reconcile).
For `recompute-replay` strategies (multistrat/vrp/crypto_tstrend/stablecoin/unlock) this ALSO persists
For `recompute-replay` strategies (multistrat/crypto_tstrend/stablecoin/unlock) this ALSO persists
the full inception series into `sim_curve_ret` (Task 1/D1) — the precomputed curve the cockpit's
`/paper/sim` reads for its IBKR books (multistrat/vrp), mirroring the Bybit book's `bybit_sleeve_ret`
`/paper/sim` reads for its IBKR books (multistrat), mirroring the Bybit book's `bybit_sleeve_ret`
precompute. One replay call feeds BOTH writes (the reconciliation ref and the sim curve), so the sim's
curve is always the SAME basis the gate reconciles the forward track against."""
import datetime as _dt
@@ -913,7 +913,7 @@ def cockpit_forward(context: AssetExecutionContext) -> None:
dsn = get_settings().operational_dsn
ingest_forward_state(dsn)
# Runtime health axis (SEPARATE from the gate): surface ANY track that stopped updating LOUDLY — a red
# cockpit badge + an ERROR log the FIRST night, so a stale recompute (the 2-week vrp rot) can never sit as
# cockpit badge + an ERROR log the FIRST night, so a stale recompute (a dead strategy's silent rot) can never sit as
# a quiet WAIT again. evaluate_health logs at ERROR with the unhealthy list; also surface the count here.
unhealthy = [v for v in evaluate_health(dsn) if not v.ok]
if unhealthy:

View File

@@ -7,9 +7,8 @@ retired here — crypto is 100% Bybit now (`bybit_warehouse_refresh` + the Bybit
`bybit_4edge` deploy book below) — was RETIRED (Phase 0b Task 7d); sixtyforty_nav/multistrat_nav
are their OWN independent live assets below, unaffected by that retirement.)
(vrp_nav ARCHIVED/FALSIFIED 2026-07-15the VRP sleeve is a shelved dead-end; its asset function is KEPT
in assets.py but DE-WIRED from the nightly here so it no longer materializes. See
project_fxhnt_diversifier_hunt_2026_07_15.)
(VRP removed entirely 2026-07-20see project_fxhnt_diversifier_hunt_2026_07_15 for the
falsification that led to the removal.)
NOTE: no `from __future__ import annotations` — Dagster 1.12 runtime type-hint inspection."""
from dagster import DefaultScheduleStatus, Definitions, ScheduleDefinition, define_asset_job

View File

@@ -43,7 +43,8 @@ class StrategyHealthRow(CockpitBase):
"""Runtime HEALTH verdict per track — a SEPARATE axis from the gate (a track can be legitimately
WAIT/building AND healthy; STALE/NO_REF/BROKEN is orthogonal). Written each nightly run by
`forward_health.evaluate_health`; the cockpit reads it to render a LOUD red badge so a live/gated track
that stops updating surfaces on day 1 (the anti-silent-rot backbone that would have caught vrp). One row
that stops updating surfaces on day 1 (the anti-silent-rot backbone that would have caught a dead
strategy's silent staleness). One row
per strategy, replaced each run (a track that recovers clears its stale row). Brand-new table →
`create_all` (ForwardNavRepo.migrate) creates it; no ALTER, Postgres-safe."""
__tablename__ = "strategy_health"
@@ -225,7 +226,7 @@ class BybitSleeveRetRow(CockpitBase):
class SimCurveRetRow(CockpitBase):
"""One recompute-replay strategy's (multistrat/vrp — the IBKR paper books the Backtest page's sim reads)
"""One recompute-replay strategy's (multistrat — the IBKR paper books the Backtest page's sim reads)
daily honest-cost return for one run_date. Mirrors `BybitSleeveRetRow`'s shape/purpose for the Bybit-book
sim: written nightly by `_persist_track_backtest_ref` (the `fxhnt-backtest-refs` Job) from the SAME
inception rows `backtest_ref`'s recompute-replay branch derives the reconciliation-gate ref from, so

View File

@@ -143,9 +143,9 @@ class ForwardNavRepo:
return list(s.scalars(select(StrategyHealthRow).order_by(StrategyHealthRow.strategy_id)))
def xsp_freeze_max_date(self) -> str | None:
"""The latest date present in the vrp freeze table (`xsp_option_bars`), ISO yyyy-mm-dd, or None when
the table is empty. The freeze-staleness axis: a vrp recompute is bounded by this max date, so if the
freeze stops advancing the track is STALE even though its forward 'updates'."""
"""The latest date present in the opra-pit-anchored freeze table (`xsp_option_bars`), ISO yyyy-mm-dd,
or None when the table is empty. The freeze-staleness axis: a frozen-input recompute is bounded by
this max date, so if the freeze stops advancing the track is STALE even though its forward 'updates'."""
from sqlalchemy import func
with Session(self._engine) as s:
d = s.scalar(select(func.max(XspOptionBarRow.date)))
@@ -196,7 +196,7 @@ class ForwardNavRepo:
def registry(self) -> list[StrategyRegistryRow]:
"""The cockpit-visible registry rows. SINGLE SOURCE for the hide: an ARCHIVED sleeve (its
`STRATEGY_REGISTRY` entry carries `archived: True` — e.g. the shelved/FALSIFIED vrp/vrp_exec) is
`STRATEGY_REGISTRY` entry carries `archived: True` — e.g. a shelved/FALSIFIED strategy) is
excluded here, so EVERY caller (fleet overview, per-strategy gate specs, `detail()`'s reg lookup)
drops it automatically. The DB row is still seeded (code + data kept) — it is only hidden from the UI."""
with Session(self._engine) as s:

View File

@@ -515,8 +515,8 @@ class PaperRepo:
# ---- sim_curve_ret: the PRECOMPUTED recompute-replay (IBKR) book curve /paper/sim reads ------------
def upsert_sim_curve_ret(self, strategy_id: str, run_date: str, ret: float, *, at: dt.datetime) -> None:
"""Idempotent per (strategy_id, run_date): write/overwrite that recompute-replay book's (multistrat/
vrp) per-day honest-cost return. `_persist_track_backtest_ref` writes the full inception series here
"""Idempotent per (strategy_id, run_date): write/overwrite that recompute-replay book's (multistrat)
per-day honest-cost return. `_persist_track_backtest_ref` writes the full inception series here
from the SAME rows it derives the reconciliation-gate ref from, so /paper/sim NEVER re-runs an IBKR
replay on a web request — mirrors `upsert_bybit_sleeve_ret`'s pattern for the Bybit-book sim."""
rd = dt.date.fromisoformat(run_date)

View File

@@ -446,12 +446,13 @@ def create_app(repo: ForwardNavRepo, svc: DashboardReadModel | None = None,
# The books the sim can configure. bybit_4edge = the precomputed 4-edge Bybit backtest (naive eq-wt —
# NOT the overlay, since the A/B proved naive is best OOS), sized from bybit_sleeve_ret. bybit_4edge_levered
# = the observe-only levered shadow. multistrat/vrp (Task 1/D1) = the IBKR paper strategies' precomputed
# = the observe-only levered shadow. multistrat (Task 1/D1) = the IBKR paper strategies' precomputed
# recompute-replay curve (sim_curve_ret) — generalizes the sim beyond the Bybit-only venue. Order = render
# order of the book-switch pills: the deploy book leads (and is the default).
# Exclude ARCHIVED books (their STRATEGY_REGISTRY entry carries `archived: True` — e.g. the
# shelved/FALSIFIED vrp): they never appear as a Backtest book pill, and a `?book=vrp` request falls
# back to `_DEFAULT_BOOK` (so the folded "Live forward" section can't surface an archived sleeve either).
# Exclude ARCHIVED books (their STRATEGY_REGISTRY entry carries `archived: True` — e.g. a
# shelved/FALSIFIED strategy): they never appear as a Backtest book pill, and a request for an
# archived book falls back to `_DEFAULT_BOOK` (so the folded "Live forward" section can't surface an
# archived sleeve either).
_SIM_BOOKS = tuple(b for b in SIM_BOOKS if not STRATEGY_REGISTRY.get(b, {}).get("archived"))
_DEFAULT_BOOK = "bybit_4edge"
# Plain-language pill labels for the sim's book switch — NEVER the raw registry id. Sourced from the
@@ -470,7 +471,7 @@ def create_app(repo: ForwardNavRepo, svc: DashboardReadModel | None = None,
_sim_returns_cache: dict[str, tuple[float, dict[str, dict[int, float]]]] = {}
def _ibkr_sim_curve(book: str) -> dict[str, dict[int, float]]:
"""The precomputed recompute-replay (IBKR) book curve for `book` (multistrat/vrp), as
"""The precomputed recompute-replay (IBKR) book curve for `book` (multistrat), as
`{book: {epoch_day: ret}}` — read from `sim_curve_ret` (written nightly by
`_persist_track_backtest_ref`/the `fxhnt-backtest-refs` Job from the SAME inception rows the
reconciliation-gate ref is derived from). NEVER recomputes the replay on this request path."""
@@ -479,7 +480,7 @@ def create_app(repo: ForwardNavRepo, svc: DashboardReadModel | None = None,
def _sim_returns(book: str = _DEFAULT_BOOK) -> dict[str, dict[int, float]]:
"""Cached per-sleeve returns for `book`, dispatched by the registry's `backtest.kind` (Task 1/D1):
`report`-kind Bybit books (bybit_4edge / bybit_4edge_levered) read the precomputed bybit_sleeve_ret
table; `recompute-replay`-kind IBKR books (multistrat / vrp) read the precomputed sim_curve_ret
table; `recompute-replay`-kind IBKR books (multistrat) read the precomputed sim_curve_ret
table via `_ibkr_sim_curve`. NEVER recomputes any edge on the request path — both sources are
nightly-precomputed. Memoized in-process with a 300s TTL, keyed by book (returns change only nightly)."""
now = time.monotonic()
@@ -548,7 +549,7 @@ def create_app(repo: ForwardNavRepo, svc: DashboardReadModel | None = None,
def _measured_pending_context(book: str) -> dict[str, Any]:
"""Graceful degrade when the precomputed curve is ABSENT: a short 'precomputing — check back after the
nightly job' note (NOT a flat slider, never a 500). The live forward track still renders for bybit_4edge
and for the IBKR recompute-replay books (multistrat/vrp) — each has its own real forward track."""
and for the IBKR recompute-replay books (multistrat) — each has its own real forward track."""
is_ibkr = registry_backtest_kind(book) == "recompute-replay"
return {
"measured_pending": True,
@@ -564,7 +565,7 @@ def create_app(repo: ForwardNavRepo, svc: DashboardReadModel | None = None,
def _ibkr_measured_context(book: str, *, capital: float, start: str | None = None,
end: str | None = None) -> dict[str, Any] | None:
"""The IBKR recompute-replay books' (multistrat/vrp) Backtest view: compound the PRECOMPUTED per-day
"""The IBKR recompute-replay books' (multistrat) Backtest view: compound the PRECOMPUTED per-day
honest-cost return series (`_sim_returns(book)`, written nightly from the SAME replay basis the
reconciliation gate reconciles the forward track against) into an equity curve via the pure
`simulate_naive_eqwt` engine — no per-coin cost model here (that is the Bybit-only measured-cost path;
@@ -725,7 +726,7 @@ def create_app(repo: ForwardNavRepo, svc: DashboardReadModel | None = None,
capital = get_settings().paper_capital
# Pre-fill AND bound the period inputs with the book's full precomputed date range, so the date fields
# are never blank ("dates not set") and the picker can't go off the data. Bybit books read their range
# from the per-coin measured cache; IBKR recompute-replay books (multistrat/vrp, Task 1/D1) read it
# from the per-coin measured cache; IBKR recompute-replay books (multistrat, Task 1/D1) read it
# from their own precomputed sim_curve_ret series (`_sim_returns`).
if registry_backtest_kind(book) == "recompute-replay":
_days = sorted(_sim_returns(book).get(book) or {})
@@ -749,7 +750,7 @@ def create_app(repo: ForwardNavRepo, svc: DashboardReadModel | None = None,
end: str | None = None, book: str = _DEFAULT_BOOK) -> HTMLResponse:
"""The honest Backtest: book + capital + period -> the precomputed curve. Capital is parsed leniently
(a cleared/comma'd/odd capital must not 422). Bybit books (report-kind) render the per-coin MEASURED-
cost cache scaled by capital + windowed by period; IBKR recompute-replay books (multistrat/vrp, Task
cost cache scaled by capital + windowed by period; IBKR recompute-replay books (multistrat, Task
1/D1) compound their own precomputed honest-cost return series the same way. Absent precompute ->
a graceful 'precomputing' note (never the flat slider, never a 500)."""
book = book if book in _SIM_BOOKS else _DEFAULT_BOOK
@@ -807,7 +808,7 @@ def create_app_from_settings() -> FastAPI:
repo.migrate()
paper_repo = PaperRepo(settings.operational_dsn)
paper_repo.migrate()
# The REAL IBKR paper-account section on the multistrat/vrp detail pages (D2.2/2.3) — a light
# The REAL IBKR paper-account section on the multistrat detail pages (D2.2/2.3) — a light
# schema-ensure migrate (create_all + a guarded additive ALTER), same shape as repo/paper_repo above.
ibkr_account_repo = IbkrAccountRepo(settings.operational_dsn)
ibkr_account_repo.migrate()

View File

@@ -57,8 +57,8 @@
{% macro rollup_plan_pill(rollup) %}{% if rollup and rollup.n_executing > 0 %}<span class="b {{ 'funded' if rollup.agg_divergence >= 0 else 'decaying' }} small"><span class="dot"></span>{{ '%.1f'|format((rollup.agg_divergence|abs) * 100) }}% {{ 'ahead of plan' if rollup.agg_divergence >= 0 else 'behind plan' }}</span>{% endif %}{% endmacro %}
{# ---- health_badge: the LOUD runtime-health axis (SEPARATE from the gate). A track that is STALE / NO_REF /
BROKEN shows a red badge — visually impossible to confuse with a healthy WAIT — so a stale recompute (the
2-week vrp rot) surfaces on day 1. HEALTHY renders nothing. #}
BROKEN shows a red badge — visually impossible to confuse with a healthy WAIT — so a stale recompute
(a strategy silently rotting for weeks) surfaces on day 1. HEALTHY renders nothing. #}
{% macro health_badge(f) %}{% if f.health and f.health != 'HEALTHY' %}<span class="badge-health {{ f.health }}" style="background:#c0392b;color:#fff;font-size:9px;font-weight:600;border-radius:3px;padding:1px 5px;margin-left:4px;letter-spacing:.03em" title="{{ f.health_reason }}">{% if f.health == 'STALE' %}STALE {{ f.health_stale_days }}d{% elif f.health == 'NO_REF' %}NO REF{% else %}{{ f.health }}{% endif %}</span>{% endif %}{% endmacro %}
{# ---- track_row: one compact edge row. `record` is the meaningful number (backfill/backtest ref, else

View File

@@ -75,7 +75,7 @@
{# ---- D3 (Task 4): the REAL IBKR paper-account "real trades" sub-row — reuses the exec-twin's
`.exrow`/`.exec-tag` style, but plain-language content (real return, click-through to holdings,
the shared `plan_pill`, honest cost-to-trade). Replaces the generic exec-twin row below for the
two IBKR real-account books (multistrat/vrp); the crypto book's own exec-twin row is unaffected. #}
IBKR real-account book (multistrat); the crypto book's own exec-twin row is unaffected. #}
{# The sub-row BORROWS the parent table's 6 columns, so each cell is self-labeled inline (like the
exec-twin row below) — the real return in particular must read as a return, not sit naked under the
"allocated" header. data-label carries the true meaning for the mobile card reflow, not the borrowed

View File

@@ -52,7 +52,7 @@ def _expected_book_symbols() -> frozenset[str]:
def _active_ibkr_account_sids() -> list[str]:
"""The IBKR-account sids that are still cockpit-visible — `_IBKR_ACCOUNT_SIDS` minus any ARCHIVED entry
(`STRATEGY_REGISTRY` `archived: True`, e.g. shelved/FALSIFIED vrp). Sorted for a deterministic render
(`STRATEGY_REGISTRY` `archived: True`, e.g. a shelved/FALSIFIED strategy). Sorted for a deterministic render
order. Every account-wide aggregation (the per-strategy breakdown, the recent-fills roll-up) iterates
THIS, so an archived book never surfaces a row/fill on the IBKR account page even though its fills are
still captured in the repo."""
@@ -86,7 +86,7 @@ class DashboardService:
def __init__(self, repo: ForwardNavRepo, ibkr_repo: IbkrAccountRepo | None = None) -> None:
self._repo = repo
# Optional: absent in most callers/tests (the ordinary crypto/registry tracks never touch it); when
# given, powers the IBKR real-account section on multistrat/vrp's detail pages (D2.2/2.3).
# given, powers the IBKR real-account section on multistrat's detail pages (D2.2/2.3).
self._ibkr_repo = ibkr_repo
def _safe_allocs(self) -> dict:
@@ -387,7 +387,7 @@ class DashboardService:
# silently carried in the account NLV — surfaced as a warning in the holdings view.
"in_book": sym in expected}
for sym, qty in positions.items()]
# GROSS exposure denominator: a defined-risk book (e.g. vrp put-credit-spreads = short put + long
# GROSS exposure denominator: a defined-risk book (e.g. a put-credit-spread book = short put + long
# wing) holds mixed-sign legs, so a signed total nets toward the small net credit and makes "% of
# book" blow past 100% / go negative. % of GROSS keeps every row's signed value but a sane [0,100]
# share that sums to ~100.
@@ -413,8 +413,8 @@ class DashboardService:
"stray_holdings": stray_holdings}
def detail(self, strategy_id: str) -> StrategyDetail | None:
# An ARCHIVED sleeve (STRATEGY_REGISTRY `archived: True` — e.g. shelved/FALSIFIED vrp/vrp_exec) must
# never render its detail page: a direct hit to `/strategy/vrp` returns None so the route 404s. This
# An ARCHIVED sleeve (STRATEGY_REGISTRY `archived: True` — e.g. a shelved/FALSIFIED strategy) must
# never render its detail page: a direct hit to its `/strategy/<id>` returns None so the route 404s. This
# is belt-and-suspenders — `registry()` below already excludes archived rows (single source) — but it
# keeps the hide explicit and independent of the repo filter.
if STRATEGY_REGISTRY.get(strategy_id, {}).get("archived"):

View File

@@ -1,5 +1,5 @@
"""display_names.py — the SINGLE internal-id -> human-readable name map used by EVERY cockpit view (the
plain-language global constraint: no raw registry id — `multistrat`/`vrp`/`bybit_4edge` — ever reaches an
plain-language global constraint: no raw registry id — `multistrat`/`bybit_4edge` — ever reaches an
end user). Absorbs the interim `_SIM_BOOK_LABELS` local map that lived in `app.py` (Task 1) for the
sim/Backtest book pills — those two overrides now live here as the SSOT, and `app.py` reads through this
module instead of its own copy.
@@ -48,7 +48,7 @@ def display_name(strategy_id: str, fallback: str | None = None) -> str:
def asset_description(symbol: str) -> str:
"""Plain 'what it is' description for an asset symbol (e.g. SPY -> 'US stocks'). An UNMAPPED symbol (a
future vrp option contract, say) falls back to a plain generic 'other' rather than echoing a raw/odd
future option contract, say) falls back to a plain generic 'other' rather than echoing a raw/odd
ticker string — the symbol itself is still shown in its own column, so nothing is lost. Never blank,
never raises."""
return ASSET_DESCRIPTIONS.get(symbol, "other")

View File

@@ -3,8 +3,8 @@ LOO marginal-Sharpe gate, run BEFORE any forward tracker is wired. This is the g
reconciliation gate on two EM-beta ETFs would PASS trivially (the forward NAV just tracks the backtest NAV)
and prove NOTHING about edge. The real question is decision-theoretic and answerable now from ~8-10y of
history: does adding CNYA.L / NDIA.L to the fund's ETF book RAISE the book's risk-adjusted return after
costs, or is it a co-crashing beta sleeve that fails the marginal gate — the SAME bar that killed VRP and
the token-unlock diversifier (see pearl_fxhnt_diversifier_hunt_2026_07_15).
costs, or is it a co-crashing beta sleeve that fails the marginal gate — the SAME bar that killed prior failed
diversifier candidates, including the token-unlock diversifier (see pearl_fxhnt_diversifier_hunt_2026_07_15).
PASS bar (predetermined, not chosen after seeing the numbers): marginal Sharpe > 0 after cost.
> 0 -> proceed to Stage 2 (wire the `em_asia` registry sleeve + reconciliation forward gate).
@@ -15,8 +15,8 @@ overlapping dates — CNYA.L ~10y, NDIA.L ~8y. Per-name verdicts (not a cross-na
windows are acceptable.
Two marginal measures are reported per name:
1. `marginal_sharpe` (SSOT weight-blend, domain/diversification/marginal.py) — DIRECTLY comparable to the
prior VRP / unlock verdicts, which used this exact primitive.
1. `marginal_sharpe` (SSOT weight-blend, domain/diversification/marginal.py) — DIRECTLY comparable to
prior failed diversifier candidates' verdicts (e.g. unlock), which used this exact primitive.
2. In-book delta-Sharpe — `book_series(FUND + candidate) - book_series(FUND)` through the FULL adaptive
machinery (vol-norm -> trust -> correlation de-risk), i.e. the candidate's effect where it would
actually live. The truer measure; if the two disagree, that disagreement is itself signal.
@@ -68,7 +68,7 @@ class CandidateVerdict:
window_start: str
window_end: str
n_days: int
marginal_sharpe_blend: float # SSOT weight-blend, cost-inclusive (comparable to VRP/unlock)
marginal_sharpe_blend: float # SSOT weight-blend, cost-inclusive (comparable to prior candidates/unlock)
delta_sharpe_inbook: float # book_series(FUND+cand) - book_series(FUND), cost-inclusive
corr_to_book: float
crash_drawdowns: dict[str, float] = field(default_factory=dict)

View File

@@ -1,19 +1,21 @@
"""Runtime staleness/health axis — the anti-silent-rot backbone.
WHY: vrp was silently broken for 2 weeks because a data failure degraded QUIETLY — it recomputed off a
frozen-but-stale `xsp_option_bars` table, its forward "updated" every night, and only a log line marked the
rot. The gate never noticed (a stale track just sits as a quiet WAIT). This module adds a HEALTH axis that is
WHY: a dead/archived strategy was once silently broken for 2 weeks because a data failure degraded
QUIETLY — it recomputed off a frozen-but-stale `xsp_option_bars` table, its forward "updated" every night,
and only a log line marked the rot. The gate never noticed (a stale track just sits as a quiet WAIT). This
module adds a HEALTH axis that is
SEPARATE from the gate: a track can be legitimately WAIT/building AND healthy; STALE/NO_REF/BROKEN is
orthogonal. Any live/gated track that stops updating surfaces LOUDLY on day 1 (red cockpit badge + ERROR log),
not a footnote two weeks later.
The health-check line is `record_mode`, which faithfully encodes "is this track EXPECTED to advance nightly":
- `recompute` tracks (sixtyforty/xsfunding/unlock/multistrat/vrp/bybit_4edge/…): the engine recomputes from
- `recompute` tracks (sixtyforty/xsfunding/unlock/multistrat/bybit_4edge/…): the engine recomputes from
fresh source data EVERY run, so a non-advancing `as_of` (or a frozen freeze table) is a real STALE fault.
vrp lives here → the incident is caught.
- `observed` tracks (the executed twins multistrat_exec/vrp_exec/bybit_4edge_exec): advance ONLY when a
human/executor books a fill, so they are legitimately DORMANT when execution is suspended (vrp_exec has no
IBKR options permission yet). Staleness/no-ref do NOT apply — they must not false-alarm. This is the
A `recompute` track lives here → an incident like the one above is caught.
- `observed` tracks (the executed twins multistrat_exec/bybit_4edge_exec): advance ONLY when a
human/executor books a fill, so they are legitimately DORMANT when execution is suspended (an executed
twin can lack IBKR options permission, for example). Staleness/no-ref do NOT apply — they must not
false-alarm. This is the
"intentionally-dormant reference_only" carve-out. (BROKEN — a health-compute error — still applies, since
a track whose health we cannot even determine is not healthy.)
@@ -21,14 +23,14 @@ States (worst wins, precedence BROKEN > STALE > NO_REF > HEALTHY):
- HEALTHY — advancing within threshold, or dormant-by-design, or a GENUINELY new recompute track (its active
`forward_anchor.t0` is within the threshold, so "no data yet" is expected, not rot).
- STALE(n) — a recompute track's source `as_of` OR its freeze table's max date is > threshold business days
behind the run date. The freeze axis catches the exact vrp failure mode: forward "updates" yet the frozen
input table is stale. Also STALE (not the HEALTHY "no data yet" carve-out): a recompute track with ZERO
behind the run date. The freeze axis catches the exact failure mode described above: forward "updates"
yet the frozen input table is stale. Also STALE (not the HEALTHY "no data yet" carve-out): a recompute track with ZERO
forward data whose anchor `t0` is OLDER than the threshold (broken-from-inception — never advanced since
it was created), or which has no active anchor at all (should have one; a live track with no anchor is a
real signal, never silently HEALTHY).
- NO_REF — a reconciliation-gated recompute track with NO resolvable backtest reference: the gate can NEVER
reconcile (it silently degrades to the min-window/clean-exec checks only). The M6 static invariant that
missed vrp, made a real runtime check.
missed the incident described above, made a real runtime check.
- BROKEN — the PER-TRACK health computation raised (e.g. a malformed registry entry for that strategy):
surfaced for that track only, not swallowed. This does NOT cover the repo-wide reads `evaluate_health`
does once up front (`migrate`/`all_summaries`/`all_backtest_summaries`/`xsp_freeze_max_date`) — those are
@@ -144,7 +146,7 @@ def evaluate_health(dsn: str, at: dt.datetime | None = None) -> list[HealthVerdi
summaries = {s.strategy_id: s for s in repo.all_summaries()}
backtest_sids = {b.strategy_id for b in repo.all_backtest_summaries()}
ref_alias = CockpitBacktestRefProvider._REF_ALIAS # the SAME alias the gate uses to resolve its ref
freeze_max = repo.xsp_freeze_max_date() # one query for the vrp freeze axis
freeze_max = repo.xsp_freeze_max_date() # one query for the frozen-input freeze axis
verdicts: list[HealthVerdict] = []
for sid, meta in STRATEGY_REGISTRY.items():

View File

@@ -1,5 +1,5 @@
"""Dispatch a sim book's honest-cost curve by its registry backtest.kind — bybit books read the precomputed
bybit_sleeve_ret table; recompute-replay books (multistrat/vrp, the IBKR paper strategies) read their own
bybit_sleeve_ret table; recompute-replay books (multistrat, the IBKR paper strategies) read their own
precomputed curve. The sim NEVER recomputes an edge on the request path (both sources are nightly-precomputed:
bybit_sleeve_ret by `_persist_bybit_book`, the IBKR curve by `_persist_track_backtest_ref`)."""
from __future__ import annotations
@@ -9,7 +9,7 @@ from collections.abc import Callable
from fxhnt.application.backtest_refs import registry_backtest_kind
# The Backtest page's book pills, in render order. bybit_4edge/bybit_4edge_levered are the existing Bybit
# books (report-kind, precomputed bybit_sleeve_ret). multistrat/vrp are the IBKR paper strategies
# books (report-kind, precomputed bybit_sleeve_ret). multistrat is the IBKR paper strategy
# (recompute-replay-kind) added by Task 1/D1 — their curve is a precomputed per-day return series read the
# same cheap way (see `sim_returns_for`'s dispatch below).
SIM_BOOKS: tuple[str, ...] = ("bybit_4edge", "bybit_4edge_levered", "multistrat", "multistrat_levered")
@@ -19,7 +19,7 @@ def sim_returns_for(book: str, *, bybit_returns: Callable[[], dict[str, dict[int
ibkr_curve: Callable[[str], dict[str, dict[int, float]]]
) -> dict[str, dict[int, float]]:
"""`book`'s per-sleeve honest-cost return series — `{sleeve: {epoch_day: ret}}` — dispatched by the
registry's `backtest.kind` for `book`: `recompute-replay` (multistrat/vrp) reads the precomputed IBKR
registry's `backtest.kind` for `book`: `recompute-replay` (multistrat) reads the precomputed IBKR
curve via `ibkr_curve(book)`; everything else (the `report`-kind Bybit books) reads the precomputed
`bybit_sleeve_ret` table via `bybit_returns()` — the existing path, unchanged."""
kind = registry_backtest_kind(book)

View File

@@ -212,7 +212,8 @@ class Settings(BaseSettings):
paper_enabled: bool = True # toggle the daily-rebalance paper-book persistence hook (T7)
# Staleness/health axis: a recompute track whose source data (as_of) OR freeze table hasn't advanced in
# more than this many BUSINESS days reads as STALE (a red cockpit badge + ERROR log). Default 3 allows
# the normal ~1-business-day ingest lag; the anti-silent-rot threshold that would have caught vrp.
# the normal ~1-business-day ingest lag; the anti-silent-rot threshold that would have caught a
# strategy silently rotting for weeks.
stale_after_business_days: int = 3
cost_bps_per_turnover: float = 10.0 # round-trip cost model (bps of traded notional)

View File

@@ -68,8 +68,8 @@ class FleetRow:
exec_twin_sid: str = ""
exec_return_pct: float | None = None
exec_divergence_pct: float | None = None
# D3 (Task 4) — the REAL IBKR paper-account "real trades" signal: "EXEC" only for the two IBKR real-
# account books (multistrat/vrp) once real fills have been captured (`exec_vs_sim(...).has_exec`); ""
# D3 (Task 4) — the REAL IBKR paper-account "real trades" signal: "EXEC" only for the IBKR real-
# account book (multistrat) once real fills have been captured (`exec_vs_sim(...).has_exec`); ""
# (with `recon=None`) for every other strategy AND for an IBKR book with no captured data yet — the
# cockpit's Overview sub-row only ever renders for a genuinely-executing row, never a fabricated one.
exec_status: str = ""
@@ -187,7 +187,7 @@ class StrategyDetail:
exec_venue: str | None = None
# D2.2/2.3 (Task 3) — the REAL IBKR paper-account section: what the book is holding right now, its
# recent trades, and the account's raw NLV series (for the value-over-time overlay curve). Empty for
# every strategy that isn't one of the IBKR real-account books (multistrat/vrp) or when no account data
# every strategy that isn't the IBKR real-account book (multistrat) or when no account data
# has been captured yet — the template renders "no trades recorded yet", never a 500.
holdings: list[dict[str, Any]] = field(default_factory=list)
# Held symbols outside the book's allowed universe (US + UCITS) — a legacy/stray position (e.g. an IBIT

View File

@@ -8,9 +8,8 @@ ONLY edits applied (no algorithm / math / constant changes):
* data root made configurable via `$FXHNT_SURFER_DATA_DIR` (default `/data/surfer`):
`pit_sweep.load()` reads `$FXHNT_SURFER_DATA_DIR/crypto_pit/*.npz`.
(The `surfer_poc` module — a value-returning `paper_step` refactor of `paper()` for the fxhnt
ForwardTracker's tail-managed VRP sleeve — was RETIRED with its dormant `MomentumVrpStrategy` caller;
Phase 0b Task 7c.)
(The `surfer_poc` module — a value-returning `paper_step` refactor of `paper()` for a since-retired
ForwardTracker sleeve — was RETIRED along with its dormant caller; Phase 0b Task 7c.)
torch is imported at module level (used in `signal_sweep.sharpe_t`/`validate`) and is kept — this is a
faithful port, not a numpy reimplementation.