From 5ceef435a28932e76e489e9ac57e1b5227d14d21 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 22:32:53 +0000 Subject: [PATCH] Clock: re-dispatch the shadow HFT every hour of the session Its hourly chain broke at 13:10 NY on 2026-11-09: the successor run failed to start (GitHub recorded it as failed with no jobs), and the clock only dispatched hft-shadow once, at 9:26. Now it re-dispatches every hour, 9:26-15:26 NY. A dispatch while one runs only queues behind it (concurrency group). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NsPrq53XXinQsUZsWqeKht --- qtrade/clock.py | 4 +++- tests/test_clock.py | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/qtrade/clock.py b/qtrade/clock.py index edb49b88..0e50881a 100644 --- a/qtrade/clock.py +++ b/qtrade/clock.py @@ -34,7 +34,9 @@ class Job: JOBS = [ Job("data-hoard.yml", "UTC", tuple(f"{h:02d}:{m:02d}" for h in range(24) for m in (7, 37)), weekdays=False, window_min=20), - Job("hft-shadow.yml", NY, ("09:26",), window_min=300), # its own chain then runs the session + # its own chain runs the session; the hourly re-dispatch restarts it if a link fails (2026-11-09: a run that + # never started stopped it at 13:10) and otherwise only queues behind the running one + Job("hft-shadow.yml", NY, tuple(f"{h:02d}:26" for h in range(9, 16)), window_min=50), Job("flip-sleeve.yml", NY, ("15:45",), window_min=400), Job("auction-sheet.yml", NY, ("16:55",), window_min=400), # after Yahoo's ~16:30-16:45 refresh of Asian bars Job("hoard-archive.yml", NY, ("16:45",), inputs=(("job", "eod"),), window_min=400), diff --git a/tests/test_clock.py b/tests/test_clock.py index 8a53096c..e4b98b5a 100644 --- a/tests/test_clock.py +++ b/tests/test_clock.py @@ -32,3 +32,9 @@ def test_run_fires_each_slot_once(monkeypatch): monkeypatch.setattr(C.time, "sleep", lambda s: clock.__setitem__("t", clock["t"] + 1800)) fired = C.run(1.0, "o/r", "main", dry_run=True) # polls at 0 s and 1800 s, both within the 17:15 slot assert sum("foreign-sheet.yml" in f for f in fired) == 1 + + +def test_hft_shadow_is_re_dispatched_every_hour_of_the_session(): + j = _job("hft-shadow.yml") + keys = [k for h in range(14, 22) for k in C.due(j, pd.Timestamp(f"2026-11-09 {h}:30", tz="UTC"))] + assert len(keys) == 7 and keys[0].endswith("09:26") and keys[-1].endswith("15:26")