2、市场状态识别:牛熊市划分、波动率聚类、趋势跟踪指标

做因子择时,最怕什么?

怕你拿着震荡市的策略去追趋势,也怕你拿着趋势策略去抄底。

说白了,市场状态没搞清楚,再好的因子也是白搭。我早年吃过这个亏——2015年股灾前,我的多因子模型还在满仓干,结果一周回撤了30%。从那以后,我把市场状态识别放在了策略流程的第一步。

2.1 牛熊市划分:不止是看涨跌

很多人觉得牛熊市就是看指数涨跌。涨多了就是牛,跌多了就是熊。嗯,这话没错,但太粗糙了。

我个人习惯用均线偏离法来划分。核心逻辑很简单:价格偏离长期均线太远,就是过热或过冷。

核心指标: 价格与250日均线的偏离度
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%也算牛市。这才把慢牛行情抓进来。

避坑指南: 我曾经把阈值设得太低(10%),结果频繁在牛熊之间切换,交易成本高得吓人。建议阈值设在15%-25%之间,具体看品种波动率。

2.2 波动率聚类:市场也有脾气

市场状态不光看方向,还得看脾气。波动率就是市场的脾气指数。

你想想看,低波动率的时候,因子策略往往表现稳定。高波动率的时候,因子容易失效甚至反向。我习惯用波动率聚类来划分市场环境。

具体做法分三步:

  1. 计算滚动波动率:用过去20日收益率的标准差
  2. 标准化处理:减去均值除以标准差,消除量纲
  3. 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=高波
注意: 聚类结果每次运行可能不同。我建议固定random_state,或者用历史数据预训练好模型,线上只做预测。

为什么要做聚类而不是直接设阈值?因为不同品种的波动率基准不一样。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
实战技巧: 我习惯把ADX和波动率聚类结合起来用。低波+高ADX,是最舒服的因子环境。高波+低ADX,赶紧减仓。

2.4 三者联动:构建市场状态矩阵

单独看一个指标,容易误判。我把牛熊市划分、波动率聚类、趋势强度三个维度组合成一个市场状态矩阵

举个例子:

  • 牛市 + 低波 + 强趋势 → 满仓干因子
  • 熊市 + 高波 + 弱趋势 → 空仓或对冲
  • 震荡市 + 中波 + 中趋势 → 半仓做均值回归因子

下面这张图是我自己项目里用的状态识别框架:

市场状态识别框架 价格 & 成交量数据 牛熊市划分 均线偏离法 波动率聚类 KMeans 3类 趋势跟踪指标 ADX + MACD 市场状态矩阵 3×3×3 = 27种状态组合 因子权重动态调整

有了这个矩阵,因子择时就不再是拍脑袋了。每个状态对应一套预设的因子权重方案。我自己的策略库里,光状态切换逻辑就写了200多行代码。

核心结论: 市场状态识别不是目的,目的是为因子权重调整提供依据。别为了识别而识别,要时刻想着「识别出来之后怎么用」。

嗯,这一章的内容就到这里。记住一句话:先看天气再出门,先判状态再交易


公众号:蓝海资料掘金营,微信deep3321