354 lines
12 KiB
Python
354 lines
12 KiB
Python
"""
|
|
FRED (Federal Reserve Economic Data) API Integration
|
|
Provides macro economic data critical for gold trading analysis.
|
|
"""
|
|
|
|
import os
|
|
import requests
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional
|
|
import time
|
|
|
|
|
|
class FREDDataProvider:
|
|
"""Federal Reserve Economic Data API provider for macro indicators."""
|
|
|
|
BASE_URL = "https://api.stlouisfed.org/fred"
|
|
|
|
# FRED Series IDs for key macro indicators
|
|
SERIES_IDS = {
|
|
# US Dollar Index
|
|
"DXY": "DTWEXBGS", # Trade Weighted U.S. Dollar Index: Broad, Goods and Services
|
|
"DXY_DAILY": "DTWEXBGS",
|
|
|
|
# Treasury Yields
|
|
"10Y_YIELD": "DGS10", # 10-Year Treasury Constant Maturity Rate
|
|
"2Y_YIELD": "DGS2", # 2-Year Treasury Constant Maturity Rate
|
|
"30Y_YIELD": "DGS30", # 30-Year Treasury Constant Maturity Rate
|
|
"10Y_TIPS": "DFII10", # 10-Year Treasury Inflation-Indexed Security
|
|
|
|
# Real Yields (calculated as nominal - inflation expectations)
|
|
"10Y_BREAKEVEN": "T10YIE", # 10-Year Breakeven Inflation Rate
|
|
|
|
# Inflation Indicators
|
|
"CPI": "CPIAUCSL", # Consumer Price Index for All Urban Consumers
|
|
"CORE_CPI": "CPILFESL", # CPI Less Food and Energy
|
|
"PCE": "PCEPI", # Personal Consumption Expenditures Price Index
|
|
"CORE_PCE": "PCEPILFE", # PCE Less Food and Energy (Fed's preferred)
|
|
"PPI": "PPIACO", # Producer Price Index
|
|
|
|
# Federal Reserve Policy
|
|
"FED_FUNDS": "FEDFUNDS", # Effective Federal Funds Rate
|
|
"FED_BALANCE": "WALCL", # Fed Balance Sheet (All Assets)
|
|
|
|
# Economic Indicators
|
|
"GDP": "GDP", # Gross Domestic Product
|
|
"UNEMPLOYMENT": "UNRATE", # Unemployment Rate
|
|
"RETAIL_SALES": "RSXFS", # Advance Retail Sales
|
|
|
|
# Market Indicators
|
|
"VIX": "VIXCLS", # CBOE Volatility Index (Fear Gauge)
|
|
"SP500": "SP500", # S&P 500 Index
|
|
}
|
|
|
|
def __init__(self, api_key: Optional[str] = None):
|
|
"""
|
|
Initialize FRED API provider.
|
|
|
|
Args:
|
|
api_key: FRED API key. If None, reads from FRED_API_KEY env variable.
|
|
"""
|
|
self.api_key = api_key or os.getenv("FRED_API_KEY")
|
|
if not self.api_key:
|
|
raise ValueError(
|
|
"FRED API key required. Set FRED_API_KEY environment variable or pass api_key parameter. "
|
|
"Get free API key at: https://fred.stlouisfed.org/docs/api/api_key.html"
|
|
)
|
|
|
|
self.session = requests.Session()
|
|
self.rate_limit_delay = 0.1 # 100ms between requests to respect rate limits
|
|
|
|
def _make_request(self, endpoint: str, params: dict) -> dict:
|
|
"""Make API request to FRED with error handling."""
|
|
params["api_key"] = self.api_key
|
|
params["file_type"] = "json"
|
|
|
|
url = f"{self.BASE_URL}/{endpoint}"
|
|
|
|
try:
|
|
time.sleep(self.rate_limit_delay) # Rate limiting
|
|
response = self.session.get(url, params=params, timeout=10)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except requests.exceptions.HTTPError as e:
|
|
if response.status_code == 400:
|
|
error_msg = response.json().get("error_message", str(e))
|
|
raise ValueError(f"FRED API error: {error_msg}")
|
|
elif response.status_code == 429:
|
|
raise Exception("FRED API rate limit exceeded. Please wait and try again.")
|
|
else:
|
|
raise Exception(f"FRED API HTTP error: {e}")
|
|
except requests.exceptions.RequestException as e:
|
|
raise Exception(f"FRED API request failed: {e}")
|
|
|
|
def get_series(
|
|
self,
|
|
series_id: str,
|
|
start_date: str,
|
|
end_date: str,
|
|
frequency: Optional[str] = None
|
|
) -> str:
|
|
"""
|
|
Get time series data from FRED.
|
|
|
|
Args:
|
|
series_id: FRED series ID or friendly name (e.g., "DXY", "10Y_YIELD")
|
|
start_date: Start date in YYYY-MM-DD format
|
|
end_date: End date in YYYY-MM-DD format
|
|
frequency: Optional frequency (d=daily, w=weekly, m=monthly, q=quarterly, a=annual)
|
|
|
|
Returns:
|
|
CSV-formatted string with date,value columns
|
|
"""
|
|
# Resolve friendly name to FRED series ID
|
|
resolved_id = self.SERIES_IDS.get(series_id.upper(), series_id)
|
|
|
|
params = {
|
|
"series_id": resolved_id,
|
|
"observation_start": start_date,
|
|
"observation_end": end_date,
|
|
}
|
|
|
|
if frequency:
|
|
params["frequency"] = frequency
|
|
|
|
data = self._make_request("series/observations", params)
|
|
|
|
# Convert to CSV format
|
|
observations = data.get("observations", [])
|
|
if not observations:
|
|
return f"# No data available for {series_id} from {start_date} to {end_date}\n"
|
|
|
|
csv_lines = [f"# FRED Series: {resolved_id} ({series_id})"]
|
|
csv_lines.append(f"# Date range: {start_date} to {end_date}")
|
|
csv_lines.append(f"# Total observations: {len(observations)}")
|
|
csv_lines.append("")
|
|
csv_lines.append("date,value")
|
|
|
|
for obs in observations:
|
|
if obs["value"] != ".": # FRED uses "." for missing values
|
|
csv_lines.append(f"{obs['date']},{obs['value']}")
|
|
|
|
return "\n".join(csv_lines)
|
|
|
|
def get_real_yield(self, start_date: str, end_date: str) -> str:
|
|
"""
|
|
Calculate real yield (10Y nominal - 10Y breakeven inflation).
|
|
|
|
Real yields are critical for gold: negative real yields = bullish for gold.
|
|
|
|
Args:
|
|
start_date: Start date in YYYY-MM-DD format
|
|
end_date: End date in YYYY-MM-DD format
|
|
|
|
Returns:
|
|
CSV-formatted string with date,real_yield,nominal_yield,breakeven_inflation
|
|
"""
|
|
# Get 10Y Treasury yield
|
|
nominal_data = self._make_request("series/observations", {
|
|
"series_id": self.SERIES_IDS["10Y_YIELD"],
|
|
"observation_start": start_date,
|
|
"observation_end": end_date,
|
|
})
|
|
|
|
# Get 10Y breakeven inflation
|
|
breakeven_data = self._make_request("series/observations", {
|
|
"series_id": self.SERIES_IDS["10Y_BREAKEVEN"],
|
|
"observation_start": start_date,
|
|
"observation_end": end_date,
|
|
})
|
|
|
|
# Create date-indexed dictionaries
|
|
nominal_dict = {obs["date"]: float(obs["value"])
|
|
for obs in nominal_data.get("observations", [])
|
|
if obs["value"] != "."}
|
|
|
|
breakeven_dict = {obs["date"]: float(obs["value"])
|
|
for obs in breakeven_data.get("observations", [])
|
|
if obs["value"] != "."}
|
|
|
|
# Calculate real yields
|
|
csv_lines = ["# Real Yield Calculation (10Y Nominal - 10Y Breakeven Inflation)"]
|
|
csv_lines.append(f"# Date range: {start_date} to {end_date}")
|
|
csv_lines.append("")
|
|
csv_lines.append("date,real_yield,nominal_yield,breakeven_inflation")
|
|
|
|
# Get common dates
|
|
common_dates = sorted(set(nominal_dict.keys()) & set(breakeven_dict.keys()))
|
|
|
|
for date in common_dates:
|
|
nominal = nominal_dict[date]
|
|
breakeven = breakeven_dict[date]
|
|
real_yield = nominal - breakeven
|
|
csv_lines.append(f"{date},{real_yield:.4f},{nominal:.4f},{breakeven:.4f}")
|
|
|
|
return "\n".join(csv_lines)
|
|
|
|
def get_dxy_analysis(self, start_date: str, end_date: str) -> str:
|
|
"""
|
|
Get US Dollar Index with technical context.
|
|
|
|
Args:
|
|
start_date: Start date in YYYY-MM-DD format
|
|
end_date: End date in YYYY-MM-DD format
|
|
|
|
Returns:
|
|
CSV with DXY values and trend analysis
|
|
"""
|
|
return self.get_series("DXY", start_date, end_date)
|
|
|
|
def get_inflation_summary(self, start_date: str, end_date: str) -> str:
|
|
"""
|
|
Get comprehensive inflation data (CPI, Core CPI, PCE, Core PCE).
|
|
|
|
Args:
|
|
start_date: Start date in YYYY-MM-DD format
|
|
end_date: End date in YYYY-MM-DD format
|
|
|
|
Returns:
|
|
CSV with multiple inflation indicators
|
|
"""
|
|
indicators = ["CPI", "CORE_CPI", "PCE", "CORE_PCE"]
|
|
|
|
csv_lines = [f"# Inflation Indicators Summary"]
|
|
csv_lines.append(f"# Date range: {start_date} to {end_date}")
|
|
csv_lines.append("")
|
|
csv_lines.append("date,CPI,Core_CPI,PCE,Core_PCE")
|
|
|
|
# Fetch all series
|
|
data_dict = {}
|
|
for indicator in indicators:
|
|
data = self._make_request("series/observations", {
|
|
"series_id": self.SERIES_IDS[indicator],
|
|
"observation_start": start_date,
|
|
"observation_end": end_date,
|
|
})
|
|
|
|
for obs in data.get("observations", []):
|
|
if obs["value"] != ".":
|
|
date = obs["date"]
|
|
if date not in data_dict:
|
|
data_dict[date] = {}
|
|
data_dict[date][indicator] = obs["value"]
|
|
|
|
# Build CSV
|
|
for date in sorted(data_dict.keys()):
|
|
row = data_dict[date]
|
|
csv_lines.append(
|
|
f"{date},"
|
|
f"{row.get('CPI', '')},"
|
|
f"{row.get('CORE_CPI', '')},"
|
|
f"{row.get('PCE', '')},"
|
|
f"{row.get('CORE_PCE', '')}"
|
|
)
|
|
|
|
return "\n".join(csv_lines)
|
|
|
|
def get_series_info(self, series_id: str) -> dict:
|
|
"""Get metadata about a FRED series."""
|
|
resolved_id = self.SERIES_IDS.get(series_id.upper(), series_id)
|
|
return self._make_request("series", {"series_id": resolved_id})
|
|
|
|
|
|
# Standalone functions for tool integration
|
|
_fred_provider = None
|
|
|
|
def _get_fred_provider():
|
|
"""Get or create singleton FRED provider."""
|
|
global _fred_provider
|
|
if _fred_provider is None:
|
|
_fred_provider = FREDDataProvider()
|
|
return _fred_provider
|
|
|
|
|
|
def get_fred_series(
|
|
series: str,
|
|
start_date: str,
|
|
end_date: str,
|
|
frequency: Optional[str] = None
|
|
) -> str:
|
|
"""
|
|
Get macro economic data from FRED.
|
|
|
|
Supported series (use friendly names):
|
|
- DXY: US Dollar Index
|
|
- 10Y_YIELD, 2Y_YIELD, 30Y_YIELD: Treasury yields
|
|
- 10Y_TIPS: Inflation-protected securities
|
|
- 10Y_BREAKEVEN: Inflation expectations
|
|
- CPI, CORE_CPI, PCE, CORE_PCE: Inflation indicators
|
|
- FED_FUNDS: Federal Funds Rate
|
|
- VIX: Volatility index
|
|
|
|
Args:
|
|
series: Series ID or friendly name (e.g., "DXY", "10Y_YIELD", "CPI")
|
|
start_date: Start date (YYYY-MM-DD)
|
|
end_date: End date (YYYY-MM-DD)
|
|
frequency: Optional frequency (d=daily, w=weekly, m=monthly)
|
|
|
|
Returns:
|
|
CSV string with economic data
|
|
"""
|
|
provider = _get_fred_provider()
|
|
return provider.get_series(series, start_date, end_date, frequency)
|
|
|
|
|
|
def get_real_yields(start_date: str, end_date: str) -> str:
|
|
"""
|
|
Calculate real yields (nominal yield - inflation expectations).
|
|
|
|
Real yields are the opportunity cost of holding gold.
|
|
Negative real yields = bullish for gold (no cost to hold non-yielding asset).
|
|
|
|
Args:
|
|
start_date: Start date (YYYY-MM-DD)
|
|
end_date: End date (YYYY-MM-DD)
|
|
|
|
Returns:
|
|
CSV with real_yield, nominal_yield, breakeven_inflation columns
|
|
"""
|
|
provider = _get_fred_provider()
|
|
return provider.get_real_yield(start_date, end_date)
|
|
|
|
|
|
def get_inflation_data(start_date: str, end_date: str) -> str:
|
|
"""
|
|
Get comprehensive inflation indicators (CPI, Core CPI, PCE, Core PCE).
|
|
|
|
Args:
|
|
start_date: Start date (YYYY-MM-DD)
|
|
end_date: End date (YYYY-MM-DD)
|
|
|
|
Returns:
|
|
CSV with multiple inflation metrics
|
|
"""
|
|
provider = _get_fred_provider()
|
|
return provider.get_inflation_summary(start_date, end_date)
|
|
|
|
|
|
def get_dxy_data(start_date: str, end_date: str) -> str:
|
|
"""
|
|
Get US Dollar Index (DXY) data.
|
|
|
|
DXY has strong negative correlation with gold (~-0.7 to -0.9).
|
|
Rising DXY = headwind for gold, Falling DXY = tailwind for gold.
|
|
|
|
Args:
|
|
start_date: Start date (YYYY-MM-DD)
|
|
end_date: End date (YYYY-MM-DD)
|
|
|
|
Returns:
|
|
CSV with DXY values
|
|
"""
|
|
provider = _get_fred_provider()
|
|
return provider.get_dxy_analysis(start_date, end_date)
|