# 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--paper + strategy--gui # con PathPrefix(/strategy__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