a9e805be05
trading/pipeline/head This commit looks good
손실%·포지션사이즈 기준 자본금을 .env TRADE_DEFAULT_CAPITAL로 설정 가능하게 분리. - TRADE_SETTINGS['default_capital']을 env에서 로드(미설정 시 1천만) - /trade/propose·/trade/scan의 하드코딩 Query default 제거 → 설정값 폴백 - docker-compose score-engine에 TRADE_DEFAULT_CAPITAL 전달 - 런타임 변경은 기존 POST /trade/settings 그대로 (재시작 시 env로 복귀) - /performance/simulation의 capital은 what-if 입력값이라 유지 검증: 컨테이너 env 인식·/trade/settings 반영·런타임 override(5천만)·healthy 확인. 실잔고(키움) 자동연동은 계좌번호 미설정으로 별도 과제. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
865 lines
26 KiB
YAML
865 lines
26 KiB
YAML
|
||
|
||
# ============================================================
|
||
# Trading AI System
|
||
# GPU 0: RTX 3060 12GB → Ollama (EXAONE 3.5 추론)
|
||
# GPU 1: RTX 3070 8GB → Ollama (bge-m3 임베딩)
|
||
# ============================================================
|
||
|
||
x-logging: &default-logging
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "${LOG_MAX_SIZE:-100m}"
|
||
max-file: "${LOG_MAX_FILE:-10}"
|
||
|
||
x-restart: &restart
|
||
restart: unless-stopped
|
||
|
||
networks:
|
||
trading-net:
|
||
driver: bridge
|
||
ipam:
|
||
config:
|
||
- subnet: "${SUBNET:-172.30.0.0/16}"
|
||
gateway: "${GATEWAY:-172.30.0.1}"
|
||
|
||
volumes:
|
||
redis-data:
|
||
qdrant-data:
|
||
ollama-data:
|
||
n8n-data:
|
||
bareun-data:
|
||
pgdata:
|
||
|
||
services:
|
||
|
||
# ── Phase 1: 네이버 뉴스 수집기 ──────────────────────────
|
||
news-collector:
|
||
build:
|
||
context: ./news-collector
|
||
dockerfile: Dockerfile
|
||
container_name: trading-news-collector
|
||
restart: unless-stopped
|
||
init: true
|
||
ports:
|
||
- "8787:8787"
|
||
environment:
|
||
REDIS_HOST: redis
|
||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||
POSTGRES_PORT: "${POSTGRES_PORT}"
|
||
POSTGRES_DB: "${POSTGRES_DB}"
|
||
POSTGRES_USER: "${POSTGRES_USER}"
|
||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
BAREUN_API_URL: "http://bareunaapi:5757"
|
||
OLLAMA_URL: "http://ollama:11434"
|
||
QDRANT_URL: "http://qdrant:6333"
|
||
GEMINI_API_KEY: "${GEMINI_API_KEY:-}"
|
||
GEMINI_MODEL: "${GEMINI_MODEL:-gemini-2.5-pro}"
|
||
TZ: "Asia/Seoul"
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.16
|
||
depends_on:
|
||
redis:
|
||
condition: service_healthy
|
||
bareunaapi:
|
||
condition: service_healthy
|
||
postgres:
|
||
condition: service_healthy
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "2.0"
|
||
memory: 2G
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8787/health')\" 2>/dev/null || exit 1"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 30s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "100m"
|
||
max-file: "5"
|
||
|
||
# ── Phase 2: 한국투자증권 주가 API ──────────────────────
|
||
kis-api:
|
||
build:
|
||
context: ./kis-api
|
||
dockerfile: Dockerfile
|
||
container_name: trading-kis-api
|
||
restart: unless-stopped
|
||
init: true
|
||
ports:
|
||
- "8585:8585"
|
||
environment:
|
||
KIWOOM_APP_KEY: "${KIWOOM_APP_KEY}"
|
||
KIWOOM_SECRET_KEY: "${KIWOOM_SECRET_KEY}"
|
||
KIWOOM_BASE_URL: "${KIWOOM_BASE_URL:-https://api.kiwoom.com}"
|
||
REDIS_HOST: redis
|
||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||
POSTGRES_PORT: "${POSTGRES_PORT}"
|
||
POSTGRES_DB: "${POSTGRES_DB}"
|
||
POSTGRES_USER: "${POSTGRES_USER}"
|
||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
TZ: "Asia/Seoul"
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.18
|
||
depends_on:
|
||
redis:
|
||
condition: service_healthy
|
||
postgres:
|
||
condition: service_healthy
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "2.0"
|
||
memory: 1G
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8585/health')\" 2>/dev/null || exit 1"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 30s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "50m"
|
||
max-file: "5"
|
||
|
||
# ── Phase 2.5: 기술적 분석 엔진 ──────────────────────────
|
||
ta-engine:
|
||
build:
|
||
context: ./ta-engine
|
||
dockerfile: Dockerfile
|
||
container_name: trading-ta-engine
|
||
restart: unless-stopped
|
||
init: true
|
||
ports:
|
||
- "8484:8484"
|
||
environment:
|
||
REDIS_HOST: redis
|
||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||
POSTGRES_PORT: "${POSTGRES_PORT}"
|
||
POSTGRES_DB: "${POSTGRES_DB}"
|
||
POSTGRES_USER: "${POSTGRES_USER}"
|
||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
OLLAMA_URL: "http://ollama:11434"
|
||
TZ: "Asia/Seoul"
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.23
|
||
depends_on:
|
||
redis:
|
||
condition: service_healthy
|
||
postgres:
|
||
condition: service_healthy
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "2.0"
|
||
memory: 1G
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8484/health')\" 2>/dev/null || exit 1"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 30s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "50m"
|
||
max-file: "5"
|
||
|
||
# ── Phase 3: 종목 점수 엔진 ─────────────────────────────
|
||
score-engine:
|
||
build:
|
||
context: ./score-engine
|
||
dockerfile: Dockerfile
|
||
container_name: trading-score-engine
|
||
restart: unless-stopped
|
||
init: true
|
||
ports:
|
||
- "8686:8686"
|
||
environment:
|
||
REDIS_HOST: redis
|
||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||
POSTGRES_PORT: "${POSTGRES_PORT}"
|
||
POSTGRES_DB: "${POSTGRES_DB}"
|
||
POSTGRES_USER: "${POSTGRES_USER}"
|
||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
TELEGRAM_BOT_TOKEN: "${TELEGRAM_BOT_TOKEN:-}"
|
||
TELEGRAM_CHAT_ID: "${TELEGRAM_CHAT_ID:-}"
|
||
ECOS_API_KEY: "${ECOS_API_KEY:-}"
|
||
GEMINI_API_KEY: "${GEMINI_API_KEY:-}"
|
||
GEMINI_MODEL: "${GEMINI_MODEL:-gemini-2.5-pro}"
|
||
TRADE_DEFAULT_CAPITAL: "${TRADE_DEFAULT_CAPITAL:-10000000}"
|
||
TZ: "Asia/Seoul"
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.19
|
||
depends_on:
|
||
redis:
|
||
condition: service_healthy
|
||
ta-engine:
|
||
condition: service_healthy
|
||
postgres:
|
||
condition: service_healthy
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "2.0"
|
||
memory: 1G
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8686/health')\" 2>/dev/null || exit 1"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 30s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "50m"
|
||
max-file: "5"
|
||
|
||
# ── Phase 3.7: 텔레그램 양방향 봇 ───────────────────────
|
||
telegram-bot:
|
||
build:
|
||
context: ./telegram-bot
|
||
dockerfile: Dockerfile
|
||
container_name: trading-telegram-bot
|
||
restart: unless-stopped
|
||
environment:
|
||
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||
POSTGRES_PORT: "${POSTGRES_PORT}"
|
||
POSTGRES_DB: "${POSTGRES_DB}"
|
||
POSTGRES_USER: "${POSTGRES_USER}"
|
||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
TELEGRAM_BOT_TOKEN: "${TELEGRAM_BOT_TOKEN}"
|
||
TELEGRAM_CHAT_ID: "${TELEGRAM_CHAT_ID}"
|
||
OLLAMA_URL: "http://ollama:11434"
|
||
EXAONE_MODEL: "exaone3.5:7.8b"
|
||
TZ: "Asia/Seoul"
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.26
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "1.0"
|
||
memory: 512M
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "50m"
|
||
max-file: "5"
|
||
|
||
# ── Phase 3.6: 보조 시그널 (컨센서스/기관수급/매크로) ──
|
||
aux-signal:
|
||
build:
|
||
context: ./aux-signal
|
||
dockerfile: Dockerfile
|
||
container_name: trading-aux-signal
|
||
restart: unless-stopped
|
||
init: true
|
||
ports:
|
||
- "8282:8282"
|
||
environment:
|
||
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||
POSTGRES_PORT: "${POSTGRES_PORT}"
|
||
POSTGRES_DB: "${POSTGRES_DB}"
|
||
POSTGRES_USER: "${POSTGRES_USER}"
|
||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
ECOS_API_KEY: "${ECOS_API_KEY:-}"
|
||
TZ: "Asia/Seoul"
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.25
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "2.0"
|
||
memory: 1G
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8282/health')\" 2>/dev/null || exit 1"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 60s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "50m"
|
||
max-file: "5"
|
||
|
||
# ── Phase 3.5: 미국증시 동조 시그널 ─────────────────────
|
||
us-market:
|
||
build:
|
||
context: ./us-market
|
||
dockerfile: Dockerfile
|
||
container_name: trading-us-market
|
||
restart: unless-stopped
|
||
init: true
|
||
ports:
|
||
- "8383:8383"
|
||
environment:
|
||
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||
POSTGRES_PORT: "${POSTGRES_PORT}"
|
||
POSTGRES_DB: "${POSTGRES_DB}"
|
||
POSTGRES_USER: "${POSTGRES_USER}"
|
||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
FINNHUB_API_KEY: "${FINNHUB_API_KEY:-}"
|
||
ALPHAVANTAGE_API_KEY: "${ALPHAVANTAGE_API_KEY:-}"
|
||
TZ: "Asia/Seoul"
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.24
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "2.0"
|
||
memory: 1G
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8383/health')\" 2>/dev/null || exit 1"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 60s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "50m"
|
||
max-file: "5"
|
||
|
||
# ── Phase 5: 그래프 신경망 (GAT) ────────────────────────
|
||
graph-engine:
|
||
build:
|
||
context: ./graph-engine
|
||
dockerfile: Dockerfile
|
||
container_name: trading-graph-engine
|
||
restart: unless-stopped
|
||
init: true
|
||
ports:
|
||
- "9090:9090"
|
||
environment:
|
||
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||
POSTGRES_PORT: "${POSTGRES_PORT}"
|
||
POSTGRES_DB: "${POSTGRES_DB}"
|
||
POSTGRES_USER: "${POSTGRES_USER}"
|
||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
GRAPH_MODEL_DIR: "/mnt/nas/models/graph"
|
||
TZ: "Asia/Seoul"
|
||
volumes:
|
||
- /mnt/nas/models:/mnt/nas/models
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.27
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "4.0"
|
||
memory: 4G
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:9090/health')\" 2>/dev/null || exit 1"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 90s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "50m"
|
||
max-file: "5"
|
||
|
||
# ── Phase 4: 대시보드 API ───────────────────────────────
|
||
dashboard-api:
|
||
build:
|
||
context: ./dashboard-api
|
||
dockerfile: Dockerfile
|
||
container_name: trading-dashboard-api
|
||
restart: unless-stopped
|
||
init: true
|
||
ports:
|
||
- "8989:8989"
|
||
environment:
|
||
REDIS_HOST: redis
|
||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||
POSTGRES_PORT: "${POSTGRES_PORT}"
|
||
POSTGRES_DB: "${POSTGRES_DB}"
|
||
POSTGRES_USER: "${POSTGRES_USER}"
|
||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
JWT_SECRET: "${JWT_SECRET}"
|
||
JWT_EXPIRE_DAYS: "${JWT_EXPIRE_DAYS}"
|
||
ADMIN_EMAILS: "${ADMIN_EMAILS}"
|
||
TRUSTED_ORIGINS: "${TRUSTED_ORIGINS}"
|
||
TZ: "Asia/Seoul"
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.22
|
||
depends_on:
|
||
redis:
|
||
condition: service_healthy
|
||
postgres:
|
||
condition: service_healthy
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "2.0"
|
||
memory: 1G
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "true"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
start_period: 15s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "50m"
|
||
max-file: "5"
|
||
|
||
# ──────────────────────────────────────────
|
||
# DART 공시 수집기 172.30.0.17
|
||
# ──────────────────────────────────────────
|
||
dart-collector:
|
||
build:
|
||
context: ./dart-collector
|
||
dockerfile: Dockerfile
|
||
container_name: trading-dart-collector
|
||
restart: unless-stopped
|
||
init: true
|
||
ports:
|
||
- "8888:8888"
|
||
environment:
|
||
DART_API_KEY: "${DART_API_KEY}"
|
||
REDIS_HOST: redis
|
||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||
POSTGRES_PORT: "${POSTGRES_PORT}"
|
||
POSTGRES_DB: "${POSTGRES_DB}"
|
||
POSTGRES_USER: "${POSTGRES_USER}"
|
||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
BAREUN_API_URL: "http://bareunaapi:5757"
|
||
OLLAMA_URL: "http://ollama:11434"
|
||
QDRANT_URL: "http://qdrant:6333"
|
||
TZ: "Asia/Seoul"
|
||
N8N_EDITOR_BASE_URL: "https://n8.kyleyang.co.kr"
|
||
WEBHOOK_URL: "https://n8.kyleyang.co.kr"
|
||
N8N_HOST: "0.0.0.0"
|
||
N8N_LISTEN_ADDRESS: "0.0.0.0"
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.17
|
||
depends_on:
|
||
redis:
|
||
condition: service_healthy
|
||
bareunaapi:
|
||
condition: service_healthy
|
||
postgres:
|
||
condition: service_healthy
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "2.0"
|
||
memory: 2G
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8888/health')\" 2>/dev/null || exit 1"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 60s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "100m"
|
||
max-file: "10"
|
||
|
||
# ──────────────────────────────────────────
|
||
# ──────────────────────────────────────────
|
||
# PostgreSQL 172.30.0.9
|
||
# ──────────────────────────────────────────
|
||
postgres:
|
||
image: postgres:16
|
||
container_name: trading-postgres
|
||
<<: *restart
|
||
ports:
|
||
- "55432:5432"
|
||
environment:
|
||
POSTGRES_USER: "${POSTGRES_USER}"
|
||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
POSTGRES_DB: "${POSTGRES_DB}"
|
||
PGDATA: /var/lib/postgresql/data
|
||
TZ: "Asia/Seoul"
|
||
volumes:
|
||
- pgdata:/var/lib/postgresql/data
|
||
- ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.9
|
||
# ── 메모리 튜닝 (32GB 호스트, 로컬 SSD 기준) ──
|
||
# shared_buffers 4GB (컨테이너 limit의 ~67% — Postgres hot cache)
|
||
# effective_cache_size 12GB (OS 페이지캐시 포함 추정 가용 캐시)
|
||
# work_mem 32MB (단일 sort/hash 메모리. max_conn × 동시쿼리 곱 주의)
|
||
# maintenance_work_mem 512MB (VACUUM/CREATE INDEX)
|
||
# random_page_cost 1.1 (SSD 비용 모델)
|
||
# effective_io_concurrency 200 (SSD)
|
||
# parallel_workers 병렬쿼리 활성화 (백테스트/학습용 큰 JOIN에 효과)
|
||
# WAL/checkpoint 쓰기 부하 줄이고 회복 시간 살짝 늘림 (적절 균형)
|
||
command: >
|
||
postgres
|
||
-c shared_buffers=4GB
|
||
-c effective_cache_size=12GB
|
||
-c work_mem=32MB
|
||
-c maintenance_work_mem=512MB
|
||
-c random_page_cost=1.1
|
||
-c effective_io_concurrency=200
|
||
-c max_parallel_workers_per_gather=4
|
||
-c max_parallel_workers=8
|
||
-c max_worker_processes=12
|
||
-c checkpoint_timeout=15min
|
||
-c max_wal_size=4GB
|
||
-c checkpoint_completion_target=0.9
|
||
-c wal_compression=on
|
||
-c log_min_duration_statement=1000
|
||
-c log_checkpoints=on
|
||
-c log_temp_files=10MB
|
||
-c jit=off
|
||
shm_size: 512mb
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "4.0"
|
||
memory: 12G
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 5
|
||
start_period: 30s
|
||
logging: *default-logging
|
||
|
||
# Redis 172.30.0.10
|
||
# ──────────────────────────────────────────
|
||
redis:
|
||
image: redis:7.2-alpine
|
||
container_name: trading-redis
|
||
<<: *restart
|
||
command: >
|
||
redis-server
|
||
--requirepass ${REDIS_PASSWORD}
|
||
--maxmemory ${REDIS_MAX_MEMORY:-2gb}
|
||
--maxmemory-policy ${REDIS_MAXMEMORY_POLICY:-allkeys-lru}
|
||
--save 900 1
|
||
--save 300 10
|
||
--appendonly yes
|
||
--appendfsync everysec
|
||
--tcp-keepalive 300
|
||
ports:
|
||
- "6379:6379"
|
||
volumes:
|
||
- redis-data:/data
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.10
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "2.0"
|
||
memory: 1G
|
||
healthcheck:
|
||
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 5
|
||
start_period: 10s
|
||
logging: *default-logging
|
||
|
||
# ──────────────────────────────────────────
|
||
# Qdrant 172.30.0.11
|
||
# ──────────────────────────────────────────
|
||
qdrant:
|
||
image: qdrant/qdrant:v1.9.2
|
||
container_name: trading-qdrant
|
||
<<: *restart
|
||
ports:
|
||
- "6333:6333"
|
||
- "6334:6334"
|
||
volumes:
|
||
- qdrant-data:/qdrant/storage
|
||
- ./qdrant-config/config.yaml:/qdrant/config/production.yaml:ro
|
||
environment:
|
||
QDRANT__SERVICE__HTTP_PORT: 6333
|
||
QDRANT__SERVICE__GRPC_PORT: 6334
|
||
QDRANT__LOG_LEVEL: INFO
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.11
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "4.0"
|
||
memory: 1G
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "true"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 3
|
||
start_period: 10s
|
||
logging: *default-logging
|
||
|
||
# ──────────────────────────────────────────
|
||
# Bareun 형태소 엔진 172.30.0.15
|
||
# ──────────────────────────────────────────
|
||
bareun:
|
||
image: bareunai/bareun:latest
|
||
container_name: trading-bareun
|
||
<<: *restart
|
||
ports:
|
||
- "5656:5656"
|
||
- "9902:9902"
|
||
volumes:
|
||
- bareun-data:/bareun/var
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.15
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "4.0"
|
||
memory: 1500M
|
||
reservations:
|
||
memory: 512M
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "curl -sf http://localhost:9902/health || exit 0"]
|
||
interval: 20s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 30s
|
||
logging: *default-logging
|
||
|
||
# ──────────────────────────────────────────
|
||
# 바른 API (FastAPI 래퍼) 172.30.0.12
|
||
# ──────────────────────────────────────────
|
||
bareunaapi:
|
||
build:
|
||
context: ./bareunaapi
|
||
dockerfile: Dockerfile
|
||
container_name: trading-bareunaapi
|
||
<<: *restart
|
||
ports:
|
||
- "5757:5757"
|
||
environment:
|
||
BAREUN_API_KEY: "${BAREUN_API_KEY}"
|
||
BAREUN_SERVER_HOST: bareun
|
||
BAREUN_SERVER_PORT: 5656
|
||
REDIS_HOST: redis
|
||
REDIS_PORT: 6379
|
||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||
REDIS_DB: 1
|
||
NEWS_DEDUP_TTL: "${NEWS_DEDUP_TTL:-86400}"
|
||
LOG_LEVEL: INFO
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.12
|
||
depends_on:
|
||
redis:
|
||
condition: service_healthy
|
||
bareun:
|
||
condition: service_started
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "4.0"
|
||
memory: 1G
|
||
healthcheck:
|
||
test: ["CMD", "curl", "-f", "http://localhost:5757/health"]
|
||
interval: 15s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 40s
|
||
logging: *default-logging
|
||
|
||
# ──────────────────────────────────────────
|
||
# Ollama 추론+임베딩 (GPU 0+1) 172.30.0.13
|
||
# GPU 0: RTX 3060 12GB → EXAONE 3.5 7.8B 추론
|
||
# GPU 1: RTX 3070 8GB → bge-m3 임베딩
|
||
# ──────────────────────────────────────────
|
||
ollama:
|
||
image: ollama/ollama:latest
|
||
container_name: trading-ollama
|
||
<<: *restart
|
||
ports:
|
||
- "11434:11434"
|
||
volumes:
|
||
- ollama-data:/root/.ollama
|
||
environment:
|
||
OLLAMA_NUM_PARALLEL: "${OLLAMA_NUM_PARALLEL:-4}"
|
||
OLLAMA_MAX_LOADED_MODELS: "2"
|
||
OLLAMA_FLASH_ATTENTION: "1"
|
||
CUDA_VISIBLE_DEVICES: "0,1"
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.13
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "8.0"
|
||
memory: 24G
|
||
reservations:
|
||
devices:
|
||
- driver: nvidia
|
||
device_ids: ["0", "1"]
|
||
capabilities: [gpu]
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "true"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 3
|
||
start_period: 10s
|
||
logging: *default-logging
|
||
|
||
# ──────────────────────────────────────────
|
||
# n8n 워크플로우 172.30.0.20
|
||
# ──────────────────────────────────────────
|
||
n8n:
|
||
image: n8nio/n8n:latest
|
||
container_name: trading-n8n
|
||
<<: *restart
|
||
ports:
|
||
- "5678:5678"
|
||
environment:
|
||
N8N_BASIC_AUTH_ACTIVE: "true"
|
||
N8N_BASIC_AUTH_USER: "${N8N_BASIC_AUTH_USER}"
|
||
N8N_BASIC_AUTH_PASSWORD: "${N8N_BASIC_AUTH_PASSWORD}"
|
||
N8N_ENCRYPTION_KEY: "${N8N_ENCRYPTION_KEY}"
|
||
WEBHOOK_URL: "${N8N_WEBHOOK_URL}"
|
||
N8N_LOG_LEVEL: info
|
||
N8N_METRICS: "true"
|
||
# DB - NAS PostgreSQL
|
||
DB_TYPE: postgresdb
|
||
DB_POSTGRESDB_HOST: "${POSTGRES_HOST}"
|
||
DB_POSTGRESDB_PORT: "${POSTGRES_PORT:-5432}"
|
||
DB_POSTGRESDB_DATABASE: "${POSTGRES_DB}"
|
||
DB_POSTGRESDB_USER: "${POSTGRES_USER}"
|
||
DB_POSTGRESDB_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
DB_POSTGRESDB_SCHEMA: n8n
|
||
DB_POSTGRESDB_POOL_SIZE: "10"
|
||
DB_POSTGRESDB_CONNECT_TIMEOUT: "30000"
|
||
# Redis Queue
|
||
QUEUE_BULL_REDIS_HOST: redis
|
||
QUEUE_BULL_REDIS_PORT: 6379
|
||
QUEUE_BULL_REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||
QUEUE_BULL_REDIS_DB: 0
|
||
EXECUTIONS_MODE: queue
|
||
N8N_GRACEFUL_SHUTDOWN_TIMEOUT: 30
|
||
TZ: "${TZ:-Asia/Seoul}"
|
||
GENERIC_TIMEZONE: "${TZ:-Asia/Seoul}"
|
||
# 내부 서비스 주소 (워크플로우에서 사용)
|
||
BAREUNAAPI_URL: "http://bareunaapi:5757"
|
||
OLLAMA_URL: "http://ollama:11434"
|
||
QDRANT_URL: "http://qdrant:6333"
|
||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||
POSTGRES_DB: "${POSTGRES_DB}"
|
||
POSTGRES_USER: "${POSTGRES_USER}"
|
||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
NEWS_SIMILARITY_THRESHOLD: "${NEWS_SIMILARITY_THRESHOLD:-0.92}"
|
||
TELEGRAM_BOT_TOKEN: "${TELEGRAM_BOT_TOKEN:-}"
|
||
TELEGRAM_CHAT_ID: "${TELEGRAM_CHAT_ID:-}"
|
||
N8N_SECURE_COOKIE: "false"
|
||
# 실행 이력 자동 정리 (느린 n8n 해결)
|
||
EXECUTIONS_DATA_PRUNE: "true"
|
||
EXECUTIONS_DATA_MAX_AGE: 168
|
||
EXECUTIONS_DATA_PRUNE_MAX_COUNT: 10000
|
||
volumes:
|
||
- n8n-data:/home/node/.n8n
|
||
- /mnt/nas:/mnt/nas
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.20
|
||
depends_on:
|
||
redis:
|
||
condition: service_healthy
|
||
qdrant:
|
||
condition: service_healthy
|
||
bareunaapi:
|
||
condition: service_healthy
|
||
postgres:
|
||
condition: service_healthy
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "4.0"
|
||
memory: 1G
|
||
healthcheck:
|
||
test: ["CMD", "wget", "-qO-", "http://localhost:5678/healthz"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 60s
|
||
logging: *default-logging
|
||
|
||
# ──────────────────────────────────────────
|
||
# n8n Worker 172.30.0.21
|
||
# ──────────────────────────────────────────
|
||
n8n-worker:
|
||
image: n8nio/n8n:latest
|
||
container_name: trading-n8n-worker
|
||
<<: *restart
|
||
command: worker
|
||
environment:
|
||
N8N_ENCRYPTION_KEY: "${N8N_ENCRYPTION_KEY}"
|
||
DB_TYPE: postgresdb
|
||
DB_POSTGRESDB_HOST: "${POSTGRES_HOST}"
|
||
DB_POSTGRESDB_PORT: "${POSTGRES_PORT:-5432}"
|
||
DB_POSTGRESDB_DATABASE: "${POSTGRES_DB}"
|
||
DB_POSTGRESDB_USER: "${POSTGRES_USER}"
|
||
DB_POSTGRESDB_PASSWORD: "${POSTGRES_PASSWORD}"
|
||
DB_POSTGRESDB_SCHEMA: n8n
|
||
DB_POSTGRESDB_POOL_SIZE: "5"
|
||
DB_POSTGRESDB_CONNECT_TIMEOUT: "30000"
|
||
QUEUE_BULL_REDIS_HOST: redis
|
||
QUEUE_BULL_REDIS_PORT: 6379
|
||
QUEUE_BULL_REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||
QUEUE_BULL_REDIS_DB: 0
|
||
EXECUTIONS_MODE: queue
|
||
TZ: "${TZ:-Asia/Seoul}"
|
||
GENERIC_TIMEZONE: "${TZ:-Asia/Seoul}"
|
||
BAREUNAAPI_URL: "http://bareunaapi:5757"
|
||
OLLAMA_URL: "http://ollama:11434"
|
||
QDRANT_URL: "http://qdrant:6333"
|
||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||
volumes:
|
||
- n8n-data:/home/node/.n8n
|
||
- /mnt/nas:/mnt/nas
|
||
networks:
|
||
trading-net:
|
||
ipv4_address: 172.30.0.21
|
||
depends_on:
|
||
- n8n
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: "4.0"
|
||
memory: 1G
|
||
logging: *default-logging
|