File size: 6,006 Bytes
9d33587
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
"""
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")