第 18 篇
多回测运行和参数分析框架
一篇「回测流水线」方法论文:用 parameteranalysis 类把「一组参数组合(笛卡尔积)」自动跑成一批回测,存成 pickle,再给指标表(收益/夏普/最大回撤/波动)与收益/超额/对数收益曲线。它用 BP 因子十分位回测演示一个关键动作:别拿等权策略直接比市值加权的上证指数,要自建「等权全指」基准、看相对基准的超额收益——结果发现 BP 分位收益在 06–10 年拉开、10 年后基本失效。本篇是量化课堂第 18 篇(postId=4351,作者 Sunx,编辑肖睿,难度入门,level-0;v1.1 于 2017-05-04)。素材见 raw/collections/jq-quant-classroom/18-18-多回测运行和参数分析框架md.md。
这是什么
研究端(非策略端)的调参与分析框架。因子研究系列要做「全市场按分位回测、比各分位收益」,本篇把这个需求框架化成 parameter_analysis 类:输入策略 id 与要调的全局参数名/取值,函数自动穷举参数组合、排队跑回测、汇总结果并可视化。文章先讲类的方法与数据项,再用 BP 因子十分位回测演示怎么用「等权全指」基准和超额收益判断因子是否真有效、何时失效。
核心要点
框架动机与总体
- 需求来源:因子研究里要把全部股票按分位回测、分析每个分位的收益;其它策略调参也一样需要。
- 整个框架写在一个
parameter_analysis类里,附在文章结尾的研究模块。 - 本篇用 Python 2 版本回测(官方另附 Python 3 教程链接)。
初始化与运行:get_backtest_data
- 初始化:
parameter_analysis(algorithm_id=None);algorithm_id 是策略 id(策略编辑页 URL 尾部)。 - 核心函数
get_backtest_data(...),参数逐个讲:algorithm_id:策略 id(初始化给了可省)。benchmark_id:自定义基准的回测 id(注意是回测不是策略);函数以它的收益率曲线为基准曲线;要求该回测起止日期与 start_date/end_date 吻合。不给则用策略自带的基准。file_name:pickle 存文件名,结尾必须 .pkl;默认 'results.pkl',存于研究 .ipynb 同目录。running_max:同时运行回测上限。平台并发上限 10,默认 10;想留空位可设 9 或更小。超出的自动排队,前面的完成后再跑。start_date='2006-01-01'、end_date='2016-11-30'、frequency='day'(或 'minute')、initial_cash='1000000':默认值如上。param_names、param_values:要调参的全局变量。param_names 是变量名 list(如 ['abc','x_y_z'],对应策略里的 g.abc、g.x_y_z,不带 g.);param_values 是 list of list,长度相等,param_values[i]是第 i 个参数的取值表。函数自动取笛卡尔积:abc∈[a,b,c] × x_y_z∈[1,2] → 6 个回测(('a',1)、('a',2)、('b',1)…)。
- 跑完把数据存 pickle,同时挂在类属性下。
数据项(存下来可以反复用)
algorithm_id:策略编码。params_df:所有回测用的参数 DataFrame;行 index = 回测编号(从 0 起),列 = 参数名。evaluations:dict of dict,evaluations[i] = 第 i 个回测的指标(收益率、夏普比率、最大回撤等)。evaluations_df:evaluations 的 DataFrame 版,并合并 params_df 的参数。backtest_ids:回测编号 → 回测 URL 编码。benchmark_id:自定义基准 id(没提供则为空)。dates:按序的交易日 list。benchmark_returns:基准收益率序列。returns/excess_returns/log_returns/log_excess_returns:dict of list,key 为回测编号;分别是对应策略的收益率、超额收益率、对数收益率、对数超额收益率。超额收益率 = 策略净值 ÷ 基准净值;对数超额 = 超额的对数。excess_max_drawdown、excess_annual_return:策略超额收益曲线的最大回撤与年化——专门看「排除基准后」的相对强弱。
读回数据与可视化
read_backtest_data(file_name='results.pkl'):关掉研究再打开时不用重跑,直接读 pickle。get_eval4_bar(sort_by=[]):画收益 / 最大回撤 / 夏普 / 波动率四张柱状图;默认按回测编号排,sort_by 可按参数名排序(多参数则先按后列再按前列)。get_eval(sort_by=[]):年化收益 + 最大回撤柱状图。get_excess_eval(sort_by=[]):超额年化 + 超额最大回撤。plot_returns()/plot_excess_returns()/plot_log_returns()/plot_log_excess_returns():收益曲线 / 超额曲线 / 对数收益曲线 / 超额对数曲线(对数轴在收益膨胀后仍能看清相对波动)。
BP 因子十分位实例
- 背景问题:因子研究里发现很多因子「最大 5% 或最小 5% 分位都轻松跑赢上证指数」。原因是上证指数偏大盘 + 市值加权,小市值暴露很低;而策略对所有分位股票等权,小市值暴露比指数大得多——直接比原始收益不公平。
- 解法:自建「等权全指」作自定义基准——每月初把资金等权分配到所有「21 个交易日没停牌」(原文口径,一个月)的股票上。
- 调用示例:
pa = parameter_analysis('bce2e5c55b3b631f91985c9bf113414f');get_backtest_data(file_name='results.pkl', running_max=10, benchmark_id='ae0684d86e9e7128b1ab9c7d77893029', start_date='2006-02-01', end_date='2016-11-01', frequency='day', initial_cash='2000000', param_names=['factor','quantile'], param_values=[['BP'], tuple(zip(range(0,100,10), range(10,101,10)))])→ 10 个回测:(0,10)、(10,20)、…、(90,100) 分位。 - 发现(照录原文结论):
- 收益曲线看 BP 因子「具有单调性质」,但单调性不是绝对的——(10,20) 分位收益最高。
- 各分位的年化/回撤/夏普/波动并不单调;「BP 值小的一端表现好于 BP 值大的一端、收益较高而最大回撤反而较小」——原文此句语序含混,见「不确定」。
- 超额收益曲线:BP 最小 10% 波动极大、超额不多(不是好选择);各分位在 06–10 年超额收益相互拉开,10 年之后不同分位没有明显超额区别 → 因子基本失效。
- 超额对数曲线:一段时间收益持平 = 没有持续放大。
- 剔除大盘后超额收益明显小于原始收益;按策略净值算,各分位最大回撤都 70% 多(同经历股灾),剥掉基准才能比超额回撤。
- 结论:不能单用 BP 当衡量标准指望超额;分行业算 BP 或结合别的方法或许仍有收益。
机制 / 论证
- 为什么要把回测批量自动化:手调参数只能看一两个点,容易「调出想要的」;参数网格把取值组合全部跑一遍、统一评估,是减少挑参数偏见的工程化手段(它仍属样本内调参,见 QuantStart 算法交易回测方法论(Part I–II) 的过拟合讨论)。
- 为什么要有 running_max 排队:平台并发回测有上限(当时 10 个);框架自动排队是「跑多参数」的前提,否则一次只能手点一个。
- 为什么自建等权全指基准:指数(上证)市值加权、偏大盘,等权策略天然暴露更多小市值——两个组合暴露不同,比原始收益不公平。等权全指把「市值暴露」这个干扰因素对齐后,剩下的差异才接近因子本身的贡献。
- 为什么超额收益比原始收益更会说话:原始收益里混着大盘 β(涨市谁都在赚、净值口径回撤都 70% 多);超额 = 策略净值 ÷ 基准净值,剥掉 β 后看策略相对基准的好坏与回撤。
- 为什么看「超额何时不再放大」:BP 分位 06–10 年拉开、10 年后走平——超额对数曲线持平说明因子不再产生相对优势。这给了「因子会失效」的可视化判据,比单看漂亮的总收益曲线诚实。
可操作
parameter_analysis框架要点(保留原文;类代码附在原文研究模块,raw 未含完整实现 [需要验证]):- 初始化传 algorithm_id(策略 URL 尾部 id)。
- get_backtest_data 关键参数:benchmark_id(自定义基准回测 id,起止须吻合)、file_name(.pkl)、running_max(≤10,超出排队)、start/end/frequency/initial_cash、param_names/param_values(笛卡尔积组合)。
- 结果对象:params_df / evaluations / evaluations_df / backtest_ids / dates / benchmark_returns / returns / excess_returns / log_returns / log_excess_returns / excess_max_drawdown / excess_annual_return。
- read_backtest_data 免重跑;画图 get_eval4_bar / get_eval / get_excess_eval / plot_returns / plot_excess_returns / plot_log_returns / plot_log_excess_returns(sort_by 排序)。
- 调参约定:param_names 写全局变量名(不带 g.);param_values 各参数取值表等长。
- BP 实例参数(保留原文):factor='BP'、quantile=(0,10)…(90,100)、等权全指基准、2006-02-01~2016-11-01、日频、初始资金 2,000,000、running_max=10。
- 平台上下文:研究模块 + 回测功能;Python 2(官方另给 Python 3 教程);并发上限与界面规则以当时平台为准 [需要验证]。
术语
- 研究模块:聚宽里独立于策略的 notebook 式研究环境(本篇框架所在)。
- 基准(benchmark):拿来比的参照收益率;策略自带或自定义回测。
- 超额收益:策略净值 ÷ 基准净值(相对表现)。
- 对数收益 / 对数超额:收益取对数后的曲线(现值膨胀后仍可看相对波动)。
- 分位回测:把股票池按某因子分成若干段(十分位等)分别回测。
- 等权全指:每月初等权买入全市场未停牌股票的自建基准(对齐市值暴露)。
- BP 因子:账面市值比类估值因子(来自因子研究一,part 27 展开)。
- pickle:Python 对象序列化存储格式(.pkl)。
不确定 / 待验证
parameter_analysis类完整代码在原文「结尾的研究模块」,raw 只保留了说明文字,没有实现——函数细节以官方源码为准 [需要验证]。- 文中「BP 较小还是体现公司的股票回报率会好于 BP 较高的股票」一句语序含混(前文收益曲线又说 BP 大的一端更好、但 (10,20) 最高),两个说法并存;本页按原文分别照录,BP 分位方向性结论待 part 27(因子研究一)与图核对 [需要验证]。
- 各分位的年化/回撤/夏普具体数字在附图柱状图里,raw 正文只有定性结论(「(10,20) 最高」「最大回撤都 70% 多」等)。
- 等权全指构建细节:原文「每月初将资金等权分配于所有二十一个交易日没停牌的股票」——「21 个交易日」的口径(过去一个月未停牌?)正文未再展开。
- running_max=10、并发上限、Python 2 等均为 2016–17 平台语境,现行平台规则可能已变。
- 「BP 因子 10 年后失效」是 2006–2016 样本内的观察;因子失效后换市场/换定义是否复活不在本文范围。
- benchmark_id 需要先手工把基准跑成一个回测拿到 id;本文没讲基准回测怎么搭(等权全指的代码在附图/研究模块)。
相关
- 多回测与参数网格分析(分位回测、超额评估) — 多回测与参数网格的系统概念页(本篇核心对象)
- 回测评价三指标(收益 / 最大回撤 / 交易次数) — 从三指标到夏普/波动/超额评估的扩展
- 因子选股(排名 / 打分 / 阈值筛选) — BP 这类因子与分位/排名做法的家族页
- QuantStart 算法交易回测方法论(Part I–II) — 回测偏差与过拟合方法论(W9,本篇参数网格的对照)
- 聚宽量化课堂(低频量化策略 43 篇) — 量化课堂 43 篇总览
更新 2026-09-06