做什么(One-liner)
基于 FRED GDPC1 季度 GDP 的 Harding-Pagan 简化版周期拐点识别:3-point 局部极值 + 严格 peak/trough 交替 + ≥ 5 季度 min-phase 过滤 → 当前 expansion/recession 阶段 + 最近 peak/trough + 所处季度数。
怎么用
Canvas 拖 macro_cycle_v1(GET /mfle/cycles/phase)或直接 macro_snapshot_v1 聚合。典型用法:
macro_snapshot_v1 → institution_composite (cycle_phase overlay)
↘
macro_cycle_v1 (直接拉) → casa_strategy (宏观 context)核心公式
Step 1: 3-point local extrema
peak[i] iff vals[i] >= vals[i-1] AND vals[i] >= vals[i+1]
trough[i] iff vals[i] <= vals[i-1] AND vals[i] <= vals[i+1]
Step 2: Strict alternation + MIN_PHASE=5 filter
keep if i-last_idx >= MIN_PHASE_QUARTERS
skip if same type as last kept
Step 3: Current phase (last turn relative to as_of)
last.type == 'trough' → 'expansion'
last.type == 'peak' → 'recession'
Step 4: Duration
quarters_in_phase = count(ts >= last_turn_date) - 1
months_in_phase = quarters_in_phase * 3
Step 5: Truncate to last 10 turns in cycles_from_gdp
(cycles_history keeps all)假设与适用场景
假设:美国 GDPC1 季度数据 + 持续阶段 ≥ 5 季度 + 3-point 局部极值检测 + 无振幅过滤。
适用:Canvas 宏观 overlay、扩张/衰退阶段估计、估算当前周期已经持续多久。
不适用:intraday/monthly、NBER 官方复现(多指标委员会)、非美国、短序列、需要 amplitude-filter 的精细周期。
输入 / 输出契约
{limit_quarters?=80} → {available, cycle_phase, cycle_peak_date, cycle_trough_date, quarters_in_phase, months_in_phase, turning_points[≤10], logic_lineage}(available=False 时附 reason)。
已知局限
- 仅 GDPC1:NBER 用多指标(就业 / IP / 收入)综合判断;本 operator 是 GDP-only 简化版
- 简化 3-point extrema:Bry-Boschan 原本用 2-quarter window + 振幅过滤;本实现是最小简化
- MIN_PHASE_QUARTERS=5 hardcoded:不可配置
- 无振幅过滤:小噪音峰可能被保留(fixture 用 σ=0.1 平滑)
- First turn 需 i ≥ MIN_PHASE:早期序列数据点丢失
- cycles_from_gdp 截断到 last 10:早期历史靠 cycles_history 端点
- Equal values (vals[i]==vals[i-1]) 同时触发 peak 和 trough 候选(罕见但可能)
- Series 按 index 排序 — 不规则 ts 会乱
- 非美国 economies 不支持
参考文献
Harding-Pagan (2002) Dissecting the cycle (JME) + Bry-Boschan (1971) Cyclical Analysis (NBER) + Burns-Mitchell (1946) Measuring Business Cycles。详见 frontmatter。
Golden Test
tests/golden/fixtures/tier3/macro_cycle/,monkey-patched FRED 60-quarter synthetic 3-cycle GDP (seed=123)。1e-6 tolerance。11 tests:
normal_path— 3 cycles synthetic → 3 turning points + recession currenthistory_path— cycles_history 返回所有拐点fred_unavailable— available=False, empty turning_pointsinsufficient_data— 5 quarters (< 10) → available=Falsedeterminism— 5 reruns 一致min_phase_quarters_locked— MIN_PHASE_QUARTERS 常量 == 5phase_enum_contract— enum subsetmonths_derived_from_quarters— months == quarters * 3turns_alternate_and_respect_min_phase— 交替 + ≥ 5 quarters apartlogic_lineage_locked— ['harding_pagan', 'GDPC1']truncates_to_last_10_turns— cycles_from_gdp 最多 10 个;history 不限
Changelog
- 1.0.0 (2026-04-20) — 首次 Active(Tier 3 batch 2.1)

