⚡ Momentum Trading System Architecture

Alpaca IEX Bars Ingestion Module Research DB (market_data) EVENT STORE (momentum_events.duckdb) append-only canonical events Watcher symbol evaluation state machine Execution Service sizing + approval order submission Alpaca Sync positions, orders, account state Alpaca Paper order submission & fills Dashboard API & UI http://127.0.0.1:8010 projections + SSE streaming Watchlist
Data Flow: Market data, bars, watchlist
Process: Computation & evaluation
Events: Canonical state changes
Storage: Persistent data
Output: User-facing interface
External: Broker APIs
📊 Complete Processing Flow (Every Tick)
1 INGEST → ingest_live_minute_bars() + ingest_daily_history()
Latest 1-minute bars from Alpaca IEX into research/market.duckdb + daily backfill for gap% and RVOL calculations
2 DISCOVER → ResearchWatchlistProvider.get_candidates()
Pull symbols from watchlist (manual, screener, or most-actives from Alpaca)
3 WATCH → Watcher.tick()
For each symbol: fetch bars from research DB, evaluate 9 setup criteria, emit symbol_discovered / state_changed events
4 EVALUATE → evaluate_setup()
Pure strategy logic: gap%, RVOL, bull-flag structure, price action quality. Emit criteria_evaluated events.
5 SIGNAL → Watcher emits signal_ready
Symbol passes ≥60% of criteria (tunable). Debounced (once per session unless it falls back to blocked).
6 EXECUTE → TradingExecutionService.tick()
On signal_ready: position sizing → risk checks (max concurrent, daily loss circuit breaker) → order_approval_requested event
7 APPROVE → Dashboard or --auto-approve
Manual: click Approve in dashboard. Auto: env flag TRADING_AUTO_APPROVE=1. Emit order_approved event.
8 SUBMIT → AlpacaPaperExecutor.submit()
Bracket order: entry (limit or market), profit target (entry + reward_multiple × risk), stop loss. Emit order_submitted event.
9 SYNC → AlpacaPaperSync.sync_account()
Poll Alpaca: account balance, open positions, pending orders. Emit account_summary_updated, account_positions_updated, account_orders_updated events.
10 PROJECT → Dashboard (SSE stream)
Read-only projections over event store: P&L, open positions, approval queue, activity feed, sparkline charts. Fall back to polling if SSE drops.
🔧 Key Modules & Responsibilities
Module Responsibility Key Files
research/ingestion Ingest market data (bars, daily history), discover candidates, classify market sessions market_data.py, watcher_task.py, scheduler.py
research/ Query and project research databases (minute/daily bars, gappers, RSS news) query.py, schema.py, multi_schema.py
runtime/watcher Symbol state machine: discovered → watching → ready | blocked. Emit all watcher events. watcher.py
strategy/evaluation Pure setup evaluation: 9 criteria (gap%, RVOL, bull-flag, structure, quality, etc.) setup_evaluator.py
strategy/risk Position sizing, risk per trade, reward multiple, daily loss circuit breaker position_sizing.py
strategy/backtest Intraday backtest engine: replay bars, simulate entry/exit, calculate P&L engine.py
storage/event_store Append-only event log (canonical source of truth). Correlate by session_id. event_store.py, event_schema.py
storage/projections Query event store for current state: approval queue, open positions, P&L, etc. projections.py
alpaca_paper/ Alpaca paper broker integration: order submission, position sync, account state client.py, execution.py, sync.py
schwab/ Schwab live integration (OAuth, market data, positions, orders, health) oauth.py, market_data.py, positions.py, health.py
api/ Dashboard JSON API + SSE streaming. Read-only projections served as REST + events. main.py, static/
trading_execution.py Signal → sizing → approval → order pipeline. Manages armed entries & timeouts. trading_execution.py
📨 Key Event Types (Event-Sourced)
Event Emitted By Purpose
symbol_discovered Watcher New symbol added to watchlist
symbol_state_changed Watcher Symbol transitions: discovered → watching → ready | blocked
criteria_evaluated Watcher (via evaluate_setup) 9 setup criteria evaluated for symbol (pass/fail/not-evaluated)
signal_ready Watcher Symbol ready: ≥60% criteria passed. Debounced once per session.
signal_blocked Watcher Symbol failed criteria check. Resets debounce.
order_approval_requested TradingExecutionService Ready signal → sizing + risk checks passed. Awaiting approval or auto-submit.
order_approved Dashboard (manual) or TradingExecutionService (auto) Approval given. Next: submit to broker.
order_rejected Dashboard (manual) or TradingExecutionService (risk rule) Approval denied or risk rule triggered. Signal dequeued.
order_submitted AlpacaPaperExecutor Bracket order sent to Alpaca (entry + target + stop).
account_positions_updated AlpacaPaperSync Open positions snapshot from Alpaca. Includes entry, qty, P&L.
account_orders_updated AlpacaPaperSync Pending orders snapshot from Alpaca. Tracks fills, cancellations, expirations.
account_summary_updated AlpacaPaperSync Account balance, buying power, margin, NAV, realized P&L.
🚀 Main Entry Point: run_live_paper.py
Overall orchestrator that ties everything together:
  1. Parse CLI args (--once, --symbols, --auto-approve, --no-dashboard)
  2. Load .env and configure Alpaca client + settings
  3. Initialize EventStore (momentum_events.duckdb)
  4. Open ResearchConnection (market_data.duckdb) + ResearchWatchlistProvider
  5. Create Watcher + TradingExecutionService + AlpacaPaperSync + AlpacaPaperExecutor
  6. Optional: start DashboardAPI server (SSE + JSON endpoints) in background thread
  7. Main loop (or single pass with --once):
    • Ingest latest minute bars + daily history
    • Watcher.tick() → symbol evaluation + state changes + signal emissions
    • TradingExecutionService.tick() → approval requests + submissions
    • AlpacaPaperSync.sync_account() → positions + orders + account state
    • Sleep (configurable interval, e.g., 60 seconds)
🔍 Debugging: Where to Look When Something Breaks
Symptom Likely Cause Check
No symbols appearing in Watcher Watchlist provider not returning candidates ResearchWatchlistProvider.get_candidates() → is research DB populated? Check data/research/market.duckdb
Symbols discovered but never go ready Criteria evaluation failing or threshold too high Watcher → criteria_evaluated events. Check setup_evaluator.py scores. Lower ready_score_pct or min_quality config.
Signal ready but no approval request TradingExecutionService.tick() not running or risk rule triggered Check trading_execution.py: max_concurrent_positions, max_daily_loss_pct. Emit risk_rule_triggered? Check event store.
Approval request but order never submitted Dashboard approval not clicked OR auto-approve not set Dashboard approval queue visible? Or set TRADING_AUTO_APPROVE=1 in .env
Order submitted but no fills from Alpaca API keys missing or Alpaca API down Check ALPACA_API_KEY / ALPACA_SECRET_KEY in .env. Run --once to test without loop.
Dashboard not updating (SSE fails) Network drop or event stream blocked Dashboard falls back to polling. Check http://127.0.0.1:8010/api/events (should return JSON). Check browser console.
Backtests showing different P&L than live Bar data mismatch or backtest engine using different fills Backtest engine simulates limit fills at exact prices. Live uses actual Alpaca fills. Compare bars in research DB vs Alpaca API.
💾 Data Storage Locations
Event Store (Canonical Source of Truth)
data/momentum_events.duckdb — append-only events, indexed by timestamp + session_id
Research Database (Market Data)
data/research/market.duckdb — minute bars + daily bars ingested from Alpaca
Schwab Integration
data/schwab_tokens.json — OAuth tokens (git-ignored, 0600 perms)
Configuration
.env — API keys, trading settings (git-ignored)