Why Combine All Four Signals?
Each of the four signals we've built tells you something different about the economy:
-
📈Yield Curve — Are investors worried about the future? (inverted = concern)
-
📊Credit Spreads — Are lenders afraid companies will default? (wide = fear)
-
🏦Real Rates — Is money actually expensive or cheap in real terms? (high = tight)
-
💹Breakevens — Does the market expect inflation to stay elevated? (high = concern)
No single signal is reliable alone. But when multiple signals align — when the yield curve is inverted AND spreads are widening AND real rates are elevated — the macro picture is significantly clearer.
The macro signal generator combines all four into one daily output: Green (conditions broadly healthy), Yellow (mixed signals, watch carefully), or Red (multiple indicators flashing concern).
How the Scoring Works
Each of the four signals gets scored on a simple -1 / 0 / +1 scale:
-
🟢+1 (Green): signal looks healthy
-
🟡0 (Yellow): signal is neutral or borderline
-
🔴-1 (Red): signal is flashing concern
The four scores are averaged. The final verdict:
-
🟢Average > +0.3 → Green (macro broadly supportive)
-
🟡Average between -0.3 and +0.3 → Yellow (mixed, stay alert)
-
🔴Average < -0.3 → Red (multiple concerns active)
Scoring Criteria Table
| Signal | Green (+1) | Yellow (0) | Red (-1) |
|---|---|---|---|
| 2s10s Spread | > +0.25% | -0.25% to +0.25% | < -0.25% (inverted) |
| HY Credit Spread | < 400 bps | 400–600 bps | > 600 bps |
| 10yr Real Rate | < 1.5% | 1.5% – 2.5% | > 2.5% |
| 10yr Breakeven | 1.75% – 2.5% | 2.5% – 2.75% | > 2.75% or < 1.75% |
The Complete fi-config.yaml
Here's the full config that drives the entire Fixed Income series:
# fi-config.yaml — Complete OpenClaw Fixed Income Config
# This single file drives all five modules in the Fixed Income series
fred:
api_key: "your_fred_api_key_here"
# Free key: https://fred.stlouisfed.org/docs/api/api_key.html
# ── Yield curve ───────────────────────────────────────────────────
yield_curve:
maturities:
- { label: "2-Year", series: "DGS2" }
- { label: "5-Year", series: "DGS5" }
- { label: "10-Year", series: "DGS10" }
- { label: "30-Year", series: "DGS30" }
alerts:
inversion: true
spread_threshold: -0.25
# ── Credit spreads ────────────────────────────────────────────────
credit_spreads:
investment_grade: "BAMLC0A0CM"
high_yield: "BAMLH0A0HYM2"
alerts:
ig_weekly_move: 50
hy_weekly_move: 150
hy_danger_level: 700
# ── Rates ─────────────────────────────────────────────────────────
rates:
fed_funds: "FEDFUNDS"
sofr: "SOFR"
real_rate: "DFII10"
ten_year: "DGS10"
alerts:
real_rate_threshold: 2.5
# ── Breakevens ────────────────────────────────────────────────────
breakevens:
five_year: "T5YIE"
ten_year: "T10YIE"
forward: "T5YIFR"
cpi_series: "CPIAUCSL"
alerts:
weekly_move: 0.15
above_fed_target: 2.75
# ── Macro signal scoring thresholds ──────────────────────────────
macro_signal:
yield_curve:
green: 0.25 # 2s10s spread above this = green
red: -0.25 # 2s10s spread below this = red
hy_spread_bps:
green: 400
red: 600
real_rate:
green: 1.5
red: 2.5
breakeven_10yr:
green_min: 1.75
green_max: 2.50
red_high: 2.75
red_low: 1.75
# ── Alert delivery ────────────────────────────────────────────────
alerts:
email: "you@example.com"
daily_digest: true
send_time: "07:00"
# ── Scheduler ─────────────────────────────────────────────────────
scheduler:
morning_brief:
script: fi_daily_brief.py
schedule: "0 7 * * 1-5"
This is the unified config that every part of the Fixed Income series consumes. One key, one config, five separate but coordinated monitoring modules.
The Macro Signal Script
Here's the Python script that combines all four signals into one daily verdict:
# fi_daily_brief.py — OpenClaw Fixed Income Daily Brief
# Combines all four signals into one macro verdict
import yaml
from fredapi import Fred
import pandas as pd
import smtplib
from email.mime.text import MIMEText
from datetime import datetime
def load_config(path: str = "fi-config.yaml") -> dict:
with open(path) as f:
return yaml.safe_load(f)
cfg = load_config()
fred = Fred(api_key=cfg["fred"]["api_key"])
MS = cfg["macro_signal"]
# ── Fetch all four signals ────────────────────────────────────────
def fetch_all_signals() -> dict:
"""
Pulls the latest values for all four signal categories.
Returns a dict with current values for scoring.
"""
signals = {}
# 1. Yield curve — 2s10s spread
try:
two_yr = float(fred.get_series("DGS2").dropna().iloc[-1])
ten_yr = float(fred.get_series("DGS10").dropna().iloc[-1])
signals["spread_2s10s"] = round(ten_yr - two_yr, 3)
signals["two_year"] = two_yr
signals["ten_year"] = ten_yr
except Exception as e:
signals["spread_2s10s"] = 0
# 2. High yield credit spread
try:
hy = fred.get_series("BAMLH0A0HYM2").dropna().iloc[-1]
signals["hy_spread_bps"] = round(float(hy) * 100, 1)
except Exception:
signals["hy_spread_bps"] = 500
# 3. Real rate
try:
rr = fred.get_series("DFII10").dropna().iloc[-1]
signals["real_rate"] = round(float(rr), 3)
except Exception:
signals["real_rate"] = 1.0
# 4. 10yr breakeven
try:
be = fred.get_series("T10YIE").dropna().iloc[-1]
signals["breakeven_10yr"] = round(float(be), 3)
except Exception:
signals["breakeven_10yr"] = 2.3
return signals
# ── Score each signal ─────────────────────────────────────────────
def score_signals(signals: dict) -> dict:
"""
Scores each signal on a -1 (red) / 0 (yellow) / +1 (green) scale.
"""
scores = {}
# Yield curve
spread = signals["spread_2s10s"]
if spread > MS["yield_curve"]["green"]:
scores["yield_curve"] = (1, "🟢", f"2s10s at {spread:+.3f}% — normal, positive slope")
elif spread < MS["yield_curve"]["red"]:
scores["yield_curve"] = (-1, "🔴", f"2s10s at {spread:+.3f}% — INVERTED")
else:
scores["yield_curve"] = (0, "🟡", f"2s10s at {spread:+.3f}% — near flat, watch closely")
# HY credit spreads
hy = signals["hy_spread_bps"]
if hy < MS["hy_spread_bps"]["green"]:
scores["credit_spreads"] = (1, "🟢", f"HY spreads {hy:.0f} bps — normal range")
elif hy > MS["hy_spread_bps"]["red"]:
scores["credit_spreads"] = (-1, "🔴", f"HY spreads {hy:.0f} bps — elevated stress")
else:
scores["credit_spreads"] = (0, "🟡", f"HY spreads {hy:.0f} bps — moderately elevated")
# Real rates
rr = signals["real_rate"]
if rr < MS["real_rate"]["green"]:
scores["real_rates"] = (1, "🟢", f"Real rate {rr:.3f}% — accommodative")
elif rr > MS["real_rate"]["red"]:
scores["real_rates"] = (-1, "🔴", f"Real rate {rr:.3f}% — restrictive territory")
else:
scores["real_rates"] = (0, "🟡", f"Real rate {rr:.3f}% — moderate")
# Breakevens
be = signals["breakeven_10yr"]
if MS["breakeven_10yr"]["green_min"] <= be <= MS["breakeven_10yr"]["green_max"]:
scores["breakevens"] = (1, "🟢", f"10yr breakeven {be:.3f}% — anchored near Fed target")
elif be > MS["breakeven_10yr"]["red_high"] or be < MS["breakeven_10yr"]["red_low"]:
scores["breakevens"] = (-1, "🔴", f"10yr breakeven {be:.3f}% — outside normal range")
else:
scores["breakevens"] = (0, "🟡", f"10yr breakeven {be:.3f}% — slightly elevated")
return scores
# ── Determine overall verdict ─────────────────────────────────────
def get_verdict(scores: dict) -> tuple:
avg = sum(s[0] for s in scores.values()) / len(scores)
if avg > 0.3:
return ("🟢 GREEN", "Macro conditions broadly supportive", avg)
elif avg < -0.3:
return ("🔴 RED", "Multiple macro concerns active — elevated caution warranted", avg)
else:
return ("🟡 YELLOW", "Mixed signals — monitor closely, no clear directional bias", avg)
# ── Print the full brief ──────────────────────────────────────────
def print_macro_brief():
signals = fetch_all_signals()
scores = score_signals(signals)
verdict, description, avg_score = get_verdict(scores)
print(f"\n{'🦞 OpenClaw Fixed Income Daily Brief':=^58}")
print(f"{'Generated: ' + datetime.now().strftime('%Y-%m-%d %H:%M'):^58}\n")
print(" SIGNAL SCORES")
print(" " + "─" * 54)
labels = {
"yield_curve": "Yield Curve (2s10s)",
"credit_spreads": "Credit Spreads (HY)",
"real_rates": "Real Rates (10yr)",
"breakevens": "Inflation Breakevens",
}
for key, label in labels.items():
val, icon, detail = scores[key]
print(f" {icon} {label:<26} {detail}")
print(f"\n{'─'*58}")
print(f" MACRO VERDICT: {verdict}")
print(f" {description}")
print(f" (Composite score: {avg_score:+.2f})")
print(f"{'':=^58}\n")
print_macro_brief()
This script is the payoff — it pulls fresh data from FRED for all four signals, scores each one, computes an average, and delivers a clear traffic light verdict. It's config-driven, deterministic, and ready to automate.
Sample Output
🦞 OpenClaw Fixed Income Daily Brief
══════════════════════════════════════════════════════════
Generated: 2025-03-14 07:03
SIGNAL SCORES
──────────────────────────────────────────────────────
🟡 Yield Curve (2s10s) 2s10s at -0.40% — INVERTED
🟢 Credit Spreads (HY) HY spreads 385 bps — normal range
🟢 Real Rates (10yr) Real rate 1.84% — accommodative
🟡 Inflation Breakevens 10yr breakeven 2.45% — slightly elevated
──────────────────────────────────────────────────────
MACRO VERDICT: 🟡 YELLOW
Mixed signals — monitor closely, no clear directional bias
(Composite score: +0.00)
══════════════════════════════════════════════════════════
In this snapshot, we see a mixed picture: yield curve inverted (concerning), spreads tight (healthy), real rates moderate (accommodative), but breakevens slightly hot. The score averages to zero — Yellow. Watch, but no clear alarm.
Automating the Morning Brief
Schedule the daily brief to run before US markets open with a simple cron entry:
# Run at 7am Mon–Fri before US markets open
0 7 * * 1-5 cd /path/to/openclaw && python3 fi_daily_brief.py >> fi_brief.log 2>&1
The script logs its output. Pipe it to email, Slack, or a dashboard. The signal is now part of your morning routine — every trading day, 7am sharp, one unified macro verdict in your inbox.
You've Completed the OpenClaw Fixed Income & Yield Curve Series
- ✅ Part 1 — Yield Curve Monitor: Daily 2s10s spread + inversion alerts
- ✅ Part 2 — Credit Spreads: IG and HY spread monitoring + risk-off alerts
- ✅ Part 3 — Rates Dashboard: Fed Funds, SOFR, real rates morning snapshot
- ✅ Part 4 — Inflation Breakevens: Market's inflation forecast vs actual CPI
- ✅ Part 5 — Macro Signal Generator: All four signals → one daily verdict
All driven by a single fi-config.yaml. One key, one config, five automated briefings.
You now have a production-grade fixed income monitoring suite. Every morning, OpenClaw reads the same signals professional traders watch — yield curves, credit spreads, real rates, inflation expectations — and delivers a single, unified macro call to your inbox. Green, Yellow, or Red. That's the signal. That's the advantage.
Ready to monitor other markets? Return to the OpenClaw Terminal to explore other modules and automation chains.