Both offline consumers now screen through strategy.population.population_reject
instead of divergent, look-ahead admits:
- learn_params.load_sessions: replaces the `gap>=0.03 OR day_rvol>=2.0` admit,
whose day_rvol was a FULL-DAY volume sum (outcome-time look-ahead, the
2026-07-08 class). gap is already a FRACTION (open vs prev close) — passed
as-is; the OPEN price is passed for the price band. Stored daily bars expose
no open-knowable volume, so volume=None (fail-open on the day-volume floor) —
no day-end volume is smuggled into admission.
- eod_replay._daily_gappers: screens on the OPEN price + STATIC open gap (was
t_close look-ahead) via the shared gate; the local gap is converted to a
FRACTION before the call, so it inherits the >=150% squeeze exception. Deletes
the stale $1-5 / 3-60% WATCHER_/TRIGGER_ constants (KEEPS RISK_PCT). The
second-stage scan_gappers is made permissive (membership already decided by
the shared gate), removing the `gap_pct <= 60%` cap that wrongly dropped
squeezes. Prod thresholds passed explicitly (bare from_env would disable gap).
Unit test replaces the live-DB smoke run: $15/+12% qualifies, $4/+45% rejected
unless >=150%+rvol5. research/labeler.py deferred to plan 006 (no locatable
price/gap admission screen); sweep_exits/sweep_filters likewise deferred.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(a) learn_params.per_setup_report: permanent per-setup P&L/earliness breakdown
in the harness (forces all-setups so the ORB-vs-rest comparison is visible).
(b) nightly_tune.py + nightly_tune.sh: backfill latest bars, sweep ORB params
over accumulated gapper sessions, write the best PROFITABLE config to
data/learned_params.json. run_live_paper reads it on boot and applies the
tuned ready_score / min_bars (explicit env still wins); won't overwrite with
a losing or thin-sample config. Cron-scheduled after the close.
Verified: 75/75 tests; boot logs the learned params and applies them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sweep over 172 real gapper sessions (16 days) with a per-setup breakdown found
the opening-range breakout is the ONLY profitable setup (+0.25R); every slow
pattern loses (bull_flag -$78k @ ~42min is the worst). Running ORB-ONLY flips
the book from -$95k to +$7.4k AND enters ~11 min earlier (23 vs 34 min avg).
- classify_setup prefers the ORB when valid and honors STRATEGY_SETUPS env
(comma-sep setup values) to restrict active setups; .env defaults to
STRATEGY_SETUPS=opening_range_break (ORB-only).
- BacktestEngine Trade.setup_type captured for per-setup attribution.
- learn_params load_sessions filters to gapper sessions (mimics live scanner) so
the backtest trades the same universe the system would.
75/75 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- learn_params.py: grid-search the strategy timing/threshold knobs over stored
sessions, ranking by total P&L AND 'entry_min' (minutes after the open the
entry fires) — a learning method to find params that enter earlier while
staying green.
- evaluate_setup(min_bars=...) + BacktestEngine(ready_score_pct/criteria/min_bars)
now tunable; watcher passes WATCHER_MIN_BARS through (was a no-op: evaluator
hard-coded 10).
Sweep finding: even the earliest params enter ~28-30 min after the open
(baseline ~33) because the first-pullback setup waits for a pullback to FORM.
Cadence/min_bars are not the bottleneck — the setup design is. Genuine earliness
needs a fast opening-breakout trigger + more historical sessions to learn from.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>