ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

动量策略失效?信息离散度指标与Python行为金融量化复现

动量策略失效?信息离散度指标与Python行为金融量化复现 简介一份聚焦金融工程信息离散度与动量效应的量化策略研究资料基于“温水煮青蛙”理论展开适合具备金融工程和量化投资基础的研究人员、基金经理及量化分析师用于理解有限关注度下的市场异象并优化动量策略。PDF首先构建信息离散度指标将股票划分为信息连续型与信息离散型实证发现连续型股票动量收益显著5.94%而离散型仅为-2.07%并通过分析师反应滞后等证据解释机制。文档附带完整Python代码及逐步注释覆盖数据准备、离散度计算、动量因子分组、回归分析等环节便于读者对照复现、调整参数结合本地行情数据开展进一步验证。资源包仅含1个PDF文件大小787KB轻便易读目前已有115人学习下载。整体内容兼顾理论与代码实操适合作为量化策略研究、课程设计或论文复现的参考资料。1. 温水煮青蛙与动量效应这份代码在研究什么如果把一只青蛙扔进沸水它会立刻跳出来但放进冷水里慢慢加热它会在不知不觉中被煮死。Da et al. (2014) 把这个温水煮青蛙效应搬进量化投资当两只股票累计涨幅差不多时每天小幅持续上涨的股票信息连续型往往被市场冷落而偶发大涨的股票信息离散型却能吸引大量关注。这份资源正是围绕该现象做的完整复现核心结论是信息连续型股票中的动量策略月收益能到 5.94%离散型里只有 -2.07%。对做量化投资的人而言动量策略不能无差别铺到全市场必须先按信息离散度切一刀。下面会从 ID 指标构造开始带你把 Python 代码跑通再指出真实数据迁移时的几个关键坑。2. 信息离散度指标从理论公式到ID计算的三个边界坑2.1 有限关注度为什么会让连续信息被低估行为金融里有个基本假设投资者注意力是稀缺资源。涨停板、大阳线、新闻头条这些离散信号容易抢占注意力而那些每天 0.1%、0.2% 的小幅上涨很难在信息流里留下痕迹。论文把这个现象归因于有限关注度limited attention并且用三个独立证据支撑第一媒体覆盖率与信息离散度正相关离散型股票天生更容易上新闻第二这个效应长期不反转如果动量收益是风险补偿长期应当出现反转但实证里没有第三分析师对连续型股票的反应更滞后预测误差更大。这三个证据正好对应代码里三个模块news_count的生成逻辑、WML 分组收益对比、analyst_analysis中的预测误差模拟。换句话说这套复现代码不是把论文结论抄一遍而是把论证链条的每个环节都变成可运行的函数。对做量化投资的人来说理论部分最大的价值是给了一个可操作的筛选维度动量策略应该优先在信息连续型股票上执行也就是细水长流型上涨的股票而不是脉冲式上涨的股票。一个直觉例子能帮你记住结论两只股票同一年都涨了 20%一只每天涨 0.1%、连续涨了 200 天另一只靠 3 根 6% 的大阳线拉起来然后横盘。后者每次大涨都可能上公告和新闻市场很快定价前者几乎没人注意等动量延续时后者的预期已经被打满前者还在半路上。这就是 5.94% 和 -2.07% 差距的来源。2.2 ID指标的计算口径与公式拆解论文的核心代理变量叫信息离散度Information Discreteness简称 ID公式很简洁ID sign(PRET) × (%neg - %pos)其中 PRET 是过去 11 个月剔除最近 1 个月的累计收益%pos 和 %neg 是这段时间里正收益天数、负收益天数的占比。ID 值越小代表信息越连续。注意这里容易绕晕如果 PRET 为正涨的天数越多%pos 越大%neg - %pos就越负ID 越小说明这只股票的上涨是温水煮青蛙式的细碎上涨。参数含义计算口径PRET过去 11 个月累计收益剔除最近 1 个月用复利累乘np.prod(1 rets) - 1%pos / %neg正/负收益天数占比按月窗口内日收益率是否大于 0 统计ID信息离散度sign(PRET) × (%neg - %pos)区间约 [-1, 1]这个公式有三个边界坑。第一个是窗口剔除PRET 必须剔除最近一个月因为短期反转效应会污染动量因子把最近 1 个月也算进去会让 ID 和 PRET 高度相关后面回归交互项符号直接翻转。第二个是方向修正sign(PRET)不能省下跌过程中的信息离散度含义和上涨过程不对称漏掉这个符号项离散型股票的分组结果会变得不显著。第三个是阈值设定0.2 只是模拟数据里的分界真实市场上 ID 的截面分布完全不同需要用分位数动态切分这个在避坑章节会单独展开。2.3 模拟数据里的连续型与离散型怎么生成复现代码的第一步是prepare_data_with_id()生成 500 只股票、30 年的月度面板。核心逻辑是让两类股票累计收益接近但信息到达方式完全不同前 30% 的股票代码里用股票名里的 1、2、3 判断按小幅波动生成日收益剩下 70% 的股票基础收益很平稳但有 5% 的交易日会被替换成大幅波动。if 1 in stock or 2 in stock or 3 in stock: # 连续型股票(30%) daily_rets np.random.normal(0.001 * pret_direction, 0.005, days) else: # 离散型股票(70%) base_rets np.random.normal(0.0001 * pret_direction, 0.002, days) big_jumps np.random.choice(days, sizeint(days * 0.05), replaceFalse) daily_rets base_rets daily_rets[big_jumps] np.random.normal(0.02 * pret_direction, 0.01, len(big_jumps))days取 250 模拟一年交易日pret_direction随机取 -1 或 1 决定整段历史的方向这样上涨和下跌的样本都有。连续型股票的日收益均值 0.001、波动 0.005对应小碎步式上涨离散型股票基础均值 0.0001但 5% 的交易日会被 0.02 均值的大波动替代对应偶尔猛涨型。np.random.choice(days, sizeint(days*0.05), replaceFalse)是随机挑出约 12 个交易日放大幅波动replaceFalse保证同一天不会被重复选中。3. 复现动量策略全流程从月度数据到WML组合收益3.1 数据准备500只股票30年月度面板动量策略的持有周期是月频ID 指标本身也逐月计算所以数据组织按month做分组键。生成完日收益率后代码逐月逐股票生成一条记录字段包括 PRET、ID、换手率、新闻数量、波动率和信息类型。这一段代码与多数只给函数不给讲解的示例不同每条记录都对应论文里的一个变量后面回归和分组都从这里取数。data.append([ month, stock, pret, id_score, turnover, news_count, volatility, 连续型 if id_score 0.2 else 离散型 ]) cols [month, stock, PRET, ID, turnover, news_count, volatility, info_type] df pd.DataFrame(data, columnscols)注意这里id_score 0.2是模拟数据的分界标准真实数据一定不能直接照搬。新闻数量的生成逻辑是np.random.poisson(3)ID 大时对np.random.poisson(1)ID 小时这就在数据层面复现了论文媒体覆盖率与信息离散度正相关的事实。换手率和波动率也是从均匀分布和日收益标准差里算出来的它们是回归模型的控制变量。3.2 动量因子PRET与信息离散度ID的计算PRET 在生成日收益率时顺手完成用的是复利累乘而不是简单加总这是金融里算累计收益的标准做法因为逐日复利反映的是真实持有收益。计算窗口只用前int(days * 11 / 12)个交易日正好对应论文过去 11 个月剔除最近 1 个月的口径。pret np.prod(1 daily_rets[:int(days * 11 / 12)]) - 1 pos_days sum(daily_rets[:int(days * 11 / 12)] 0) neg_days int(days * 11 / 12) - pos_days pos_ratio pos_days / int(days * 11 / 12) neg_ratio neg_days / int(days * 11 / 12) id_score np.sign(pret) * (neg_ratio - pos_ratio)这段代码的输出范围是 [-1, 1]。直觉上一只持续缓慢上涨的股票 PRET 0、pos_ratio 接近 1ID 为负落在连续型区间一只靠几次大涨拉起来的股票 pos_ratio 可能只有 0.5 左右ID 为正。.sum(condition 0)这种写法比len(positive)快但在大数据量下建议改用 numpy 的向量化操作后面避坑章节会提到。3.3 分组检验连续型与离散型股票中的WML对比momentum_analysis是核心分析函数按月份对 PRET 做五分位切分取顶层 Win5 和底层 Loss5 构建 WML赢家减输家组合。这里必须用pd.qcut按分位数切而不是固定阈值因为不同月份的 PRET 截面分布差异很大牛市里全市场 PRET 都高固定阈值会把大部分股票都归为赢家。data[momentum_group] data.groupby(month)[PRET].transform( lambda x: pd.qcut(x, 5, labels[Loss5, Loss4, Neutral, Win4, Win5])) data[next_month_ret] data.groupby(stock)[PRET].shift(-1)shift(-1)是关键步骤它按股票自身的时间序列把收益往前平移一格得到当月建仓、下月实现的组合收益。做这一步时必须按groupby(stock)分组如果误用groupby(month)等于把不同股票的收益互相搬家WML 会回归到零附近。分组完成后groupby([info_type, momentum_group])[next_month_ret].mean().unstack()得到二维收益表再算Win5 - Loss5就是每个信息类型下的动量收益。3.4 可视化四张图看ID与关注度、波动率的关系plot_results函数画四张图WML 柱状对比、ID 与 PRET 散点图、媒体覆盖率箱线图、ID 与波动率回归图。这四张图不是装饰而是论文结论的可视化证据链。第二张散点图尤其重要它展示连续型和离散型股票在 (PRET, ID) 平面上的分布——如果 ID 构造正确两类股票应该清晰分开而不是混在一起。plt.subplot(2, 2, 2) sns.scatterplot(xPRET, yID, hueinfo_type, datadf.sample(1000))sample(1000)是控制散点数量500 只股票 30 年全画出来有十几万个点渲染卡顿还看不清重叠区域。第四张图用sns.regplot(xID, yvolatility)画 ID 和波动率的关系如果 ID 构造合理离散型股票应该对应更高的波动率。跑这些图之前先确认 seaborn 和 matplotlib 版本兼容Python 3.10 以上建议 seaborn 0.12 配 matplotlib 3.7否则图例和样式容易报错。4. 回归分析验证有限关注度交互项、媒体覆盖率与分析师滞后4.1 三个回归模型的设定与变量说明分组检验只能证明连续型股票里 WML 更高但要说明机制是有限关注度而非风险补偿需要回归模型。代码依次跑三个 OLS模型 1 是基础模型放 PRET、ID、PRET×ID 交互项和波动率模型 2 加入新闻数量与换手率模型 3 把 PRET 拆成正向和负向两个变量检验动量效应的不对称性。这三个模型合起来就是一份完整的 python 量化交易策略代码里最常见的因子验证框架。X1 data[[PRET, ID, PRETxID, volatility]] X1 sm.add_constant(X1) model1 sm.OLS(y, X1).fit()被解释变量统一是next_month_ret即下月收益。sm.add_constant给设计矩阵加截距列必须加否则回归会把截距强制设为 0系数估计会偏差很大。模型 2 中news_count和turnover作为控制变量用来排除离散型股票只是因为新闻多、换手高才动量弱的替代解释如果加了控制变量后交互项依然显著说明有限关注度有独立解释力。4.2 关键系数解读PRET×ID交互项为什么显著为负模拟数据跑出来PRET×ID 交互项系数通常显著为负t 值在 -3 到 -10 之间。这个交互项的含义是动量收益的预测能力如何随 ID 变化系数为负说明 ID 越高、信息越离散、动量效应越弱和连续型股票 WML 高、离散型股票 WML 低的分组结果互相印证。这里有个验证机制是否正确的办法做一次反事实实验把离散型股票的生成参数也改成小幅波动然后重跑回归。交互项会变得不显著说明当前结果是数据结构造成的不是回归硬凑出来的。反事实实验是量化研究里最被低估的验证工具很多论文结论换个数据生成过程就站不住这套代码的好处是参数全暴露在外面改两行就能做。加控制变量后交互项保持显著为负媒体覆盖率系数通常为负和论文媒体覆盖率会减弱动量效应一致——新闻多的股票信息早已被定价动量自然弱。4.3 分析师预测误差信息连续型股票为什么更难预测analyst_analysis函数模拟分析师预测误差连续型股票误差均值设定为 0.02离散型为 0.01。这是对有限关注度的第三个佐证分析师也是人注意力同样有限对连续到达的信息反应更慢预测误差自然更大。data[analyst_error] data.apply( lambda x: np.random.normal(0.02, 0.01) if x[info_type] 连续型 else np.random.normal(0.01, 0.008), axis1)这段模拟的局限在于预测误差完全由信息类型决定没有加入分析师覆盖人数、调研频次、盈利预测修正次数等真实变量。真实研究通常会引入 IBES 或朝阳永续的分析师一致预期数据构造实际 EPS 与一致预期之差来替代这里的模拟值。对做实盘的投研人员来说这个结论的实际启示是对连续型股票光靠卖方研报预期修正做 alpha 是不够的因为分析师的反应本身就有滞后你要么比分析师更早识别连续信息要么用价格动量直接交易而不是等到预期修正落地。5. 避坑指南复现这套代码最常见的五个问题这一章把我在复现和迁移这套代码时踩过的坑整理出来每个按现象、原因、解决的顺序写。第一条就是血泪教训后面四条是换真实数据时必踩的。坑1ID 计算结果全是 0 或 NaN现象跑完 ID 计算后info_type分组时报错检查发现 ID 大量为空。 原因月窗口里存在停牌填充的 0 收益或 NaNpos_ratio和neg_ratio的分母被无效天数污染另一个常见原因是上市不满 11 个月的次新股PRET 窗口不足整行被丢弃。 解决先df df.dropna(subset[return])再用groupby(stock)[return].transform(count)过滤月度有效交易日不足 20 天的股票。我在迁移到 A 股数据时把int(days * 11 / 12)改成按实际存在天数动态计算避免窗口取到无数据区间。坑2WML 组合月收益高到离谱超过 10%现象模拟数据跑出 WML 月收益 15% 以上明显失真。 原因模拟参数里连续型和离散型的日收益均值差了一个量级0.001 vs 0.02分组后两组股票的收益天然有巨大差异WML 混入了类型差而不是动量差。 解决论文要验证的是累计收益相近时信息离散度的影响所以模拟时应让两类股票 PRET 的截面分布尽量接近再比较 WML。把 0.001/0.02 改成 0.005/0.008 这类更接近真实市场的参数WML 会落到月收益 2%~5% 的合理区间。坑3ID 阈值 0.2 套到真实数据上完全不适用现象把id_score 0.2套到沪深 300 成分股上结果 90% 的股票都变成连续型。 原因0.2 是模拟数据里调出来的分界真实日收益率分布和正态假设相差很远ID 整体分布偏负。 解决改用月度截面分位数动态切分比如data.groupby(month)[ID].transform(lambda x: pd.qcut(x, [0, 0.3, 1], labels[连续型, 离散型]))每个月按当月的 ID 分布切避免市场风格切换导致分组失衡。分位数比例建议参考论文 30%/70%。坑4groupby(month)和groupby(stock)搞混现象赢家组合和输家组合的下月收益几乎一样WML 全在零附近。 原因next_month_ret必须按股票时间序列做shift(-1)在groupby(month)里 shift 等于把同月不同股票的收益互相搬家。 解决严格按两层逻辑写——截面分组用groupby(month)时间平移用groupby(stock)。合并前打印几行人工核对同一记录里股票代码不变月份推到下一格收益对应原记录下月的值。这个核对动作只用两分钟但能过滤掉一半以上的错位问题。坑5回归交互项系数为正和论文结论相反现象PRET×ID 交互项系数变正连续型股票动量效应反而不显著。 原因PRET 没有剔除最近一个月把短期反转信息也算进动量因子导致 PRET 和 ID 高度相关多重共线性把交互项符号翻过来。 解决严格按论文口径截取daily_rets[:int(days * 11 / 12)]确保 PRET 窗口是 11 个月而不是 12 个月。符号翻转时先算 PRET 和 ID 的相关系数超过 0.6 就优先怀疑窗口口径不要急着改回归模型。6. 把模拟代码迁移到真实行情数据shuffle检验与参数调整技巧拿到这套代码别直接拿真实数据库跑。模拟数据和真实数据之间隔着四道适配第一数据频率不同真实数据一般从行情接口拉日线需要resample(M)或按月末取值聚合成月频第二ST、停牌、退市股票必须提前剔除停牌期间收益率为 0 会污染 ID 的分母第三PRET 和 ID 的窗口计算要在日线表里按股票分组滚动完成再在月末聚合而不是在月频表上硬算第四分组阈值全部换成动态分位数。这四道适配做完代码才算真正落地。我一般会加一道验证工序用同一份策略代码分别在模拟数据、真实数据、随机 shuffle 后的数据上各跑一遍三者的 WML 收益应当呈现模拟 真实 随机的递减顺序。如果随机数据还能跑出显著 WML说明代码里存在前视偏差或错位问题这时候回头查shift(-1)的方向和 PRET 窗口口径。shuffle 验证的原理很简单——把当月信息和下月收益的对应关系打乱动量效应应当消失如果没消失一定是未来信息漏进了因子计算。shuffled df.copy() shuffled[next_month_ret] shuffled.groupby(stock)[next_month_ret].transform( lambda x: x.sample(frac1).values) wml_shuffled shuffled.groupby(info_type).apply( lambda g: g.groupby(month)[next_month_ret].mean().mean()) print(wml_shuffled) # 预期连续型和离散型的 WML 都接近 0这个验证的成本极低但能暴露七八成代码层面的前视偏差。x.sample(frac1)是打乱当前股票下月收益序列的顺序groupby(stock)保证只在单只股票内部打乱不会跨股票污染。跑完后如果 WML 还在 2% 以上基本可以断定因子计算里混入了未来数据常见位置是rolling(63).sum()的窗口对不齐或者复权价格用错。从那以后我做任何动量类因子复现都会强制走一遍这个 shuffle 检验从数据组织、因子计算到收益对齐逐层排查。这套流程为我省下的调试时间远超过写代码本身希望帮到你。本文还有配套的精品资源点击获取
返回列表