fix: judge paper trader runs windowless and re-watches earlier symbols after a restart

On 2026-10-02 the trader died 5 times (13:15-15:05 ET). It left no traceback, and
each death came within the task's 5-min re-fire window, which fits its console
window being closed (as on 2026-09-29) while the PC was in use. Every relaunch popped
a new window.
- Task Scheduler now launches it with pythonw: no console window to close. Under
  pythonw, stdout/stderr go to data/judge/logs/live_<date>.log, so a real crash
  still leaves its traceback.
- A relaunched trader re-loads every symbol today's earlier runs were watching (from
  the journal's "add" rows). It used to know only the names still passing the
  scanner, so it stopped deciding on the others and never saved their ticks; the
  review then had nothing to compare CABO and APLX against.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B3PXXn2tHBzdv6rYtGPz86
This commit is contained in:
momentum devandClaude Opus 5.5 committed 2026-10-02 16:08:55 -05:00
1 parent ac92280b9a
commit 7e6f881b17
2 files changed
+38 -1

No files matched your search

+20 -1
View File
@@ -141,7 +141,7 @@ class Trader:
def _earlier_today(self) -> dict:
"""What earlier runs of today's session did (the task relaunches the trader if it dies):
orders per symbol, so the per-symbol cap holds across restarts, and each symbol's last fill."""
out = {"orders": {}, "fills": {}}
out = {"orders": {}, "fills": {}, "watched": {}}
f = OUT / "journal" / f"{self.day}.jsonl"
if not f.exists():
return out
@@ -154,8 +154,22 @@ class Trader:
out["orders"][r["symbol"]] = out["orders"].get(r["symbol"], 0) + 1
elif r.get("kind") == "fill":
out["fills"][r["symbol"]] = r
elif r.get("kind") == "add" and r.get("prev_close"):
out["watched"][r["symbol"]] = float(r["prev_close"])
return out
async def rewatch(self):
"""Re-load every symbol an earlier run of today was watching. Without this, a relaunched trader only knew the
names still passing the scanner, so it stopped deciding on the others, and their ticks were never saved for
the paper-vs-sim review (2026-10-02: CABO, APLX)."""
for sym, pc in self.earlier.get("watched", {}).items():
if sym in self.books or len(self.books) >= 30:
continue
try:
await self.add_symbol(sym, pc)
except Exception as e: # noqa: BLE001
self.j.log("error", where="rewatch", symbol=sym, err=str(e)[:200])
async def adopt(self):
"""Take over positions left by an earlier run today, so they still get the trail, time stop and EOD flat.
A position with no known stop (no journalled fill and no working stop order) is closed rather than guessed at."""
@@ -589,6 +603,7 @@ class Trader:
tasks = [asyncio.create_task(self.stream_loop()), asyncio.create_task(self.scan_loop()),
asyncio.create_task(self.decide_loop())]
await self.adopt()
await self.rewatch()
await self.manage_loop()
for t in tasks:
t.cancel()
@@ -597,4 +612,8 @@ class Trader:
if __name__ == "__main__":
if sys.stdout is None or sys.stderr is None: # pythonw (no console window): keep crash output in the log
(OUT / "logs").mkdir(parents=True, exist_ok=True)
_log = open(OUT / "logs" / f"live_{now_et():%Y-%m-%d}.log", "a", encoding="utf-8", buffering=1)
sys.stdout = sys.stderr = _log
asyncio.run(Trader().run())
+18
View File
@@ -122,3 +122,21 @@ def test_soft_stop_walks_trades_in_order():
assert Trader._soft_stop_hit(pz, [11.25]) == "soft_trail"
pz = Pos("X", 100, 10.0, 9.5, 0.5, live.now_et(), "o", "leg", 10.0, "burst", 0.4)
assert Trader._soft_stop_hit(pz, [9.4, 12.0]) == "soft_stop" # order matters: the dip came first
def test_restart_rewatches_symbols_from_earlier_runs(tmp_path, monkeypatch):
(tmp_path / "journal").mkdir()
(tmp_path / "journal" / "2026-10-02.jsonl").write_text(
'{"kind":"add","symbol":"CABO","prev_close":11.5}\n{"kind":"add","symbol":"APLX","prev_close":7.2}\n',
encoding="utf-8")
monkeypatch.setattr(live, "OUT", tmp_path)
t = Trader.__new__(Trader)
t.day, t.books, t.j = "2026-10-02", {}, J()
t.earlier = t._earlier_today()
added = []
async def fake_add(sym, pc):
added.append((sym, pc)); t.books[sym] = object()
t.add_symbol = fake_add
asyncio.run(t.rewatch())
assert added == [("CABO", 11.5), ("APLX", 7.2)]