23、信号回测框架:向量化回测、事件驱动回测、性能评估
做高频信号回测,说白了就是验证你的策略在历史上能不能赚钱。我见过太多人,策略写得花里胡哨,一回测就露馅。今天咱们聊聊回测框架的两种主流玩法——向量化回测和事件驱动回测,以及怎么评估策略到底行不行。
向量化回测:快,但有点糙
向量化回测,我个人习惯叫它“批量计算”。你把所有历史数据一次性塞进去,用矩阵运算算出结果。速度快得飞起,适合初步筛选信号。
核心思路:用pandas的向量化操作,避免循环。
import pandas as pd
import numpy as np
# 假设我们有一个价格DataFrame
df = pd.DataFrame({
'close': [100, 102, 101, 103, 105, 104, 106, 108]
})
# 生成信号:收盘价突破5日均线
df['ma5'] = df['close'].rolling(5).mean()
df['signal'] = np.where(df['close'] > df['ma5'], 1, 0)
# 计算每日收益率
df['return'] = df['close'].pct_change()
# 策略收益率 = 信号 * 收益率(假设T+1成交)
df['strategy_return'] = df['signal'].shift(1) * df['return']
# 累计收益
df['cumulative_return'] = (1 + df['strategy_return']).cumprod()
print(df[['close', 'signal', 'strategy_return', 'cumulative_return']])
你看,几行代码就搞定了。但这里有个坑——未来函数。我曾经在项目里用shift(1)没注意,结果信号用了当天的数据,回测曲线漂亮得不像话,实盘直接崩了。嗯,这里要注意:信号必须基于历史数据生成,不能偷看未来。
避坑指南:我曾经用向量化回测做高频策略,发现收益率异常高。排查了半天,原来是pct_change()和shift()的顺序搞反了。记住:先shift信号,再计算收益率。
事件驱动回测:真实,但慢
事件驱动回测,说白了就是模拟真实交易过程。每个tick来了,你都得判断要不要下单、能不能成交、滑点多少。我建议做高频策略时,至少用事件驱动回测验证一遍。
为什么?因为向量化回测假设你总能以收盘价成交,但高频交易里,你看到的价和实际成交价可能差好几个tick。
class EventDrivenBacktest:
def __init__(self, data, initial_capital=100000):
self.data = data
self.capital = initial_capital
self.position = 0
self.trades = []
def on_tick(self, tick):
# 每个tick到来时触发
signal = self.generate_signal(tick)
if signal == 1 and self.position == 0:
# 买入,考虑滑点
buy_price = tick['ask'] * 1.0001 # 0.01%滑点
shares = self.capital // buy_price
self.position = shares
self.capital -= shares * buy_price
self.trades.append(('buy', tick['timestamp'], buy_price, shares))
elif signal == -1 and self.position > 0:
# 卖出
sell_price = tick['bid'] * 0.9999
self.capital += self.position * sell_price
self.position = 0
self.trades.append(('sell', tick['timestamp'], sell_price, self.position))
def generate_signal(self, tick):
# 这里放你的信号逻辑
# 比如:买一卖一价差超过阈值
spread = tick['ask'] - tick['bid']
if spread > 0.02:
return 1
return 0
你看,事件驱动回测里,你得处理订单簿、滑点、成交延迟。我做过一个统计:同样的策略,向量化回测年化收益30%,事件驱动回测只有12%。差距就在滑点和成交概率上。
个人经验:我建议先用向量化回测快速筛选信号,再用事件驱动回测精细验证。别一上来就搞事件驱动,调试起来太费时间。
性能评估:别只看收益率
很多人回测完一看年化50%,就觉得自己要发财了。醒醒,那可能是过拟合。我评估策略时,至少看这几个指标:
| 指标 | 含义 | 我的经验阈值 |
|---|---|---|
| 夏普比率 | 每单位风险获得的超额收益 | 高频策略至少 > 2.0 |
| 最大回撤 | 账户从峰值跌到谷底的最大幅度 | 不超过20% |
| 胜率 | 盈利交易占比 | 高频策略40%-60%正常 |
| 盈亏比 | 平均盈利 / 平均亏损 | 至少 > 1.5 |
| 交易次数 | 总交易笔数 | 高频策略每天几百到几千笔 |
我一般用这个函数来算核心指标:
def evaluate_strategy(returns, risk_free_rate=0.02):
# 年化收益率
annual_return = returns.mean() * 252 * 100
# 年化波动率
annual_vol = returns.std() * np.sqrt(252) * 100
# 夏普比率
sharpe = (returns.mean() * 252 - risk_free_rate) / (returns.std() * np.sqrt(252))
# 最大回撤
cumulative = (1 + returns).cumprod()
peak = cumulative.expanding().max()
drawdown = (cumulative - peak) / peak
max_drawdown = drawdown.min() * 100
# 胜率
win_rate = len(returns[returns > 0]) / len(returns[returns != 0])
return {
'年化收益率': f'{annual_return:.2f}%',
'年化波动率': f'{annual_vol:.2f}%',
'夏普比率': f'{sharpe:.2f}',
'最大回撤': f'{max_drawdown:.2f}%',
'胜率': f'{win_rate:.2%}'
}
重要提醒:高频策略的夏普比率容易被高估。因为tick级数据噪声大,收益率序列自相关性强。我习惯用调整后的夏普比率,或者看Calmar比率(年化收益/最大回撤)。
回测框架的核心逻辑
下面这张图,是我做回测时脑子里始终绷着的一根弦:
你看,数据进来后,两条路:向量化回测快,适合初步筛选;事件驱动回测慢,但更真实。最后都汇总到性能评估,不行就回去改信号。
我的习惯:先跑向量化回测,看夏普比率和最大回撤。如果夏普 < 1.5,直接放弃,别浪费时间。过了这关,再搞事件驱动回测,加上滑点和手续费。最后用样本外数据验证一遍。
做高频回测,最怕的就是过拟合。我见过有人把参数调到历史数据上完美拟合,结果实盘一周亏光。记住:回测是帮你排除烂策略的,不是帮你找完美策略的。