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>
_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>
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>
- 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>
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>
Bug #2 (paper trader): lookback entries sized using historical entry_price_est
but filled at current market price, causing cash overdraft. Fix: override
entry_price_est with get_latest_bars() close before entering _process_entries.
Bug #3 (paper trader + backtester): paper trader was missing the per-candidate
MHD expiration check that the backtester already had. Also adds
lookback_min_remaining_days (default 3) to reject candidates with too little
holding time remaining — prevents entering a position the day before forced exit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Delete v7.360-v7.363 experiment configs (rotation/momentum tests)
- Remove _schedule_momentum_breakout_candidates() from backtester run.py
- Remove MomentumBreakoutConfig from domain.py
- Delete momentum_calendar.py, momentum_screener.py, build_momentum_calendar.py
- Delete data/momentum_calendar/ parquet data
Valid period performance was -31.36% vs +152.4% baseline — sleeve is not viable
without walk-forward validation. Abandoning for now.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PaperTradingEngine now accepts an optional SnapshotStore and uses it
for run_next_open candidate fetching, ensuring live candidate selection
matches the backtester's pre-computed scores exactly. run_reaction_close
keeps EventDetector for real-time intraday event detection. Adds
load_snapshot_store_for_session() helper with auto-refresh logic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- enrich_tier2: prefetch price bars (parallel ThreadPool) and short ratio
(single batch DB query) instead of per-row HTTP/DB calls (~20min → ~2min)
- canonical_snapshots: add PYTHONUNBUFFERED=1 to enrichment subprocesses
so progress output is visible in real time
- backtest_sim: use incremental_update_canonical_snapshot when existing
snapshot is present, falling back to full rebuild only when needed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Live paper trader (engine.py):
- On first run_next_open per daemon session, call get_candidates_for_lookback()
to fetch events from [today - max_mhd*2, today) that are still active
- Skip gap-cap check for lookback entries (multi-day drift ≠ overnight gap)
- Initialize days_held to elapsed trading days when saving strategy state
EventDetector (event_detector.py):
- Extract shared enrichment logic into _enrich_raw_rows(raw_rows, bar_end_date, config)
- Add _fetch_events_for_date_range(start, end): single DB query with entry_date range
- Add get_candidates_for_lookback(today, start_date, config): annotates each row
with is_lookback_entry=True and lookback_days_elapsed=N
Mock broker (backtest_sim.py):
- Extend slice_by_date_range start backward when lookback_entry_enabled, mirroring
the same logic already present in apps/backtester/run.py main()
Verified: BX/EBAY/ENB all entered 2026-03-30 via lookback in both research
backtest and mock broker. Parking, idle_alpha, form4 sleeves unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a backtest starts mid-stream (via --start), events that fired
before the start date but are still within their max_holding_days
window can now be entered on the first simulation day.
- Add `lookback_entry_enabled: bool = False` to ExecutionConfig
- On first sim day, _collect_lookback_candidates() gathers pre-start
events, runs them through the same select_candidates() pipeline,
and injects them before normal candidates
- Entry fills at the first day's open price; gap-cap check is skipped
since the event is multi-days old
- days_held is initialized to the elapsed trading days so TIME exits
fire at the correct time relative to the original event date
- Store slice is extended backward by max_mhd calendar buffer so
pre-start rows survive slice_by_date_range when feature is enabled
- Enabled in return_max_long_v7.119 for testing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backtester (run.py):
- cash_available = (self._cash + parking_value) * multiplier caused trades to be
approved even when self._cash ≈ 0 (all money in SGOV/QQQ). Trades executed
by deducting from self._cash → negative cash (phantom money).
- Fix: after simulate_entry, if self._cash < actual trade cost and parking exists,
call _liquidate_parking_for_cash(shortfall) before deducting from cash.
- Verified: 2022-2026 backtest with qqqm_low_dd shows 0 cash_negative events.
Live engine (engine.py):
- Add _parking_liquidate_for_event(): frees parking cash to fund event entries.
SGOV (virtual) reduces entry_value in DB; QQQM/QQQ sells real shares via broker.
- Both entry loops (engines mode + flat/reaction_close mode) now attempt parking
liquidation when plan.skip_reason == "insufficient_cash" before giving up.
Also includes prior session work (accumulated since last commit):
- 6 novel parking gate signals: VRP, Market Temperature, Hurst exponent, Rolling
Kurtosis, Return Autocorrelation, SPY-QQQ Correlation (composite risk score v2)
- QQQM parking symbol support (lower expense ratio vs QQQ)
- Snapshot auto-refresh + bar extension cache (pickle) to avoid 10-min re-fetches
- Bar extension clamps to last market-closed date (ET 4PM check)
- fithia2 refresh command; --no-refresh flag for paper backtest
- Paper backtest macro extension beyond last event date (parking-only periods)
- parking_state DB schema: 7 new columns (peak_price, gate_in_sgov,
committed_target, pending_target, pending_days, sgov_entry_value, sold_today)
- Live engine: target confirmation (2-day), top-up drawdown gate, trailing stop,
SGOV interest accrual, full 6-signal gate evaluation
- New PARKING_PRESETS: qqqm_low_dd, composite_v2, vv_24_vrp8, vt_24_t13, etc.
- Web GUI / CLI result parity fix (Oracle URL via get_settings().stock_oracle_url)
- Force-close uses last_exec_date (has bar data); parking liquidates at last_date
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Builds a full synthetic market data pipeline to test strategies against
12 diverse market regimes (bull/bear/crash/chop/rotation/liquidity drought)
that may not exist in historical data. Computes Regime Robustness Score (RRS)
to detect overfitting and environment-specific fragility.
- libs/backtest/scenarios/: price_gen, macro_gen, event_gen, coupling,
store_builder, scenarios (12 pre-built), robustness (RRS)
- apps/scenario/cli.py: `fithia2 scenario-test` with Rich output
- apps/tracker/cli.py: scenario-test command routing
- tests/: 83 unit tests across 3 new test files
- docs/scenario_test.md: usage guide and result interpretation
- docs/research_workflow_and_handoff.md: Step 5.5 scenario test added
Fix: no_signal scenario uses drift=0% (was +10%) for fair signal integrity scoring.
Fix: synthetic candidates now carry macro_vix/macro_hy_spread from macro_by_date
to pass selector engine filters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>