第二章:测试环境搭建——Docker容器化部署、模拟行情源配置、日志与监控系统初始化

说实话,搭建测试环境这件事,我见过太多团队栽跟头了。

有人花了两周配环境,结果发现行情源地址写错了。有人跑了一晚上回测,第二天发现日志根本没写进去。嗯,这些坑我都踩过。所以这一章,咱们就把环境搭建这件事,一次说清楚。

2.1 为什么非要用Docker?

你想想看,量化测试最怕什么?

最怕「在我机器上能跑,到你那就崩了」。我早期做事件驱动系统时,就因为Python版本差了0.1,折腾了整整一个下午。后来我学乖了——直接用Docker把整个环境打包。

核心思路:把操作系统、依赖库、配置文件、行情源地址,全部写进Dockerfile里。任何人拉下来,docker-compose up 一键启动。

我个人习惯用三层镜像结构:

# 基础层:Python 3.9 + 系统依赖
FROM python:3.9-slim AS base
RUN apt-get update && apt-get install -y \
    gcc \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

# 依赖层:安装量化库
FROM base AS dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 应用层:放代码
FROM dependencies AS app
COPY ./src /app/src
COPY ./config /app/config
WORKDIR /app
CMD ["python", "src/main.py"]

为什么要分三层?因为这样改代码时,只需要重新构建最上层,不用每次都重装依赖。我在项目中遇到过,有人把所有东西写在一层里,每次改个配置文件都要等10分钟编译——太浪费时间了。

2.2 模拟行情源配置

行情源是事件驱动系统的「燃料」。没有行情,系统就是个空壳。

但真实行情源太贵了,而且测试时你也不想真的去交易。所以我们需要一个模拟行情源。

我常用的方案是:

  • 回放模式:把历史行情数据存成CSV或Parquet,按时间戳逐条推送
  • 随机生成模式:用随机数模拟价格波动,适合压力测试
  • 混合模式:先回放一段历史数据,然后切换到随机生成

这里给个简单的模拟行情源配置示例:

# config/market_simulator.yaml
simulator:
  mode: replay  # replay | random | mixed
  data_path: /data/historical/2024-01-01.parquet
  speed: 1.0    # 1.0表示实时速度,2.0表示两倍速
  symbols:
    - BTC/USDT
    - ETH/USDT
    - SOL/USDT
  random_config:
    base_price: 50000
    volatility: 0.002  # 0.2%的波动率
    interval_ms: 100   # 每100ms推送一次

小技巧:我建议在模拟行情源里加一个「异常注入」开关。比如随机插入一些空数据、重复数据、或者价格跳变。这样能提前测试你的系统容错能力。我曾经因为没测这个,上线第一天就被一个异常行情搞崩了。

2.3 日志系统初始化

日志这东西,平时没人看,出问题的时候才觉得它珍贵。

我见过最惨的情况是:系统崩了,但日志里只有一行 Error: something went wrong。这跟没写有什么区别?

所以我的日志系统有三个原则:

  1. 结构化:用JSON格式,方便后续分析
  2. 分级别:DEBUG、INFO、WARNING、ERROR,一个都不能少
  3. 带上下文:每条日志都要包含事件ID、时间戳、模块名

这是我常用的日志配置:

# config/logging.yaml
version: 1
formatters:
  json:
    format: '{"time": "%(asctime)s", "level": "%(levelname)s", "module": "%(module)s", "event_id": "%(event_id)s", "message": "%(message)s"}'
handlers:
  console:
    class: logging.StreamHandler
    level: INFO
    formatter: json
  file:
    class: logging.handlers.RotatingFileHandler
    filename: /var/log/trading_system.log
    maxBytes: 10485760  # 10MB
    backupCount: 5
    level: DEBUG
    formatter: json
loggers:
  market:
    level: DEBUG
    handlers: [console, file]
  strategy:
    level: INFO
    handlers: [console, file]
  risk:
    level: WARNING
    handlers: [file]

注意:千万别把日志写到容器内部!容器一重启,日志就没了。我早期犯过这个错,排查问题时发现日志全丢了,那种绝望感...嗯,你懂的。一定要用volume挂载到宿主机。

2.4 监控系统初始化

日志是事后诸葛亮,监控才是实时保镖。

我个人习惯用Prometheus + Grafana这套组合。轻量、开源、社区活跃。

监控的核心指标:

指标类别 具体指标 告警阈值
行情延迟 行情到达时间 - 行情生成时间 > 500ms
事件处理速率 每秒处理的事件数 < 1000 events/s
内存使用率 进程RSS / 总内存 > 80%
错误率 ERROR日志数 / 总日志数 > 1%

这里有个小坑:监控本身也会消耗资源。我见过有人把监控粒度设到1秒,结果监控进程占用了20%的CPU。建议行情延迟用1秒粒度,其他指标用10秒粒度就够了。

2.5 整体架构图

说了这么多,咱们用一张图把整个环境串起来:

Docker容器 模拟行情源 回放/随机/混合模式 异常注入开关 事件处理引擎 事件队列 + 策略执行 风险管理模块 日志系统 结构化JSON日志 滚动文件 + 控制台输出 监控系统 Prometheus 指标采集 + 告警规则 Grafana 可视化仪表盘 告警通知 邮件/钉钉/企业微信 外部数据源(可选)

这张图说白了就是:行情源进来,事件引擎处理,日志和监控全程盯着。三个模块各司其职,互不干扰。

2.6 一键启动脚本

最后,给个完整的 docker-compose.yml

version: '3.8'
services:
  market-simulator:
    build: ./market-simulator
    volumes:
      - ./data:/data
      - ./config:/app/config
    environment:
      - MODE=replay
      - SPEED=1.0
    networks:
      - trading-net

  event-engine:
    build: ./event-engine
    depends_on:
      - market-simulator
    volumes:
      - ./logs:/var/log
      - ./config:/app/config
    environment:
      - LOG_LEVEL=DEBUG
    networks:
      - trading-net

  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    networks:
      - trading-net

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    networks:
      - trading-net

networks:
  trading-net:
    driver: bridge

启动命令:docker-compose up -d

查看日志:docker-compose logs -f event-engine

访问Grafana:浏览器打开 http://localhost:3000,默认账号密码都是 admin

嗯,环境搭好了,下一步就是写测试用例了。不过那是下一章的事,咱们先把这套环境跑起来再说。


专注资料整理