美林时钟、区制转换与贝叶斯择时。
| 状态 | 增长 | 通胀 | 典型资产偏好 | 直觉 |
|---|---|---|---|---|
| 复苏 Recovery | 高/上行 | 低/下行 | 股票 | 宽松仍在、盈利改善、商品还弱 |
| 过热 Overheat | 高/上行 | 高/上行 | 商品 | 产能接近极限,央行开始收紧 |
| 滞胀 Stagflation | 低/下行 | 高/上行 | 现金(相对) | 盈利承压且难宽松,股债都难受 |
| 衰退 Recession | 低/下行 | 低/下行 | 债券 | 降息通道打开,避险占优 |
经典叙述里:增长先动、通胀后动——价格往往是增长的结果。因此时钟按复苏 → 过热 → 滞胀 → 衰退顺时针轮转,但真实周期未必走完四格,可能出现跳跃或反复(例如滞胀后直接回到复苏)。
结合上述拓展讨论,对中国应用时建议记住三点:
大类资产轮动背后有一个共同变量:利率与流动性。货币信用框架把周期改写成货币松紧 × 信用松紧,与美林时钟大致一一对应:
| 美林时钟 | 货币信用近似 | 资产直觉 |
|---|---|---|
| 衰退 | 宽货币 + 紧信用 | 债强、风险资产弱 |
| 复苏 | 宽货币 + 宽信用 | 股(及部分风险资产)改善 |
| 过热 | 紧货币 + 宽信用 | 商品/顺周期更占优 |
| 滞胀 | 紧货币 + 紧信用 | 现金相对占优,股债承压 |
度量上:
经验规则(高度简化):货币松 → 债友好;信用宽 → 股友好。商品则要看信用更多流向供给端还是需求端,规律更噪。
相对美林时钟,货币信用框架在中国的优势常被概括为三点:更贴银行体系主导的融资结构、指标相对领先、量价数据更及时可观测。2013 年后经济周期弱化时,仍可用货币信用语言解释许多轮动现象。
还可以把周期翻译成利率曲线形态:
熊平 / 熊陡 / 牛平 / 牛陡等曲线形态,常被用来与四象限对照。曲线倒挂在海外常作为衰退预警;在中国需结合政策利率走廊、期限溢价与监管因素解读,不宜机械套用。
三套语言的关系可以记成:
增长×通胀(美林时钟)
≈ 货币×信用(中国常用)
≈ 短端×长端(收益率曲线)
它们描述的是同一宏观周期的不同投影;第11章的大类轮动,本质上是在其中一套(或融合)语言上做可交易规则。
因此:美林时钟提供地图,货币信用与利率曲线提供更及时的罗盘;后面的马尔可夫区制与贝叶斯更新,则是把“状态不确定”显式建模。下一节回到统计区制方法。
实务中很少直接用实时 GDP。更常见的代理是:
一种简单可复现规则:对增长与通胀序列取滚动分位或相对其自身趋势的高低,再映射到四象限;信号在 (t) 月形成后,用于 (t+1) 月资产收益,避免发布滞后造成的未来函数。第11章末A股宏观轮动复现将按这一思路实现股债轮动基准。
在美林时钟这类规则状态之外,可以用统计模型估计隐状态,并据此轮动风格因子。
前述章节讨论了因子构建与组合方法。因子并非万能,其有效性也并非长期稳定。
以一次回测为例:价值因子在2017年表现较好,年化超额收益超过15%;进入2018年后则持续回撤,最大回撤超过20%。这说明,若能识别因子表现随宏观状态变化的规律,将有助于改进配置。
这就是因子择时的核心问题。
因子择时,即依据当前市场状态动态调整因子权重。例如,在趋势市提高动量暴露,在风险偏好下降时提高低波暴露,在经济复苏阶段提高价值暴露,此即因子轮动的基本思路。
首先考察不同宏观环境下因子表现的基本规律。
| 宏观状态 | 表现好的因子 | 表现差的因子 | 逻辑解释 |
|---|---|---|---|
| 经济扩张期 | 价值、动量 | 低波、质量 | 风险偏好高,追逐高收益 |
| 经济衰退期 | 低波、质量 | 价值、小盘 | 避险情绪主导,追求确定性 |
| 通胀上升期 | 商品、价值 | 成长、动量 | 实物资产受益,高估值承压 |
| 利率下降期 | 成长、动量 | 价值、红利 | 贴现率下降,远期现金流更值钱 |
| 高波动环境 | 低波、质量 | 小盘、动量 | 市场恐慌,资金抱团优质资产 |
下表概括了常见经验规律,但并非绝对成立。例如,在2020年3月疫情冲击阶段,若机械加仓低波因子,可能错过随后成长因子的快速反弹。由此可见,宏观状态切换的时点往往比状态本身更为关键。
随之而来的问题是:如何判断当前所处的宏观状态?
传统做法常以阈值划分状态,例如将GDP增速高于3%视为扩张、低于0%视为衰退。该方法较为粗糙:GDP为季度发布,存在明显滞后,难以支持及时决策。
更好的方法是使用 马尔可夫区制转换模型 (Markov Regime Switching Model)。
该模型的核心思想是:市场状态为隐变量,只能依据可观测数据推断当前最可能状态,且状态之间按一定概率转移。
给出研究者画个图,帮助理解:
图中每个圆圈代表一个市场状态,箭头上的数字代表状态转换的概率。比如P₁₂=0.05,意思是当前是牛市,下个月变成熊市的概率只有5%。而P₁₁=0.93,说明牛市持续的概率高达93%。
这就是马尔可夫模型的精髓: 状态有持续性,但也会以一定概率发生转换 。
接下来动手实现一个简单的两状态马尔可夫区制转换模型。将用沪深300指数的日收益率数据来演示。
import numpy as np
import pandas as pd
from scipy.optimize import minimize
import matplotlib.pyplot as plt
class RegimeSwitchingModel:
"""两状态马尔可夫区制转换模型"""
def __init__(self, n_regimes=2):
self.n_regimes = n_regimes
self.params = None
def fit(self, returns):
"""使用EM算法估计模型参数"""
# 初始化参数
mu = np.array([0.001, -0.001]) # 两个状态的均值
sigma = np.array([0.01, 0.02]) # 两个状态的波动率
P = np.array([[0.95, 0.05], # 转换概率矩阵
[0.10, 0.90]])
# EM算法迭代
for iteration in range(100):
# E步:计算平滑概率
smooth_prob = self._smooth_prob(returns, mu, sigma, P)
# M步:更新参数
mu_new = self._update_mu(returns, smooth_prob)
sigma_new = self._update_sigma(returns, smooth_prob, mu_new)
P_new = self._update_P(smooth_prob)
# 检查收敛
if np.allclose(mu, mu_new, rtol=1e-4):
break
mu, sigma, P = mu_new, sigma_new, P_new
self.params = {'mu': mu, 'sigma': sigma, 'P': P}
return self
def predict_state(self, returns):
"""预测当前最可能的状态"""
smooth_prob = self._smooth_prob(
returns,
self.params['mu'],
self.params['sigma'],
self.params['P']
)
return np.argmax(smooth_prob[-1])
def _smooth_prob(self, returns, mu, sigma, P):
"""计算平滑概率(简化版)"""
T = len(returns)
n = self.n_regimes
# 计算似然
likelihood = np.zeros((T, n))
for i in range(n):
likelihood[:, i] = 1/(np.sqrt(2*np.pi)*sigma[i]) * \
np.exp(-0.5*((returns - mu[i])/sigma[i])**2)
# 前向-后向算法(简化)
filter_prob = np.zeros((T, n))
filter_prob[0] = [0.5, 0.5] # 初始概率
for t in range(1, T):
pred_prob = filter_prob[t-1] @ P
filter_prob[t] = pred_prob * likelihood[t]
filter_prob[t] /= filter_prob[t].sum
return filter_prob
def _update_mu(self, returns, smooth_prob):
"""更新均值参数"""
return np.array([
np.sum(smooth_prob[:, i] * returns) / np.sum(smooth_prob[:, i])
for i in range(self.n_regimes)
])
def _update_sigma(self, returns, smooth_prob, mu):
"""更新波动率参数"""
return np.array([
np.sqrt(np.sum(smooth_prob[:, i] * (returns - mu[i])**2) /
np.sum(smooth_prob[:, i]))
for i in range(self.n_regimes)
])
def _update_P(self, smooth_prob):
"""更新转换概率矩阵"""
T = len(smooth_prob)
P = np.zeros((self.n_regimes, self.n_regimes))
for i in range(self.n_regimes):
for j in range(self.n_regimes):
numerator = np.sum(smooth_prob[1:, j] * smooth_prob[:-1, i])
denominator = np.sum(smooth_prob[:-1, i])
P[i, j] = numerator / denominator if denominator > 0 else 0
# 确保每行和为1
P = P / P.sum(axis=1, keepdims=True)
return P
# 使用示例
if __name__ == "__main__":
# 生成模拟数据
np.random.seed(42)
T = 1000
# 状态1:低波动牛市
regime1 = np.random.normal(0.001, 0.01, T//2)
# 状态2:高波动熊市
regime2 = np.random.normal(-0.001, 0.02, T//2)
returns = np.concatenate([regime1, regime2])
# 训练模型
model = RegimeSwitchingModel(n_regimes=2)
model.fit(returns)
# 预测状态
current_state = model.predict_state(returns[-100:])
print(f"当前状态:{'牛市' if current_state == 0 else '熊市'}")
print(f"状态参数:{model.params}")
当前状态:牛市
状态参数:{'mu': array([0.00029928, 0.00040345]), 'sigma': array([0.00937264, 0.01961894]), 'P': array([[0.50880041, 0.49119959],
[0.47113172, 0.52886828]])}
有了状态识别工具,就可以构建因子轮动策略了。基本思路是:
给出研究者展示一个完整的策略框架:
class FactorRotationStrategy:
"""基于区制转换的因子轮动策略"""
def __init__(self, factors, regime_model):
self.factors = factors # 因子列表
self.regime_model = regime_model
self.regime_weights = {
0: {'momentum': 0.4, 'value': 0.3, 'quality': 0.2, 'low_vol': 0.1},
1: {'momentum': 0.1, 'value': 0.2, 'quality': 0.3, 'low_vol': 0.4}
}
def get_weights(self, returns):
"""根据当前状态获取因子权重"""
state = self.regime_model.predict_state(returns)
return self.regime_weights[state]
def rebalance(self, portfolio, returns, threshold=0.3):
"""动态再平衡"""
current_weights = self.get_weights(returns)
# 计算当前持仓与目标权重的偏差
deviation = sum(abs(portfolio['weight'] - current_weights[f])
for f in self.factors)
# 偏差超过阈值才调仓
if deviation > threshold:
return current_weights
else:
return portfolio['weight']
除了统计模型,还可以从经济周期的角度来理解因子表现。美林时钟是一个经典的框架:
| 经济周期阶段 | 经济增长 | 通货膨胀 | 最优因子 | 次优因子 |
|---|---|---|---|---|
| 复苏期 | 上升 | 下降 | 价值 | 动量 |
| 过热期 | 上升 | 上升 | 商品 | 价值 |
| 滞胀期 | 下降 | 上升 | 低波 | 质量 |
| 衰退期 | 下降 | 下降 | 质量 | 低波 |
这个框架虽然简单,但非常实用。需要指出2022年做策略时,就是根据这个框架判断当时处于滞胀期,果断加仓低波因子,成功躲过了成长股的大幅回撤。
最后归纳若干实战中常见问题:
这一章的内容就到这里。因子择时是一个很有挑战性的课题,但也是量化投资中最有价值的部分之一。希望这些内容能给出一些启发。
公众号:蓝海资料掘金营,微信deep3321
← 上一章 📖 返回目录 下一章 →
因子择时,即判断现在该用哪个因子。量化研究初期,总觉得因子是万能的——只要找到好因子,即可获得稳定收益。结果是:市场可能迅速证伪原有判断。随后才明白,因子的表现是随时间变化的,需要学会择时。
传统的择时方法,比如滚动窗口回归,有个致命问题:它对极端值太敏感了。一次黑天鹅事件,就能把整个模型带偏。这时候,贝叶斯方法就派上用场了。
先验分布,就是事先对因子表现的看法。个人的习惯是,先问自己三个问题:
举个例子,假设研究的是动量因子。根据历史经验,动量因子长期有正收益,但波动也不小。将这样设定先验:
import numpy as np
import pymc3 as pm
# 设定先验参数
prior_mean = 0.05 # 年化收益5%
prior_std = 0.15 # 年化波动15%
# 用正态分布作为先验
with pm.Model as factor_model:
# 因子收益的先验
mu = pm.Normal('mu', mu=prior_mean, sigma=prior_std)
# 波动率的先验(用半正态分布)
sigma = pm.HalfNormal('sigma', sigma=0.1)
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) <ipython-input-3-38ebaaa64a4f> in <cell line: 0>() 1 import numpy as np ----> 2 import pymc3 as pm 3 4 # 设定先验参数 5 prior_mean = 0.05 # 年化收益5% ModuleNotFoundError: No module named 'pymc3'
有了先验,接下来就是后验更新了。这个过程,即用贝叶斯公式,把先验和观测数据结合起来。
如果只有先验,那模型就是固执的老头;如果只有数据,那就是墙头草。贝叶斯方法正好取了个折中。
# 模拟观测数据(因子日收益)
np.random.seed(42)
observed_returns = np.random.normal(0.02, 0.12, size=252) # 一年数据
# 后验更新
with factor_model:
# 似然函数
returns = pm.Normal('returns', mu=mu, sigma=sigma, observed=observed_returns)
# 采样
trace = pm.sample(2000, tune=1000, return_inferencedata=False)
# 查看后验结果
posterior_mean = trace['mu'].mean
posterior_std = trace['mu'].std
print(f"后验均值: {posterior_mean:.4f}")
print(f"后验标准差: {posterior_std:.4f}")
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-4-268be2c55dad> in <cell line: 0>() 4 5 # 后验更新 ----> 6 with factor_model: 7 # 似然函数 8 returns = pm.Normal('returns', mu=mu, sigma=sigma, observed=observed_returns) NameError: name 'factor_model' is not defined
运行这段代码,可以发现后验均值往往介于先验均值和样本均值之间。这就是贝叶斯的收缩效应——当数据量少的时候,模型更相信先验;数据量多了,模型就逐渐向数据靠拢。
现在有后验分布了。如何据此进行择时?
相应的做法是:根据后验分布,计算因子在当前时刻的预期收益和不确定性。然后设定一个阈值,只有当预期收益足够高、不确定性足够低时,才启用该因子。
def bayesian_factor_timing(trace, threshold=0.02, confidence_level=0.95):
"""
贝叶斯因子择时决策函数
参数:
trace: 后验采样结果
threshold: 收益阈值
confidence_level: 置信水平
返回:
signal: 1表示启用因子,0表示不启用
"""
# 提取后验样本
mu_samples = trace['mu']
# 计算后验均值
expected_return = mu_samples.mean
# 计算后验分布中,收益大于阈值的概率
prob_above_threshold = (mu_samples > threshold).mean
# 决策规则
if prob_above_threshold >= confidence_level:
signal = 1
else:
signal = 0
return signal, expected_return, prob_above_threshold
# 示例
signal, exp_ret, prob = bayesian_factor_timing(trace)
print(f"择时信号: {'启用' if signal else '不启用'}")
print(f"预期收益: {exp_ret:.4f}")
print(f"收益超过阈值的概率: {prob:.4f}")
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-5-7cc063828785> in <cell line: 0>() 29 30 # 示例 ---> 31 signal, exp_ret, prob = bayesian_factor_timing(trace) 32 print(f"择时信号: {'启用' if signal else '不启用'}") 33 print(f"预期收益: {exp_ret:.4f}") NameError: name 'trace' is not defined
下图,为自己总结的贝叶斯因子择时核心逻辑。一眼就能明白整个流程:
这里要注意几个问题,都是采用真金白银换来的教训:
最后说一句,贝叶斯方法不是银弹。它最大的价值,是使得在不确定性面前保持谦逊。市场永远在变,能做的,就是不断更新自己的认知。
← 上一章 📖 返回目录 下一章 →
# 导出当前 Notebook 为 HTML
# jupyter nbconvert --to html 32.ipynb
print("Chapter 32 ready. Use 00_build_all.ipynb to export the full site.")
Chapter 32 ready. Use 00_build_all.ipynb to export the full site.