momentum devandClaude Opus 5 aae7269538 fix: stop the exit stack amputating every winner
The live exit ran FOUR independent stop ratchets at once: a +5% breakeven
inherited silently from the env default, a 0.25R step-trail, a prior-low
trail, and profit-lock tiers. Stacked, they pinned the stop at or just
above entry within the first minute of any real move, so winners were
capped near +1R while losers still ran to the full stop.

Measured over 62 live Tradier + 172 Alpaca paper round trips:

  - trades whose stock ran >=20% after entry (avg +63.7%) kept 9.3% of it
  - median hold 10 min for winners vs 19 min for losers -- inverted
  - PCLA 2026-09-11: entered 12.8233, stopped 12.82, a 0.03% stop

breakeven_at_pct was the only exit lever in ExitConfig that defaulted ON
from the env (0.05) while its dataclass default and every sibling lever
defaulted to off, so the live stack ran it without ever setting it. Turn
the env default off so "not configured" means "not applied".

Replayed through simulate_exit -- same entries, same initial stops, only
the management varying -- with each stack split 70/30 by date. A 10%
trail running from entry looked best in-sample and went negative out-of-
sample; what survives is a trail armed only after +1R. The 8-20% range
all beat the old config in all four train/test cells (mean R 0.20-0.25 vs
0.125), so the live value of 12% is a plateau pick rather than a fitted
one.

Live config_overrides updated separately (market closed, no open
positions); this commit is the code default and the regression guard.
Risk per trade is unchanged and both safety backstops -- the -10%
catastrophe exit and naked-stop enforcement -- are untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2LepdJfPCfK5Q8uznjFBM
2026-09-13 12:00:29 -05:00

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_EXIT
    • TRADING_REENTRY_MIN_LOSS_PCT, TRADING_EOD_FLATTEN_TIME.
  • Safety: TRADING_MAX_DAILY_LOSS_PCT — set to 0.03 before 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 the events table 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).

S
Description
Event-sourced small-cap momentum (ORB gap-and-go) day-trading system — Alpaca paper, PostgreSQL event store
Readme
2.6 MiB
0 Stars 1 Watchers 0 Forks
Languages
Python 92.6%
HTML 6.8%
Shell 0.3%
Go Template 0.1%