2. 行情数据校验:交易所行情协议解析、数据完整性校验、异常Tick过滤、时钟同步机制
做高频交易,最怕什么?
不是策略亏钱,是数据错了。
你想想看,一个错误的Tick进来,策略瞬间给你开一百手单子,等发现行情不对,已经来不及了。我见过太多团队,策略模型跑得飞起,结果栽在行情数据上。所以今天咱们聊聊行情数据校验,这块是风控系统的第一道防线,也是我最重视的环节之一。
2.1 交易所行情协议解析
国内期货交易所,主流就那几家:中金所、上期所、大商所、郑商所。每家都有自己的协议格式。我最早做这块时,以为各家协议大同小异,结果被坑得不轻。
举个例子,中金所用的是FIX协议变种,上期所早期用Step协议,后来也往FIX靠。但细节上差异很大:
- 消息头长度:有的固定8字节,有的带变长字段
- 时间戳精度:有的精确到毫秒,有的到微秒
- 字段顺序:同一个字段在不同交易所的偏移量不同
我个人习惯,在解析层做一层抽象。不管底层协议怎么变,上层看到的都是统一的数据结构。这样换交易所或者协议升级时,改动最小。
核心原则:解析层只做格式转换,不做业务判断。校验逻辑放在下一层。
这里给一段伪代码,展示我常用的解析框架:
// 统一行情数据结构
struct MarketDataTick {
uint64_t timestamp; // 纳秒级时间戳
uint32_t instrumentID; // 合约代码
double lastPrice; // 最新价
uint64_t volume; // 成交量
double bidPrice; // 买一价
double askPrice; // 卖一价
uint32_t bidVolume; // 买一量
uint32_t askVolume; // 卖一量
};
// 协议解析器接口
class ProtocolParser {
public:
virtual bool parse(const char* rawData, size_t len, MarketDataTick& tick) = 0;
virtual bool validateHeader(const char* rawData) = 0;
};
2.2 数据完整性校验
数据完整性,说白了就是检查收到的行情包有没有丢、有没有错、有没有乱序。
我在项目中遇到过最典型的问题:网络抖动导致行情包乱序。你收到一个Tick,时间戳是10:00:01.000,下一个Tick是10:00:00.999。时间倒流了?不是,是包乱序了。
所以完整性校验,我一般做三层检查:
- 序列号连续性检查:每个行情包都有递增的序列号,如果发现跳号,说明有丢包
- 时间戳单调性检查:同一合约的Tick时间戳必须严格递增
- CRC校验:每个包末尾的校验码,确保传输过程中没被篡改
注意:序列号检查不能只看绝对值。交易所重启后序列号会重置,需要做特殊处理。我曾经因为这个bug,在实盘时误报了上百次丢包告警。
这里是我常用的校验逻辑:
bool checkDataIntegrity(const MarketDataTick& tick, uint64_t lastSeqNo, uint64_t lastTimestamp) {
// 1. 序列号检查
if (tick.seqNo <= lastSeqNo) {
logWarning("序列号异常: 当前=%llu, 上次=%llu", tick.seqNo, lastSeqNo);
return false;
}
// 2. 时间戳检查
if (tick.timestamp < lastTimestamp) {
logWarning("时间戳回退: 当前=%llu, 上次=%llu", tick.timestamp, lastTimestamp);
return false;
}
// 3. CRC校验(假设CRC字段在tick中)
if (!verifyCRC(tick)) {
logError("CRC校验失败");
return false;
}
return true;
}
2.3 异常Tick过滤
行情数据里,偶尔会出现一些「脏数据」。比如价格突然跳到一个离谱的值,或者买卖价差大得离谱。这些异常Tick如果不过滤掉,策略会做出错误决策。
我总结了几种常见的异常Tick类型:
| 异常类型 | 判断依据 | 处理方式 |
|---|---|---|
| 价格突变 | 价格变化超过N个标准差 | 丢弃,记录告警 |
| 买卖价差异常 | ask - bid > 阈值 | 丢弃,等待下一个Tick |
| 成交量异常 | 单笔成交量超过日均量10倍 | 标记可疑,人工复核 |
| 重复Tick | 价格、成交量、时间戳完全相同 | 直接丢弃 |
嗯,这里要注意一点:阈值设置不能太死。比如价格突变,在开盘集合竞价时,价格波动本来就大。我建议用动态阈值,根据近期波动率自适应调整。
小技巧:异常Tick过滤最好分两级。第一级用简单规则(比如价格范围检查),计算快;第二级用统计模型(比如Z-score),精度高。两级串联,既保证速度又保证准确。
2.4 时钟同步机制
时钟同步,是高频交易里最容易被忽视的问题。
你想想看,你的交易服务器和交易所的时钟差了1毫秒,那你收到的行情时间戳就是错的。策略基于这个时间戳做决策,等于在错误的时间轴上跑。
我常用的时钟同步方案:
- PTP(精确时间协议):硬件级同步,精度可达亚微秒级。但需要交换机支持
- NTP(网络时间协议):软件级同步,精度一般在1-10毫秒。够用但不够快
- GPS时钟:直接接收卫星信号,精度最高。但需要天线和专用设备
我个人建议,做高频交易至少用PTP。如果条件不允许,NTP加本地时钟漂移补偿也能凑合。但千万别只用系统时间,那误差能到秒级。
这里是我做时钟漂移补偿的代码片段:
class ClockSync {
private:
int64_t localOffset_; // 本地时钟与交易所时钟的偏差
double driftRate_; // 时钟漂移率(纳秒/秒)
public:
// 每次收到行情Tick时,更新偏差和漂移率
void update(int64_t exchangeTimestamp, int64_t localTimestamp) {
int64_t newOffset = exchangeTimestamp - localTimestamp;
// 用滑动平均平滑偏差
localOffset_ = localOffset_ * 0.9 + newOffset * 0.1;
// 计算漂移率(简化版)
driftRate_ = (newOffset - localOffset_) / 1.0e9; // 假设1秒间隔
}
// 获取校正后的本地时间
int64_t getCorrectedTime() {
int64_t now = getLocalNanoseconds();
return now + localOffset_ + driftRate_ * (now - lastSyncTime_);
}
};
警告:时钟同步不是一劳永逸的。交换机重启、网络波动、温度变化都会影响同步精度。建议每秒钟做一次校准,并监控偏差值,超过阈值就告警。
2.5 整体校验流程
说了这么多,咱们把整个流程串起来。下面这张图是我设计的行情数据校验架构:
整个流程走下来,你会发现:数据校验不是单一环节,而是一整套链路。从网络接收开始,到最终输出有效Tick,每一步都在做减法——把脏数据、错数据、乱数据剔除掉。
最后说一句:行情数据校验做得好不好,直接决定了你的策略能不能稳定跑。别在这块省钱省力,否则后面填坑的成本更高。
总结:协议解析要抽象、完整性校验要分层、异常过滤要动态、时钟同步要硬件。这四块做好了,行情数据这块就稳了。