第 5 章 · 01 向量化回测


文档摘要

第 5 章 · 01 向量化回测 本节摘要:本节讲策略评估的「快刀」——向量化回测。它是验证交易信号收益最快的方式:把整条收益率序列当成 NumPy/pandas 向量,用矩阵运算一次性算出持仓、收益和累计净值,几行代码就能评估一个多空组合。本节先讲清它的原理(信号→持仓→前向收益→组合收益),再写一个完整的均值回退/ML 信号的多空组合回测,并和 S&P500 基准对比。重点强调它「快但失真」的边界——不含手续费、滑点、容量、订单撮合,因此只能用于信号初筛,不能当作实盘依据。 内容来源:原项目 、 ,汉化并套用体系化模板。 ⚠️ 风险提示:向量化回测忽略了交易成本、滑点、资金占用和订单拒单,容易高估收益。任何「向量化夏普很高」的策略在加入成本后都可能变负,务必用事件驱动回测复核。

第 5 章 · 01 向量化回测

本节摘要:本节讲策略评估的「快刀」——向量化回测。它是验证交易信号收益最快的方式:把整条收益率序列当成 NumPy/pandas 向量,用矩阵运算一次性算出持仓、收益和累计净值,几行代码就能评估一个多空组合。本节先讲清它的原理(信号→持仓→前向收益→组合收益),再写一个完整的均值回退/ML 信号的多空组合回测,并和 S&P500 基准对比。重点强调它「快但失真」的边界——不含手续费、滑点、容量、订单撮合,因此只能用于信号初筛,不能当作实盘依据。

内容来源:原项目 ch08/02_vectorized_backtest.ipynbch05/01_backtest_with_trades.ipynb,汉化并套用体系化模板。

⚠️ 风险提示:向量化回测忽略了交易成本、滑点、资金占用和订单拒单,容易高估收益。任何「向量化夏普很高」的策略在加入成本后都可能变负,务必用事件驱动回测复核。

学习目标

阅读完本节,你应当能够:

  1. 说清向量化回测与事件驱动回测的核心差异。
  2. 用 pandas 一行代码把预测排序成多空持仓信号。
  3. .shift(-1) 构造前向收益避免未来函数。
  4. 把多空持仓与前向收益点乘出组合收益曲线。
  5. 知道向量化回测的适用边界(初筛)与局限。

一、为什么需要向量化回测

在第 4 章我们造了一堆 alpha 因子,在第 7 章会用 ML 模型预测收益。预测值出来了,第一个该问的问题是:「这个预测到底赚不赚钱?」这时如果直接上事件驱动回测引擎(像 backtrader、zipline 那样逐根 K 线撮合),配置繁琐、跑得慢,改一次参数要等半天。

向量化回测就是为了快速回答这个问题:把收益率当成一个矩阵,持仓当成另一个矩阵,两者点乘就是策略收益,一次算完所有时间点。它牺牲了真实性(没有撮合、没有成本),换来了毫秒级的迭代速度,非常适合在因子/模型筛选阶段做初判。

二、两种回测范式的对比

维度 向量化回测 事件驱动回测
执行方式 矩阵/向量一次性算完 逐根 K 线模拟撮合
速度 极快(毫秒级) 慢(秒到分钟级)
撮合细节 无(假设信号即成交) 有(限价/市价/滑点)
交易成本 难以精确建模 可逐笔建模
资金/仓位 简化为等权或目标权重 完整账户、保证金、拒单
适用阶段 信号初筛、因子排序 接近实盘的最终验证

💡 核心心法:把向量化回测当成「信号质量的快速体检」,而不是「实盘成绩单」。它回答的是「这个因子有没有 Alpha」,而非「这套交易成本下能不能赚钱」。

三、数据准备:预测与基准

向量化回测的输入很轻:predictions(模型/因子在每个日期每个资产的预测值)和 sp500(基准收益)。

import numpy as np import pandas as pd import pandas_datareader.data as web # 模型预测:MultiIndex(date, ticker) -> predicted data = pd.read_hdf('00_data/backtest.h5', 'data') sp500 = web.DataReader('SP500', 'fred', '2014', '2018').pct_change()

⚠️ 数据形状:predictions 必须是 (date, ticker) 的长表或经过 unstack('ticker') 的宽表,才能用 rank(axis=1) 做横截面排序。

四、构造前向收益(避免未来函数)

这是向量化回测最关键、最容易出错的一步。我们要测的是「今天根据信号买卖,明天收益如何」,所以要用前向收益 fwd_returns = daily_returns.shift(-1)

daily_returns = data.open.unstack('ticker').sort_index().pct_change() fwd_returns = daily_returns.shift(-1) # 关键:用明天的开盘对今天开盘的涨幅

为什么是 .shift(-1)?因为 daily_returns[t] 是「t-1 到 t」的涨幅,而我们在 t 日收盘才看到信号,实际能吃到的是「t 到 t+1」的涨幅,即 daily_returns.shift(-1)[t]。如果忘了 shift,等于用未来收益算今天信号,这是经典的未来函数错误,会把亏钱策略算成赚钱。

五、把预测变成多空信号

有了预测值,用 rank(axis=1) 在每个时间截面上对所有股票排序,取预测最高的 N 只做多、最低的 N 只做空:

predictions = data.predicted.unstack('ticker') N_LONG = N_SHORT = 15 # 预测值最大的前 N 只 -> 做多信号 1 long_signals = ((predictions .where(predictions > 0) .rank(axis=1, ascending=False) <= N_LONG) .astype(int)) # 预测值最小(最负)的前 N 只 -> 做空信号 1 short_signals = ((predictions .where(predictions < 0) .rank(axis=1) <= N_SHORT) .astype(int))

rank(axis=1, ascending=False) 表示「按行从大到小排名」,排名 ≤ 15 就是预测最高的 15 只。做空那行 ascending=True 是默认值,排名 ≤ 15 是预测最负的 15 只。where(predictions > 0) 先把非正预测滤掉,避免把接近零的也排进来。

六、点乘出组合收益与累计净值

最后一步,把信号和前向收益按元素相乘,再跨股票取均值(等权),多空相加就是策略每日收益:

long_returns = long_signals.mul(fwd_returns).mean(axis=1) short_returns = short_signals.mul(-fwd_returns).mean(axis=1) strategy = long_returns.add(short_returns).to_frame('Strategy') # 累计净值 cum = strategy.join(sp500).add(1).cumprod().sub(1)

注意做空那行乘了 -fwd_returns:做空盈利等于收益的负数。mean(axis=1) 是等权平均(把组合每日收益归一),也可以换成按信号强度加权。

res = strategy.join(sp500).dropna() print(res.std()) # 日波动率,策略 vs 基准 print(res.corr()) # 与基准的相关性,越低越独立

本节要点回顾

  1. 向量化回测用矩阵运算一次性算出策略收益,适合因子/模型信号的快速初筛,不替代实盘回测。
  2. 前向收益是关键:fwd_returns = daily_returns.shift(-1),忘了 shift 就是未来函数。
  3. 横截面排序:predictions.rank(axis=1) 给每个时间截面的股票排序,取 top/bottom N 形成多空信号。
  4. 组合收益:信号 × 前向收益mean(axis=1) 等权聚合,多空相加得策略日收益。
  5. 局限:无成本、无滑点、无撮合、无仓位约束,「向量化夏普高」不等于实盘可盈利,必须用事件驱动回测复核。

下一节,我们看事件驱动回测引擎 backtrader——它逐根 K 线模拟撮合、加成本加滑点,是向量化结果的「实盘校正器」。


发布者: 作者: 灏天文库 转发
评论区 (0)
U