第四章:行情数据模块设计
行情数据,说白了就是做市商的“眼睛”。没有它,你就是在黑箱里做交易。我见过不少团队,策略模型写得天花乱坠,结果行情模块一崩,全盘皆输。今天咱们就来聊聊,怎么把这双“眼睛”练得又亮又稳。
4.1 行情数据源接入
行情源接入,是第一步,也是最容易踩坑的一步。我个人习惯,先把主流交易所的API文档通读一遍——别笑,真有人不看文档直接上手,结果被限流、断连搞得焦头烂额。
常见的行情源有几种:
- 交易所WebSocket:实时性最好,但连接不稳定,需要重连机制
- REST API:适合补数据、查历史,但延迟高,不能用于实时交易
- 第三方数据商:比如彭博、路透,贵但稳定,适合机构
- 自建行情网关:把多个交易所的数据统一接入,内部用protobuf传输
嗯,这里要注意:千万别只依赖一个数据源。我在项目中遇到过,某次某大交易所的WebSocket突然挂了,整整5分钟没推送数据。如果我们没有备用源,那5分钟就是裸奔状态,风险极高。
避坑指南:我曾经因为没做数据源冗余,导致一次行情断流,策略在错误的价格上成交了。从那以后,我强制要求至少接入两个独立数据源,并且做交叉校验。
4.2 数据清洗与归一化
不同交易所的数据格式千奇百怪。有的用时间戳是毫秒,有的是微秒;有的价格是字符串,有的是整数。你想想看,如果每个源都单独处理,代码会变成什么样?
所以,归一化是必须的。我一般会定义一个内部标准格式:
// 内部统一行情结构
message MarketData {
string symbol = 1; // 统一交易对名称,如 "BTC-USDT"
int64 timestamp = 2; // 统一为微秒时间戳
double bid_price = 3;
double ask_price = 4;
double bid_size = 5;
double ask_size = 6;
double last_price = 7;
double volume = 8;
string source = 9; // 数据源标识
}
清洗这一步,很多人会忽略。其实脏数据很常见:
- 价格为零或负数——直接丢弃
- 时间戳乱跳——比如比当前时间晚了一小时,可能是交易所时钟不同步
- 买卖价差为负——理论上不可能,但偶尔会出现,需要做排序修正
我个人习惯,在清洗层加一个“异常检测”逻辑。比如,如果某笔行情的价格比上一笔波动超过5%,就标记为可疑,需要二次确认。别小看这一步,它能帮你过滤掉不少交易所的“乌龙指”事件。
4.3 实时行情推送架构
实时行情推送,核心就一个字:快。但快不是唯一指标,还得稳、还得准。
我常用的架构是这样的:
这个架构里,有几个关键点:
- 行情网关:负责维护与交易所的长连接,处理重连、心跳、鉴权。我建议用独立的进程或线程池,别跟策略混在一起
- 消息队列:我用过Kafka、Redis Pub/Sub、ZeroMQ。如果延迟要求极高(微秒级),ZeroMQ是首选;如果更看重持久化和回溯,Kafka更合适
- 多级缓存:在网关层和清洗层之间,我习惯加一个环形缓冲区,防止突发流量打崩下游
个人经验:我曾经在推送层用了一个“背压”机制。当下游消费速度跟不上时,不是丢弃数据,而是降级——比如把1ms的tick聚合为10ms的OHLC,保证核心数据不丢。
4.4 历史行情存储方案
历史行情,主要用于回测和复盘。存储方案的选择,取决于你的数据量和查询模式。
我总结了几种常见方案:
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| CSV/Parquet文件 | 小规模回测 | 简单、易读 | 查询慢、不支持并发 |
| 时序数据库(InfluxDB、TimescaleDB) | 中等规模、需要快速查询 | 查询快、支持聚合 | 存储成本较高 |
| 列式存储(ClickHouse) | 大规模、分析型查询 | 压缩率高、查询极快 | 写入延迟稍高 |
| 分布式文件系统(HDFS)+ Parquet | 超大规模、离线分析 | 扩展性好、成本低 | 实时性差 |
我个人比较推荐分层存储:
- 热数据(最近7天):放在内存或SSD上,用时序数据库,支持毫秒级查询
- 温数据(7天~3个月):放在ClickHouse里,压缩后存储,查询速度也能接受
- 冷数据(3个月以上):归档到HDFS或云存储,用Parquet格式,需要时再加载
核心原则:历史行情存储,不是为了存而存,而是为了“能快速回放”。我建议在存储层之上,封装一个统一的“行情回放API”,让策略和回测系统可以像订阅实时行情一样,订阅历史行情。
嗯,最后说一句:别忘了做数据校验。我曾经因为存储系统的一个bug,导致某天的行情数据全部错位了1秒。回测时策略表现异常好,实盘却一塌糊涂。从那以后,我每天都会跑一次“数据完整性检查”,对比原始日志和存储数据,确保万无一失。