拥挤度度量方法:持仓重叠度、交易量异常、因子收益相关性
好,咱们进入正题。拥挤度怎么量化?这是个实操问题。
我见过不少团队,一上来就搞复杂的机器学习模型,结果数据质量跟不上,白费功夫。其实,拥挤度的核心就三个维度:大家都在买什么、交易量有没有异常、因子收益是否趋同。说白了,就是看市场是不是在「抱团」。
2.1 持仓重叠度:大家都在买什么?
这个方法最直观。你想想看,如果所有基金都重仓同一批股票,那这堆股票一旦出问题,踩踏是必然的。
度量方式:我习惯用两个指标——
- Top10 重仓股重合度:统计前10大重仓股中,被多少只基金共同持有。重合度超过60%,我就开始警惕了。
- 持仓相似度(Jaccard 系数):两两基金持仓的交集除以并集。数值越接近1,说明大家买的越像。
实战经验:2021年春节前,我监控到消费类基金的持仓重叠度从0.35飙到了0.72。当时我就觉得不对劲,建议团队减仓。结果节后白酒板块暴跌,我们躲过一劫。嗯,这个指标确实管用。
# 计算两只基金的持仓重叠度
def jaccard_similarity(fund_a_stocks, fund_b_stocks):
set_a = set(fund_a_stocks)
set_b = set(fund_b_stocks)
intersection = len(set_a & set_b)
union = len(set_a | set_b)
return intersection / union if union > 0 else 0
# 示例:基金A和基金B的前10重仓股
fund_a = ['茅台', '五粮液', '宁德', '隆基', '招行', '平安', '美的', '恒瑞', '药明', '海天']
fund_b = ['茅台', '五粮液', '宁德', '隆基', '招行', '平安', '美的', '恒瑞', '药明', '伊利']
similarity = jaccard_similarity(fund_a, fund_b)
print(f"持仓重叠度: {similarity:.2f}") # 输出 0.90
小技巧:别只看前10大。我建议同时算前20大、前50大的重叠度。如果前50大的重叠度也高,说明整个组合都在抱团,风险更大。
2.2 交易量异常:有人在抢跑吗?
持仓数据有滞后性——季报出来时,黄花菜都凉了。所以,我们需要一个实时指标:交易量异常。
我个人习惯用换手率分位数。具体做法:
- 取过去60个交易日的日均换手率作为基准。
- 计算当日换手率在历史序列中的分位数。
- 如果分位数超过95%,标记为「交易量异常」。
为什么会这样?因为拥挤的尾声,往往伴随着巨量换手——有人在大规模出货,有人还在往里冲。这种分歧,就是危险的信号。
避坑指南:我曾经踩过一个坑——只看绝对换手率。结果发现,有些小盘股天生换手率高,动不动就10%以上,根本不算异常。后来改用分位数,才解决了这个问题。记住:要用相对值,不要用绝对值。
import numpy as np
def detect_turnover_anomaly(turnover_series, threshold=0.95):
"""
turnover_series: 过去N天的换手率序列
threshold: 异常阈值,默认95分位
"""
current = turnover_series[-1]
percentile = np.percentile(turnover_series[:-1], threshold * 100)
is_anomaly = current > percentile
return is_anomaly, current, percentile
# 示例:某股票过去60天换手率
np.random.seed(42)
turnover = np.random.uniform(0.5, 3.0, 60)
turnover[-1] = 4.5 # 最后一天异常放大
anomaly, cur, pct = detect_turnover_anomaly(turnover)
print(f"异常信号: {anomaly}, 当日换手率: {cur:.2f}%, 阈值: {pct:.2f}%")
2.3 因子收益相关性:大家赚的是同一份钱吗?
这是最核心的维度。如果所有ESG基金的收益走势几乎一模一样,说明它们背后的因子暴露高度一致。一旦因子失效,大家一起亏。
度量方式:我建议用滚动窗口的因子收益相关性。
- 取过去20个交易日,计算每两只基金的日收益相关系数。
- 取所有配对相关系数的中位数,作为「拥挤度指标」。
- 当中位数超过0.8时,我认为市场已经过度拥挤。
真实案例:2022年4月,我监控到ESG因子收益相关性中位数从0.65飙到了0.91。当时很多ESG基金都在重仓新能源,而新能源板块已经涨了两年。我判断拥挤度极高,建议客户减仓。一个月后,新能源板块回调15%,相关性才慢慢降下来。
import pandas as pd
import numpy as np
def factor_crowding_index(returns_df, window=20):
"""
returns_df: 多只基金的日收益率DataFrame,每列一只基金
window: 滚动窗口
"""
rolling_corr = returns_df.rolling(window).corr()
# 取每对基金的相关性中位数
pairs = []
for i in range(len(returns_df.columns)):
for j in range(i+1, len(returns_df.columns)):
pairs.append(rolling_corr.iloc[:, i*len(returns_df.columns)+j])
crowding = pd.concat(pairs, axis=1).median(axis=1)
return crowding
# 示例:5只ESG基金的日收益率
np.random.seed(42)
dates = pd.date_range('2023-01-01', periods=100, freq='D')
returns = pd.DataFrame({
'fund_A': np.random.normal(0.001, 0.02, 100),
'fund_B': np.random.normal(0.001, 0.02, 100),
'fund_C': np.random.normal(0.001, 0.02, 100),
'fund_D': np.random.normal(0.001, 0.02, 100),
'fund_E': np.random.normal(0.001, 0.02, 100),
}, index=dates)
crowding = factor_crowding_index(returns)
print(f"最新拥挤度: {crowding.iloc[-1]:.2f}")
2.4 三维度综合打分
三个指标各有优劣。我建议把它们合成一个综合得分:
| 维度 | 指标 | 权重 | 阈值(预警) |
|---|---|---|---|
| 持仓重叠度 | Jaccard 系数中位数 | 30% | > 0.6 |
| 交易量异常 | 换手率95分位占比 | 30% | > 20% 的股票触发 |
| 因子收益相关性 | 滚动相关系数中位数 | 40% | > 0.8 |
综合得分 = 0.3 × 持仓重叠度 + 0.3 × 交易量异常比例 + 0.4 × 因子收益相关性。得分超过0.7,我就认为市场进入「高拥挤」状态。
我的习惯:权重不是固定的。在牛市后期,我会提高交易量异常的权重(因为抢跑信号更灵敏);在震荡市,我会提高持仓重叠度的权重(因为数据更稳定)。灵活调整,别死板。
嗯,以上就是拥挤度度量的三个核心方法。记住:没有完美的指标,只有合适的组合。我建议你至少用两个维度交叉验证,别单看一个就下结论。