第七章:回测框架搭建
做量化交易,回测框架就是你的实验室。没有它,你的策略就是空中楼阁。我个人习惯把回测框架分成两大流派:向量化回测和事件驱动回测。今天咱们就把这两套东西掰开揉碎了讲清楚。
7.1 向量化回测 vs 事件驱动回测
先说说向量化回测。说白了,就是用pandas的向量化操作,一次性算完所有信号和持仓。速度快,代码短,适合快速验证想法。
核心思想:用数组运算代替循环,一次处理整个时间序列。
import pandas as pd
import numpy as np
def vectorized_backtest(data, signal_col='signal'):
"""
向量化回测核心逻辑
data: DataFrame,包含价格和信号
"""
# 计算每日收益率
data['returns'] = data['close'].pct_change()
# 信号平移一天,避免未来信息
data['position'] = data[signal_col].shift(1)
# 策略收益率 = 持仓 * 每日收益率
data['strategy_returns'] = data['position'] * data['returns']
# 累计净值
data['equity'] = (1 + data['strategy_returns']).cumprod()
return data
但向量化有个大坑——它假设你可以在收盘价瞬间成交。我在实盘中吃过这个亏,回测曲线漂亮得很,一上实盘就崩。为什么?因为滑点和流动性问题被忽略了。
事件驱动回测就不一样了。它模拟真实交易流程:收到行情→判断信号→发单→成交确认。每一步都有时间戳,能精确模拟订单簿变化。
class EventDrivenBacktest:
"""
事件驱动回测引擎(简化版)
"""
def __init__(self, initial_capital=1000000):
self.capital = initial_capital
self.positions = {} # 持仓字典
self.orders = [] # 订单队列
self.trades = [] # 成交记录
def on_bar(self, bar_data):
"""处理每个K线事件"""
# 1. 检查是否有待成交订单
self.process_orders(bar_data)
# 2. 生成新信号
signal = self.generate_signal(bar_data)
# 3. 如果信号变化,生成新订单
if signal != self.current_signal:
self.place_order(signal, bar_data)
def process_orders(self, bar_data):
"""模拟订单成交(考虑滑点)"""
for order in self.orders:
# 模拟成交价 = 开盘价 + 滑点
fill_price = bar_data['open'] * (1 + self.slippage)
# 更新持仓和资金
self.fill_order(order, fill_price)
我的建议:初期用向量化快速迭代策略逻辑,等策略稳定了再转事件驱动做精细回测。别一上来就搞事件驱动,调试起来太痛苦。
7.2 手续费与滑点模拟
手续费和滑点是回测里最容易造假的地方。我见过太多人把手续费设成0,回测年化50%,实盘直接亏到姥姥家。
咱们得把成本算清楚:
| 成本类型 | 期货市场 | 股票市场 |
|---|---|---|
| 手续费 | 按手数固定(如3元/手)或按成交额比例 | 佣金万2.5 + 印花税千1(卖出) |
| 滑点 | 1-2个tick(看流动性) | 0.01-0.05元/股 |
| 冲击成本 | 大单时额外加0.5-1个tick | 按成交额比例估算 |
def calculate_trade_cost(price, volume, market='futures'):
"""
计算交易成本
"""
if market == 'futures':
# 期货:按手数收费
commission_per_lot = 3 # 每手3元
lots = volume / 10 # 假设每手10吨
commission = lots * commission_per_lot
# 滑点:1个tick
tick_size = 1 # 假设1元/tick
slippage = tick_size * volume
elif market == 'stock':
# 股票:按成交额比例
turnover = price * volume
commission = turnover * 0.00025 # 万2.5
stamp_tax = turnover * 0.001 if 'sell' else 0 # 卖出印花税
# 滑点:0.02元/股
slippage = 0.02 * volume
return commission + slippage
避坑指南:我曾经在回测里只算了手续费没算滑点,结果实盘第一天就被滑点打爆了。记住:滑点成本往往比手续费高一个数量级。
7.3 资金管理模块
资金管理是量化交易的命门。没有好的资金管理,再牛的策略也会爆仓。我习惯用凯利公式做仓位参考,但实际执行时会打折扣。
class RiskManager:
"""
资金管理模块
"""
def __init__(self, initial_capital, max_risk_per_trade=0.02):
self.capital = initial_capital
self.max_risk = max_risk_per_trade # 单笔最大风险2%
def kelly_position(self, win_rate, avg_win, avg_loss):
"""
凯利公式计算最优仓位
"""
# 凯利公式:f* = (p*b - q) / b
# p = 胜率, q = 1-p, b = 盈亏比
b = avg_win / avg_loss
p = win_rate
q = 1 - p
kelly_f = (p * b - q) / b
# 实际使用半凯利,降低风险
return min(kelly_f * 0.5, 0.25) # 不超过25%仓位
def position_sizing(self, entry_price, stop_loss, capital_pct=None):
"""
基于止损计算仓位
"""
if capital_pct is None:
capital_pct = self.max_risk
risk_per_share = entry_price - stop_loss
total_risk = self.capital * capital_pct
shares = total_risk / risk_per_share
return int(shares)
个人经验:凯利公式算出来的仓位往往偏大。我一般用半凯利,也就是算出来的一半。虽然收益会降低,但回撤控制得好,睡得着觉。
7.4 绩效指标计算
回测做完,得算算策略到底行不行。我一般看这几个指标:
def calculate_performance_metrics(equity_curve, risk_free_rate=0.03):
"""
计算核心绩效指标
"""
# 日收益率序列
daily_returns = equity_curve.pct_change().dropna()
# 年化收益率
total_return = equity_curve.iloc[-1] / equity_curve.iloc[0] - 1
n_years = len(daily_returns) / 252
annual_return = (1 + total_return) ** (1/n_years) - 1
# 年化波动率
annual_vol = daily_returns.std() * np.sqrt(252)
# 夏普比率
sharpe = (annual_return - risk_free_rate) / annual_vol
# 最大回撤
rolling_max = equity_curve.expanding().max()
drawdown = (equity_curve - rolling_max) / rolling_max
max_drawdown = drawdown.min()
# 卡玛比率
calmar = annual_return / abs(max_drawdown)
# 胜率
win_rate = len(daily_returns[daily_returns > 0]) / len(daily_returns)
return {
'annual_return': annual_return,
'annual_vol': annual_vol,
'sharpe_ratio': sharpe,
'max_drawdown': max_drawdown,
'calmar_ratio': calmar,
'win_rate': win_rate
}
这里有个细节:夏普比率的分母是波动率。波动率越低,夏普越高。但低波动不代表低风险,你想想看,一个策略天天不赚钱,波动率当然低,夏普可能还挺好看。所以不能只看夏普。
核心指标解读:
- 夏普比率 > 1:可以接受;> 2:优秀;> 3:顶级
- 最大回撤 < 20%:正常;< 10%:很好
- 卡玛比率 > 2:不错;> 3:优秀
7.5 回测框架整体架构
最后,我把整个回测框架的结构画出来。你照着这个搭,基本不会漏东西。
嗯,这个架构图基本覆盖了回测需要的所有模块。你从数据层开始,一层层往下搭,每层都做单元测试。我当年就是偷懒没测数据层,结果用了一周才发现行情数据有缺失,白白浪费了时间。
最后说一句:回测框架不是越复杂越好。先跑起来,再优化。别一开始就想搞个完美的框架,那只会让你陷入过度工程化的泥潭。
公众号:蓝海资料掘金营,微信deep3321