用Python复现金工研报:从因子构建到回测的完整实战指南 简介这是一份面向量化投资初学者与金融工程学习者的Python实战复现资源聚焦国内主流券商金工研报的核心模型与因子逻辑解决理论研报难以落地、代码复现门槛高的痛点。资源适用于计算机、金融工程、统计学等相关专业学生及从业者可用于课程设计、毕业设计、量化入门训练或策略原型验证。压缩包共316个文件173.39MB涵盖69个可执行Python脚本、44个Jupyter Notebook复现文档含完整推导与可视化、62份原始研报PDF、18个实证数据CSV如factors_frame、sw_level1、gold_stock_frame等以及配套说明文档与中间结果图表。已有209人下载学习所有代码均经实测运行通过支持远程答疑与基础教学结构上按券商分目录组织每份研报对应独立可运行环境便于模块化学习与策略迁移拓展。 做量化研究的朋友尤其是刚入行或者想从主观转向系统化交易的人一定遇到过这样的场景读了一篇券商金工研报觉得里面的思路很精彩因子逻辑也说得通但真要自己动手把里面的策略跑出来却不知道从哪下手。研报里的公式往往写得比较简略参数就那么几个字母数据口径也不明说回测区间的选择更是藏着不少门道。我过去两年一直在做这件事——用 Python 把国内各大券商的金工研报复现出来并且把每一篇复现的代码、数据说明、参数处理和回测结果都整理成了文档。这篇博客就是把这个过程的完整方法论、实操细节和踩坑经验一次性讲清楚适合有一定 Python 基础、想系统学习量化策略落地的读者。先说结论研报复现这件事最大的价值不是让你拿到一个能直接赚钱的策略而是训练一种“把文字描述变成可执行代码”的能力。券商研报里的策略尤其是多因子、择时、行业轮动这类本质上是一个完整的决策系统从数据预处理到信号生成再到组合构建和回测评估每一步都有大量的隐藏细节。研报复现就是把作者没有写出来的那些“潜规则”重新挖掘出来这会逼着你去读原始文献、去查数据文档、去对比不同实现方式的差异这个过程本身就是最好的量化学习路径。1. 金工研报复现这件事到底在复现什么1.1 研报复现的核心价值如果你只是想要一个交易策略直接去开源社区找现成的量化框架和策略库就够了根本不用去啃研报。研报复现真正的价值在于拆解“从 idea 到可验证结果”的完整链条。金工研报通常是研究员基于学术文献、市场观察和数据分析得出的结论但研报的篇幅有限很多细节只能一笔带过。比如研报里写“我们构建了一个量价因子具体计算方式为过去 20 日收益率的偏度”这看起来很清晰但真正的难点在于收益率用对数收益率还是简单收益率偏度用样本偏度还是调整偏度20 日窗口要不要包含当日因子值要不要做行业市值中性化中性化用回归残差还是分组相减……这些细节每一个都会对最终结果产生显著影响。我的复现思路是先不看研报的实现细节只看它的结论和图表然后用我认为最合理的标准做法去实现再把结果和研报对照。如果差距很大就去研究差距来源是数据差异、参数差异还是逻辑理解偏差。这个过程比单纯照着研报敲代码有价值得多因为它逼着你建立自己的判断框架。1.2 适合谁来复现金工研报如果你刚学 Python 不久还不太熟悉 pandas 和 numpy我建议先不要直接上金工研报复现因为你会被各种数据处理细节困住很难体会到策略本身的逻辑。最好是有至少三个月以上的 pandas 使用经验处理过日线行情数据熟悉 merge、groupby、rolling 这些操作再开始尝试复现。如果你已经在做量化交易有自己的回测框架那么研报复现就更直接了——用一套统一的框架把不同研报的策略跑在同一批数据、同一套评估标准下对比它们的有效性。这有点像做菜时的“配方对比实验”同样的食材和锅具才能看出不同配方的真实差异。我见过很多人在不同数据源、不同时间段里测试策略结果好坏根本没有可比性这就是没做好控制变量。1.3 复现前必须想清楚的三件事第一件事是数据。研报里用的数据大多是万得或者朝阳永续这类商用终端导出的你手上的数据不一定和它们完全一致尤其是复权价格、ST 处理、停牌规则这些细节。我一开始用的是某免费数据源结果发现它的复权因子和万得差异挺大后来干脆统一用后复权价格并且在文档里明确标注。第二件事是复现的目标区间。很多研报会在某个特定的市场阶段写出来比如 2015 年牛市之后大量“防御型策略”涌现2020 年之后“小市值因子”又特别火。如果直接拿现在的数据去复现效果可能完全不同这不代表研报逻辑错了而是市场环境变了。所以我一般会把研报原区间和当前区间都跑一遍对比差异。第三件事是评估口径。研报说的“超额收益”是用什么基准中证 500、沪深 300 还是中证全指“年化波动率”是日波动率乘以多少这些口径差异会让同一个策略看起来是好是坏。我的做法是统一用一套绩效指标来计算包括年化收益率、年化波动率、夏普比率、最大回撤、卡玛比率和相对基准的超额收益所有复现报告都用同一套口径。2. 复现一套金工研报的技术路线与架构设计2.1 研报复现依赖的四个核心模块一套完整的研报复现代码无论策略是简单还是复杂都应该分成四个模块数据层、因子层、策略层、回测层。数据层负责从数据源拉取原始行情和财务数据做清洗、对齐和缓存因子层负责把原始数据转换成因子值包括中性化、标准化、去极值这些预处理策略层负责根据因子值生成持仓信号比如选哪些股票、权重怎么配回测层负责模拟交易过程计算绩效指标。一开始我也犯过把所有代码写在一个 notebooks 里的错误后来发现同一个因子在不同研报里会被反复用到把它们写成独立模块才是正确的做法。我最终采用的目录结构是这样的project/ ├── data/ # 数据缓存目录 │ ├── raw/ # 原始数据 │ └── processed/ # 清洗后的数据 ├── factors/ # 因子计算模块 │ ├── momentum.py # 动量类因子 │ ├── quality.py # 质量类因子 │ ├── sentiment.py # 情绪类因子 │ └── utils.py # 预处理工具 ├── strategies/ # 策略模块 │ ├── stock_selection.py │ ├── timing.py │ └── portfolio.py ├── backtest/ # 回测模块 │ ├── engine.py │ └── metrics.py ├── reports/ # 复现文档输出目录 └── main.py # 总入口这个结构的核心思想是每一层只依赖更底层模块提供的能力互不侵入。比如你在因子层改了一个中性化方法策略层和回测层完全不用动只要接口不变。2.2 数据基础设施怎么搭数据是研报复现的地基我强烈建议不要每次跑策略都去拉全量数据那样太慢了而且容易遇到接口限流。我的做法是写一个数据管理模块第一次跑的时候下载全量日线数据到本地用 parquet 格式存好之后每次读本地缓存就行。这里有一个关键细节股票池的确定。国内 A 股数量是动态变化的有新股上市、退市、ST很多研报里的股票池做法是“当前已上市公司”这会导致严重的幸存者偏差。正确做法是用“历史时点截面积”也就是说在回测的每一天只使用当时已经上市并且没有被 ST、没有停牌的股票而不是用现在的股票列表去复现历史。这个细节我在初学阶段没注意到导致很多因子结果虚高后来用历史快照重新跑才发现问题在哪。另一个细节是复权问题。复权分为前复权、后复权和不复权研报复现中我建议用后复权价格。原因很简单后复权价格从上市首日开始累计收益不存在“未来数据引入”的问题而且不同时点看到的价格都是基于同一基准计算收益率时天然连续。不复权价格在分红除权的时候会出现价格跳空直接算收益率会失真。2.3 数据清洗与对齐的实操细节数据清洗是复现过程最枯燥但最关键的环节。第一步是剔除无效股票包括 ST、退市整理期、上市不满 60 日的次新股这些股票的异常表现不是策略逻辑本身产生的却会严重干扰因子评估。第二步是处理停牌停牌期间的收益率应该是 0 还是缺失需要根据研报策略的调仓频率来决定。我的默认做法是在调仓日如果股票停牌则保留原来的仓位不强制交易。第三步是对齐交易日历。A 股的交易日历和自然日不一样节假日调休导致每个月的交易日数量不固定而因子计算依赖“滚动 N 日”这种窗口如果直接用自然日相减会出错。我建议使用交易日历作为索引所有因子计算和回测都基于交易日序列。3. 三份典型研报的复现实战拆解3.1 反转因子的截面选股策略第一份我复现比较典型的是某券商的多因子选股研报其中一个核心因子是短期反转。研报里的定义很简单过去 20 个交易日收益率最低的一组股票未来一段时间的平均收益高于过去收益率最高的那组。这个因子在 A 股市场长期有效但实现过程有几个坑。第一步先算 20 日收益率import pandas as pd import numpy as np # data 是 MultiIndex DataFrame索引为 (trade_date, stock_code) # 列为 open, high, low, close, volume, amount data[ret_20] data.groupby(stock_code)[close].pct_change(20)这里有个值得注意的问题pct_change(20) 是简单收益率也就是第 t 日收盘价相对于第 t-20 日收盘价的百分比变化。但研报里用的是“收益率”没有说清楚是简单收益率还是对数收益率。在我实测下来用简单收益率和对数收益率做截面排序结果差别不大因为排序本质上是比较相对大小。因子方向处理后做一个横截面的排序分组分为 10 组def assign_group(data, factor, groups10): data data.copy() # 每个交易日按因子值排序分位 data[group] data.groupby(trade_date)[factor].transform( lambda x: pd.qcut(x, groups, labelsFalse, duplicatesdrop) ) return data注意这里用了 transform 而不是 apply因为我们需要为每一行分配组号同时保持原始数据的行数。duplicatesdrop是处理因子值重复的情况比如停牌股票因子值全为空或者完全相同的极端情况不处理的话 qcut 会直接抛异常。分组之后计算每组下一期收益# 计算未来 5 日收益 data[fwd_ret_5] data.groupby(stock_code)[close].transform( lambda x: x.shift(-5) / x - 1 )然后按 group 分组求均值就能画出和研报类似的分组收益图。复现结果显示最低反转组组 0的未来 5 日平均收益显著高于最高反转组组 9多空组合的月度胜率在 60% 左右和研报结论一致。3.2 双均线系统的择时策略第二份是择时类研报核心是双均线系统。研报的表述是当短期均线上穿长期均线时看多下穿时看空。这听起来简单但实现起来有两个问题均线是用收盘价还是复权价上穿和下穿的具体判定条件是什么我的实现用了后复权收盘价均线周期选了常见的 5 日和 20 日。上穿判定用“前一天短期均线低于长期均线当天短期均线高于长期均线”def golden_cross(price, short_win5, long_win20): short_ma price.rolling(short_win).mean() long_ma price.rolling(long_win).mean() # 上穿前一天短均 长均当天短均 长均 cross_up (short_ma.shift(1) long_ma.shift(1)) (short_ma long_ma) # 下穿前一天短均 长均当天短均 长均 cross_down (short_ma.shift(1) long_ma.shift(1)) (short_ma long_ma) return cross_up, cross_down策略信号生成后需要决定仓位是满仓空仓的 0/1 模式还是可以根据均线偏离度调整仓位研报里用的是 0/1 模式我也按照这个来。但实际回测后发现双均线系统在震荡市里会被反复打脸产生大量无效交易所以我在复现文档里额外做了一个优化版本加上一个波动率过滤只有当前波动率低于某个阈值时才允许开仓。这个优化不是研报内容而是我自己的扩展测试用来评估“原始信号是否还有提升空间”。从复现结果来看双均线策略放在沪深 300 上过去十年年化收益大约 8%最大回撤接近 25%表现其实一般。这提示了一个很重要的道理研报里的策略不一定赚钱复现的价值在于验证逻辑而不是直接拿实盘。我后来还测过 10 日和 60 日均线的组合结果更差这也很正常均线策略本身在趋势市才有效A 股长期是震荡市纯均线策略很难跑出漂亮曲线。3.3 多因子打分组合的构建第三份是最复杂的一篇涉及多因子合成和打分选股。研报选取了三个维度的因子估值EP、成长营业收入增长率、质量ROE然后对所有股票在每个调仓日计算各因子分位数按一定权重合成综合得分最后选综合得分最高的 30 只股票等权持仓月度调仓。这个策略实现的核心难点在于因子预处理。原始因子值不服从正态分布而且不同股票的数值范围差异巨大直接加权平均的话数值大的因子会主导综合得分。研报里的做法我推测是分位数映射我实现的时候用的是先做截面排序然后映射到 0 到 1 之间的分位数def factor_to_percentile(factor_series): valid factor_series.dropna() percentiles valid.rank(pctTrue) return percentiles这一步骤把每个因子从“量纲差异巨大”的原始值转换成统一的分位数之后再按照等权加权合成。研报里没写权重我按等权处理然后在文档里记录了不同权重下的敏感性测试结果。调仓日期上研报写的“月度调仓”但没说具体是每个月哪一天。我按“每个月最后一个交易日调仓”来执行这也是国内公募基金比较常见的做法。调仓日收盘后计算因子和信号次日开盘价成交这样避免使用未来数据。回测结果显示这个组合策略年化超额收益约 12%信息比率 1.5 左右但在 2017 年和 2021 年这样的风格极端年份里超额收益回撤很大说明多因子组合依然承担着不小的风格风险。4. 回测引擎与绩效指标的正确姿态4.1 回测引擎里的撮合细节我复现了大量研报之后最大的体会是只要回测引擎存在微小偏差最终结论就不可信。真正专业的复现文档必须把每一个撮合细节说清楚。在我的回测框架里撮合逻辑如下调仓日收盘后产生目标持仓下一个交易日以开盘价成交。这样做的好处是避免“用当天收盘价买入然后当天收盘价计算收益”这种自带未来函数的漏洞。另外手续费和滑点设置也必须有明确数值佣金按万分之二点五印花税卖出时千分之一滑点按开盘价加减千分之一来模拟。成交数量处理上需要考虑一手100 股取整的问题。初始本金设定为 100 万目标权重乘以当日总市值再除以开盘价得到买入股数然后向下取整到 100 股的整数倍。如果资金不足优先保证权重最高的股票成交剩下的现金留在账户里。这个细节直接影响组合的实际持仓数量和超额收益尤其是在小市值策略里一手取整的偏差会非常明显。4.2 绩效指标的计算口径评估一个策略是否成立需要统一指标口径。我使用的核心指标包括指标公式说明年化收益率(期末净值 / 期初净值)^(252 / 交易天数) - 1按交易日折算年化波动率日收益率标准差 × sqrt(252)衡量波动幅度夏普比率(年化收益率 - 无风险利率) / 年化波动率默认无风险利率 2%最大回撤max(1 - 当日净值 / 区间内最高净值)衡量极端亏损卡玛比率年化收益率 / 最大回撤收益回撤比超额收益策略年化收益率 - 基准年化收益率基准默认沪深 300信息比率超额收益年化值 / 超额收益年化波动率衡量超额收益稳定性研报里的“超额收益”有时候用的是“相对基准的超额”有时候用的是“相对中证 500 的超额”复现时必须仔细甄别不然会对策略表现产生误判。我在文档的“评估口径说明”部分会把每一项指标如何计算写在文档头部方便后续任何人复现。5. 复现中最高频的坑与排查方法5.1 未来函数最容易犯的致命错误未来函数是回测里最隐蔽的陷阱它指的是在计算第 t 天的信号时使用了 t 天之后才产生的信息。最常见的一个例子是在截面因子里用了整个测试区间的全局均值和标准差做标准化这等于把未来信息泄漏进了历史信号。我检查未来函数的方法很简单写一个单测随机抽几个交易日手算信号值然后和代码输出对比。另外更系统的方法是做“信息时间移位测试”——把信号整体向前挪一天或者向后挪一天看策略收益是否出现断崖式变化。如果挪一天之后收益大幅变化说明策略很大程度依赖未来信息。5.2 幸存者偏差历史回测与实盘的天壤之别幸存者偏差是指用当前还活着的股票列表去跑历史回测把那些已经退市、被 ST 的股票全部排除了结果自然好看但无法真实反映历史。比如一个“买低价股”的策略在当前股票池里跑出来收益惊人但如果把历史上那些退市的低价股加进去结果可能完全不一样。解决办法是使用历史快照数据。国内有几个免费数据源支持按历史日期查询当时的成分股列表虽然接口慢一点但能根治幸存者偏差。我的数据层设计就包含了历史快照逻辑每期调仓时会从快照里读取当时的股票池而不是从当前全量股票里筛选。5.3 参数过拟合研报复现的“学术事故”很多研报为了展示好看的结果会在参数选择上做大量“实验”最终报出来的参数很可能已经过拟合。复现时我不建议一上来就按研报参数跑而是先跑一个参数网格看策略表现对参数的敏感度。如果稍微改一下参数收益就大幅波动说明策略本身不稳定这个“规律”大概率是数据挖掘的结果。我做了一个参数敏感性热力图均线策略在不同短周期和长周期下的夏普比率变化。如果热力图上出现“孤岛式”高收益区域比如只有 5 日和 23 日这个组合特别高周围都不行那基本可以判定这个参数组合是过拟合的产物不值得投入实盘。5.4 数据对齐和停牌处理的隐藏问题最后还有一个经常被忽略的细节数据对齐。不同数据源的交易日历可能不一致有的包含上午下午拆分的分钟数据有的只有日线。如果混用数据源交易日期对不上会导致 merge 时产生大量缺失值。我的做法是统一用交易所官方的交易日历做主键所有数据按这个日历对齐缺失值在数据清洗阶段统一处理。停牌处理的逻辑也值得单独说明。在因子计算阶段停牌股票的价格长期不变会导致收益率计算失真。我在因子计算前会先把停牌股票当日成交量为 0 的股票标记出来它们的因子值设为 NaN在截面排序时自动剔除。到了调仓阶段如果持仓股票停牌那就保持原有仓位等复牌后再按信号调仓。5.5 验证复现是否成功的最后一道关卡代码跑完结果也出来了怎么确认自己复现得对不对呢我总结了一套最低限度的验证流程第一检查各年年化收益是否和研报图表趋势一致不需要数值一致但方向不能差太多第二做“白噪声检验”把因子值随机打乱后重新回测如果收益依然很高说明原始结果可能来自数据泄漏而不是因子本身第三换一个时间段比如研报区间之外的样本外区间重新跑如果因子完全失效甚至反向说明它大概率只是特定市场环境下的产物。说实话我复现过的研报里真正能通过这三重检验的策略不超过三分之一。但这并不代表剩下三分之二的研报没有价值它们至少帮我排除了大量无效方向节省了未来实盘测试的时间成本。6. 写在最后的几个诚恳建议如果让我给正在做或者准备做研报复现的人提几条建议我会这么说第一不要迷信任何研报结论包括我写的这篇所有结论都必须拿到自己的数据体系里重新验证一遍才算数。第二一定要把复现代码和文档当作一个持续积累的资产今天复现一篇明天在这个基础上加一个新的因子慢慢地你就会拥有一套完全属于自己的量化研究基础设施。第三数据质量永远比策略选择更重要与其到处找“高级策略源码”不如先花一个月把自己手上的数据洗干净、对齐好。我自己的流程走到了现在最大的收获不是某个能赚钱的策略而是建立了一套从文字到代码、从研报到实证的完整方法论。每一篇研报复现文档不只包含代码和结果还记录了我在每个岔路口为什么做这样的选择踩过的坑是什么哪些参数试了但最终放弃。这些东西才是复现工作里真正值得沉淀的资产。本文还有配套的精品资源点击获取