1. 量化回测系统设计思路
做量化交易的朋友都知道,回测是验证策略有效性的关键环节。一个靠谱的回测系统能帮我们避免实盘交学费,今天就来拆解如何从零搭建一个专业的回测模块。
我见过太多新手直接用现成平台回测,结果实盘时发现滑点、手续费、数据质量等问题让策略完全失效。自己搭建回测系统虽然前期麻烦,但能完全掌控每个细节,这才是量化交易的"硬核"玩法。
2. 核心组件设计
2.1 数据引擎
回测的核心是数据,我推荐使用Tushare Pro获取数据(需要注册获取token)。数据存储建议用DuckDB,这个轻量级数据库处理金融时间序列性能极佳:
import duckdb import tushare as ts # 初始化DuckDB内存数据库 conn = duckdb.connect(':memory:') # 获取日线数据示例 pro = ts.pro_api('你的token') df = pro.daily(ts_code='600519.SH', start_date='20200101', end_date='20231231') # 存入数据库 conn.register('stock_data', df)注意:Tushare的日线数据默认是后复权,做回测时建议使用前复权数据,避免未来函数问题。
2.2 事件驱动引擎
市面上90%的回测系统都用的是向量化回测(Vectorized Backtest),但这种回测方式无法模拟真实市场环境。我强烈建议用事件驱动架构:
class EventEngine: def __init__(self): self._queue = queue.PriorityQueue() def put(self, event): """事件入队""" self._queue.put((event.timestamp, event)) def run(self): """事件循环""" while not self._queue.empty(): _, event = self._queue.get() self._process(event)事件类型至少需要包含:
- MarketEvent 行情事件
- SignalEvent 信号事件
- OrderEvent 订单事件
- FillEvent 成交事件
3. 回测核心实现
3.1 策略模板
所有策略应该继承自基类策略,这是我的标准模板:
class Strategy(ABC): @abstractmethod def generate_signals(self): """生成交易信号""" pass class MovingAverageCross(Strategy): def __init__(self, short_window=5, long_window=20): self.short_window = short_window self.long_window = long_window def generate_signals(self, data): signals = pd.DataFrame(index=data.index) signals['signal'] = 0.0 # 计算均线 signals['short_ma'] = data['close'].rolling(self.short_window).mean() signals['long_ma'] = data['close'].rolling(self.long_window).mean() # 生成信号 signals['signal'][self.short_window:] = np.where( signals['short_ma'][self.short_window:] > signals['long_ma'][self.short_window:], 1.0, 0.0) return signals3.2 组合管理
组合管理是回测中最容易出问题的地方,关键是要准确计算仓位和资金变化:
class Portfolio: def __init__(self, initial_capital=100000): self.initial_capital = initial_capital self.current_balance = initial_capital self.positions = {} # 持仓字典 def update(self, fill_event): """更新持仓和资金""" symbol = fill_event.symbol fill_price = fill_event.fill_price fill_quantity = fill_event.quantity commission = fill_event.commission # 更新持仓 if symbol not in self.positions: self.positions[symbol] = fill_quantity else: self.positions[symbol] += fill_quantity # 更新资金 cost = fill_price * fill_quantity * (1 if fill_event.direction == 'BUY' else -1) self.current_balance -= (cost + commission)4. 回测关键问题处理
4.1 滑点模型
滑点是实盘和回测差异的主要来源,我常用的三种滑点模型:
- 固定比例滑点:成交价±0.1%
- 随机滑点:在指定范围内随机波动
- 成交量加权滑点:根据当前成交量调整滑点幅度
class SlippageModel: def apply_slippage(self, order, market_data): """应用滑点""" if order.direction == 'BUY': return order.price * (1 + self.slippage) else: return order.price * (1 - self.slippage)4.2 手续费计算
不同市场的手续费规则差异很大,A股常见的是:
- 佣金:万2.5(最低5元)
- 印花税:卖出时千1
- 过户费:万0.2
def calculate_commission(order): """计算A股手续费""" value = order.quantity * order.price commission = max(value * 0.00025, 5) # 佣金 stamp_tax = value * 0.001 if order.direction == 'SELL' else 0 # 印花税 transfer_fee = value * 0.00002 # 过户费 return commission + stamp_tax + transfer_fee5. 回测结果分析
5.1 绩效指标计算
完整的回测报告应该包含这些核心指标:
def calculate_metrics(returns): """计算关键绩效指标""" metrics = {} # 年化收益率 metrics['annual_return'] = (1 + returns).prod() ** (252/len(returns)) - 1 # 最大回撤 cum_returns = (1 + returns).cumprod() peak = cum_returns.expanding().max() drawdown = (cum_returns - peak) / peak metrics['max_drawdown'] = drawdown.min() # 夏普比率 metrics['sharpe_ratio'] = returns.mean() / returns.std() * np.sqrt(252) return metrics5.2 可视化分析
用Matplotlib绘制关键图表:
def plot_results(portfolio): """绘制回测结果""" plt.figure(figsize=(12, 8)) # 资金曲线 plt.subplot(211) portfolio['equity_curve'].plot(title='资金曲线') # 回撤曲线 plt.subplot(212) portfolio['drawdown'].plot(title='回撤曲线', color='red') plt.tight_layout() plt.show()6. 实战踩坑经验
- 未来函数陷阱:使用复权数据时一定要用"前复权",后复权会引入未来数据
- 幸存者偏差:回测时要包含已经退市的股票,否则结果会过于乐观
- 参数优化陷阱:避免过度拟合,建议使用Walk-Forward优化方法
- 停牌处理:实际交易中会遇到停牌,回测时要模拟这种情况
- 涨停板限制:A股有涨跌停限制,回测时要考虑这个流动性约束
重要提示:回测结果好不代表实盘能赚钱,建议先用模拟盘验证1-3个月再实盘。
7. 系统优化方向
- 多线程回测:用concurrent.futures实现并行回测
- GPU加速:用CUDA加速指标计算
- 数据库优化:改用ClickHouse存储海量数据
- 实时风控:加入实时风险监控模块
- 多时间框架:支持分钟、小时、日线多周期回测
最后分享一个实用技巧:回测时保存所有中间结果,方便后续分析问题。我习惯用HDF5格式存储完整的回测过程数据:
with pd.HDFStore('backtest_results.h5') as store: store.put('signals', signals_df) store.put('trades', trades_df) store.put('portfolio', portfolio_df)这套系统我从2018年开始迭代,现在每天能跑上千次回测。关键是要理解每个环节的实现细节,不能只当"调包侠"。量化交易没有银弹,自己搭建系统虽然辛苦,但能真正掌握核心技能。