M2 Money Supply Signal
A reproducible specification for an M2‑based macro liquidity signal (FRED M2SL).
Abstract
This document formalises a rule‑based signal derived from the growth and short‑term momentum of the US M2 money stock (M2SL). We compute 12‑month and 3‑month percentage changes, then map observations to Bullish, Neutral, or Bearish regimes using transparent thresholds intended for macro‑risk applications (e.g., precious metals, cyclicals).
1. Data Sources
2. Preparation
3. Transformations
- Year‑over‑Year growth:
M2_{YoY,t} = 100 \times \frac{M2_t - M2_{t-12}}{M2_{t-12}}
- 3‑month momentum:
M2_{3M,t} = 100 \times \frac{M2_t - M2_{t-3}}{M2_{t-3}}
4. Signal Classification
5. Implementation Notes (Python)
# Fetch & prepare (no interpolation by default)
m2sl_df = fetch_fred_series_df('M2SL')
m2sl_df['Date'] = pd.to_datetime(m2sl_df['Date'])
m2sl_df['Value'] = pd.to_numeric(m2sl_df['Value'], errors='coerce')
a = m2sl_df.sort_values('Date').reset_index(drop=True)
a['missing_data_flag'] = a['Value'].isna()
# Transforms (YoY and 3M)
a['M2SL_YoY'] = a['Value'].pct_change(12) * 100
a['M2SL_3M_Change'] = a['Value'].pct_change(3) * 100
# Optional: rolling z-scores (example: 120 months = 10 years)
WINDOW = 120
a['M2SL_YoY_Z'] = (a['M2SL_YoY'] - a['M2SL_YoY'].rolling(WINDOW).mean()) / a['M2SL_YoY'].rolling(WINDOW).std()
a['M2SL_3M_Z'] = (a['M2SL_3M_Change'] - a['M2SL_3M_Change'].rolling(WINDOW).mean()) / a['M2SL_3M_Change'].rolling(WINDOW).std()
# Regime rules (baseline thresholds)
HIGH_YOY = 6.0; LOW_YOY = 2.0; ACC3M = 1.0; DEC3M = -1.0
def level_regime(yoy):
if pd.isna(yoy): return 'Neutral'
if yoy > HIGH_YOY: return 'Bullish'
if yoy < LOW_YOY: return 'Bearish'
return 'Neutral'
def momentum_regime(m3):
if pd.isna(m3): return 'Neutral'
if m3 > ACC3M: return 'Bullish'
if m3 < DEC3M: return 'Bearish'
return 'Neutral'
a['Level_Regime'] = a['M2SL_YoY'].apply(level_regime)
a['Momentum_Regime'] = a['M2SL_3M_Change'].apply(momentum_regime)
# Example overall aggregation: "worst-of" (risk-control)
order = {'Bearish':0,'Neutral':1,'Bullish':2}
inv = {v:k for k,v in order.items()}
a['Overall_Regime'] = a.apply(lambda r: inv[min(order[r['Level_Regime']], order[r['Momentum_Regime']])], axis=1)
6. Assumptions & Limitations
7. Reproducibility
- Persist source ID (
M2SL), retrieval timestamps, and library versions. - Log missing‑data treatment (interpolation, fills) and any threshold overrides.
- Store the output panel with
M2SL_YoY,M2SL_3Month_Change, andM2_Signalcolumns for audit.
8. Applications
Use as a standalone macro‑liquidity indicator or as an input to broader composites (e.g., the Federal Reserve Liquidity Composite). Helpful for conditioning risk‑on/off tilts in metals and cyclical assets.