第四节:常见策略Bug类型——数据对齐错误、未来函数、幸存者偏差、浮点精度问题
做量化策略,说白了就是跟数据打交道。数据不对,策略白费。我这些年调试过的策略,十个里有八个栽在这四个坑里。今天咱们就一个一个掰开了揉碎了讲清楚。
一、数据对齐错误:最隐蔽的杀手
数据对齐错误,是我个人认为最容易被忽视的Bug。你想想看,你的策略里同时用了日线数据和分钟线数据,收盘时间不一样,索引对不上,结果就全乱了。
典型场景:
- 日线数据用日期索引,分钟线用时间戳索引
- 不同数据源的时间戳精度不一致(秒 vs 毫秒)
- 复权数据和非复权数据混用
我在项目中遇到过最离谱的一次,是回测看起来年化收益30%,实盘直接亏成狗。查了两天才发现,是日线收盘价和次日开盘价没对齐,策略在「未来数据」上做了交易决策。
我的习惯做法:
每次合并数据后,先打印前5行和后5行,肉眼检查时间戳是否对齐。别嫌麻烦,这步能省你三天调试时间。
# 错误示例:索引不对齐
import pandas as pd
df_daily = pd.DataFrame({'close': [100, 101, 102]},
index=pd.to_datetime(['2024-01-01', '2024-01-02', '2024-01-03']))
df_minute = pd.DataFrame({'close': [100.5, 101.2, 102.1]},
index=pd.to_datetime(['2024-01-01 09:30', '2024-01-02 09:31', '2024-01-03 09:32']))
# 直接合并会出问题
merged = pd.concat([df_daily, df_minute], axis=1) # 索引不匹配!
# 正确做法:先对齐索引
df_daily_aligned = df_daily.reindex(df_minute.index, method='ffill') # 向前填充
二、未来函数:回测的「作弊器」
未来函数,说白了就是你在回测时用到了「未来才知道」的数据。这玩意儿是量化新手最容易犯的错误,也是最致命的。
为什么会这样?因为回测环境里所有数据都是现成的,你一不小心就「看到」了未来的价格。
常见未来函数场景:
- 用当天的收盘价计算当天的买入信号
- 用未来N天的数据计算当前指标
- 在信号生成时使用了未来才有的数据点
我记得有一次帮朋友调试策略,他的策略回测曲线漂亮得不像话。我一看代码,好家伙,他用的是 shift(-1) 把明天的数据挪到今天用了。这就是典型的未来函数。
# 错误示例:未来函数
def generate_signal(df):
# 错误:用今天的收盘价和明天的收盘价比较
df['signal'] = (df['close'] > df['close'].shift(-1)) # shift(-1) 用了未来数据!
return df
# 正确做法:只用历史数据
def generate_signal_correct(df):
df['signal'] = (df['close'] > df['close'].shift(1)) # 只用过去的数据
return df
避坑指南:
我曾经在代码里加了一个断言:assert (df.index == df.index.sort_values()).all(),确保数据是按时间升序排列的。然后所有用到 shift 的地方,只允许正数,不允许负数。这招帮我挡掉了至少80%的未来函数Bug。
三、幸存者偏差:回测的「美颜滤镜」
幸存者偏差,说白了就是你只看到了活下来的股票,忽略了那些已经退市的。你想想看,如果只拿现在还在交易的股票做回测,那结果能不准吗?
我刚开始做量化时也犯过这个错。用现在的沪深300成分股回测过去5年,收益曲线漂亮得不得了。结果实盘一跑,发现那些退市的股票才是真正的「坑」。
幸存者偏差的典型表现:
- 只使用当前存活的股票进行历史回测
- 忽略退市、ST、停牌的股票
- 使用当前指数成分股回测历史数据
# 错误示例:幸存者偏差
# 只用了当前存活的股票
current_stocks = get_current_stock_list() # 2024年的股票列表
historical_data = get_historical_data(current_stocks, start='2020-01-01') # 回测2020年
# 正确做法:使用历史快照
def get_stock_list_at_time(date):
# 获取该时间点的实际股票列表
pass
historical_data = []
for date in trading_dates:
stocks = get_stock_list_at_time(date)
data = get_data_for_date(stocks, date)
historical_data.append(data)
我的经验:
做回测时,一定要用「时间点快照」数据。也就是说,你在2020年1月1日做决策时,只能用当时存在的股票。这个细节,决定了你的回测是「真实」还是「虚假」。
四、浮点精度问题:小数点后的魔鬼
浮点精度问题,是每个程序员都会遇到的坑。但在量化交易里,这个坑特别深。你想想看,几百万的资金,小数点后几位的一点点误差,可能就是几万块的出入。
为什么会这样?因为计算机用二进制表示十进制小数,有些小数是无限循环的。比如0.1在二进制里就是无限循环小数,存的时候就会有精度损失。
# 浮点精度问题演示
a = 0.1 + 0.2
print(a) # 输出:0.30000000000000004
print(a == 0.3) # 输出:False
# 在量化交易中的影响
price = 100.55
quantity = 1000
cost = price * quantity # 理论上应该是100550
print(cost) # 可能输出100549.99999999999
解决方案:
- 使用
decimal.Decimal处理金额计算 - 比较浮点数时用
abs(a - b) < 1e-8而不是a == b - 计算完成后四舍五入到合理精度
# 正确做法:使用Decimal
from decimal import Decimal, getcontext
getcontext().prec = 28 # 设置精度
price = Decimal('100.55')
quantity = Decimal('1000')
cost = price * quantity
print(cost) # 输出:100550.00
# 比较浮点数
def float_equal(a, b, epsilon=1e-8):
return abs(a - b) < epsilon
print(float_equal(0.1 + 0.2, 0.3)) # 输出:True
核心总结:
这四个Bug类型,我称之为量化策略的「四大天坑」。数据对齐错误让你「算不对」,未来函数让你「假回测」,幸存者偏差让你「看不清」,浮点精度让你「算不准」。任何一个没处理好,策略就是废的。
最后说两句:
这四个Bug类型,每一个我都踩过坑。数据对齐让我熬夜到凌晨三点,未来函数让我白高兴一场,幸存者偏差让我怀疑人生,浮点精度让我亏了真金白银。嗯,现在回想起来,这些都是宝贵的经验。你只要记住一句话:回测时多一分怀疑,实盘时少一分风险。