Add Docker Compose stack for local development
Single-machine equivalent of the Helm package: builds the app from the Dockerfile, runs PostgreSQL with a persistent volume, and starts the trading loop with its embedded dashboard on :8010. - postgres:16 with healthcheck + named volume; app waits on it healthy - DATABASE_URL overridden to the in-network postgres service (self-contained); .env is optional (dry mode without ALPACA_* keys) - journal / eod-replay / nightly-tune under a "jobs" profile, run on demand; they share the app-data volume so learned_params.json reaches the loop - Grafana under a "monitoring" profile with a compose-specific datasource override pointed at the postgres service - deploy/README.md documents the Compose workflow Validated with `docker compose config` across all profiles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JCVMm2AbmZvvxwLTX6UpNR
This commit is contained in:
3 files changed
+193
No files matched your search
@@ -15,6 +15,43 @@ its **PostgreSQL datastore** — inside a single Kubernetes namespace.
|
||||
The app loop and the CronJobs run the **same image** (built from the repo
|
||||
`Dockerfile`), just with different commands.
|
||||
|
||||
## Local development with Docker Compose
|
||||
|
||||
For a single-machine stack (same components, no Kubernetes) use the root
|
||||
`docker-compose.yml`. It builds the app from the `Dockerfile`, brings up
|
||||
PostgreSQL with a persistent volume, and runs the loop with its embedded
|
||||
dashboard. Config comes from a local `.env` (optional — without `ALPACA_*` keys
|
||||
the loop runs in dry mode; the dashboard and DB still work). `DATABASE_URL` is
|
||||
overridden to the in-network `postgres` service, so the stack is self-contained.
|
||||
|
||||
```bash
|
||||
cp .env.example .env # optional: add ALPACA_* keys for live data
|
||||
|
||||
docker compose up -d --build # postgres + app loop + dashboard (:8010)
|
||||
docker compose logs -f app # watch the loop
|
||||
open http://localhost:8010 # dashboard
|
||||
|
||||
docker compose --profile monitoring up -d # also start Grafana (:3000)
|
||||
```
|
||||
|
||||
The scheduled jobs sit under the `jobs` profile so they don't auto-start; run
|
||||
them on demand (or from host cron / a scheduler on the ET times in `.env`):
|
||||
|
||||
```bash
|
||||
docker compose run --rm nightly-tune
|
||||
docker compose run --rm eod-replay
|
||||
docker compose run --rm journal
|
||||
```
|
||||
|
||||
Postgres credentials default to `momentum`/`momentum`/`momentum`; override with
|
||||
`POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` in `.env`. Named volumes
|
||||
`pg-data`, `app-data`, and `grafana-data` persist across restarts (the jobs share
|
||||
`app-data` with the app so `nightly_tune.py`'s `learned_params.json` reaches the
|
||||
loop). The Compose Grafana uses its own datasource override at
|
||||
`deploy/compose/grafana/datasources/momentum.yaml` (pointed at the `postgres`
|
||||
service); the legacy `grafana/docker-compose.yml` is unrelated and left for
|
||||
Grafana-only use against an external DB.
|
||||
|
||||
## Build & publish the image
|
||||
|
||||
Pushes to the working branch / `main` / `v*` tags trigger
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Grafana datasource for the docker-compose stack — points at the in-network
|
||||
# `postgres` service (not the LAN IP used by grafana/provisioning/datasources).
|
||||
# Credentials are interpolated from the PG_* env vars set on the grafana service.
|
||||
apiVersion: 1
|
||||
datasources:
|
||||
- name: Momentum
|
||||
uid: momentum_pg
|
||||
type: postgres
|
||||
access: proxy
|
||||
url: postgres:5432
|
||||
user: ${PG_USER}
|
||||
jsonData:
|
||||
database: ${PG_DATABASE}
|
||||
sslmode: disable
|
||||
postgresVersion: 1600
|
||||
secureJsonData:
|
||||
password: ${PG_PASSWORD}
|
||||
isDefault: true
|
||||
editable: true
|
||||
@@ -0,0 +1,137 @@
|
||||
# Full local stack for the Momentum System — mirrors the Helm package (deploy/).
|
||||
#
|
||||
# docker compose up -d # postgres + app loop + dashboard
|
||||
# docker compose --profile monitoring up -d # also start Grafana
|
||||
# docker compose run --rm nightly-tune # run a scheduled job on demand
|
||||
#
|
||||
# Config/secrets come from a local .env (copy from .env.example). DATABASE_URL is
|
||||
# overridden below to point at the in-network postgres service regardless of what
|
||||
# .env says, so the stack is self-contained. Without ALPACA_* keys the loop runs
|
||||
# in dry mode (dashboard + DB still work).
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-momentum}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-momentum}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-momentum}
|
||||
# Keep data in a subdir so the volume root doesn't clash with PGDATA init.
|
||||
PGDATA: /var/lib/postgresql/data/pgdata
|
||||
volumes:
|
||||
- pg-data:/var/lib/postgresql/data
|
||||
ports:
|
||||
# Exposed for psql/Grafana-on-host debugging; remove if not needed.
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-momentum} -d ${POSTGRES_DB:-momentum}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
# Trading loop WITH the embedded dashboard (the dashboard's approve/reject/exit
|
||||
# endpoints need the in-process TradingExecutionService — see deploy/README.md).
|
||||
app:
|
||||
build: .
|
||||
command: ["python", "run_live_paper.py"]
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
environment: &app-env
|
||||
# Built from the postgres service; overrides any DATABASE_URL in .env.
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER:-momentum}:${POSTGRES_PASSWORD:-momentum}@postgres:5432/${POSTGRES_DB:-momentum}
|
||||
DASHBOARD_HOST: "0.0.0.0"
|
||||
DASHBOARD_PORT: "8010"
|
||||
# Schema auto-creates from storage/pg_schema.sql on first connect.
|
||||
PG_SKIP_SCHEMA: "0"
|
||||
ports:
|
||||
- "8010:8010"
|
||||
volumes:
|
||||
# DuckDB research file, logs, learned_params.json, schwab tokens. Shared
|
||||
# with the job services so nightly-tune's output reaches the loop.
|
||||
- app-data:/app/data
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
# ---- Scheduled jobs (the *.sh crontab equivalents). On-demand only: they sit
|
||||
# under the "jobs" profile so `up` doesn't start them. Run with:
|
||||
# docker compose run --rm journal | eod-replay | nightly-tune
|
||||
# (Use host cron / a scheduler to invoke these on the ET schedule in .env.)
|
||||
journal:
|
||||
build: .
|
||||
profiles: ["jobs"]
|
||||
command: ["python", "momentum_cli.py", "journal"]
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
environment: *app-env
|
||||
volumes:
|
||||
- app-data:/app/data
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
restart: "no"
|
||||
|
||||
eod-replay:
|
||||
build: .
|
||||
profiles: ["jobs"]
|
||||
command: ["python", "eod_replay.py"]
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
environment: *app-env
|
||||
volumes:
|
||||
- app-data:/app/data
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
restart: "no"
|
||||
|
||||
nightly-tune:
|
||||
build: .
|
||||
profiles: ["jobs"]
|
||||
command: ["python", "nightly_tune.py"]
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
environment: *app-env
|
||||
volumes:
|
||||
- app-data:/app/data
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
restart: "no"
|
||||
|
||||
# ---- Grafana (optional): `docker compose --profile monitoring up -d`
|
||||
grafana:
|
||||
image: grafana/grafana:11.2.0
|
||||
profiles: ["monitoring"]
|
||||
environment:
|
||||
GF_AUTH_ANONYMOUS_ENABLED: "true"
|
||||
GF_AUTH_ANONYMOUS_ORG_ROLE: "Admin"
|
||||
GF_AUTH_DISABLE_LOGIN_FORM: "true"
|
||||
GF_DASHBOARDS_MIN_REFRESH_INTERVAL: "2s"
|
||||
# Interpolated into the provisioned datasource (see datasource override).
|
||||
PG_USER: ${POSTGRES_USER:-momentum}
|
||||
PG_PASSWORD: ${POSTGRES_PASSWORD:-momentum}
|
||||
PG_DATABASE: ${POSTGRES_DB:-momentum}
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
# Compose-specific datasource points at the in-network postgres service;
|
||||
# the dashboard + provider are reused from the existing grafana/ dir.
|
||||
- ./deploy/compose/grafana/datasources:/etc/grafana/provisioning/datasources:ro
|
||||
- ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
|
||||
- grafana-data:/var/lib/grafana
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
pg-data:
|
||||
app-data:
|
||||
grafana-data:
|
||||
Reference in new issue
Block a user