10、数据存储与历史分析:时序数据库选型、数据归档、回测框架、绩效归因分析

10.1 时序数据库选型:别让数据成为瓶颈

做市商系统里,数据量有多大?我举个例子。我们一套策略跑下来,每秒要处理上千笔行情。每笔行情包含买卖五档、最新价、成交量。一天下来,光一个品种就是几千万条记录。你想想看,用传统的关系型数据库去存?嗯,我试过,查询一次历史波动率要等十几秒,这谁受得了。

所以,时序数据库是必须的。我个人习惯用 ClickHouseInfluxDB。ClickHouse 适合做大规模历史分析,列式存储,聚合查询极快。InfluxDB 适合实时写入和短周期查询,运维简单。

选型核心指标:

  • 写入吞吐量:至少支持每秒百万级写入。我在项目中遇到过,某开源库号称支持高并发,结果一上生产,写入延迟直接飙到 200ms,差点把行情网关堵死。
  • 压缩比:时序数据冗余度高,好的压缩算法能省 80% 磁盘。ClickHouse 的 LZ4 压缩,我实测能把 1TB 原始数据压到 150GB。
  • 查询延迟:回测时经常要扫几年数据,聚合查询必须秒级返回。InfluxDB 的 TSI 索引在这方面表现不错。

我的避坑指南: 我曾经选过一个冷门时序库,社区不活跃,遇到个 bug 没人修。后来我定了个规矩:优先选大厂背书、社区活跃的。ClickHouse 和 InfluxDB 目前最稳。

10.2 数据归档:热温冷三层架构

数据不能一股脑全放在高性能存储上。太贵了。我一般分三层:

层级 存储介质 数据范围 访问频率
热数据 SSD + 内存 最近 7 天 实时策略、风控
温数据 普通 HDD 7 天 ~ 6 个月 日常回测、报表
冷数据 对象存储(S3/MinIO) 6 个月以上 监管审计、长期研究

归档策略很简单:写个定时任务,每天凌晨把 7 天前的数据从 ClickHouse 导出到 Parquet 格式,压缩后扔到 MinIO。查询冷数据时,用 ClickHouse 的 ENGINE = S3 表引擎直接读,不用来回导数据。

-- 创建冷数据表,直接映射到 S3 上的 Parquet 文件
CREATE TABLE trades_cold
ENGINE = S3('https://minio:9000/archive/trades/*.parquet', 'AKIA...', 'secret...')
SETTINGS format = 'Parquet';

-- 查询时和普通表一样
SELECT symbol, avg(price) FROM trades_cold WHERE ts > '2023-01-01' GROUP BY symbol;

注意: 冷数据查询虽然方便,但延迟较高。我建议在查询语句里加个 LIMIT,防止一次扫太多文件把对象存储打爆。

10.3 回测框架:让历史告诉你答案

回测框架的核心就三件事:数据回放策略执行绩效统计。说白了,就是把历史行情重新跑一遍,看你的策略能不能赚钱。

我常用的回测框架是自己搭的,基于事件驱动。结构很简单:

class BacktestEngine:
    def __init__(self, data_source, strategy):
        self.data = data_source  # 从时序库读取
        self.strategy = strategy
        self.events = []

    def run(self, start, end):
        for tick in self.data.stream(start, end):
            # 1. 更新市场状态
            self.strategy.on_tick(tick)
            # 2. 检查是否有信号
            signal = self.strategy.check_signal()
            if signal:
                # 3. 模拟成交
                self.events.append(self.execute(signal))
        # 4. 计算绩效
        return self.calculate_performance(self.events)

这里有个坑:未来函数。我曾经在回测里不小心用了当天的收盘价来生成开盘信号,结果回测曲线漂亮得不行,实盘直接亏成狗。后来我强制要求:所有回测数据必须按时间戳严格排序,且策略只能看到当前 tick 之前的数据。

回测数据质量检查清单:

  • 是否有缺失的 tick?用前向填充还是插值?
  • 是否有异常价格?比如 0 元或者 999999 元。
  • 是否有停牌期间的数据?必须过滤掉。
  • 复权处理了吗?分红送股会影响价格连续性。

10.4 绩效归因分析:赚钱要赚得明白

回测跑完了,不能只看总收益率。你得知道钱是怎么赚的。是方向判断对了?还是波动率交易赚的?还是纯粹运气好?

我一般用 Brinson 归因模型,把收益拆成三部分:

  • 资产配置效应:你配了多少股票、多少债券、多少期权?
  • 择时效应:你买卖时点选得好不好?
  • 选券效应:你选的品种是不是比同类强?

对于做市商,我更关注 风险因子归因。说白了,就是看你的收益有多少来自市场风险(Beta),有多少来自做市能力(Alpha)。

# 简单的因子归因示例
import statsmodels.api as sm

# 假设我们有策略每日收益率和基准指数收益率
returns_strategy = [...]  # 策略日收益率
returns_benchmark = [...]  # 基准日收益率

# 回归:策略收益 = alpha + beta * 基准收益 + 残差
X = sm.add_constant(returns_benchmark)
model = sm.OLS(returns_strategy, X).fit()

alpha = model.params[0]  # 超额收益
beta = model.params[1]   # 市场暴露

alpha 为正,说明你的做市策略确实创造了价值。beta 接近 0,说明你不靠市场涨跌吃饭。这才是做市商该有的样子。

一个小技巧: 我习惯把归因结果做成热力图。横轴是月份,纵轴是因子,颜色深浅代表贡献度。一眼就能看出哪个月份、哪个因子在拖后腿。

10.5 知识体系总览

下面这张图,是我对本章内容的总结。数据从采集到归档,再到回测和归因,是一条完整的链路。

数据存储与历史分析知识体系 行情数据采集 时序数据库(ClickHouse / InfluxDB) 热数据(SSD,7天) 温数据(HDD,6个月) 冷数据(S3,长期) 回测框架(事件驱动) 绩效归因分析(Brinson / 因子模型)

嗯,这套体系我用了三年,从数据采集到最终归因,每个环节都踩过坑。但一旦跑顺了,你会发现做策略迭代变得特别快。回测跑一遍只要几分钟,归因报表自动生成,哪里有问题一目了然。

最后说一句: 数据是量化系统的血液。存储选型、归档策略、回测框架、归因分析,这四个环节缺一不可。别想着一步到位,先跑通,再优化。我见过太多团队在数据层折腾半年,策略还没写一行。没必要。

无相订单流研究社 微信Lucian808555