Was using non-existent gap_pct key in enrichment dict; now computes from
today_open and prev_close fields. V30 results: hurst INVERTED+G2 FAIL,
ou_theta G1 FAIL (near-zero Pearson). gap_vs_market re-running as V30b.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 1-min microstructure features tested on V24 200d trade set (n=101).
range_coil_orb shows directional signal (Pearson=-0.128, WR gap +9.9pp)
but fails G1 (n=101 < 120) and G2 (avg_R gap 0.195R < 0.30R threshold).
p-value ~0.10 — insufficient for promotion. V24 remains champion.
Script fetches 1-min bars from Oracle and caches to data/cache/intraday_1min/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 1 diagnostic (diag_orb_short_volume.py) tests 3 short-ratio features
(prior-day, 20d avg, 20d z-score) on V24 200d trade set via FINRA CDN data.
All features fail all gates (|Pearson| ≤ 0.062, avg_R gap < 0.30R, WR gap < 5pp).
Direction inverts Boehmer 2020 hypothesis: high short ratio correlates positively
with ORB r_multiple (short-squeeze dynamics dominate at intraday ORB timescale).
V25 short-volume overlay not wired. V24 remains live champion.
Also adds scripts/audit_short_volume_coverage.py (Phase 0 DB coverage audit).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
V23 400d TRUE (correct pipeline, 2026-04-21): +146.09%, DD -13.66%, Sharpe 2.33.
Prior result (+120.87%, DD -23.97%) was from buggy AH-close pipeline; data fix
improved DD by 10.31pp. V23 now strictly dominates Safe v9 on ALL 400d metrics
(+45pp return, +3.5pp DD, +0.34 Sharpe). Updated Safe v9 status to
validated_200d_only and clarified Korean framing to remove the misleading
"최종 검증 전략" without qualification.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Should be actual_sym (defined in _parking_evaluate_gate scope),
not sym (which is only defined in _parking_buy).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If Oracle is unreachable at 09:20 ET, without this check the pre-screen silently
waits through 5 chunks × 15s timeout (75s) before logging the empty-bars warning.
The new 3s probe logs a CRITICAL warning immediately so the operator can restart
Oracle before the market opens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
os.kill(pid, 0) returns success for zombie (defunct) processes.
Added ps stat check — if process is in Z state, treat as dead and
clean up PID file so subsequent start() calls work correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Paper trader: Alpaca broker fixes, catchup-thread state improvements
- Web GUI: intraday backtest duplicate run button, paper trading fixes
- Experiment registry: cleanup old v15/v16 experiments, update index
- Tests: Oracle client test additions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- orb_simulator.py: min_abs_gap_pct filter, premarket dollar vol filter,
rolling_loss circuit breaker, drawdown_governor, streak_sizing,
trailing_tighten_at_r, allow_doji/red_to_green breakout, abs_gap scoring
for gainers_leader, ORBSimulationState, run_orb_simulation_with_state API
- metrics.py: loss_containment_score and related metrics
- features.py: enrich_daily_bars with gap_zscore, ATR ratio, range compression
- domain.py: extended ORBStrategyParams with new fields
- cache.py: DailyBarCache with merged parquet storage and coverage metadata
- simulator.py: base simulator updates for new entry/exit mechanics
- configs/intraday: updated orb_gainers_v23.yaml with canonical params
- Added BLD to midlarge symbol snapshot
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two fixes:
1. _rebuild_daily_from_intraday_cache now filters to regular market hours
(9:30–16:00 ET) before computing OHLCV. Previously used bars[-1] which
included after-hours data, distorting prev_close for gap calculations.
Root cause of V23 regression: HIMS Aug-4 after-hours drop to $54.81
made it appear as a +0.89% gap on Aug 5 instead of the correct -12.85%
gap (from $63.45 market close), causing it to fail min_abs_gap_pct filter.
V23 with fix: +109.32%, WR 58.1%, Sharpe 3.01, DD -12.91%
2. Remove temporary debug instrumentation (HIMS/2025-08-05 trace blocks)
that was left in orb_simulator.py during regression investigation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_parking_evaluate_gate() was returning only QQQM or SGOV, never TQQQ,
because the low-vol overlay logic (_evaluate_low_vol_overlay_target in
backtester) was not implemented in the live engine. Add overlay check
at the end of the gate: vol threshold, temperature, entropy, and Hurst
R/S analysis — matching the backtester's conditions exactly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
12 tests covering all screener filters: min_price, min_atr_14,
min_avg_dollar_volume, date selection (latest-before, future excluded),
and the V23 quality filters min_atr_pct / max_atr_pct added last session
but previously untested.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests the stop evaluation loop that mirrors orb_simulator.py:477-580:
- Long/short stop_loss hit
- Breakeven promotion (stop moves to entry at 1R)
- trailing_tighten_at_r: verifies tight ATR multiplier fires at 2R vs
normal multiplier, exercising the tighten logic added in V23 port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug: pending candidate DB records were only swept inside the "has open
positions" branch of run_eod_exit, so a server restart mid-day (ORB
detection ran, no breakouts, no positions) left candidates as "pending"
forever.
Fix: move the in-memory and DB pending sweep to run unconditionally before
the positions check.
Test: TestEodDbSweep verifies both code paths (no-position + in-memory).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests the three structural guards that were previously untested:
- Rolling loss filter: skip logic, boundary, window slicing, date exclusion
- Circuit breaker: 25% drawdown halts session
- max_simultaneous_entries: blocks new entries when at cap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- run_eod_exit now queries DB for any pending candidates not in-memory
(guards against server restart mid-day leaving zombie pending records)
- Remove compound_returns=True force-override; live engine doesn't use it
and V23 config has compound_returns=false
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- screener.py: add min_atr_pct/max_atr_pct filters to live_pre_screen
- engine.py: prepend regime ticker (QQQ) to bar fetch so regime filter works
- engine.py: add rolling loss + account circuit breaker at run_orb_detection
- engine.py: patch today_open from first 1-min bar so regime gap is real
- engine.py: explicit regime + breadth filter before compute_orb_candidates
- engine.py: max_simultaneous_entries guard in run_breakout_check
- engine.py: _compute_sizing_capital with daily_budget_reset (fixed $10k base),
drawdown governor, and streak sizing (win bonus / loss penalty)
- engine.py: trailing_tighten_at_r in run_stop_check (tight multiplier at 2R)
Bug fixes in _compute_sizing_capital:
- streak direction: remove reversed() so outcomes[0] = newest trade
- daily reset: use initial_equity as base (not growing equity), matching V23
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stage A for V28 (attention-axis second sleeve) exploration.
Probes existing orb_attention cache against V23 trade outcomes.
Result: ABORT — cache miss 35-43% on V23's traded (ticker, date) pairs;
only n=9 trades hit wiki>=1.5 across full 400d run (p=0.166, not significant).
Stage A inconclusive due to sparsity on V23's trading domain, not signal noise.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Make _parking_buy async (asyncio.sleep instead of time.sleep) and extend
confirmation from 5s to 30s per attempt with up to 3 retries on timeout.
On timeout, cancels the stale order before retrying with fresh price data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New mode (risk.daily_budget_reset=True) where cash_available and sizing
equity reset to initial_equity at the start of each day, regardless of
how many open positions or realized P&L exist. Unlike fixed_capital_sizing
(단리, sizing only), this also treats buying power as if no positions are
held — useful for evaluating signal quality independent of capital constraints.
- domain.py: daily_budget_reset field on RiskConfig
- run.py: _daily_budget_reset flag; _sizing_equity / _sleeve_equity_est /
_build_portfolio_state all honor the new flag
- backtest_sim.py: daily_budget_reset param threaded through
- direct_runner.py: --daily-budget-reset CLI flag
- routers/backtest.py: BacktestRequest field + cmd arg
- client.ts: BacktestParams / BacktestTask types updated
- Backtest.tsx: checkbox in form + DBR badge in task list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All five build_planned_order() calls in apps/paper_trader/engine.py were
missing execution_config, causing build_planned_order to fall back to
base config.execution and silently ignore per-engine target_1_r_override,
target_1_fraction_override, max_holding_days, tiered-target settings, etc.
The backtester has always passed execution_config=_build_effective_execution_config()
(run.py:2210). This divergence caused paper trading to compute wrong target
prices and partial-exit fractions — e.g. AVGO entered with target_r=1.5
(base) instead of 3.0 (engine override), triggering a premature partial
exit on 4/15 that the backtest never produced.
Fix: add execution_config=build_effective_execution_config(candidate, self._config)
to all five call sites and hoist the function to the module-level import.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Copy button per run row in Run History table; opens pre-filled RunConfigPopup
with original run's period/universe/compound_returns settings
- Make RunConfigPopup larger (500px wide) and more spacious with proper header/X button
- Prevent dialog from closing on backdrop click; only Cancel or X button closes it
- Range date inputs laid out side-by-side in 2-column grid
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- When _parking_liquidate_for_event returns False but parking had shares
(sell failed due to transient Alpaca error), skip without recording so
event retries on next run_next_open instead of being permanently blocked
- Improve parking_sell_for_event_failed log: include exc_type, symbol, qty
so we can see the actual exception (was silently swallowed with empty msg)
- Also deleted AVGO from processed_events so it retries tomorrow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AutoScheduler._run_catchup: if server starts after 9:35 AM ET but before
market close (16:00 ET), and run_open hasn't already run today
(checked via processed_phases), run it immediately instead of silently
skipping it — prevents AVGO/event entries being missed on late starts
- filing_poller: log exc_type alongside error so empty-string exceptions
(e.g. HTTPError()) are still identifiable by their type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- screener: switch from non-existent single-ticker endpoint to multi-ticker
/alpaca/intraday batch calls (grouped by date, chunk ≤ 75); fixes 0-trades
- cache: bump version 2→3 to invalidate stale IEX Parquet files
- oracle_client: add get_multi_intraday_bars_today() for IEX real-time feed
- paper_trader: use /alpaca/intraday/today for live sessions, /alpaca/intraday
for historical (SIP)
- intraday.py: define _BUILTIN_STRATEGIES={} to fix /api/orb/strategies import
- delete orb_p1–p10_winner + variant configs; add strategies/orb_default.yaml
(Phase 10 params) as the single registered web strategy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add run_pre_screen() at 9:20 ET: fetch daily bars + enrichment + quality filter
before market open, narrowing universe for faster orb_detect intraday fetch
- run_orb_detection() uses cached pre-screen data when available; falls back to
full pipeline if pre_screen missed (late start, failure)
- Add _last_trading_day() helper to skip weekends/holidays for bars_end,
preventing Alpaca 502 on Mondays (today-1 = Sunday was causing failures)
- Fix Oracle client chunk_size 300→75: Alpaca rejects 100+ ticker URL requests
- Add pre_screen event to build_schedule() at 9:20 ET and dispatch in _run_trading()
- run_session_now() runs pre_screen before orb_detect for efficiency
- Add ORB daemon, engine, models, state, screener, and intraday strategy configs
- Add intraday library (libs/intraday/) and web routes for ORB/intraday trading
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three bugs fixed:
1. _verify_order_fill now distinguishes alpaca_rejected vs order_timeout:
- alpaca_rejected → record processed_event permanently (real problem)
- order_timeout → do NOT record, allows retry on next run_next_open
2. Add _is_market_open() guard before every market buy submission:
skips without recording so event retries when market opens
3. _parking_liquidate_for_event: sleep 1s → 3s after SGOV sell to give
Alpaca time to settle cash; if plan still shows insufficient_cash after
parking freed (race condition), skip without recording instead of
permanently rejecting the event
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- libs/oracle_client/alpaca.py: Added get_multi_daily_bars() and
get_multi_intraday_bars() helpers that call Oracle's /api/v1/price/data
and /api/v1/alpaca/intraday endpoints respectively. Oracle handles
symbol normalization (e.g. BF-B → BF.B) internally, so symbols like
BF-B no longer crash the screening chunk.
- apps/paper_trader/alpaca_broker.py: get_bars() and get_intraday_bars()
now use the new Oracle client helpers instead of the Alpaca SDK
StockBarsRequest, eliminating direct Alpaca bar API calls from broker.
- apps/orb_trader/engine.py: Removed per-symbol BF-B workaround (now
unnecessary since Oracle normalizes the symbol server-side); kept outer
try/except for chunk-level resilience.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Daily bars chunk loop: on failure, retry symbol-by-symbol to isolate
and skip the bad ticker rather than crashing the entire detection.
Intraday bars chunk loop: catch and log failures, continue with rest.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- First log call moved to very top of run_session_now() so user always
sees the task started, even if subsequent DB/engine calls fail
- Entire function body wrapped in try/except: errors go to scheduler log
instead of vanishing in asyncio's unhandled-exception machinery
- Endpoint wraps create_task() in a logging shim (_task()) for the same reason
- 'already ran' path now logs a visible warning instead of silent return
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously run_session_now() only ran orb_detect and injected future
scheduled events, but scheduled breakout windows were already past so
no breakout check ever fired.
Now: after orb_detect, immediately run one breakout check with current
snapshot prices (the core of '지금 시작'). All scheduled breakout events
are marked completed. Only stop_check and eod_exit events are injected
into the live schedule going forward.
Also improve alert message to clarify background execution (~1-2 min).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- POST /orb/auto/clear-log: clears in-memory log lines and deletes
the orb_scheduler.log file on disk
- ORBAutoScheduler.clear_log(): implements the wipe
- Log panel header now has an Eraser icon button on the right;
disabled when log is empty
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ORBDailyStateRow.phase defaults to 'idle' even when no DB row exists,
so bool(daily.phase) was True for brand-new sessions, hiding the
'지금 시작' button. Fix: ran_today = phase not in ('idle', '', None).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /orb/sessions now returns ran_today boolean (true if daily_state.phase
is set, meaning engine ran ORB detection for today)
- POST /orb/sessions/{id}/run_today: fires ORB detection in background and
injects remaining today-events (breakout, stop, EOD) into the live schedule
- ORBAutoScheduler.run_session_now(): coroutine that runs detection then
splices session's future events into self._today_schedule
- Session card shows a cyan "지금 시작 (현재 가격 기준)" button when
ran_today === false; hides it once detection has run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Breakout check interval: change from every-1-min to every sim_bar_minutes,
matching the backtest bar aggregation frequency; align timeout base to
market open (consistent with orb_simulator.py)
- Rejected/cancelled orders: add order_rejected flag so cancelled orders no
longer fall through to position creation (phantom positions)
- Stop/EOD exit fill price: poll broker fill price after close_position()
instead of recording at current_stop, capturing gap-through losses
- Stop/EOD close_position: pass qty=int(pos.shares) so multi-session
same-ticker scenarios only close the current session's share count
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove alias chips next to strategy names
- Use clamp() for responsive padding
- Add flex-wrap to header so title/buttons wrap on narrow screens
- Add flexShrink:0 to button group
- Use min() for search input width
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>