4、开发环境与工具链搭建:Python、C++、Redis、Kafka、数据库选型与配置
做期权做市商系统,说白了就是跟时间赛跑。你想想看,行情数据每秒几千笔,订单簿深度实时变化,你的策略必须在毫秒级做出反应。这时候,开发环境选不对,工具链搭不好,后面全是坑。
我个人习惯,先把整个技术栈的骨架搭清楚,再往里填肉。这一章,我就把我在搭建生产环境时踩过的坑、总结的经验,一股脑倒给你。
核心原则:做市商系统是典型的「延迟敏感 + 吞吐量高 + 数据一致性要求严」的系统。选型时,延迟优先,吞吐量次之,一致性兜底。
4.1 编程语言选型:Python 与 C++ 的分工
很多新手会问:「到底用 Python 还是 C++?」我的答案是:都要用,但各司其职。
C++ 负责核心交易引擎。订单簿维护、策略计算、风控检查、下单执行,这些对延迟极度敏感的环节,必须用 C++。我在项目中遇到过,用 Python 做订单簿更新,一次深度拷贝就吃掉 200 微秒,这在做市商系统里是不可接受的。
Python 负责外围服务。数据回放、策略回测、监控面板、日志分析、配置管理。这些环节对延迟不敏感,但对开发效率要求高。Python 的 pandas、numpy、matplotlib 生态,能让你一天干完 C++ 一周的活。
我的经验:Python 和 C++ 之间通过 Redis 或共享内存通信。Python 负责「大脑」,C++ 负责「手脚」。Python 把策略参数、风控阈值推给 C++,C++ 把成交回报、订单状态推回给 Python。
4.2 C++ 开发环境配置
嗯,这里要注意,C++ 的版本和编译器选择,直接影响你的延迟表现。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| 编译器 | GCC 11+ 或 Clang 14+ | 支持 C++20,编译优化 -O3 -march=native |
| 构建工具 | CMake 3.22+ | 配合 Ninja 构建,比 Make 快 3-5 倍 |
| 依赖管理 | vcpkg 或 Conan | 我习惯用 vcpkg,集成简单 |
| 关键库 | Boost、fmt、spdlog、librdkafka | 日志、序列化、Kafka 客户端 |
我曾经因为编译器版本太低,导致 std::atomic 的 memory_order 语义不对,回测数据全错。排查了整整两天,最后发现是 GCC 7 的一个 bug。从那以后,我坚持用最新稳定版编译器。
# CMakeLists.txt 核心配置示例
cmake_minimum_required(VERSION 3.22)
project(OptionMarketMaker VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -march=native -mtune=native -flto")
# 使用 vcpkg 管理依赖
find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
find_package(RdKafka CONFIG REQUIRED)
target_link_libraries(market_maker
PRIVATE
fmt::fmt
spdlog::spdlog
RdKafka::rdkafka
)
4.3 Python 虚拟环境与依赖管理
Python 这边,我强烈建议用 Poetry 替代 pip + requirements.txt。为什么?因为 Poetry 能锁定依赖版本,避免「在我机器上能跑」的尴尬。
我在团队里推行过一个规则:所有 Python 项目必须用 Poetry,且 lock 文件必须提交到 Git。有一次新同事没锁版本,装了个新版 pandas,结果回测脚本的 groupby 行为变了,数据对不上。嗯,从那以后,没人敢手动 pip install 了。
# pyproject.toml 核心配置
[tool.poetry]
name = "option-mm-backtest"
version = "0.1.0"
description = "期权做市商回测框架"
[tool.poetry.dependencies]
python = "^3.10"
pandas = "^2.0"
numpy = "^1.24"
redis = "^4.5"
confluent-kafka = "^2.1"
sqlalchemy = "^2.0"
psycopg2-binary = "^2.9"
dash = "^2.9" # 监控面板
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api
4.4 Redis:低延迟缓存与状态同步
Redis 在期权做市商系统里,扮演三个角色:
- 行情缓存:实时行情数据,TTL 设为 1 秒,过期自动清理
- 策略状态同步:Python 写入策略参数,C++ 读取执行
- 订单簿快照:每 100ms 保存一次订单簿快照,用于回放分析
配置上,我建议用 Redis Stack,它内置了 RedisJSON 模块,可以直接存储 JSON 格式的订单簿数据,省去序列化开销。
避坑指南:我曾经在生产环境遇到过 Redis 主从切换导致数据丢失的问题。做市商系统对数据一致性要求极高,建议开启 AOF 持久化 + 至少 1 个从节点。另外,maxmemory-policy 不要用 allkeys-lru,用 volatile-lru 更安全。
# Redis 生产配置关键项
# /etc/redis/redis.conf
# 持久化
appendonly yes
appendfsync everysec
# 内存管理
maxmemory 8gb
maxmemory-policy volatile-lru
# 网络
bind 0.0.0.0
port 6379
timeout 0
tcp-keepalive 300
# 慢查询日志
slowlog-log-slower-than 10000
slowlog-max-len 128
4.5 Kafka:高吞吐消息总线
Kafka 是整个系统的「数据高速公路」。行情数据、成交回报、风控告警,全部通过 Kafka 流转。
我见过很多团队把 Kafka 当消息队列用,其实不对。Kafka 的核心优势是 持久化 + 回溯消费。做市商系统里,我们需要回放历史行情来验证策略,Kafka 天然支持这个场景。
| Topic 名称 | 分区数 | 保留时间 | 说明 |
|---|---|---|---|
| market_data | 8 | 7 天 | 实时行情数据 |
| order_fill | 4 | 30 天 | 成交回报 |
| risk_alert | 2 | 90 天 | 风控告警 |
| strategy_signal | 4 | 1 天 | 策略信号 |
配置上,我建议用 KRaft 模式(去掉 ZooKeeper),减少一个依赖组件。另外,acks=all 和 min.insync.replicas=2 是必须的,保证数据不丢。
# Kafka 生产者配置(C++ 客户端)
RdKafka::Conf *conf = RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL);
conf->set("bootstrap.servers", "kafka1:9092,kafka2:9092,kafka3:9092", errstr);
conf->set("acks", "all", errstr);
conf->set("compression.type", "lz4", errstr);
conf->set("linger.ms", "5", errstr); // 攒 5ms 再发,提升吞吐
conf->set("batch.num.messages", "1000", errstr);
4.6 数据库选型:时序数据与关系数据分离
做市商系统的数据,天然分成两类:
- 时序数据:行情 tick、订单簿快照、成交记录。特点是写入频繁、查询范围大、不需要事务
- 关系数据:策略配置、账户信息、风控参数。特点是写入少、查询频繁、需要事务
我的做法是:时序数据用 ClickHouse,关系数据用 PostgreSQL。
ClickHouse 的列式存储,对时序数据的聚合查询简直是降维打击。我试过用 PostgreSQL 存 1 亿条 tick 数据,查询一天的 OHLC 要 30 秒。换成 ClickHouse,同样的查询只要 200 毫秒。
小技巧:ClickHouse 的 ReplacingMergeTree 引擎可以自动去重,适合做订单簿快照的存储。配合 TTL 自动过期旧数据,省心省力。
-- ClickHouse 建表语句
CREATE TABLE market_data.tick_data
(
symbol String,
timestamp DateTime64(3),
bid_price Float64,
ask_price Float64,
bid_size Float64,
ask_size Float64,
last_price Float64,
volume Float64
)
ENGINE = ReplacingMergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (symbol, timestamp)
TTL timestamp + INTERVAL 30 DAY DELETE;
-- PostgreSQL 建表语句
CREATE TABLE strategy_config (
id SERIAL PRIMARY KEY,
strategy_name VARCHAR(64) NOT NULL,
params JSONB NOT NULL,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
4.7 整体架构图
下面这张图,是我做市商系统开发环境的完整架构。你看一眼,就能明白各个组件怎么配合。
4.8 环境搭建避坑总结
最后,我把这些年踩过的坑,浓缩成几条经验:
- 不要在生产环境用 Docker 跑 C++ 交易引擎。Docker 的网络栈和 CPU 调度会引入不可控的延迟抖动。我试过,平均延迟多了 50 微秒,做市商系统根本扛不住。
- Kafka 的 Topic 分区数不要随意设。分区数 = 消费者线程数 × 2 左右。分区太多,Leader 选举和 Rebalance 会变慢。
- Redis 一定要设置密码和防火墙。我见过有人把 Redis 裸奔在公网上,结果被挖矿程序扫到,数据全被删了。
- ClickHouse 的 MergeTree 引擎要定期 OPTIMIZE。否则数据碎片太多,查询性能会下降 10 倍以上。
- Python 和 C++ 之间的数据格式统一用 Protobuf。JSON 解析太慢,MsgPack 兼容性差。Protobuf 是唯一能在延迟和兼容性之间取得平衡的方案。
最后说一句:工具链只是起点,不是终点。真正拉开差距的,是你对每个组件底层原理的理解。别急着写代码,先把环境搭稳了,后面才能跑得快。