6655e425fa
Bug principale: in scripts/run_paper_trading.py il fetch usava
end = now.replace(minute=0,...), troncando sempre all'ora. ETH è
dichiarato timeframe=5m (commit 23b7273) ma di fatto veniva
valutato 1 volta ogni 60 min — 502 poll del run 39e027df hanno
prodotto solo 43 evaluazioni/asset, tutte a HH:00. Il commento
in load_assets segnala esplicitamente che a 1h la strategia
perde -33% su 7y: regressione vs backtest.
Fix: helper _align_end_to_timeframe(now, timeframe) snappa end
al boundary nativo dell'asset. Mappa 1m/5m/15m/30m/1h/4h/1d.
Test regression in src/strategy_crypto/tests con 9 casi.
Hardening accessorio incluso nello stesso commit:
- docker-compose.yml: state/ in RW per strategy-crypto-gui
(SQLite WAL richiede SHM writable anche da reader).
- multi_swarm_core/dashboard/nicegui_app.py: ui.timer ora
deactivate on_disconnect su 3 pagine (index/convergence/genomes)
per evitare leak di timer dopo client disconnect.
- strategy_crypto/frontend/data.py: retry 5s su sqlite.connect
per cold-start race quando GUI parte prima del paper writer.
- state/validation-hardened-001.json: output WFA tooling
multi-fold del run phase1-hardened-001.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
159 lines
6.6 KiB
YAML
159 lines
6.6 KiB
YAML
# docker-compose.yml — Multi-Swarm Coevolutive
|
|
#
|
|
# Tre servizi della strategia crypto, condividono la stessa immagine
|
|
# `multi-swarm-coevolutive:dev` buildata dal Dockerfile root (uv workspace):
|
|
#
|
|
# * strategy-crypto-paper — paper-trading runner long-running
|
|
# (scripts/run_paper_trading.py)
|
|
# * strategy-crypto-gui — NiceGUI dashboard esposta da Traefik su
|
|
# https://swarm.${DOMAIN_NAME:-tielogic.xyz}/strategy_crypto_gui
|
|
# * multi-swarm-core-gui — NiceGUI dashboard GA esposta su
|
|
# https://swarm.${DOMAIN_NAME:-tielogic.xyz}/multi_swarm_core_gui
|
|
#
|
|
# Entrambi joinano la rete external `traefik` cosi' il client Cerbero
|
|
# risolve direttamente l'host `cerbero-mcp` (porta 9000) senza passare
|
|
# dal gateway pubblico ne' dal TLS.
|
|
#
|
|
# Pattern N strategie future: aggiungere strategy-<asset>-paper + strategy-<asset>-gui
|
|
# con PathPrefix(/strategy_<asset>_gui) e DASHBOARD_ROOT_PATH dedicato.
|
|
#
|
|
# Dati persistenti via bind mount dalla cartella del repo:
|
|
# ./data cache OHLCV intermedia
|
|
# ./series cache parquet per timeframe/symbol
|
|
# ./state contiene runs.db (GA) + strategy_crypto.db (paper) + WAL/SHM
|
|
# ./src/strategy_crypto/strategy_crypto/strategies JSON freezate (ro)
|
|
#
|
|
# Secrets (token Cerbero + OpenRouter): caricati da .env via env_file.
|
|
|
|
networks:
|
|
traefik:
|
|
external: true
|
|
|
|
x-swarm-env: &swarm-env
|
|
# Override: rotta interna verso cerbero-mcp (no TLS, no traefik hop)
|
|
CERBERO_BASE_URL: http://cerbero-mcp:9000
|
|
# Override: path container per persistenza
|
|
DATA_DIR: /app/data
|
|
SERIES_DIR: /app/series
|
|
# DB separati per dominio:
|
|
GA_DB_PATH: /app/state/runs.db
|
|
STRATEGY_CRYPTO_DB_PATH: /app/state/strategy_crypto.db
|
|
# DASHBOARD_ROOT_PATH e' ora per-servizio (vedi environment blocks sotto).
|
|
# IMPORTANT: NON usare StripPrefix middleware con questo. NiceGUI/Starlette
|
|
# gestisce internamente il root_path su request path che ARRIVANO con prefix.
|
|
# StripPrefix causa doppio prefix negli asset URL (NiceGUI prefixa + uvicorn
|
|
# rilegge X-Forwarded-Prefix e prefixa di nuovo).
|
|
|
|
services:
|
|
strategy-crypto-paper:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
image: multi-swarm-coevolutive:dev
|
|
container_name: strategy-crypto-paper
|
|
restart: unless-stopped
|
|
networks: [traefik]
|
|
env_file: .env
|
|
environment:
|
|
<<: *swarm-env
|
|
volumes:
|
|
- ./data:/app/data
|
|
- ./series:/app/series
|
|
- ./state:/app/state
|
|
- ./src/strategy_crypto/strategy_crypto/strategies:/app/strategies:ro
|
|
# Niente HTTP da controllare: ci affidiamo a `restart: unless-stopped`
|
|
# e ai log per la liveness del runner.
|
|
command:
|
|
- python
|
|
- /app/scripts/run_paper_trading.py
|
|
- --name=${PAPER_RUN_NAME:-phase3-papertrade-prod}
|
|
- --initial-capital=${PAPER_INITIAL_CAPITAL:-1000}
|
|
- --fees-bp=${PAPER_FEES_BP:-5.0}
|
|
- --poll-seconds=${PAPER_POLL_SECONDS:-300}
|
|
- --lookback-bars=${PAPER_LOOKBACK_BARS:-500}
|
|
- --strategies-dir=/app/strategies
|
|
labels:
|
|
- com.centurylinklabs.watchtower.enable=true
|
|
|
|
strategy-crypto-gui:
|
|
image: multi-swarm-coevolutive:dev
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: strategy-crypto-gui
|
|
restart: unless-stopped
|
|
networks: [traefik]
|
|
env_file: .env
|
|
environment:
|
|
<<: *swarm-env
|
|
DASHBOARD_ROOT_PATH: /strategy_crypto_gui
|
|
volumes:
|
|
# RW richiesto: SQLite WAL mode richiede write-access dal reader per SHM.
|
|
- ./state:/app/state
|
|
entrypoint:
|
|
- python
|
|
- -m
|
|
- strategy_crypto.frontend.nicegui_app
|
|
command: []
|
|
healthcheck:
|
|
test:
|
|
- "CMD"
|
|
- "python"
|
|
- "-c"
|
|
- "import os, urllib.request; urllib.request.urlopen(f'http://localhost:{os.environ.get(\"SWARM_DASHBOARD_PORT\",\"8080\")}/', timeout=3).close()"
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 30s
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.docker.network=traefik
|
|
- "traefik.http.routers.strategy-crypto-gui.rule=Host(`swarm.${DOMAIN_NAME:-tielogic.xyz}`) && PathPrefix(`/strategy_crypto_gui`)"
|
|
- traefik.http.routers.strategy-crypto-gui.tls=true
|
|
- traefik.http.routers.strategy-crypto-gui.entrypoints=websecure
|
|
- traefik.http.routers.strategy-crypto-gui.tls.certresolver=mytlschallenge
|
|
- "traefik.http.services.strategy-crypto-gui.loadbalancer.server.port=${SWARM_DASHBOARD_PORT:-8080}"
|
|
# replacepathregex (NON stripprefix): strippa il prefix dalla request senza
|
|
# aggiungere X-Forwarded-Prefix header. uvicorn vede solo root_path da
|
|
# ui.run(root_path=...), quindi gli asset URL vengono prefissati una sola
|
|
# volta (no doppio prefix come succederebbe con stripprefix).
|
|
- "traefik.http.middlewares.strategy-crypto-replace.replacepathregex.regex=^/strategy_crypto_gui(/.*|$$)"
|
|
- "traefik.http.middlewares.strategy-crypto-replace.replacepathregex.replacement=$$1"
|
|
- "traefik.http.routers.strategy-crypto-gui.middlewares=strategy-crypto-replace"
|
|
- com.centurylinklabs.watchtower.enable=true
|
|
|
|
multi-swarm-core-gui:
|
|
image: multi-swarm-coevolutive:dev
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: multi-swarm-core-gui
|
|
restart: unless-stopped
|
|
networks: [traefik]
|
|
env_file: .env
|
|
environment:
|
|
<<: *swarm-env
|
|
DASHBOARD_ROOT_PATH: /multi_swarm_core_gui
|
|
volumes:
|
|
- ./state:/app/state:ro
|
|
entrypoint: [python, -m, multi_swarm_core.dashboard.nicegui_app]
|
|
command: []
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import os, urllib.request; urllib.request.urlopen(f'http://localhost:{os.environ.get(\"SWARM_DASHBOARD_PORT\",\"8080\")}/', timeout=3).close()"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 30s
|
|
labels:
|
|
- traefik.enable=true
|
|
- traefik.docker.network=traefik
|
|
- "traefik.http.routers.multi-swarm-core-gui.rule=Host(`swarm.${DOMAIN_NAME:-tielogic.xyz}`) && PathPrefix(`/multi_swarm_core_gui`)"
|
|
- traefik.http.routers.multi-swarm-core-gui.tls=true
|
|
- traefik.http.routers.multi-swarm-core-gui.entrypoints=websecure
|
|
- traefik.http.routers.multi-swarm-core-gui.tls.certresolver=mytlschallenge
|
|
- "traefik.http.services.multi-swarm-core-gui.loadbalancer.server.port=${SWARM_DASHBOARD_PORT:-8080}"
|
|
- "traefik.http.middlewares.multi-swarm-core-replace.replacepathregex.regex=^/multi_swarm_core_gui(/.*|$$)"
|
|
- "traefik.http.middlewares.multi-swarm-core-replace.replacepathregex.replacement=$$1"
|
|
- "traefik.http.routers.multi-swarm-core-gui.middlewares=multi-swarm-core-replace"
|
|
- com.centurylinklabs.watchtower.enable=true
|