Files
momentum-system/tests/unit/test_judge_engine.py
T
momentum devandClaude Opus 5.5 f2c7e52c79 feat: judgement-model (v2) paper trader on the Alpaca "Momentum V2" account
A gradient-boosted model scores burst / micro-pullback / whole-dollar signals on
squeeze stocks from tick data, and only its top-scored regular-hours signals
are traded. Validated in data/ticksim before any live use:
  - v1 on a fresh 600-day sample: real ranking (AUC 0.63), thin edge. FAIL.
  - v2 (trained on Jun-Jul of samples 1+2), pre-registered, on 500 NEW Aug-Sep
    symbol-days: +1.11%/trade after the spread (day-block 90% lo +0.54%),
    n=332 over 38 days. PASS. It held at 2 s latency and +0.2% extra cost.

- judge/engine.py: the validated signals, execution gates and features, as one
  shared source.
- judge/model.py: trains and freezes v2 (cutoff = the training top 20%).
- judge/decide.py: pure decision logic.
- judge/replay.py: a live-style replay of recorded days. Parity: 40 of 41 research
  trades matched with identical model scores.
- judge/live.py: the paper trader. Watchlist from the snapshot scan with
  backfill, SIP trade/quote websocket, decisions each second, a limit at the ask
  with a broker OTO stop (cancelled if unfilled in 3 s), trail 6% after +1R,
  20-min time stop, flat by 15:55. $20 risk and $1,000 max notional per trade.
  Everything is journalled and the day's ticks are kept for paper-vs-simulation
  review. Weekends and holidays exit.
- Runs from the workstation (Task Scheduler, weekdays 09:10 ET) with the
  research venv. Paper only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3S5EFKThFVrjmN4HHsHMN
2026-09-28 18:25:26 -05:00

31 lines
1.6 KiB
Python

"""judge.engine: data windows, execution gates and exact-cross timing (the look-ahead fix)."""
import numpy as np
import pandas as pd
from judge import engine
def test_windows_merge_open_and_squeeze_window():
ws = engine.windows("2026-09-28", pd.Timestamp("2026-09-28 10:00"))
assert ws == [[pd.Timestamp("2026-09-28 09:30"), pd.Timestamp("2026-09-28 11:00")]]
ws = engine.windows("2026-09-28", pd.Timestamp("2026-09-28 06:03"))
assert ws[0] == [pd.Timestamp("2026-09-28 06:01"), pd.Timestamp("2026-09-28 07:03")]
assert engine.in_windows(pd.Timestamp("2026-09-28 09:45"), ws) and not engine.in_windows(pd.Timestamp("2026-09-28 08:00"), ws)
def test_entry_check_gates():
assert engine.entry_check(4.99, 5.00, 5.00, 4.80) == (5.00, 4.80)
assert engine.entry_check(4.70, 5.00, 5.00, 4.80) is None # spread 6% > 3%
assert engine.entry_check(5.09, 5.10, 5.00, 4.80) is None # ask > signal price + 1% chase
assert engine.entry_check(4.99, 5.00, 5.00, 4.00) is None # stop 20% away > 8% max
entry, stop = engine.entry_check(4.99, 5.00, 5.00, 4.99) # stop inside 1% -> widened
assert stop == 5.00 * (1 - engine.EXEC["min_stop_pct"])
def test_cross_time_uses_the_first_trade_through_the_level_not_the_bar_start():
base = np.datetime64("2026-09-28T09:40:00")
tt = np.array([base + np.timedelta64(ms, "ms") for ms in (0, 1500, 3200, 4100)])
tp = np.array([4.95, 4.99, 5.01, 5.05])
assert engine.cross_time(tt, tp, base, 5.00) == base + np.timedelta64(3200, "ms")
assert engine.cross_time(tt, tp, base, 6.00) is None