Day-22 forensic (BIYA session): 6 entry submissions, 0 fills — IREX missed +1.7R, IRE +1.65R by one cent. Two root causes fixed: 1. Slow-path limit rested AT the trigger while price ran 2% past it, and retries reused the same stale price. Marketable model now prices from max(trigger, live last) via price_provider; the anti-chase extension gate gets the live ref too (was an entry_ref==trigger no-op on this path). TRADING_ENTRY_FILL_MODEL flipped to marketable. 2. Fast trigger path armed nothing all day: legacy rvol (cum-so-far / full-day MEAN) is ~0.1-0.3 minutes after the open for a normal heavy name and its mean is poisoned by reverse-split/prior-squeeze days (BIYA legacy 1.27 vs real activity 481x median). New rvol_tod = cum / (MEDIAN(20d) * elapsed-session fraction) drives arm eligibility and the scanner keep-filter; legacy rvol untouched for validated boundaries (gap-squeeze rvol>=5). Also: stagnation time-stop (TRADING_EXIT_STAGNATION_MIN/_R, default OFF) in simulate_exit + manage_live with sweep_exits.py variants for validation before any live enable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploying Momentum
Three supported shapes, one image/codebase. Pick by where it runs:
| Mode | Status | Use when | Docs |
|---|---|---|---|
| Docker Compose | ✅ canonical (production since 2026-07-09) | Any always-on Linux host; TrueNAS SCALE | docker/README.md, docker-compose.yml (root), docker/truenas-compose.yml |
| Local (systemd) | ✅ maintained | Development machine, WSL, bare metal without Docker | systemd/README.md + unit templates in deploy/systemd/ |
| Helm / Kubernetes | 🚧 planned refresh | Future k8s hosting | helm/ — the chart predates the 2026-07 architecture; starting point, not deployable |
Docker Compose (canonical)
The root docker-compose.yml runs the full stack self-contained: postgres 18,
trading loop, dashboard (:8765), admin panel (:8767), telegram bot, transcripts
pipeline, nightly scheduler, on-demand jobs, optional Grafana. The image is
published to docker.io/fez5587/momentum-app and ghcr.io/fez5587/momentum-app
by CI on every master push.
For TrueNAS SCALE use docker/truenas-compose.yml (pull-only, dataset host
paths, inline env). Read the gotchas in docker/README.md before editing the
YAML — anchor expansion, per-service env, and path case-sensitivity have each
caused an outage.
Local (systemd user services)
The original deployment: unit templates in deploy/systemd/ running a git
checkout + venv, config from .env. Still the best loop for development —
tests, backtests, and research scripts run here. Keep the units disabled
while the compose stack owns production: two loops against one Alpaca account
create duplicate orders.
Helm (future)
deploy/helm/ holds the original chart (single app deployment + CronJobs). It
predates the service split, the session gate, and the env-flag inventory; the
refresh is tracked in GitHub issues. Until then, compose is the way.
Config in all modes
.env.example(committed, sanitized) is the full variable inventory and says which layer each belongs in.- Behavior flags that MUST be set explicitly in any deployment — silent
defaults bite:
TRADING_AUTO_APPROVE=1(else entries queue for manual approval),ALPACA_DATA_FEED=sip,OLLAMA_ENABLED=true(catalyst engine),FMP_API_KEY(float cache).TRADING_SESSION_ONLYdefaults ON in deployed configs (bars after-hours entries). - Runtime overrides: the admin panel writes
data/overrides.env, which every service loads over its baked env at boot (runtime/env_overrides.py).