主题切换
15.4 经济周期择时
名词解释:经济周期择时(Business Cycle Timing)
经济周期择时是指识别当前经济所处的周期阶段(衰退、复苏、扩张、放缓),并据此调整资产配置。与市场择时不同,经济周期择时的信号频率较低(月度到季度),关注的是经济基本面的结构性转变,而非短期价格波动。
一、美林时钟模型及其改进
1.1 经典美林时钟
美林时钟(Merrill Lynch Investment Clock)将经济周期按增长和通胀两个维度划分为四个象限:
| 阶段 | 增长 | 通胀 | 最优资产 | 最优板块 |
|---|---|---|---|---|
| 复苏(Recovery) | 上升 | 下降 | 权益 > 债券 > 商品 > 现金 | 可选消费、科技 |
| 过热(Overheat) | 上升 | 上升 | 商品 > 权益 > 现金 > 债券 | 能源、材料、工业 |
| 滞胀(Stagflation) | 下降 | 上升 | 现金 > 商品 > 债券 > 权益 | 公用事业、必需消费 |
| 衰退(Recession) | 下降 | 下降 | 债券 > 现金 > 权益 > 商品 | 医疗、必需消费 |
1.2 改进:三维经济周期模型
经典美林时钟仅使用增长和通胀两个维度,但忽略了流动性和信贷条件这一关键维度:
此处有展示代码展开 ▼
python
import numpy as np
import pandas as pd
from scipy import stats
import matplotlib.pyplot as plt
def enhanced_investment_clock(growth_momentum: float,
inflation_momentum: float,
credit_momentum: float,
thresholds: dict = None) -> dict:
"""
三维增强版美林时钟。
参数:
growth_momentum: 增长动量(标准化后的趋势强度)
inflation_momentum: 通胀动量
credit_momentum: 信用条件动量(正=信贷宽松,负=信贷紧缩)
thresholds: 各维度的阈值
返回:
包含阶段分类和资产配置建议的字典
"""
if thresholds is None:
thresholds = {
'growth': 0.0,
'inflation': 0.0,
'credit': 0.0
}
# 确定状态
growth_state = 'up' if growth_momentum > thresholds['growth'] else 'down'
infl_state = 'up' if inflation_momentum > thresholds['inflation'] else 'down'
credit_state = 'tight' if credit_momentum < thresholds['credit'] else 'loose'
# 8种可能的状态组合
state = (growth_state, infl_state, credit_state)
# 状态与资产配置的映射
config_map = {
('up', 'down', 'loose'): {
'phase': 'Goldilocks (金发姑娘)',
'equity': 0.55, 'bonds': 0.25, 'commodities': 0.10, 'cash': 0.10,
'description': '最佳宏观环境:增长强劲、通胀温和、流动性充裕'
},
('up', 'down', 'tight'): {
'phase': 'Recovery with Tight Credit',
'equity': 0.40, 'bonds': 0.30, 'commodities': 0.15, 'cash': 0.15,
'description': '增长恢复但信贷偏紧'
},
('up', 'up', 'loose'): {
'phase': 'Overheat (过热)',
'equity': 0.30, 'bonds': 0.05, 'commodities': 0.45, 'cash': 0.20,
'description': '通胀压力上升'
},
('up', 'up', 'tight'): {
'phase': 'Tightening Cycle (紧缩周期)',
'equity': 0.20, 'bonds': 0.20, 'commodities': 0.30, 'cash': 0.30,
'description': '央行收紧政策对抗通胀'
},
('down', 'up', 'loose'): {
'phase': 'Stagflation-lite (轻滞胀)',
'equity': 0.20, 'bonds': 0.10, 'commodities': 0.30, 'cash': 0.40,
'description': '增长放缓但通胀仍高'
},
('down', 'up', 'tight'): {
'phase': 'Stagflation (滞胀)',
'equity': 0.15, 'bonds': 0.15, 'commodities': 0.25, 'cash': 0.45,
'description': '最糟糕组合:低增长+高通胀+紧信用'
},
('down', 'down', 'loose'): {
'phase': 'Early Recession (衰退初期)',
'equity': 0.25, 'bonds': 0.50, 'commodities': 0.10, 'cash': 0.15,
'description': '经济下行,债券为王'
},
('down', 'down', 'tight'): {
'phase': 'Credit Crunch (信用紧缩)',
'equity': 0.10, 'bonds': 0.40, 'commodities': 0.10, 'cash': 0.40,
'description': '信用收缩加剧经济下行'
}
}
allocation = config_map.get(state, config_map[('down', 'down', 'tight')])
return allocation
# ===== 合成数据最小示例(数据为模拟,机制真实)=====
for _g, _inf in [(0.6, -0.2), (0.6, 0.8), (-0.5, 0.9), (-0.5, -0.3)]:
_clk = enhanced_investment_clock(growth_momentum=_g, inflation_momentum=_inf, credit_momentum=0.1)
print(f'增长{_g:+.1f}/通胀{_inf:+.1f} ->', _clk if not isinstance(_clk, dict) else {k: v for k, v in list(_clk.items())[:2]})点击展开可浏览运行结果
增长+0.6/通胀-0.2 -> {'phase': 'Goldilocks (金发姑娘)', 'equity': 0.55}
增长+0.6/通胀+0.8 -> {'phase': 'Overheat (过热)', 'equity': 0.3}
增长-0.5/通胀+0.9 -> {'phase': 'Stagflation-lite (轻滞胀)', 'equity': 0.2}
增长-0.5/通胀-0.3 -> {'phase': 'Early Recession (衰退初期)', 'equity': 0.25}二、经济周期阶段的定量识别
2.1 基于滤波的趋势-周期分解
Hodrick-Prescott (HP) 滤波和 Christiano-Fitzgerald (CF) 带通滤波是分离经济数据趋势和周期成分的标准工具:
此处有展示代码展开 ▼
python
import numpy as np
import pandas as pd
from scipy.signal import savgol_filter
from statsmodels.tsa.filters.hp_filter import hpfilter
def decompose_economic_cycle(gdp_series: pd.Series,
method: str = 'hp',
hp_lambda: float = 1600) -> dict:
"""
分解经济时间序列的趋势和周期成分。
参数:
gdp_series: 经济时间序列(如季度GDP)
method: 分解方法 ('hp' 或 'savgol')
hp_lambda: HP滤波的平滑参数(季度数据=1600,月度=129600)
返回:
包含 trend, cycle, cycle_std 的字典
"""
if method == 'hp':
cycle, trend = hpfilter(gdp_series, lamb=hp_lambda)
elif method == 'savgol':
# Savitzky-Golay 滤波作为趋势
window = max(5, len(gdp_series) // 4)
if window % 2 == 0:
window += 1
trend = pd.Series(
savgol_filter(gdp_series.values, window, 2),
index=gdp_series.index
)
cycle = gdp_series - trend
else:
raise ValueError(f"Unknown method: {method}")
# 周期成分标准化
cycle_standardized = (cycle - cycle.mean()) / cycle.std()
return {
'trend': trend,
'cycle': cycle,
'cycle_standardized': cycle_standardized,
'current_gap': cycle.iloc[-1],
'output_gap_pct': (cycle.iloc[-1] / trend.iloc[-1]) * 100
}
# ===== 合成数据最小示例(数据为模拟,机制真实)=====
np.random.seed(13)
_gdp = pd.Series(np.linspace(100, 118, 60) + np.sin(np.linspace(0, 6 * np.pi, 60)) * 2
+ np.random.randn(60) * 0.5,
index=pd.date_range('2012-03-01', periods=60, freq='QE'))
_cycle = decompose_economic_cycle(_gdp, method='hp')
for _k, _v in _cycle.items():
print(f'--- {_k} ---')
print(_v.tail() if hasattr(_v, 'tail') else _v)点击展开可浏览运行结果
--- trend --- 2025-12-31 116.140721 2026-03-31 116.355240 2026-06-30 116.571654 2026-09-30 116.789275 2026-12-31 117.007355 Freq: QE-DEC, Name: trend, dtype: float64 --- cycle --- 2025-12-31 -0.268294 2026-03-31 -0.935866 2026-06-30 -0.092231 2026-09-30 0.457253 2026-12-31 0.736662 Freq: QE-DEC, Name: cycle, dtype: float64 --- cycle_standardized --- 2025-12-31 -0.206188 2026-03-31 -0.719227 2026-06-30 -0.070881 2026-09-30 0.351406 2026-12-31 0.566136 Freq: QE-DEC, Name: cycle, dtype: float64 --- current_gap --- 0.7366619709395792 --- output_gap_pct --- 0.6295860357711748
2.2 多种经济指标的融合打分
此处有展示代码展开 ▼
python
def economic_cycle_scoreboard(indicators: dict) -> dict:
"""
综合多指标打分当前经济周期位置。
参数:
indicators: 各指标的当前得分字典
{'pmi': 55, 'unemployment': 3.5, 'retail_sales': 3.2, ...}
返回:
综合打分和周期判断
"""
# 各指标的标准化规则
scoring_rules = {
'pmi': {
'score': lambda x: min(100, max(0, (x - 45) / 15 * 100)),
'weight': 0.20,
'direction': 1 # PMI越高越好
},
'unemployment_rate': {
'score': lambda x: min(100, max(0, (8 - x) / 5 * 100)),
'weight': 0.15,
'direction': -1 # 失业率越低越好
},
'retail_sales_yoy': {
'score': lambda x: min(100, max(0, (x + 5) / 10 * 100)),
'weight': 0.15,
'direction': 1
},
'industrial_production_yoy': {
'score': lambda x: min(100, max(0, (x + 5) / 10 * 100)),
'weight': 0.15,
'direction': 1
},
'credit_spread': {
'score': lambda x: min(100, max(0, (5 - x) / 5 * 100)),
'weight': 0.15,
'direction': -1 # 信用利差越低越好
},
'yield_curve_slope': {
'score': lambda x: min(100, max(0, (x + 1) / 3 * 100)),
'weight': 0.10,
'direction': 1 # 收益率曲线越陡越好
},
'housing_starts_yoy': {
'score': lambda x: min(100, max(0, (x + 10) / 20 * 100)),
'weight': 0.10,
'direction': 1
}
}
total_score = 0
component_scores = {}
for name, rules in scoring_rules.items():
if name in indicators:
raw_score = rules['score'](indicators[name])
weighted = raw_score * rules['weight']
total_score += weighted
component_scores[name] = {
'raw_value': indicators[name],
'score': raw_score,
'weighted_score': weighted
}
# 周期阶段判断
if total_score > 65:
phase = 'expansion'
elif total_score > 45:
phase = 'slow_growth'
elif total_score > 25:
phase = 'contraction_risk'
else:
phase = 'recession'
return {
'composite_score': total_score,
'phase': phase,
'component_scores': component_scores,
'confidence': abs(total_score - 50) / 50 # 0到1
}
# ===== 合成数据最小示例(数据为模拟,机制真实)=====
_ind = {'pmi': 51.2, 'unemployment_rate': 3.9, 'retail_sales_yoy': 2.5,
'industrial_production_yoy': 1.1, 'credit_spread': 1.4,
'yield_curve_slope': -0.2, 'housing_starts_yoy': -5.0}
_sb = economic_cycle_scoreboard(_ind)
for _k, _v in _sb.items():
print(f'{_k}: {_v}')点击展开可浏览运行结果
composite_score: 56.93333333333333
phase: slow_growth
component_scores: {'pmi': {'raw_value': 51.2, 'score': 41.33333333333335, 'weighted_score': 8.266666666666671}, 'unemployment_rate': {'raw_value': 3.9, 'score': 82.0, 'weighted_score': 12.299999999999999}, 'retail_sales_yoy': {'raw_value': 2.5, 'score': 75.0, 'weighted_score': 11.25}, 'industrial_production_yoy': {'raw_value': 1.1, 'score': 61.0, 'weighted_score': 9.15}, 'credit_spread': {'raw_value': 1.4, 'score': 72.0, 'weighted_score': 10.799999999999999}, 'yield_curve_slope': {'raw_value': -0.2, 'score': 26.666666666666668, 'weighted_score': 2.666666666666667}, 'housing_starts_yoy': {'raw_value': -5.0, 'score': 25.0, 'weighted_score': 2.5}}
confidence: 0.1386666666666666三、先行/同步/滞后指标体系
3.1 指标分类
经济指标按与经济周期的时序关系分为三类:
| 类型 | 特征 | 代表指标 | 用途 |
|---|---|---|---|
| 先行指标(Leading) | 在经济见顶/见底前 3-12个月转向 | PMI新订单、消费者信心、建筑许可、股价 | 预测周期拐点 |
| 同步指标(Coincident) | 与经济周期同步 | 工业生产、零售销售、非农就业、GDP | 确认当前周期位置 |
| 滞后指标(Lagging) | 在经济见顶/见底后转向 | 失业率、CPI、单位劳动力成本、商业贷款 | 确认历史拐点、过滤假信号 |
3.2 先行指标的扩散指数
此处有展示代码展开 ▼
python
import numpy as np
def diffusion_index(indicator_changes: np.ndarray) -> float:
"""
计算领先指标的扩散指数。
扩散指数 = (上升的指标数 / 总指标数) × 100
当DI > 50时,多数指标扩张;DI < 50时,多数收缩。
参数:
indicator_changes: 各指标的最新变化(正=扩张,负=收缩)
返回:
扩散指数 [0, 100]
"""
n_up = np.sum(indicator_changes > 0)
n_total = len(indicator_changes)
di = (n_up / n_total) * 100
return di
# ===== 合成数据最小示例(数据为模拟,机制真实)=====
_chg = np.array([0.3, -0.2, 0.5, 0.1, -0.4, 0.2, 0.6, -0.1, 0.2, 0.4])
print('扩散指数:', round(diffusion_index(_chg), 3))点击展开可浏览运行结果
扩散指数: 70.0
四、基于 HMM 的周期状态切换识别
4.1 隐马尔可夫模型(HMM)
HMM 非常适合建模经济周期,因为周期状态(衰退/扩张)是不可直接观测的隐状态,但可以通过经济指标(观测值)来推断。
此代码依赖 hmmlearn,浏览器环境不支持。 在 Colab 打开 ↗|本地运行:
bash setup.sh → jupyter lab此处有展示代码展开 ▼
python
from hmmlearn import hmm
import warnings
def hmm_cycle_detection(economic_data: np.ndarray,
n_states: int = 2,
n_iter: int = 1000) -> dict:
"""
使用隐马尔可夫模型识别经济周期状态的切换。
参数:
economic_data: (T, D) 经济指标矩阵
n_states: 隐状态数量(通常2=衰退/扩张,或4对应ML时钟)
n_iter: EM算法迭代次数
返回:
包含 state_probabilities, states, model 的字典
"""
# 数据标准化
data_std = (economic_data - np.mean(economic_data, axis=0)) / \
(np.std(economic_data, axis=0) + 1e-10)
# 拟合高斯HMM
model = hmm.GaussianHMM(
n_components=n_states,
covariance_type='full',
n_iter=n_iter,
random_state=42
)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
model.fit(data_std)
# 预测最可能的隐状态序列
hidden_states = model.predict(data_std)
# 计算状态概率(平滑后验概率)
state_probs = model.predict_proba(data_std)
# 状态标签解读
# 假设:高增长状态的均值更高
state_means = model.means_
growth_idx = np.argmax(state_means[:, 0]) # 增长维度均值最高的状态
recession_idx = 0 if growth_idx == 1 else 1
return {
'states': hidden_states,
'state_probabilities': state_probs,
'model': model,
'growth_state': growth_idx,
'recession_state': recession_idx,
'transition_matrix': model.transmat_,
'current_state': hidden_states[-1],
'recession_probability': state_probs[-1, recession_idx]
}点击展开可浏览运行结果
📘 本段代码定义了 1 个函数/类:函数 `hmm_cycle_detection`(使用隐马尔可夫模型识别经济周期状态的切换)。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。
4.2 周期转向概率
HMM 的状态转移矩阵给出了状态间切换的概率。这可用于量化周期转向的风险:
其中
HMM 方法的一个关键优势是概率化的信号输出,允许投资者进行渐进的仓位调整——当衰退概率从 10% 上升到 30% 时开始减仓,而不是等到确定性信号出现后才行动。
经济周期的定量识别为宏观资产配置提供了科学的决策依据。接下来我们将探讨如何将周期判断转化为具体的跨资产宏观交易策略。
常见误区
误区1:把美林时钟当精确时钟。 它是概念框架不是择时模型,现实中周期经常跳跃(衰退直接到过热)或停滞。
误区2:用同步指标判断拐点。 同步指标确认时市场往往已定价,拐点判断必须依赖先行指标(新订单、信贷脉冲等)。
误区3:HMM 状态数拍脑袋定。 状态数需要用 BIC 等准则和样本外稳定性检验,而不是事后看得清的"几段行情"。
误区4:忽视政策突变。 疫情、地缘冲突等外生冲击会瞬间改写周期位置,任何模型都需要人工否决通道。
本章要点
- 先行/同步/滞后指标体系是周期定位的骨架,先行指标决定拐点信号
- 周期阶段识别输出的是概率分布,不是硬标签
- HMM 等状态切换模型需对参数个数与样本外稳定性做严格检验
- 不同周期阶段资产表现分散化失效的可能要纳入预案
跨模块延伸:CTA 策略