第四节:常见策略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类型知识体系 四大Bug类型 数据对齐错误 常见场景 • 不同频率数据混用 • 时间戳精度不一致 • 复权数据混用 未来函数 常见场景 • shift(-1) 用未来数据 • 当天收盘价算当天信号 • 未来N天数据计算指标 幸存者偏差 常见场景 • 只用当前存活股票 • 忽略退市/ST股票 • 当前成分股回测历史 浮点精度问题 解决方案 • 使用 Decimal 类型 • 比较时用 epsilon • 计算后四舍五入

最后说两句:

这四个Bug类型,每一个我都踩过坑。数据对齐让我熬夜到凌晨三点,未来函数让我白高兴一场,幸存者偏差让我怀疑人生,浮点精度让我亏了真金白银。嗯,现在回想起来,这些都是宝贵的经验。你只要记住一句话:回测时多一分怀疑,实盘时少一分风险

专注资料整理