backtest: explain the regime-gated empty window + toggle to view raw-criteria trades
Owner saw a blank /backtest — it was CORRECT (the regime gate sat out every recent session) but unexplained. Now: ?regime=0|1 override on /api/backtest, an 'apply regime gate (as deployed)' checkbox, and an empty-state message saying WHY there are no trades and what to click. Gated view = what the bot would do; ungated = the raw simple criteria. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3ce8594605
commit
053c927cf7
3 files changed
+43
-3
No files matched your search
+8
-2
@@ -511,7 +511,7 @@ class DashboardState:
|
||||
return rows
|
||||
|
||||
def backtest(self, days: int | None = 10, since: str | None = None,
|
||||
until: str | None = None) -> dict:
|
||||
until: str | None = None, regime_override: bool | None = None) -> dict:
|
||||
"""Theoretical trades under CURRENT live settings (plan 015) —
|
||||
assembly only, the simulation lives in research.experiments.replay.
|
||||
|
||||
@@ -530,6 +530,10 @@ class DashboardState:
|
||||
pop = PopulationConfig.from_env()
|
||||
regime_on = os.environ.get("TRADING_REGIME_GATE_ENABLED", "0").strip().lower() in {
|
||||
"1", "true", "yes", "on"}
|
||||
if regime_override is not None:
|
||||
# /backtest UX: let the page show RAW-criteria trades when the gate
|
||||
# sits out the whole window (owner: 'no data in backtest').
|
||||
regime_on = regime_override
|
||||
fidelity = {"atr_frac": 0.25, "spread_mult": 3, "adverse_ticks": 1}
|
||||
settings = {
|
||||
"population": {"price_min": pop.price_min, "price_max": pop.price_max,
|
||||
@@ -805,8 +809,10 @@ class DashboardHandler(BaseHTTPRequestHandler):
|
||||
since = (q.get("since") or [""])[0] or None
|
||||
until = (q.get("until") or [""])[0] or None
|
||||
days = (q.get("days") or [""])[0]
|
||||
rg = (q.get("regime") or [""])[0]
|
||||
self._send_json(self.state.backtest(
|
||||
days=int(days) if days else 10, since=since, until=until))
|
||||
days=int(days) if days else 10, since=since, until=until,
|
||||
regime_override=(rg == "1") if rg in ("0", "1") else None))
|
||||
elif route == "/api/attribution":
|
||||
# per-component stage ratings (research/attribution.py); computed on
|
||||
# demand — a handful of indexed event queries, fine per click
|
||||
|
||||
@@ -141,6 +141,9 @@
|
||||
<input type="date" id="since-input">
|
||||
<input type="date" id="until-input">
|
||||
<button class="run" id="run-custom">Run range</button>
|
||||
<label style="font: 600 11px var(--mono); color: var(--muted); display:flex; align-items:center; gap:5px; margin-left:8px">
|
||||
<input type="checkbox" id="regime-toggle" checked> apply regime gate (as deployed)
|
||||
</label>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -209,6 +212,9 @@ function setLoading(msg) {
|
||||
|
||||
async function runBacktest(params, activeBtn) {
|
||||
document.querySelectorAll(".picker button[data-days]").forEach(b => b.classList.toggle("active", b === activeBtn));
|
||||
state.lastParams = params; state.lastBtn = activeBtn;
|
||||
// the gate toggle rides every request: unchecked = raw-criteria trades
|
||||
params = { ...params, regime: $("regime-toggle").checked ? "1" : "0" };
|
||||
const n = params.days || "the requested";
|
||||
setLoading("replaying " + n + " session" + (n === 1 ? "" : "s") + "… (can take up to 30s)");
|
||||
try {
|
||||
@@ -229,6 +235,7 @@ async function runBacktest(params, activeBtn) {
|
||||
document.querySelectorAll(".picker button[data-days]").forEach(btn => {
|
||||
btn.onclick = () => runBacktest({ days: btn.dataset.days }, btn);
|
||||
});
|
||||
$("regime-toggle").onchange = () => runBacktest(state.lastParams || { days: 10 }, state.lastBtn || null);
|
||||
$("run-custom").onclick = () => {
|
||||
const since = $("since-input").value, until = $("until-input").value;
|
||||
if (!since || !until) return;
|
||||
@@ -285,7 +292,13 @@ function renderSummary(sum, skipped, screened) {
|
||||
// (cum_pnl -> cum_r, day_pnl -> sum_r); click a day filters the trade table.
|
||||
function renderDaily(days) {
|
||||
const host = $("daily-curve");
|
||||
if (!days || !days.length) { host.innerHTML = '<span class="empty">No trades in this window.</span>'; return; }
|
||||
if (!days || !days.length) {
|
||||
const reg = ((state.data || {}).screened || {}).regime || 0;
|
||||
host.innerHTML = '<span class="empty">' + (reg > 0
|
||||
? 'No trades: the regime gate sat out every replayed session in this window — the bot would not have traded these days at all. Uncheck \u201capply regime gate\u201d above to see what the raw criteria would have done.'
|
||||
: 'No trades in this window (no qualifying setups).') + '</span>';
|
||||
return;
|
||||
}
|
||||
const n = days.length;
|
||||
const W = Math.max(host.clientWidth || 680, n * 48);
|
||||
const H = 184, padT = 16, padB = 30, padX = 12, plotH = H - padT - padB;
|
||||
|
||||
@@ -244,3 +244,24 @@ def test_replay_window_replays_live_track_only():
|
||||
from research.experiments.replay import replay_window
|
||||
out = replay_window(store, str(SESS1), str(SESS2))
|
||||
assert len(out["trades"]) == 4 # the 4 v1 fires only; liq1 excluded
|
||||
|
||||
|
||||
def test_backtest_regime_override_bypasses_gate(monkeypatch):
|
||||
"""/backtest UX fix (owner: 'no data in backtest'): when the regime gate
|
||||
zeroes a whole window the page must be able to show RAW-criteria trades
|
||||
via ?regime=0, and the response must echo which mode ran."""
|
||||
import api.main as m
|
||||
store = _seed_store()
|
||||
monkeypatch.setenv("TRADING_REGIME_GATE_ENABLED", "1")
|
||||
# unfavorable scores for BOTH sessions -> gated run has zero trades
|
||||
from research import regime as rg
|
||||
rg.apply_schema(store.con)
|
||||
for sess in (SESS1, SESS2):
|
||||
store.con.execute(
|
||||
"INSERT INTO regime_scores (model_version, session_date, p_profit, favorable, computed_at) "
|
||||
"VALUES ('meta_v1', ?, 0.1, false, now()) ON CONFLICT DO NOTHING", [sess])
|
||||
state = m.DashboardState("momentum")
|
||||
gated = state.backtest(days=10)
|
||||
raw = state.backtest(days=10, regime_override=False)
|
||||
assert gated["summary"]["n"] == 0 and gated["settings"]["regime_gate"] is True
|
||||
assert raw["summary"]["n"] == 4 and raw["settings"]["regime_gate"] is False
|
||||
Reference in new issue
Block a user