Jikipedia
第 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_namesparam_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_drawdownexcess_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;本文没讲基准回测怎么搭(等权全指的代码在附图/研究模块)。

相关

更新 2026-09-06

检索知识库

按标题、类型或正文检索