docs(vrp): scrub stale VRP comment/docstring references across cockpit + shared infra
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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-15 — the 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-20 — see 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
5
src/fxhnt/vendor/surfer/__init__.py
vendored
5
src/fxhnt/vendor/surfer/__init__.py
vendored
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user