2、市场状态识别:牛熊市划分、波动率聚类、趋势跟踪指标
做因子择时,最怕什么?
怕你拿着震荡市的策略去追趋势,也怕你拿着趋势策略去抄底。
说白了,市场状态没搞清楚,再好的因子也是白搭。我早年吃过这个亏——2015年股灾前,我的多因子模型还在满仓干,结果一周回撤了30%。从那以后,我把市场状态识别放在了策略流程的第一步。
2.1 牛熊市划分:不止是看涨跌
很多人觉得牛熊市就是看指数涨跌。涨多了就是牛,跌多了就是熊。嗯,这话没错,但太粗糙了。
我个人习惯用均线偏离法来划分。核心逻辑很简单:价格偏离长期均线太远,就是过热或过冷。
def bull_bear_classify(close, ma_window=250, threshold=0.2):
"""
牛熊市划分
threshold: 偏离阈值,超过20%视为极端
"""
ma = close.rolling(ma_window).mean()
deviation = (close - ma) / ma
# 分类逻辑
conditions = [
(deviation > threshold), # 牛市过热
(deviation < -threshold), # 熊市过冷
(deviation.between(-0.05, 0.05)) # 震荡市
]
choices = ['牛市', '熊市', '震荡市']
return np.select(conditions, choices, default='弱趋势')
我在项目中遇到过一个问题:单纯用偏离度,2017年的慢牛行情根本识别不出来。因为指数涨得慢,偏离度一直没超过20%。后来我加了一个动量辅助条件——过去60日涨幅超过15%也算牛市。这才把慢牛行情抓进来。
2.2 波动率聚类:市场也有脾气
市场状态不光看方向,还得看脾气。波动率就是市场的脾气指数。
你想想看,低波动率的时候,因子策略往往表现稳定。高波动率的时候,因子容易失效甚至反向。我习惯用波动率聚类来划分市场环境。
具体做法分三步:
- 计算滚动波动率:用过去20日收益率的标准差
- 标准化处理:减去均值除以标准差,消除量纲
- KMeans聚类:分成3类——低波、中波、高波
from sklearn.cluster import KMeans
def volatility_cluster(returns, window=20, n_clusters=3):
# 计算滚动波动率
vol = returns.rolling(window).std() * np.sqrt(252)
vol_clean = vol.dropna().values.reshape(-1, 1)
# KMeans聚类
kmeans = KMeans(n_clusters=n_clusters, random_state=42)
labels = kmeans.fit_predict(vol_clean)
# 按波动率大小排序标签
centers = kmeans.cluster_centers_.flatten()
sorted_idx = np.argsort(centers)
label_map = {sorted_idx[i]: i for i in range(n_clusters)}
return [label_map[l] for l in labels] # 0=低波, 1=中波, 2=高波
为什么要做聚类而不是直接设阈值?因为不同品种的波动率基准不一样。A股的低波可能是15%,加密货币的低波可能是40%。聚类能自适应地找到每个品种的「正常区间」。
2.3 趋势跟踪指标:抓住市场的惯性
趋势跟踪的核心就一句话:强者恒强,弱者恒弱。但怎么定义「强」和「弱」?
我常用的三个指标:
| 指标 | 计算方式 | 适用场景 |
|---|---|---|
| ADX | 平均趋向指数,衡量趋势强度 | 判断是否该做趋势策略 |
| MACD | 指数平滑移动平均线,判断方向 | 确认趋势方向 |
| 唐奇安通道 | 过去N日最高最低价通道 | 突破入场信号 |
我个人最常用的是ADX。为什么?因为它不告诉你方向,只告诉你趋势的强度。这正好用来做因子择时的开关——ADX低于20,说明市场没趋势,因子策略减仓或空仓。
def compute_adx(high, low, close, period=14):
# 计算+DI和-DI
up_move = high.diff()
down_move = low.diff()
plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0)
minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0)
tr = pd.concat([high - low,
(high - close.shift()).abs(),
(low - close.shift()).abs()], axis=1).max(axis=1)
atr = tr.rolling(period).mean()
plus_di = 100 * (pd.Series(plus_dm).rolling(period).mean() / atr)
minus_di = 100 * (pd.Series(minus_dm).rolling(period).mean() / atr)
dx = 100 * (plus_di - minus_di).abs() / (plus_di + minus_di)
adx = dx.rolling(period).mean()
return adx
2.4 三者联动:构建市场状态矩阵
单独看一个指标,容易误判。我把牛熊市划分、波动率聚类、趋势强度三个维度组合成一个市场状态矩阵。
举个例子:
- 牛市 + 低波 + 强趋势 → 满仓干因子
- 熊市 + 高波 + 弱趋势 → 空仓或对冲
- 震荡市 + 中波 + 中趋势 → 半仓做均值回归因子
下面这张图是我自己项目里用的状态识别框架:
有了这个矩阵,因子择时就不再是拍脑袋了。每个状态对应一套预设的因子权重方案。我自己的策略库里,光状态切换逻辑就写了200多行代码。
嗯,这一章的内容就到这里。记住一句话:先看天气再出门,先判状态再交易。
公众号:蓝海资料掘金营,微信deep3321