Backtrader 数据质量:回测好不好,先看数据靠不靠谱
很多人以为回测最重要的是策略代码。
其实更常见的情况是:
策略没有问题,但数据有问题。
如果数据错了,后面算出的收益、回撤和胜率都可能失真。
一、金融数据不只是几列价格
一份常见的行情数据通常包含:
datetime:时间 open:开盘价 high:最高价 low:最低价 close:收盘价 volume:成交量但真正研究时,还要知道:
- 数据来自哪里;
- 是实时数据还是历史数据;
- 是否复权;
- 使用什么时区;
- 价格和成交量的单位是什么;
- 是否包含停牌和分红信息。
二、第一步:检查日期
读取数据后,先做这几件事:
df=pd.read_csv("data.csv",parse_dates=["datetime"])df=df.sort_values("datetime")df=df.drop_duplicates("datetime")df=df.set_index("datetime")检查日期是否正常:
assertdf.index.is_monotonic_increasingassertdf.index.is_unique如果日期乱了,指标和交易顺序都会出问题。
三、第二步:检查价格逻辑
一根正常 K 线应该满足:
assert(df["high"]>=df[["open","close"]].max(axis=1)).all()assert(df["low"]<=df[["open","close"]].min(axis=1)).all()assert(df["high"]>=df["low"]).all()如果最高价比开盘价和收盘价都低,或者最低价比它们都高,数据大概率有问题。
还要检查:
- 价格是否小于等于 0;
- 成交量是否为负数;
- 是否突然出现极端跳变;
- 是否有整段时间缺失。
四、第三步:处理缺失值
检查缺失值:
print(df.isna().sum())不要看到 NaN 就直接全部填 0。
不同字段的处理方式不同:
- 价格缺失:通常需要查原始数据;
- 成交量缺失:不能随便当成 0;
- 指标预热期的 NaN:可能是正常现象;
- 交易日没有数据:可能是节假日,不一定要补。
金融时间序列最好先弄清楚“为什么缺失”,再决定怎么处理。
五、复权到底是什么?
股票分红、拆股后,历史价格会出现跳变。
例如一只股票从 100 元拆成 50 元,图上像是暴跌 50%,但投资者未必真的亏了这么多。
复权数据会调整历史价格,使收益计算更合理。
常见选择:
- 前复权:更适合观察当前价格下的历史走势;
- 后复权:更适合观察长期持有收益;
- 不复权:更接近当时实际成交价格。
重点不是哪一个绝对正确,而是:
研究、信号、收益计算和实盘价格必须保持一致。
六、时区和交易日不能忽略
不同市场的交易时间不同:A 股、美股、港股、期货和加密资产都不一样。
如果时间没有统一,可能出现:
- 用美股当天收盘价配对 A 股第二天价格;
- 把尚未收盘的数据当成已经完成;
- 多资产之间出现隐形未来函数。
多资产研究时,要明确:
哪个市场先开盘? 哪个市场先收盘? 信号形成后,下一次可交易时间是什么?七、数据质量的最小检查模板
required=["open","high","low","close","volume"]missing=[cforcinrequiredifcnotindf.columns]ifmissing:raiseValueError(f"缺少列:{missing}")ifdf[required].isna().any().any():raiseValueError("行情数据存在缺失值")if(df["close"]<=0).any():raiseValueError("收盘价存在非正数")建议把这些检查放在正式回测之前。
八、最常见的 7 个数据坑
- 日期没有排序;
- 日期重复;
- OHLC 列名映射错误;
- 复权方式前后不一致;
- 成交量单位不一致;
- 用未来数据填补缺失值;
- 数据源更新后,历史结果发生变化却没有记录。
最后总结
数据质量可以记成四句话:
先查日期,再查价格; 先问缺失原因,再决定填不填; 先确认复权,再计算收益; 先统一时间,再做多资产回测。数据是回测的地基。地基不稳,策略代码写得越复杂,错误只会被藏得越深。