供应链集中度因子:用客户/供应商依赖量化基本面风险
一家公司的风险,不只在自己账上,也在它最依赖的那几个客户和供应商手里。客户集中度 HHI 越高,大客户流失时收入塌方越狠;供应商集中度越高,断供时成本飙升越凶。本文用自洽合成面板从零构造客户/供应商集中度、注入依赖冲击,构建『空高集中度/多低集中度』因子,横截面 t 统计量 −15.7、年化 9.5%、Sharpe 1.66,且 alpha 几乎全部来自危机期(危机 71.9% vs 平静 3.9%),并诚实拆穿披露失真、共同冲击、幸存者偏差、行业结构、集中度≠风险、做空成本六类真实陷阱(中阶)。
2021 年那颗汽车芯片,让无数整车厂停产;一家果链公司被踢出供应链,股价一天腰斩。一家公司的基本面风险,很大一部分不在它自己利润表里,而在它最依赖的那几个名字手里。 客户集中度、供应商集中度,就是给这种「依赖风险」量化的尺子。本文把它做成一个可回测的横截面因子。
直觉很直接:客户集中度高的公司,前两大客户贡献一半营收,其中一个不续约,收入就塌一块;供应商集中度高的公司,关键原材料只有一两家能供,断供或涨价就直接吃进毛利。反之,客户分散、供应商多元的公司,单点失效伤不到筋骨。多低集中度、空高集中度,赚的就是『单点依赖的脆弱性折价』。
一、用 HHI 量化集中度#
集中度用赫芬达尔指数(HHI)度量。给定一家公司的客户份额向量 :
份额越平均,HHI 越接近 ;份额越集中到一两家,HHI 越接近 1。供应商侧同理 。我们用 Dirichlet 抽样合成份额,再平方求和:
import numpy as np
N, T = 150, 240
rng = np.random.default_rng(20260721)
def make_hhi(n_cust, alpha_conc):
base = rng.dirichlet([alpha_conc] * n_cust, size=N)
return (base**2).sum(1) # 真实 HHI, 前 n_cust 大份额
HHI_c = make_hhi(4, 1.0) # 客户集中度(前 4 大客户)
HHI_s = make_hhi(4, 0.9) # 供应商集中度(前 4 大供应商)
size = rng.lognormal(0, 0.6, N)python图 1 是合成的客户集中度分布,跨度从 0.25 到 0.78——足够让分组有区分度(真实 A 股前五大客户占比中位数约 30%,HHI 与此同源)。

二、注入「依赖冲击」:风险如何变成亏损#
我们用一个隐藏的「依赖冲击」事件来释放集中度风险——在 150–175 月注入一次大客户流失的需求冲击,和一次断供的供给冲击:
MKT = rng.normal(0.008, 0.04, T-1)
demand_shock = np.zeros(T-1)
supply_shock = np.zeros(T-1)
for t in range(150, 175):
demand_shock[t] = rng.normal(-0.05, 0.02) # 大客户流失: 收入塌
supply_shock[t] = rng.normal(0.045, 0.02) # 断供: 成本升/停产
beta = rng.normal(1.0, 0.3, N)
kappa, sb_c, sb_s = 0.009, 3.0, 2.4
R = (MKT[None, :]
+ beta[:, None] * MKT[None, :]
- kappa * HHI_c[:, None] # 持续脆弱性折价
+ sb_c * HHI_c[:, None] * demand_shock[None, :] # 大客户流失 -> 高集中度亏更狠
- sb_s * HHI_s[:, None] * supply_shock[None, :] # 断供 -> 高集中度亏更狠
+ rng.normal(0, 0.018, (N, T-1)))python关键设计:需求/供给冲击都乘以 HHI,所以冲击对高集中度公司是放大器,对低集中度公司几乎无感。图 2 把 140–185 月的累计净值拆出来——高集中度组在冲击期明显下沉,低集中度组基本走平。这就是因子 alpha 的物理来源。

三、构建因子:空高集中度 / 多低集中度#
和碳因子一样,按 HHI 分五分位,多空组合:
order = np.argsort(HHI_c)
n = N // 5
groups = [order[i*n:(i+1)*n] for i in range(5)]
grp_ret = np.array([R[g, :].mean(0) for g in groups]) # (5, T-1)
scc_factor = grp_ret[0] - grp_ret[4] # 多 Q1(低) / 空 Q5(高)python净值见图 3。因子年化 9.5%、Sharpe 1.66,且红色阴影区(危机期)贡献了绝大部分上行——这是一个典型的「危机保险型」因子:平时赚一点脆弱性折价,危机时赚一大笔依赖风险重定价。

四、横截面检验:t 统计量说了算#
因子有没有「统计上的存在感」,要看横截面回归的 t 值。控制市场和规模后,回归个股月均收益对 HHI:
def xs_reg(y, Xcols):
X = np.column_stack([np.ones(N)] + Xcols)
coef, *_ = np.linalg.lstsq(X, y, rcond=None)
resid = y - X @ coef
dof = N - X.shape[1]
se = np.sqrt((resid @ resid)/dof) * np.sqrt(np.diag(np.linalg.inv(X.T @ X)))
return coef, se
coef, se = xs_reg(R.mean(1), [HHI_c, size])
t_HHI = coef[1] / se[1] # ≈ -15.7pythont(HHI) = −15.7,远超过 |2| 的显著性门槛,负号说明「客户集中度越高,收益越低」。图 4 的拟合线直观展示这条负斜率。注意我们控制了 size——否则小公司天然更脆弱,集中度效应会和规模效应混淆。

五、五分位单调性#
def ann(r): return (np.prod(1 + r))**(12/len(r)) - 1
dec_ann = np.array([ann(grp_ret[k]) for k in range(5)])
# 从 Q1(低 ci) 到 Q5(高 ci) 单调下行python图 5 显示五分位年化收益严格随客户集中度升高而下降——因子是「干净」的单调暴露,不是头尾两组臆造。

六、六类真实陷阱#
- 披露失真与滞后:年报只披露「前五大客户占比」,不给完整份额向量,你算出的 HHI 是下界估计;且披露滞后一整年,而在芯片荒、疫情那种冲击里,集中度风险是即时释放的——数据频率卡死了因子的反应速度。
- 共同冲击混淆:一次全行业需求下行,会让所有公司一起跌,高低集中度组都亏。本文把冲击设成「乘 HHI」来分离纯度,但真实世界里你很难证明某次下跌「纯粹来自客户集中」还是「来自宏观」。
- 幸存者偏差(最致命):高集中度公司如果因为大客户流失而退市/ST,它会从面板里消失,你算 HHI 时只看到「活下来的低集中度公司」——因子效果会被幸存者偏差系统性高估。必须用含退市的全样本。
- 行业结构混淆:ToB 制造业天然客户集中(给主机厂供货),ToC 消费天然分散。不做行业中性化,你交易的其实是「制造业空头」。和碳因子一样,行业中性化是必做项。
- 集中度 ≠ 风险:极少数公司是「主动集中」——比如独家供应某爆款、议价权极强,高集中度反而是护城河。把这类公司和高集中度脆弱公司混在一起做空,会吃到哑巴亏。实战要用「集中度 × 议价能力(毛利率/转换成本)」做二阶过滤。
- 做空成本与可得性:因子要空高集中度组,但这类公司常是小盘、低流动性,借券难、冲击成本高;A 股几乎不可直接做空,退化成「只多低集中度」后,因子从「危机保险」变成「平时小幅跑赢」,Sharpe 结构会变。
七、落地清单#
- 数据层:拉取年报「前五大客户/供应商占比」,对单一大客户占比 >30% 直接标红;用行业中位数补全未披露项;
- 组合层:组内等权、季度调仓(匹配披露频率)、强制申万一级行业内中性化;
- 二阶过滤:在空高集中度组里剔除「高毛利 + 高转换成本」的主动集中型公司,避免做空真护城河;
- 危机增强:当宏观进入供应链压力窗口(如运价/芯片库存异动),临时超配该因子作为组合保险。
结语#
供应链集中度因子量化的是一句大白话:你的命脉攥在几个人手里,你就有多脆弱。 客户/供应商 HHI 越高,单点失效时的收入塌方、成本飙升就越狠。本文用自洽合成证明:横截面 t 统计量 −15.7、年化 9.5%、Sharpe 1.66,且 alpha 几乎全部来自危机期(71.9% vs 平静 3.9%)——它是一个典型的「危机保险型」因子。但六类陷阱里,幸存者偏差和披露滞后是真正会让你在实盘里栽跟头的两道坎:你回测看到的漂亮曲线,可能只是因为最惨的那批公司已经「消失」在数据里了。