import requests import json from datetime import datetime, timedelta from typing import Annotated, Dict, List, Optional from .config import get_api_key, DATA_DIR import os import pandas as pd def get_fred_api_key(): """Get FRED API key from config or environment""" try: api_key = get_api_key("fred_api_key", "FRED_API_KEY") except: api_key = None if not api_key: api_key = os.getenv("FRED_API_KEY") return api_key def get_fred_data(series_id: str, start_date: str, end_date: str) -> Dict: """ Get economic data from FRED API Args: series_id: FRED series ID (e.g., 'FEDFUNDS', 'CPIAUCSL') start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format Returns: Dictionary with FRED data """ api_key = get_fred_api_key() if not api_key: return {"error": "FRED API key not found. Please set FRED_API_KEY environment variable."} url = "https://api.stlouisfed.org/fred/series/observations" params = { 'series_id': series_id, 'api_key': api_key, 'file_type': 'json', 'observation_start': start_date, 'observation_end': end_date, 'sort_order': 'desc', 'limit': 100 } try: response = requests.get(url, params=params) response.raise_for_status() return response.json() except Exception as e: return {"error": f"Failed to fetch FRED data for {series_id}: {str(e)}"} def get_treasury_yield_curve(curr_date: str) -> str: """ Get current Treasury yield curve data Args: curr_date: Current date in YYYY-MM-DD format Returns: Formatted string with yield curve data """ # Treasury yield series IDs yield_series = { "1 Month": "DGS1MO", "3 Month": "DGS3MO", "6 Month": "DGS6MO", "1 Year": "DGS1", "2 Year": "DGS2", "3 Year": "DGS3", "5 Year": "DGS5", "7 Year": "DGS7", "10 Year": "DGS10", "20 Year": "DGS20", "30 Year": "DGS30" } start_date = (datetime.strptime(curr_date, "%Y-%m-%d") - timedelta(days=30)).strftime("%Y-%m-%d") result = f"## Treasury Yield Curve as of {curr_date}\n\n" yield_data = [] for maturity, series_id in yield_series.items(): data = get_fred_data(series_id, start_date, curr_date) if "error" in data: continue observations = data.get("observations", []) if observations: latest = observations[0] if latest.get("value") != ".": yield_data.append({ "maturity": maturity, "yield": float(latest["value"]), "date": latest["date"] }) if yield_data: result += "| Maturity | Yield (%) | Date |\n" result += "|----------|-----------|------|\n" for item in yield_data: result += f"| {item['maturity']} | {item['yield']:.2f}% | {item['date']} |\n" # Calculate yield curve analysis result += "\n### Yield Curve Analysis\n" # Find 2Y and 10Y for inversion check two_year = next((item for item in yield_data if item["maturity"] == "2 Year"), None) ten_year = next((item for item in yield_data if item["maturity"] == "10 Year"), None) if two_year and ten_year: spread = ten_year["yield"] - two_year["yield"] result += f"- **2Y-10Y Spread**: {spread:.2f} basis points\n" if spread < 0: result += "- **⚠️ INVERTED YIELD CURVE**: Potential recession signal\n" elif spread < 50: result += "- **📊 FLAT YIELD CURVE**: Economic uncertainty\n" else: result += "- **📈 NORMAL YIELD CURVE**: Healthy economic expectations\n" else: result += "No recent yield curve data available.\n" return result def get_economic_indicators_report(curr_date: str, lookback_days: int = 90) -> str: """ Get comprehensive economic indicators report Args: curr_date: Current date in YYYY-MM-DD format lookback_days: How many days to look back for data Returns: Formatted string with economic indicators """ start_date = (datetime.strptime(curr_date, "%Y-%m-%d") - timedelta(days=lookback_days)).strftime("%Y-%m-%d") # Key economic indicators indicators = { "Federal Funds Rate": { "series": "FEDFUNDS", "description": "Federal Reserve's target interest rate", "unit": "%" }, "Consumer Price Index (CPI)": { "series": "CPIAUCSL", "description": "Inflation measure based on consumer goods", "unit": "Index", "yoy": True }, "Producer Price Index (PPI)": { "series": "PPIACO", "description": "Inflation measure at producer level", "unit": "Index", "yoy": True }, "Unemployment Rate": { "series": "UNRATE", "description": "Percentage of labor force unemployed", "unit": "%" }, "Nonfarm Payrolls": { "series": "PAYEMS", "description": "Monthly change in employment", "unit": "Thousands", "mom": True }, "GDP Growth Rate": { "series": "GDP", "description": "Gross Domestic Product growth", "unit": "Billions", "qoq": True }, "ISM Manufacturing PMI": { "series": "NAPM", "description": "Manufacturing sector health indicator", "unit": "Index" }, "Consumer Confidence": { "series": "CSCICP03USM665S", "description": "Consumer sentiment indicator", "unit": "Index" }, "VIX": { "series": "VIXCLS", "description": "Market volatility index", "unit": "Index" } } result = f"## Economic Indicators Report ({start_date} to {curr_date})\n\n" for indicator_name, config in indicators.items(): data = get_fred_data(config["series"], start_date, curr_date) if "error" in data: result += f"### {indicator_name}\n**Error**: {data['error']}\n\n" continue observations = data.get("observations", []) if not observations: result += f"### {indicator_name}\n**No data available**\n\n" continue # Filter out missing values valid_obs = [obs for obs in observations if obs.get("value") != "."] if not valid_obs: result += f"### {indicator_name}\n**No valid data available**\n\n" continue latest = valid_obs[0] latest_value = float(latest["value"]) latest_date = latest["date"] result += f"### {indicator_name}\n" result += f"- **Latest Value**: {latest_value:.2f} {config['unit']} (as of {latest_date})\n" result += f"- **Description**: {config['description']}\n" # Calculate changes if we have enough data if len(valid_obs) >= 2: previous = valid_obs[1] previous_value = float(previous["value"]) change = latest_value - previous_value change_pct = (change / previous_value) * 100 if previous_value != 0 else 0 result += f"- **Change**: {change:+.2f} {config['unit']} ({change_pct:+.2f}%)\n" result += f"- **Previous**: {previous_value:.2f} {config['unit']} (as of {previous['date']})\n" # Calculate year-over-year change for inflation indicators if config.get("yoy") and len(valid_obs) >= 12: year_ago = valid_obs[11] if len(valid_obs) > 11 else valid_obs[-1] year_ago_value = float(year_ago["value"]) yoy_change = ((latest_value - year_ago_value) / year_ago_value) * 100 result += f"- **Year-over-Year**: {yoy_change:+.2f}%\n" # Add interpretation if indicator_name == "Federal Funds Rate": if latest_value > 4.0: result += "- **💡 Analysis**: Restrictive monetary policy stance\n" elif latest_value < 2.0: result += "- **💡 Analysis**: Accommodative monetary policy stance\n" else: result += "- **💡 Analysis**: Neutral monetary policy stance\n" elif "CPI" in indicator_name or "PPI" in indicator_name: if len(valid_obs) >= 12: if yoy_change > 3.0: result += "- **💡 Analysis**: Above Fed's 2% inflation target\n" elif yoy_change < 1.0: result += "- **💡 Analysis**: Below Fed's 2% inflation target\n" else: result += "- **💡 Analysis**: Near Fed's 2% inflation target\n" elif indicator_name == "Unemployment Rate": if latest_value < 4.0: result += "- **💡 Analysis**: Very low unemployment, tight labor market\n" elif latest_value > 6.0: result += "- **💡 Analysis**: Elevated unemployment, loose labor market\n" else: result += "- **💡 Analysis**: Moderate unemployment levels\n" elif "PMI" in indicator_name: if latest_value > 50: result += "- **💡 Analysis**: Expanding manufacturing sector\n" else: result += "- **💡 Analysis**: Contracting manufacturing sector\n" elif indicator_name == "VIX": if latest_value > 30: result += "- **💡 Analysis**: High market volatility/fear\n" elif latest_value < 15: result += "- **💡 Analysis**: Low market volatility/complacency\n" else: result += "- **💡 Analysis**: Moderate market volatility\n" result += "\n" return result def get_fed_calendar_and_minutes(curr_date: str) -> str: """ Get Federal Reserve meeting calendar and recent minutes Args: curr_date: Current date in YYYY-MM-DD format Returns: Formatted string with Fed calendar information """ result = f"## Federal Reserve Calendar & Policy Updates\n\n" # Get recent Fed Funds rate data to show policy trajectory start_date = (datetime.strptime(curr_date, "%Y-%m-%d") - timedelta(days=365)).strftime("%Y-%m-%d") fed_data = get_fred_data("FEDFUNDS", start_date, curr_date) if "error" not in fed_data: observations = fed_data.get("observations", []) valid_obs = [obs for obs in observations if obs.get("value") != "."] if valid_obs and len(valid_obs) >= 2: result += "### Recent Federal Funds Rate History\n" result += "| Date | Rate (%) | Change |\n" result += "|------|----------|--------|\n" for i, obs in enumerate(valid_obs[:6]): # Show last 6 observations rate = float(obs["value"]) if i < len(valid_obs) - 1: prev_rate = float(valid_obs[i + 1]["value"]) change = rate - prev_rate change_str = f"{change:+.2f}%" if change != 0 else "No change" else: change_str = "-" result += f"| {obs['date']} | {rate:.2f}% | {change_str} |\n" result += "\n" # Fed meeting schedule (approximate - would need real Fed calendar API) result += "### 2025 FOMC Meeting Schedule\n" result += "- **January 28-29**: FOMC Meeting\n" result += "- **March 18-19**: FOMC Meeting\n" result += "- **April 29-30**: FOMC Meeting\n" result += "- **June 10-11**: FOMC Meeting\n" result += "- **July 29-30**: FOMC Meeting\n" result += "- **September 16-17**: FOMC Meeting\n" result += "- **October 28-29**: FOMC Meeting\n" result += "- **December 16-17**: FOMC Meeting\n\n" result += "### Key Policy Considerations\n" result += "- **Dual Mandate**: Maximum employment and price stability\n" result += "- **Inflation Target**: 2% annual PCE inflation\n" result += "- **Balance Sheet**: Quantitative tightening operations\n" result += "- **Forward Guidance**: Communication of future policy intentions\n\n" result += "### Recent Economic Projections Summary\n" result += "- Monitor Fed dot plot for interest rate projections\n" result += "- Watch for changes in economic growth forecasts\n" result += "- Track inflation expectations updates\n" result += "- Observe unemployment rate projections\n\n" return result def get_macro_economic_summary(curr_date: str, lookback_days: int = 90) -> str: """ Get comprehensive macro economic summary combining economic indicators, yield curves, and Fed data Args: curr_date: Current date in YYYY-MM-DD format lookback_days: How many days to look back for data Returns: Complete macro economic analysis """ result = f"# Macro Economic Analysis - {curr_date}\n\n" # Get all components indicators_report = get_economic_indicators_report(curr_date, lookback_days) yield_curve = get_treasury_yield_curve(curr_date) fed_calendar = get_fed_calendar_and_minutes(curr_date) # Combine all reports result += indicators_report + "\n" result += yield_curve + "\n" result += fed_calendar + "\n" # Add trading implications result += "## Trading Implications\n\n" result += "### Interest Rate Environment\n" result += "- **Rising Rates**: Favor financials, pressure growth stocks\n" result += "- **Falling Rates**: Support growth stocks, pressure financials\n" result += "- **Yield Curve**: Inversion signals recession risk\n\n" result += "### Inflation Impact\n" result += "- **High Inflation**: Favor commodities, real assets\n" result += "- **Low Inflation**: Support bonds, growth stocks\n" result += "- **Deflation Risk**: Flight to quality assets\n\n" result += "### Economic Growth\n" result += "- **Strong Growth**: Favor cyclical sectors\n" result += "- **Weak Growth**: Favor defensive sectors\n" result += "- **Recession Risk**: Increase cash, quality focus\n\n" result += "### Market Volatility\n" result += "- **High VIX**: Opportunity for contrarian plays\n" result += "- **Low VIX**: Risk of complacency\n" result += "- **Vol Regime Change**: Adjust position sizing\n\n" return result