Files
crypto_trading/docker-compose.yml
T
matitos 2ca5032e7e Notification upgrades: lifecycle, configurable levels, summaries
- NOTIFY_LEVELS chooses which event levels are sent (default trade,error).
- NOTIFY_LIFECYCLE sends explicit start/stop messages; the kill switch always
  notifies at error level.
- SUMMARY_INTERVAL_MINUTES sends a periodic portfolio summary (equity, per
  strategy P&L, allocation, halted state) via a background task started with
  the run and stopped on stop/kill/shutdown.
- Wires the new vars plus the go-live vars through both compose files and the
  deploy workflow. Adds tests.
2026-09-22 13:18:02 +00:00

135 lines
6.1 KiB
YAML

name: crypto_trading
services:
backend:
build:
context: ./backend
args:
BUILD_VERSION: ${CRYPTO_BUILD_VERSION:-unknown}
image: crypto_trading-backend
container_name: crypto-trading-backend
restart: unless-stopped
mem_limit: ${CRYPTO_MEM_LIMIT:-1g}
cpus: ${CRYPTO_CPUS:-2}
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
security_opt:
- no-new-privileges:true
# Reachable only on the internal network; the frontend nginx proxies to it.
expose:
- "8000"
environment:
- MODE=${CRYPTO_MODE:-paper}
- EXCHANGE=${CRYPTO_EXCHANGE:-binance}
- SYMBOL=${CRYPTO_SYMBOL:-BTC/USDT}
- TIMEFRAME=${CRYPTO_TIMEFRAME:-5m}
- POLL_INTERVAL=${CRYPTO_POLL_INTERVAL:-5}
- STARTING_BALANCE=${CRYPTO_STARTING_BALANCE:-10000}
- FEE_BPS=${CRYPTO_FEE_BPS:-10}
- SLIPPAGE_BPS=${CRYPTO_SLIPPAGE_BPS:-5}
- DATABASE_URL=sqlite:////data/trader.db
- CORS_ORIGINS=${CRYPTO_CORS_ORIGINS:-*}
- EXCHANGE_API_KEY=${CRYPTO_EXCHANGE_API_KEY:-}
- EXCHANGE_API_SECRET=${CRYPTO_EXCHANGE_API_SECRET:-}
- EXCHANGE_SANDBOX=${CRYPTO_EXCHANGE_SANDBOX:-false}
- MAX_DAILY_LOSS=${CRYPTO_MAX_DAILY_LOSS:-0}
- MAX_DRAWDOWN_PCT=${CRYPTO_MAX_DRAWDOWN_PCT:-0}
- MAX_POSITION_PCT=${CRYPTO_MAX_POSITION_PCT:-1.0}
- MAX_TRADES_PER_DAY=${CRYPTO_MAX_TRADES_PER_DAY:-0}
- TELEGRAM_BOT_TOKEN=${CRYPTO_TELEGRAM_BOT_TOKEN:-}
- TELEGRAM_CHAT_ID=${CRYPTO_TELEGRAM_CHAT_ID:-}
- NOTIFY_WEBHOOK_URL=${CRYPTO_NOTIFY_WEBHOOK_URL:-}
- NOTIFY_LEVELS=${CRYPTO_NOTIFY_LEVELS:-trade,error}
- NOTIFY_LIFECYCLE=${CRYPTO_NOTIFY_LIFECYCLE:-true}
- SUMMARY_INTERVAL_MINUTES=${CRYPTO_SUMMARY_INTERVAL_MINUTES:-0}
- LIVE_CONFIRMATION=${CRYPTO_LIVE_CONFIRMATION:-}
- MIN_LIVE_BALANCE=${CRYPTO_MIN_LIVE_BALANCE:-100}
- API_TOKEN=${CRYPTO_API_TOKEN:-}
- API_TOKEN_HEADER=${CRYPTO_API_TOKEN_HEADER:-X-API-Token}
volumes:
# Persist the SQLite database across releases. Prefer an absolute host
# path (CRYPTO_DATA_DIR / PERSISTENT_MOUNT); the repo-relative default
# can be cleaned by runner maintenance.
- ${CRYPTO_DATA_DIR:-./data}:/data
healthcheck:
test: ["CMD-SHELL", "python -c \"import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/api/health', timeout=5).status==200 else 1)\""]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
networks:
- internal
frontend:
build:
context: ./frontend
args:
BUILD_VERSION: ${CRYPTO_BUILD_VERSION:-unknown}
image: crypto_trading-frontend
container_name: crypto-trading-frontend
restart: unless-stopped
mem_limit: ${CRYPTO_MEM_LIMIT:-1g}
cpus: ${CRYPTO_CPUS:-2}
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
security_opt:
- no-new-privileges:true
depends_on:
backend:
condition: service_healthy
environment:
- API_TOKEN=${CRYPTO_API_TOKEN:-}
- API_TOKEN_HEADER=${CRYPTO_API_TOKEN_HEADER:-X-API-Token}
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:80/ || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
networks:
- internal
- docker_default
labels:
- "traefik.enable=true"
- "traefik.docker.network=docker_default"
- "traefik.http.routers.crypto-trading.rule=Host(`crypto.${BASE_DOMAIN:-matitos.org}`)"
- "traefik.http.routers.crypto-trading.entrypoints=websecure"
- "traefik.http.routers.crypto-trading.tls.certresolver=myresolvercd"
- "traefik.http.services.crypto-trading.loadbalancer.server.port=80"
# Basic auth on the WebUI (htpasswd), required: the stack refuses to
# start when CRYPTO_FRONTEND_AUTH_USERS is unset (no admin/admin
# fallback). A per-source rate limit reduces credential brute-forcing.
- "traefik.http.routers.crypto-trading.middlewares=crypto-trading-auth@docker,crypto-trading-sec@docker,crypto-trading-authlimit@docker"
- "traefik.http.middlewares.crypto-trading-auth.basicauth.users=${CRYPTO_FRONTEND_AUTH_USERS:?CRYPTO_FRONTEND_AUTH_USERS is required (htpasswd string, e.g. htpasswd -nbB user pass)}"
# Browsers do not reliably replay Basic-auth credentials on a WebSocket
# upgrade, which left the live feed stuck "reconnecting". Give /api/ws its
# own higher-priority router that skips Basic auth so the feed connects.
# If CRYPTO_API_TOKEN is set, the backend still requires it (nginx injects
# it server-side); if it is unset, /api/ws is as open as the rest of /api.
- "traefik.http.routers.crypto-trading-ws.rule=Host(`crypto.${BASE_DOMAIN:-matitos.org}`) && PathPrefix(`/api/ws`)"
- "traefik.http.routers.crypto-trading-ws.entrypoints=websecure"
- "traefik.http.routers.crypto-trading-ws.tls.certresolver=myresolvercd"
- "traefik.http.routers.crypto-trading-ws.priority=100"
- "traefik.http.routers.crypto-trading-ws.middlewares=crypto-trading-sec@docker,crypto-trading-authlimit@docker"
- "traefik.http.routers.crypto-trading-ws.service=crypto-trading"
- "traefik.http.middlewares.crypto-trading-authlimit.ratelimit.average=${CRYPTO_AUTH_RATE_LIMIT_AVERAGE:-10}"
- "traefik.http.middlewares.crypto-trading-authlimit.ratelimit.burst=${CRYPTO_AUTH_RATE_LIMIT_BURST:-5}"
- "traefik.http.middlewares.crypto-trading-sec.headers.stsSeconds=31536000"
- "traefik.http.middlewares.crypto-trading-sec.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.crypto-trading-sec.headers.forceSTSHeader=true"
- "traefik.http.middlewares.crypto-trading-sec.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.crypto-trading-sec.headers.browserXssFilter=true"
- "traefik.http.middlewares.crypto-trading-sec.headers.frameDeny=true"
- "traefik.http.middlewares.crypto-trading-sec.headers.referrerPolicy=strict-origin-when-cross-origin"
networks:
internal:
docker_default:
external: true