Files
momentum-system/deploy/docker/truenas-compose.yml
T
momentum devandClaude Opus 4.8 befcec2965 Post-migration DB health: healthcheck DDL storm + broken-pipe fixes
The loop healthcheck opened a fresh connection every 60s with schema
auto-apply enabled; the CREATE-IF-NOT-EXISTS lock bursts cancelled
minute_bars autovacuum every single cycle (visible as 'canceling
autovacuum task' each minute post-migration), so the 19M-row table
never got analyzed, snapshot queries crawled, browsers timed out, and
the dashboard log filled with double BrokenPipeError tracebacks.

- healthchecks now set PG_SKIP_SCHEMA=1 (both compose files)
- api/main.py _send_json swallows BrokenPipe/ConnectionReset (client
  hung up; nothing to tell it)
- live DB: ALTER SYSTEM max_wal_size=4GB, shared_buffers=1GB,
  effective_cache_size=3GB, checkpoint_completion_target=0.9 (the
  checkpoint-every-24s warning under migration load on NAS storage)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 18:41:12 -04:00

135 lines
4.7 KiB
YAML

# Momentum stack for TrueNAS SCALE (Apps -> Discover Apps -> Custom App -> YAML).
#
# PULL-ONLY: TrueNAS custom apps cannot `build:` — the image comes from GHCR
# (published by .github/workflows/docker.yml on every master push). Make the
# ghcr.io/fez5587/momentum-app package PUBLIC once (GitHub -> Packages ->
# momentum-app -> Package settings -> Change visibility), or add registry
# credentials in TrueNAS (Apps -> Settings -> Manage Container Images).
#
# BEFORE DEPLOYING:
# 1. Create a dataset for the stack, e.g. /mnt/<pool>/apps/momentum
# with subdirs: data/ pgdata/
# 2. Replace /mnt/tank/apps/momentum below with your dataset path.
# 3. Fill every <FILL-ME> in the environment block (these are the secrets
# you wanted to manage from a protected space — TrueNAS stores the YAML
# server-side, or mount an env file from a restricted dataset instead).
#
# TZ is America/New_York: the loop's market clock thinks in ET.
x-app: &app
image: docker.io/fez5587/momentum-app:latest # also published to ghcr.io/fez5587/momentum-app
environment: &app-env
TZ: America/New_York
# ---- infra
DATABASE_URL: postgresql://momentum:<FILL-ME-PG-PASSWORD>@postgres:5432/momentum
PG_SKIP_SCHEMA: "0"
OLLAMA_HOST: http://192.168.1.9:11434
ALPACA_DATA_FEED: sip
# ---- secrets
ALPACA_API_KEY: <FILL-ME>
ALPACA_SECRET_KEY: <FILL-ME>
ALPACA_BASE_URL: https://paper-api.alpaca.markets
TELEGRAM_BOT_TOKEN: <FILL-ME>
TELEGRAM_CHAT_ID: <FILL-ME>
YOUTUBE_API_KEY: <FILL-ME>
# ---- strategy knobs (current validated config, 2026-07-09; full
# inventory with comments: .env.example in the repo)
TRIGGER_GAP_MIN: "10.0"
TRIGGER_GAP_MAX: "35"
TRIGGER_GAP_SQUEEZE_MIN: "150"
TRIGGER_GAP_SQUEEZE_RVOL: "5.0"
TRADING_MIN_ENTRY_GAP_PCT: "0.10"
TRADING_MIN_DAY_VOLUME: "300000"
TRADING_PRICE_MIN: "2.0"
TRADING_PRICE_MAX: "20.0"
TRADING_MIN_QUALITY_SCORE: "0.60"
TRADING_MAX_FRESH_ENTRIES_PER_DAY: "10"
TRADING_ENTRY_CONFIRM_BY_MINUTE: "0"
volumes:
- /mnt/volume/Apps/momentum/data:/app/data
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
services:
postgres:
image: postgres:18
environment:
POSTGRES_USER: momentum
POSTGRES_PASSWORD: <FILL-ME-PG-PASSWORD>
POSTGRES_DB: momentum
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- /mnt/volume/Apps/momentum/pgdata:/var/lib/postgresql/data
ports:
# published on 5433 so it can coexist with the EXISTING postgres (5432)
# during migration — pgAdmin/pg_restore reach the new instance on 5433.
# In-stack services still use postgres:5432 (the container port).
- "5433:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U momentum -d momentum"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
loop:
<<: *app
command: ["python", "-u", "run_live_paper.py", "--no-dashboard"]
healthcheck:
# PG_SKIP_SCHEMA=1: a healthcheck must NOT re-apply schema DDL — the 60s
# CREATE-IF-NOT-EXISTS lock burst cancelled minute_bars autovacuum forever
# (2026-07-09 post-migration: stats rotted, dashboard queries crawled)
test: ["CMD", "python", "-c", "import os; os.environ['PG_SKIP_SCHEMA']='1'; import datetime,sys; from research.multi_schema import open_research_db; r=open_research_db('market').execute('SELECT max(timestamp) FROM events').fetchall()[0][0]; sys.exit(0 if r and (datetime.datetime.now()-r).total_seconds()<300 else 1)"]
interval: 60s
timeout: 20s
retries: 5
start_period: 180s
dashboard:
<<: *app
command: ["python", "-u", "dashboard_api.py"]
environment:
<<: *app-env
DASHBOARD_HOST: "0.0.0.0"
DASHBOARD_PORT: "8765"
ports:
- "8765:8765"
qabot:
<<: *app
command: ["python", "-u", "telegram_bot.py"]
transcripts:
<<: *app
command: ["python", "-u", "-m", "youtube_claims.run", "run"]
admin:
<<: *app
command: ["python", "-u", "admin_api.py"]
environment:
<<: *app-env
ADMIN_PORT: "8767"
ADMIN_TOKEN: <FILL-ME-ADMIN-TOKEN>
ports:
- "8767:8767"
volumes:
- /mnt/volume/Apps/momentum/data:/app/data
- /var/run/docker.sock:/var/run/docker.sock # service status + restarts
scheduler:
<<: *app
command:
- /bin/sh
- -c
- |
echo "[scheduler] up; fires 20:00 ET Mon-Fri"
while true; do
if [ "$$(date +%H:%M)" = "20:00" ] && [ "$$(date +%u)" -le 5 ]; then
python -m research.premarket_eval || echo "[scheduler] eval FAILED"
sleep 120
fi
sleep 20
done