Skip to content

What (One-liner)

Simplified Harding-Pagan cyclical turning-point detection on FRED GDPC1 quarterly GDP: 3-point local extrema + strict peak/trough alternation + ≥ 5-quarter min-phase filter → current expansion/recession phase + most recent peak/trough + number of quarters in the current phase.

How to use

Drop macro_cycle_v1 (GET /mfle/cycles/phase) or use macro_snapshot_v1 aggregation directly. Typical pattern:

macro_snapshot_v1 → institution_composite    (cycle_phase overlay)

      macro_cycle_v1 (direct call) → casa_strategy (macro context)

Core formulas

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)

Assumptions & applicability

Assumptions: US GDPC1 quarterly data + phase durations ≥ 5 quarters + 3-point local-extrema detection + no amplitude filter.

Applies to: Canvas macro overlay, expansion/recession phase estimation, estimating how long the current cycle has been running.

Does not apply to: intraday/monthly, NBER-official reproduction (multi-indicator committee), non-US, short series, scenarios requiring amplitude-filtered fine-grained cycle dating.

Input / Output contract

{limit_quarters?=80}{available, cycle_phase, cycle_peak_date, cycle_trough_date, quarters_in_phase, months_in_phase, turning_points[≤10], logic_lineage} (with reason when available=False).

Known limitations

  1. GDPC1 only: NBER uses multiple indicators (employment / IP / income) in committee judgment; this operator is a GDP-only simplification
  2. Simplified 3-point extrema: original Bry-Boschan uses a 2-quarter window + amplitude filter; this is the minimal simplification
  3. MIN_PHASE_QUARTERS=5 hard-coded: not configurable
  4. No amplitude filter: small noise peaks may be retained (fixture uses σ=0.1 smoothing)
  5. First turn requires i ≥ MIN_PHASE: early-series data points lost
  6. cycles_from_gdp truncated to last 10: full history via cycles_history endpoint
  7. Equal values (vals[i]==vals[i-1]) trigger BOTH peak and trough candidates (rare but possible)
  8. Series is sorted by index — irregular ts is garbage
  9. Non-US economies not supported

References

Harding-Pagan (2002) Dissecting the cycle (JME) + Bry-Boschan (1971) Cyclical Analysis (NBER) + Burns-Mitchell (1946) Measuring Business Cycles. See 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 current
  • history_path — cycles_history returns all turning points
  • fred_unavailable — available=False, empty turning_points
  • insufficient_data — 5 quarters (< 10) → available=False
  • determinism — 5 reruns identical
  • min_phase_quarters_locked — MIN_PHASE_QUARTERS constant == 5
  • phase_enum_contract — enum subset
  • months_derived_from_quarters — months == quarters * 3
  • turns_alternate_and_respect_min_phase — alternate + ≥ 5 quarters apart
  • logic_lineage_locked — ['harding_pagan', 'GDPC1']
  • truncates_to_last_10_turns — cycles_from_gdp caps at 10; history unlimited

Changelog

  • 1.0.0 (2026-04-20) — First Active (Tier 3 batch 2.1)

Verifiable intelligence for the decisions that demand scrutiny.