Every POST endpoint (approve/reject/exit/watch) crashed with NameError: do_POST references os.environ + hmac.compare_digest but neither was imported. Adds the imports, a non-loopback regression test for the token gate (no token -> routes; token set -> 401 without/with-wrong header, 200 with correct), and plumbs DASHBOARD_TOKEN + PG_SKIP_SCHEMA=1 into both compose dashboard services (+ .env.example docs for DASHBOARD_TOKEN / COMMAND_BUS_ENABLED). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Momentum
Event-sourced small-cap momentum day-trading system (Ross Cameron–style gap-and-go / opening-range breakout). It screens the day's top overnight gappers — the year-scale-validated population: $2–20, static open gap 10–35%, real volume, plus a volume-verified >150% squeeze exception — arms an opening-range-breakout trigger the moment the range forms, fires a bracket order on the live cross (regular session only), and manages the exit with a breakeven ratchet + trailing profit-lock ladder. Execution runs on an Alpaca paper account over the SIP consolidated feed; everything is recorded in a PostgreSQL append-only event store and replayable.
Alpaca SIP bars ─► research DBs (minute+daily) ─► screen gappers ─► ArmedTriggerBook
(1-min, live) │ live price
▼ crosses trigger
PostgreSQL event store ◄────────────────────── TradingExecutionService ─► Alpaca paper
(append-only: signals, orders, │ bracket (entry+stop+target)
fills, account, risk events) ▼
▲ LiveExitManager ─► trail stop ↑ / flatten
└──────────── AlpacaPaperSync ◄──────── (account / positions / orders)
Every read path (dashboard, journal, monitoring board) is a pure projection over the event store, so any session can be replayed and audited.
Quick start (paper trading)
pip install -r requirements.txt
cp .env.example .env # fill in ALPACA_API_KEY / ALPACA_SECRET_KEY + DATABASE_URL
# PostgreSQL is the datastore. Point DATABASE_URL at your instance, e.g.
# DATABASE_URL=postgresql://user:pass@127.0.0.1:5432/momentum
python run_live_paper.py # full loop (+ embedded dashboard)
No keys? python run_live_paper.py --once runs a single dry pass to verify wiring.
Deployment
Three supported shapes — Docker Compose is canonical (production runs the full stack self-contained on TrueNAS SCALE: postgres, loop, dashboard :8765, admin panel :8767, telegram bot, transcripts, nightly scheduler). Local systemd units remain the development shape; a Helm refresh is planned. See deploy/README.md for all three, including the behavior flags every deployment must set explicitly.
Commands
Everything is driven from the repo root. The ./momentum wrapper runs the
inspection CLI in the project venv; the analysis scripts are plain python.
Live trading loop — run_live_paper.py
| Command | What it does |
|---|---|
python run_live_paper.py |
Full live loop: screen → arm → fire → manage exits → EOD flatten, with the embedded dashboard. |
python run_live_paper.py --no-dashboard |
Same, headless (how it runs in production / under a process manager). |
python run_live_paper.py --once |
One pass then exit — smoke-test the wiring. |
python run_live_paper.py --auto-approve |
Auto-arm brackets instead of waiting for dashboard approval (TRADING_AUTO_APPROVE=1). |
python run_live_paper.py --symbols AAPL,SNDL |
Force specific symbols onto the watchlist. |
Monitoring & inspection — ./momentum
| Command | What it does |
|---|---|
./momentum watch |
Live monitoring board (armed triggers, ready signals, today's trades + day P&L, risk events). Refreshes every 3s. |
./momentum watch --once |
Render one frame and exit (for logging / a cron snapshot). |
./momentum journal |
Today's trade journal — every round-trip (ET times), broker-authoritative day P&L (matched · open), win rate. The 16:02 close capture. |
./momentum doctor |
Health check across each pipeline stage (DB reachable, ingest fresh, broker sync, loop heartbeat). |
./momentum inspect events |
Tail the raw event stream. |
./momentum inspect bars SYM |
Today's minute bars for a symbol. |
./momentum inspect discovery |
What the screener surfaced. |
./momentum inspect criteria SYM |
Per-criterion pass/fail for one symbol (why it is/isn't a setup). |
./momentum inspect signals |
Recent ready signals. |
Analysis, backtest & tuning
| Command | What it does |
|---|---|
python eod_replay.py [YYYY-MM-DD] |
End-of-day whole-market replay: run today's logic over every qualifying gapper (not just the ~20 watched live) + a perfect-hindsight line. Also grows the stored dataset for future verification. Defaults to today. |
python backtest_cli.py |
Intraday backtests over stored sessions. |
python sweep_exits.py |
Sweep managed-exit variants (trail / breakeven / profit-lock tiers), train/test split. |
python sweep_filters.py |
Sweep entry-filter variants (gap / rvol / price / liquidity). |
python diagnose_entries.py |
Entry-quality analysis — what the losers had in common. |
python nightly_tune.py |
Self-tune params into data/learned_params.json (applied on next boot). |
python research_cli.py |
Ad-hoc: session symbols, gapper scan, RSS news pull. |
python milestone3_verify.py |
Schwab integration health walk-through. |
Data ingestion
| Command | What it does |
|---|---|
python fetch_minute_bars.py |
Ad-hoc minute-bar ingestion. |
python backfill_history.py |
Backfill daily/minute history for the universe. |
Scheduled jobs (cron wrappers)
The .sh wrappers set the venv + cwd so cron can call them directly:
2 16 * * 1-5 /path/momentum/journal.sh # capture the close trade journal
5 16 * * 1-5 /path/momentum/eod_replay.sh # whole-market replay (grows dataset)
30 17 * * 1-5 /path/momentum/nightly_tune.sh # retune params for tomorrow
Tests
DATABASE_URL=postgresql://user:pass@host:5432/momentum python -m pytest
# ~365 tests: strategy, storage, triggers, execution (cooldown/re-entry/EOD
# flatten/naked-stop guards), exits, journal P&L, api, research.
Tests isolate via throwaway mem_* Postgres schemas; tests/conftest.py sweeps
them at session end.
Configuration
All behavior is environment-driven — see .env.example for every key with
the why. The knobs that matter most:
- Entry population (year-scale validated 2026-07):
TRADING_PRICE_MIN/MAX(2–20),TRADING_MIN_ENTRY_GAP_PCT(0.10, static open gap),TRIGGER_GAP_MAX(35 — the 35-150% band tested dead),TRIGGER_GAP_SQUEEZE_MIN/RVOL(150/5 — re-admits real squeezes),TRADING_MIN_DAY_VOLUME,TRADING_SESSION_ONLY(bars after-hours entries). - Sizing:
TRADING_RISK_PER_TRADE_PCT(1%),TRADING_MAX_POSITION_PCT,TRADING_LIQUIDITY_MAX_VOLUME_PCT(size down thin names). - Exits:
TRADING_EXIT_TRAIL_MODE=prior_low,TRADING_EXIT_TRAIL_AFTER_R,TRADING_EXIT_BREAKEVEN_R,TRADING_EXIT_PROFIT_TIERS. - Discipline:
TRADING_BACKOUT_COOLDOWN_SECONDS,TRADING_BLOCK_REENTRY_AFTER_EXITTRADING_REENTRY_MIN_LOSS_PCT,TRADING_EOD_FLATTEN_TIME.
- Safety:
TRADING_MAX_DAILY_LOSS_PCT— set to0.03before real-money trading (it ships loosened for testing).
Database & performance
The single datastore is PostgreSQL (DATABASE_URL) — bars, events,
research tables, everything (the DuckDB era is over; data/ holds only logs,
transcripts, and boot-time state). The event store is one append-only events
table that every projection reads.
Hot-path index. Projections overwhelmingly run
WHERE event_type=? ORDER BY timestamp DESC LIMIT n (latest snapshot of a type).
A composite index makes this an index seek regardless of how stale the type is —
without it, looking up the latest of an idle type backward-scans the whole table
(measured 373 ms → 0.018 ms on 235k rows). It's created automatically by
storage/event_store.py:
CREATE INDEX idx_events_type_ts ON events (event_type, timestamp DESC);
Plugins / extensions. The big win here is the index above, not an extension. For ongoing tuning:
pg_stat_statements— enable (shared_preload_libraries, server restart) to see which queries actually cost time. Diagnostic, not a speedup itself.pg_prewarm— warm the cache for theeventstable after a restart.- TimescaleDB — only worth it if the event/bar volume grows large (hypertables
- time partitioning + compression). Not required at current scale, and must be installed on the server.
Maintenance is automated. The nightly job (scheduler service / systemd
timer) prunes diagnostic events older than 14 days, rotates logs, refreshes the
day's bars to SIP truth, and updates the float/dilution shadow caches.
Autovacuum is tuned per-table; run ANALYZE manually only after bulk imports.
Repository map
run_live_paper.py the orchestrator: screen → arm → fire → manage → EOD flatten
momentum_cli.py the `momentum` CLI (watch / journal / doctor / inspect)
trading_execution.py signal → sizing → approval → bracket order; risk discipline
runtime/ triggers (ArmedTriggerBook), exit_manager, flatten helper
strategy/ pure strategy engine: evaluation, structure, exits, backtest
storage/ event schema + store (PostgreSQL), projections
research/ research DBs, ingestion (bars, RSS, gappers), screeners
alpaca_paper/ Alpaca paper client, executor, account/order sync
api/ + dashboard_api.py dashboard JSON API + static UI (:8765)
admin_api.py admin panel (:8767): health, config overrides, profiles, restarts
youtube_claims/ trader-video transcript -> claim extraction pipeline
controlroom/ NiceGUI control room (disabled by owner preference)
deploy/ compose (canonical) / systemd (local) / helm (planned)
eod_replay.py end-of-day whole-market replay
docs/history/ point-in-time plans from earlier eras — don't follow them
tests/ pytest suite (unit + integration)
Security
.env and all of data/ are git-ignored and
never committed; token files are written 0600. The .env holds live broker /
database / webhook credentials — keep it local. If any secret was ever
committed to a repo's history, rotate it (re-issue Alpaca keys / re-run the
Schwab OAuth flow).