3、数据模型设计:时间序列数据模型、订单簿数据结构、交易对与资产模型、数据存储方案(Parquet/Feather)、数据清洗与预处理

做回测引擎,说白了就是跟数据打交道。你策略写得再花哨,数据模型设计得不好,回测结果就是垃圾。我见过太多人一上来就写策略,结果数据格式不对、时间戳对不齐、订单簿重建出来全是错的……嗯,今天咱们就把这块地基打牢。

3.1 时间序列数据模型

回测里最核心的数据,就是K线(OHLCV)。我个人习惯用时间戳作为索引,而不是自增ID。为什么?因为时间戳天然有序,而且能直接对齐外部数据源。

来看一个典型的结构:

// 用结构体表示一根K线
struct KLine {
    int64   timestamp;   // 毫秒级时间戳
    float   open;        // 开盘价
    float   high;        // 最高价
    float   low;         // 最低价
    float   close;       // 收盘价
    double  volume;      // 成交量
    int32   count;       // 成交笔数(可选)
};

这里有个坑——时间戳的精度。我在项目中遇到过,有的交易所给的是秒级,有的是毫秒级,甚至微秒级。你如果不统一,回测时K线对不齐,策略信号会乱跳。

注意: 建议统一使用毫秒级时间戳(13位整数)。如果源数据是秒级,乘以1000;如果是微秒级,除以1000。千万别混用。

3.2 订单簿数据结构

订单簿(Order Book)是高频回测的硬骨头。它本质上是一个价格-数量的映射,但实现方式决定了你的回测速度。

我推荐用跳表(Skip List)或红黑树来维护订单簿。为什么不用数组?因为订单簿的插入和删除操作太频繁了,数组的O(n)复杂度扛不住。

一个精简的订单簿结构:

struct OrderBookLevel {
    float price;        // 价格
    double quantity;    // 数量
    int32   orderCount; // 挂单数量
};

struct OrderBook {
    vector<OrderBookLevel> bids;  // 买单,按价格降序
    vector<OrderBookLevel> asks;  // 卖单,按价格升序
    int64   timestamp;            // 快照时间
};
小技巧: 如果你只做日频或分钟级回测,其实不需要实时维护订单簿。直接用K线的最高最低价模拟滑点就够了。别给自己找麻烦。

3.3 交易对与资产模型

交易对(Trading Pair)是回测引擎里的基本交易单元。比如BTC/USDT,你得同时管理基础资产(BTC)和计价资产(USDT)。

我习惯这样设计:

struct TradingPair {
    string baseAsset;   // 基础资产,如 "BTC"
    string quoteAsset;  // 计价资产,如 "USDT"
    float  minQty;      // 最小交易数量
    float  tickSize;    // 最小价格变动
    int32  precision;   // 价格精度(小数位数)
};

资产模型更简单,就是一个资产-余额的映射:

map<string, double> wallet;  // key: 资产名称, value: 可用余额

这里有个经验——永远用double存余额。float精度不够,尤其是做高频交易时,小数点后第6位的误差会累积成灾难。我曾经因为用float,回测跑了一周发现收益多了0.3%,查了两天才找到原因……

3.4 数据存储方案:Parquet vs Feather

回测数据动不动就是几十GB,选对存储格式能省一半时间。我主要用两种:

特性 Parquet Feather
压缩率 高(可压缩到原始大小的20%) 低(基本不压缩)
读取速度 中等(需要解压) 极快(内存映射)
列式存储
适用场景 长期归档、跨平台交换 本地快速回测、中间结果

我个人习惯:原始数据用Parquet存,因为省空间;回测时转成Feather,因为读得快。你想想看,回测一次要读几百个文件,Feather能快3-5倍,省下来的时间够你多调几次参数了。

3.5 数据清洗与预处理

数据清洗是回测里最脏最累的活,但躲不掉。我总结了一套流程:

  1. 去重:同一时间戳的K线只保留一条。交易所偶尔会推送重复数据。
  2. 补缺失:如果某根K线缺失,用前一根的收盘价填充,或者直接删除(取决于策略敏感度)。
  3. 去异常值:价格超过3倍标准差?成交量突然为0?这些都得标记出来。
  4. 对齐时间戳:不同交易对的数据可能时间戳不一致,统一对齐到整点或整分钟。
核心原则: 宁可少数据,不要脏数据。一个异常值可能让你的策略看起来盈利100%,实盘却亏掉底裤。

我曾经接手过一个项目,回测年化收益50%,结果发现是数据里混入了几个价格极低的异常点,策略在那些点疯狂买入……嗯,从那以后我每次回测前都会先跑一遍数据质量检查。

3.6 知识体系总览

下面这张图概括了本章的核心内容,你可以把它当作数据模型设计的检查清单:

数据模型设计知识体系 回测数据模型 时间序列数据模型 订单簿数据结构 交易对与资产模型 数据存储方案 数据清洗与预处理 Parquet Feather 去重 补缺失 去异常值 对齐时间戳 每个模块都直接影响回测结果的准确性和性能

数据模型设计这块,说白了就是把现实世界的交易行为,用计算机能高效处理的方式表达出来。你设计得好,回测引擎跑得又快又准;设计得不好,后面全是坑。

我个人建议,刚开始做回测引擎时,先把时间序列和资产模型搞定,订单簿可以后面再优化。别想着一步到位,迭代开发才是正道。


专注资料整理