halo 的技术博客

返回

如果说分红是「把利润分给股东」,那回购就是管理层用真金白银在二级市场买自己家的股票——这比分红信息含量更高。分红可以是例行公事,但回购需要董事会真金白银的授权、真的风险承担。一个 CEO 愿意在当下价格回购,隐含的判断是「我现在被低估了」

这篇文章把「回购强度」做成一个可回测的横截面因子:

年化回购收益率(回购金额 / 市值,标准化) 作为信号,月度再平衡,多高回购 / 空低回购甚至净增发的公司,验证它长期为正、十分组严格单调,且因子对市场 beta 接近零——说明它不是「押市场」,而是独立的低 beta alpha。

数据声明:全文为自洽合成(管理层在认为低估时回购强度更高,且回购强度与未来收益正相关,仅用于演示方法),目的是把回购因子的构建与检验机制跑通、可复现。所有量级均为合成校准,真实市场里会被信号滞后、做空成本、幸存者偏差大幅压缩——重点看方法

一、回购强度怎么度量:用「收益率」而非「绝对额」#

最朴素的错误是用「回购金额绝对值」排序——苹果一年回购几百亿,小公司回购几千万,绝对值毫无可比性。正确口径是回购收益率

回购强度 = 当期回购金额 / 当期平均市值
plaintext

这是「给股东的资本返还占身家的比例」,跨规模可比。我们进一步做截面 z 标准化,让它在不同月份有统一量纲(和 GSI / REV 同口径):

buyback_z = (buyback - buyback.mean(axis=0, keepdims=True)) / \
            (buyback.std(axis=0, keepdims=True) + 1e-6)
python

截面分布右偏,左侧还有负值——负值就是净增发(一边回购一边大额定增/股权激励摊薄,算下来是负返还):

回购强度截面:右偏,左侧为负值(净增发公司)

二、因子的逻辑:为什么回购能预测收益#

三个微观机制:

  1. 低估信号(undervaluation signal)。管理层比市场更懂公司价值。当他们认为股价低于内在价值,用闲钱回购是理性选择——所以回购强度与未来收益正相关,本质是「内部人信息」的代理变量。
  2. 资本返还溢价(capital return)。回购直接减少流通股本、抬升每股价值,对长期股东是实打实的回报,尤其在税优口径下优于分红。
  3. 供需改善。回购是市场上持续的净买盘,在流动性紧张时托底股价。

合成数据里我们把未来收益设成「随标准化回购强度正相关 + 市场因子 + 噪声」:

future = (0.004 + 0.004 * buyback_z + 0.35 * mkt
          + rng.normal(0, 0.03, size=(N, M))
          + rng.normal(0, 0.003, size=N)[:, None])
python

三、月度再平衡的长短因子#

每月末按回购强度排序,多最高 60 只 / 空最低 60 只(空组里包含净增发公司),累计:

def ls_curve(signal, n=60):
    ret = np.zeros(M)
    for t in range(M):
        order = np.argsort(signal[:, t])
        ret[t] = future[order[-n:], t].mean() - future[order[:n], t].mean()
    return np.cumprod(1 + ret)

bb_cum = ls_curve(BUY)
python

回购强度长短因子:高回购多/低回购空,长期为正

净值长期向上。注意它比很多动量因子「更钝」——没有大起大落,这正是低 beta alpha 的特征:它不赌方向,只赚结构性的「低估修复 + 资本返还」。

四、十分组单调性#

每月截面按回购强度切 10 组,算未来 1 月平均收益:

dec_avg = np.zeros(10)
for t in range(M):
    order = np.argsort(BUY[:, t])
    for d in range(10):
        idx = order[d * 60:(d + 1) * 60]
        dec_avg[d] += future[:, t][idx].mean()
dec_avg /= M
python

回购强度十分组:单调递增,D10-D1 显著为正

D1(净增发组)到 D10(高强度回购组)严格单调递增。最左组(净增发)甚至为负——这很符合直觉:一边回购一边大额摊薄的公司,长期给股东是负贡献。

五、关键检验:它是 alpha 还是市场押注?#

很多「因子」其实只是市场 beta 的马甲——市场涨它就涨。我们用因子月度收益对市场月度收益做回归,看 beta 和截距:

factor_ret = np.array([future[np.argsort(BUY[:, t])[-60:], t].mean()
                       - future[np.argsort(BUY[:, t])[:60], t].mean()
                       for t in range(M)])
b1, b0 = np.polyfit(mkt, factor_ret, 1)
python

因子 vs 市场:低 beta、正截距 = 独立 alpha 而非市场押注

结果:

  • 拟合 beta ≈ 0.01,几乎为零。因子收益和「下个月市场涨跌」几乎不相关。
  • 截距显著为正(合成下约 +1.28%/月),这正是 alpha 的定义:不靠市场、独立赚到的那部分。

这一点在操作上极有价值:回购因子可以和市场组合叠加而不增加方向性风险——它提供的是「和市场低相关的收益来源」,是组合分散化的好拼图。

六、真实的四类陷阱(必须说清楚)#

陷阱一:信号滞后(reporting lag)。 回购金额只在季报/年报披露,且常滞后 1~2 个月才进入数据库。若用「已披露回购」在披露日当天交易,实际信息是旧的——正确做法是信号在披露月确定、下个调仓点才用,别假装实时可得。

它和「股息率因子」不是一回事#

很多人把回购和分红混为一谈,但两者信息含量不同:

  • 股息率 高度连续、可预测,市场早已 price in,低信息增量。
  • 回购强度(尤其 意外 的回购计划上调)是离散事件、管理层有主动裁量权,信息含量更高。

实证里「回购强度」因子常比「股息率」因子有更干净的溢价,正是因为前者更贴近「内部人当下的低估判断」,后者更像「历史惯例」。

不同利率环境下的表现差异#

回购因子在低利率、信用宽松时最强(公司现金流充裕、融资成本低,敢大笔回购);在加息、信用收缩时走弱,且杠杆回购型公司可能反转为负贡献。所以它不是「全天候」因子,更适合在宽松周期超配、紧缩周期减配——这和《量化紧缩与央行资产负债表》那篇的流动性框架是一致的。

陷阱二:做空约束。 因子要「空低回购 / 空净增发」。但低回购组常是成长股、小盘股,做空成本高甚至借不到券。空腿在实盘里往往建不起来,于是「多高回购」半截策略才是最常见的落地形态。

陷阱三:幸存者偏差。 持续高强度回购的公司通常是现金流充裕的赢家,它们活下来的概率本就更高;而净增发、最终退市的样本从指数里消失,让历史回测的空头收益被高估。用「point-in-time」数据库(含已退市股票)才能缓解。

陷阱四:回购贷款 / 杠杆回购。近年流行「发债回购」(如美股 2010s 的低息债回购潮)。这种回购不完全是「低估信号」,也可能是财务杠杆操作——一旦利率上行,这类公司的回购 Alpha 会反向变成风险。因子里应剔除「靠大幅加杠杆支撑的回购」。

七、落地路径(真实数据怎么做)#

  1. 取回购数据:A 股用交易所「回购股份」公告 + 财报「库存股/回购支出」;美股用 Compustat PRSTKC(回购支出)/ 流通股变动倒推。
  2. 算回购收益率回购支出 / 平均市值,滚动 TTM 求和,避免单季噪声。
  3. 标准化 + 分层:截面 z 分数,月末排序,多高组空低组(或只做多高组)。
  4. 剔除杠杆回购:用资产负债率或「回购/经营现金流」过滤,避免把加杠杆当低估。
  5. 行业中性化:回购强度在高现金流行业(如白酒、互联网)天然偏高,建议对行业哑变量回归取残差,避免「买行业」马甲。

八、小结#

回购强度因子的核心一句话:回购是管理层用真金白银给自家股票投票,是少数「内部人真有信息」的资本动作信号。用年化回购收益率标准化做成月度再平衡横截面因子,验证十分组严格单调、长短因子长期为正,且因子对市场 beta 近零、截距显著为正——它是独立的低 beta alpha,而非市场押注。四道坎(信号滞后、做空约束、幸存者偏差、杠杆回购)每一道都在真实落地时砍一刀,但正因如此它才没有被套利干净。对想要「和市场低相关收益来源」的组合来说,回购强度是一块值得长期放在工具箱里的拼图。

代码与配图均由自洽合成数据真实计算生成,可复现;真实量级请以你的数据重跑为准。

回购强度因子:把『管理层用真金白银投票』做成可交易 alpha
https://blog.halo26812.eu.org/blog/buyback-intensity-factor
Author halo
Published at 2026年7月20日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨