Spaces:
Sleeping
Sleeping
| """ | |
| FRED (Federal Reserve Economic Data) client for fetching risk-free interest rates. | |
| """ | |
| import os | |
| import ssl | |
| from typing import Optional | |
| from datetime import datetime, timedelta | |
| from fredapi import Fred | |
| from dotenv import load_dotenv | |
| import certifi | |
| # Load environment variables | |
| load_dotenv() | |
| # Fix SSL certificate verification for macOS | |
| os.environ['SSL_CERT_FILE'] = certifi.where() | |
| os.environ['REQUESTS_CA_BUNDLE'] = certifi.where() | |
| class FREDClient: | |
| """Client for fetching risk-free rate data from FRED API.""" | |
| def __init__(self, api_key: Optional[str] = None): | |
| """ | |
| Initialize FRED client. | |
| Args: | |
| api_key: FRED API key. If not provided, reads from FRED_API_KEY env var. | |
| """ | |
| self.api_key = api_key or os.getenv('FRED_API_KEY') | |
| if not self.api_key: | |
| raise ValueError( | |
| "FRED API key not found. Set FRED_API_KEY environment variable or pass api_key parameter. " | |
| "Get a free API key at: https://fred.stlouisfed.org/docs/api/api_key.html" | |
| ) | |
| self.fred = Fred(api_key=self.api_key) | |
| def get_risk_free_rate( | |
| self, | |
| series_id: str = 'DGS3MO', | |
| days: int = 1 | |
| ) -> float: | |
| """ | |
| Get the risk-free interest rate. | |
| Args: | |
| series_id: FRED series ID. Options: | |
| - 'DGS3MO': 3-Month Treasury Constant Maturity Rate (default) | |
| - 'DGS1MO': 1-Month Treasury | |
| - 'DGS6MO': 6-Month Treasury | |
| - 'DGS1': 1-Year Treasury | |
| - 'DTB3': 3-Month Treasury Bill Secondary Market Rate | |
| days: Number of days to look back if current data is unavailable | |
| Returns: | |
| Risk-free rate as decimal (e.g., 0.05 for 5%) | |
| """ | |
| try: | |
| # Get most recent data | |
| end_date = datetime.now() | |
| start_date = end_date - timedelta(days=days) | |
| data = self.fred.get_series( | |
| series_id, | |
| observation_start=start_date, | |
| observation_end=end_date | |
| ) | |
| if data.empty: | |
| raise ValueError(f"No data returned for series {series_id}") | |
| # Get most recent non-null value | |
| rate = data.dropna().iloc[-1] | |
| # Convert from percentage to decimal | |
| return float(rate) / 100.0 | |
| except Exception as e: | |
| # Fallback to default rate if FRED API fails (SSL errors, network issues, etc.) | |
| print(f"Warning: FRED API failed ({str(e)}), using default rate of 4.5%") | |
| return 0.045 # Default 4.5% (approximate current 3-month Treasury rate) | |
| def get_rate_for_maturity(self, days_to_maturity: int) -> float: | |
| """ | |
| Get risk-free rate for a specific maturity. | |
| Args: | |
| days_to_maturity: Days to maturity (e.g., 30, 90, 180, 365) | |
| Returns: | |
| Risk-free rate as decimal | |
| """ | |
| # Map maturity to appropriate FRED series | |
| if days_to_maturity <= 30: | |
| series_id = 'DGS1MO' # 1-month | |
| elif days_to_maturity <= 90: | |
| series_id = 'DGS3MO' # 3-month | |
| elif days_to_maturity <= 180: | |
| series_id = 'DGS6MO' # 6-month | |
| elif days_to_maturity <= 365: | |
| series_id = 'DGS1' # 1-year | |
| elif days_to_maturity <= 365 * 2: | |
| series_id = 'DGS2' # 2-year | |
| elif days_to_maturity <= 365 * 5: | |
| series_id = 'DGS5' # 5-year | |
| elif days_to_maturity <= 365 * 10: | |
| series_id = 'DGS10' # 10-year | |
| else: | |
| series_id = 'DGS30' # 30-year | |
| return self.get_risk_free_rate(series_id=series_id, days=7) | |
| def get_treasury_curve(self) -> dict: | |
| """ | |
| Get the entire Treasury yield curve. | |
| Returns: | |
| Dictionary with maturities as keys and rates as values | |
| """ | |
| maturities = { | |
| '1M': 'DGS1MO', | |
| '3M': 'DGS3MO', | |
| '6M': 'DGS6MO', | |
| '1Y': 'DGS1', | |
| '2Y': 'DGS2', | |
| '5Y': 'DGS5', | |
| '10Y': 'DGS10', | |
| '30Y': 'DGS30' | |
| } | |
| curve = {} | |
| for maturity, series_id in maturities.items(): | |
| try: | |
| rate = self.get_risk_free_rate(series_id=series_id, days=7) | |
| curve[maturity] = rate | |
| except Exception as e: | |
| print(f"Warning: Failed to fetch {maturity} rate: {str(e)}") | |
| curve[maturity] = None | |
| return curve | |
| def get_risk_free_rate(days_to_maturity: Optional[int] = None) -> float: | |
| """ | |
| Convenience function to get risk-free rate. | |
| Args: | |
| days_to_maturity: Optional maturity in days. If None, returns 3-month rate. | |
| Returns: | |
| Risk-free rate as decimal | |
| """ | |
| client = FREDClient() | |
| if days_to_maturity: | |
| return client.get_rate_for_maturity(days_to_maturity) | |
| else: | |
| return client.get_risk_free_rate() | |
| if __name__ == "__main__": | |
| # Test the client | |
| print("Testing FRED client...") | |
| try: | |
| client = FREDClient() | |
| # Get 3-month rate | |
| rate_3m = client.get_risk_free_rate() | |
| print(f"3-Month Treasury Rate: {rate_3m:.4f} ({rate_3m*100:.2f}%)") | |
| # Get rate for 30-day maturity | |
| rate_30d = client.get_rate_for_maturity(30) | |
| print(f"30-Day Rate: {rate_30d:.4f} ({rate_30d*100:.2f}%)") | |
| # Get full yield curve | |
| print("\nTreasury Yield Curve:") | |
| curve = client.get_treasury_curve() | |
| for maturity, rate in curve.items(): | |
| if rate is not None: | |
| print(f" {maturity}: {rate:.4f} ({rate*100:.2f}%)") | |
| print("\n✅ FRED client test passed!") | |
| except Exception as e: | |
| print(f"\n❌ FRED client test failed: {str(e)}") | |
| print("\nMake sure you have set FRED_API_KEY in your .env file") | |
| print("Get a free API key at: https://fred.stlouisfed.org/docs/api/api_key.html") | |