fix: backtest starvation — schedule the labeler nightly, make inserts idempotent
Dashboard backtest sources sessions from setup_events v1, which only the labeler writes — and the labeler was never in the nightly scheduler, so setups (and therefore backtests) froze at the last manual run (07-17). Root blocker was _insert crashing on duplicate setup_ids on any re-run; now ON CONFLICT DO NOTHING, so incremental nightly builds are safe. Scheduler gains 'labeler build' + 'build-tod1' after eod_replay in both compose files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5f33799a9a
commit
ed176b3712
3 files changed
+22
-2
No files matched your search
@@ -569,7 +569,7 @@ services:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "echo \"[scheduler] up; fires 20:00 ET Mon-Fri\"\nwhile true; do\n if [ \"$$(date +%H:%M)\" = \"20:00\" ] && [ \"$$(date +%u)\" -le 5 ]; then\n python -m research.premarket_eval || echo \"[scheduler] eval FAILED\"\n python -m research.ingestion.market_context || echo \"[scheduler] market_context FAILED\"\n python -m research.eod_reconcile || echo \"[scheduler] reconcile FAILED\"\n python eod_replay.py || echo \"[scheduler] eod_replay FAILED\"\n python nightly_tune.py || echo \"[scheduler] nightly_tune FAILED\"\n python -m research.regime build-features || echo \"[scheduler] regime build FAILED\"\n python -m research.regime score --model threshold_v1 || echo \"[scheduler] regime threshold FAILED\"\n python -m research.regime score --model meta_v1 || echo \"[scheduler] regime meta FAILED\"\n python -m research.experiments run || echo \"[scheduler] experiments run FAILED\"\n python -m research.experiments adjudicate || echo \"[scheduler] experiments adjudicate FAILED\"\n sleep 120\n fi\n sleep 20\ndone\n"
|
||||
- "echo \"[scheduler] up; fires 20:00 ET Mon-Fri\"\nwhile true; do\n if [ \"$$(date +%H:%M)\" = \"20:00\" ] && [ \"$$(date +%u)\" -le 5 ]; then\n python -m research.premarket_eval || echo \"[scheduler] eval FAILED\"\n python -m research.ingestion.market_context || echo \"[scheduler] market_context FAILED\"\n python -m research.eod_reconcile || echo \"[scheduler] reconcile FAILED\"\n python eod_replay.py || echo \"[scheduler] eod_replay FAILED\"\n python -m research.labeler build || echo \"[scheduler] labeler build FAILED\"\n python -m research.labeler build-tod1 || echo \"[scheduler] labeler build-tod1 FAILED\"\n python nightly_tune.py || echo \"[scheduler] nightly_tune FAILED\"\n python -m research.regime build-features || echo \"[scheduler] regime build FAILED\"\n python -m research.regime score --model threshold_v1 || echo \"[scheduler] regime threshold FAILED\"\n python -m research.regime score --model meta_v1 || echo \"[scheduler] regime meta FAILED\"\n python -m research.experiments run || echo \"[scheduler] experiments run FAILED\"\n python -m research.experiments adjudicate || echo \"[scheduler] experiments adjudicate FAILED\"\n sleep 120\n fi\n sleep 20\ndone\n"
|
||||
environment:
|
||||
ALPACA_API_KEY: <FILL-ME-ALPACA-KEY>
|
||||
ALPACA_BACKFILL_FEED: sip
|
||||
|
||||
+4
-1
@@ -443,9 +443,12 @@ def compute_setup(symbol, session_date, df, prior_close, avg_vol, cats: dict,
|
||||
|
||||
|
||||
def _insert(con, table, row):
|
||||
# ON CONFLICT DO NOTHING: builds are re-runnable (the nightly scheduler calls
|
||||
# them incrementally); already-built setup_ids are skipped, not crashed on.
|
||||
cols = list(row.keys())
|
||||
con.execute(f"INSERT INTO {table} ({', '.join(cols)}) VALUES "
|
||||
f"({', '.join(['?'] * len(cols))})", [row[c] for c in cols])
|
||||
f"({', '.join(['?'] * len(cols))}) ON CONFLICT DO NOTHING",
|
||||
[row[c] for c in cols])
|
||||
|
||||
|
||||
def build(con, limit=None, rebuild=False):
|
||||
|
||||
@@ -237,3 +237,20 @@ def test_tod1_silent_without_volume_explosion():
|
||||
df = _tod1_bars(base_vol=300) # 300/min vs med 100k -> tod << 50
|
||||
assert compute_tod1_setups("QUIET", D, df, prior_close=2.00,
|
||||
med_vol=100_000, cats={}) == []
|
||||
|
||||
|
||||
def test_insert_is_idempotent_on_setup_id():
|
||||
"""Nightly incremental builds re-scan history — a duplicate setup_id must be
|
||||
skipped (ON CONFLICT DO NOTHING), not crash the whole build."""
|
||||
from storage.event_store import EventStore
|
||||
from research.labeler import _insert
|
||||
s = EventStore(":memory:")
|
||||
con = s.con
|
||||
row = dict(setup_id="X:2026-06-01:v9", symbol="X", setup_time=datetime(2026, 6, 1, 9, 40),
|
||||
session_date=date(2026, 6, 1), setup_name="t", setup_version="v9",
|
||||
entry_reference_price=1.0, invalidation_price=0.9)
|
||||
_insert(con, "setup_events", row)
|
||||
_insert(con, "setup_events", row) # duplicate: must not raise
|
||||
n = con.execute("SELECT count(*) FROM setup_events WHERE setup_version='v9'").fetchone()[0]
|
||||
assert n == 1
|
||||
s.close()
|
||||
Reference in new issue
Block a user