# tradingSystem (PMS) 容器编排
# 用法见 README「Docker 部署」。.env 放在本文件同目录 (服务器上手工维护, 不入库)。
# 三组服务:
#   默认              pms-web                 管理页面 (38100)
#   --profile sched   pms-beat / pms-worker   Celery 调度与执行
#   --profile ws      pms-ws                  QMT WebSocket 常驻连接 (协议 V1.0)
#
# pms-ws 与 worker 的关系: worker 里的 executor 把待发委托写进 pms_qmt_order 出口表,
# pms-ws 亚秒轮询取走并发出。两者**只经数据库耦合**, 谁先起谁后起都不影响 —— pms-ws 没起
# 来时 dispatcher 会因心跳陈旧而拒发, 指令保持原状, 不会静默堆在队列里。

x-pms-base: &pms-base
  build: .
  image: pms:latest
  env_file: .env
  environment:
    - TZ=Asia/Shanghai
  volumes:
    - ./logs:/app/logs
  # 上游选股计划服务 (:8300) 跑在**宿主机本机**上。容器里访问宿主机的物理网卡 IP
  # (192.168.16.155) 会 ConnectTimeout —— docker 只给 FORWARD 链放行, 宿主机 INPUT 链
  # 对 docker 网桥进来的包默认不放, 表现就是丢包超时 (不是 refused)。
  # host-gateway 把宿主机在网桥上的地址 (172.x.0.1) 映射成 host.docker.internal,
  # 走这条路一般能通 (firewalld 会把网桥放在 docker zone, 默认 ACCEPT)。
  # 还不通就是宿主机 INPUT 拦了 8300, 放行网桥网段即可:
  #   sudo firewall-cmd --permanent --zone=trusted --add-source=172.16.0.0/12 && sudo firewall-cmd --reload
  extra_hosts:
    - "host.docker.internal:host-gateway"
  restart: unless-stopped
  logging:
    driver: json-file
    options:
      max-size: "50m"
      max-file: "5"

services:
  pms-web:
    <<: *pms-base
    container_name: pms-web
    command: uvicorn app.web.main:app --host 0.0.0.0 --port 38100
    ports:
      - "38100:38100"
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:38100/health', timeout=3)"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

  pms-beat:
    <<: *pms-base
    container_name: pms-beat
    profiles: ["sched"]
    command: celery -A app.scheduler.celery_app beat -l info

  pms-worker:
    <<: *pms-base
    container_name: pms-worker
    profiles: ["sched"]
    command: celery -A app.scheduler.celery_app worker -l info -c 2

  # QMT WebSocket 常驻连接。协议 §1 规定 PMS 只开一条连接 (多开会让指令乱序),
  # 所以这个服务**绝不能扩副本** —— 不要 deploy.replicas, 不要 docker compose up --scale。
  # 退出流程见 app/ws/runner.py 的 _shutdown: 刷水位 → 最后一次 ack → 关连接 → 清心跳,
  # stop_grace_period 给足 25 秒是为了让这一串跑完; 强杀会丢掉最后一批 ack (不丢数据,
  # 只是让 QMT 多留一会儿, 重连后按 last_seq 补发)。
  pms-ws:
    <<: *pms-base
    container_name: pms-ws
    profiles: ["ws"]
    command: python -m app.ws.runner
    stop_grace_period: 25s
    stop_signal: SIGTERM
