TradingAgents/tests/unit/dataflows/test_fred.py

635 lines
24 KiB
Python

"""
Test suite for FRED API Data Retrieval Functions (fred.py).
This module tests:
1. get_interest_rates() - Federal funds rate retrieval
2. get_treasury_rates() - Treasury yield retrieval
3. get_money_supply() - M2 money supply retrieval
4. get_gdp() - GDP data retrieval
5. get_inflation() - CPI/inflation data retrieval
6. get_unemployment() - Unemployment rate retrieval
7. get_fred_series() - Generic FRED series retrieval
Test Coverage:
- Unit tests for each data function
- Default parameters and custom parameters
- Date range filtering
- Different series IDs and maturities
- Error handling (returns error strings, not exceptions)
- Cache integration
- Empty data scenarios
"""
import pytest
import pandas as pd
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime, date
pytestmark = pytest.mark.unit
# ============================================================================
# Fixtures
# ============================================================================
@pytest.fixture
def mock_fred_request():
"""Mock _make_fred_request function."""
with patch('tradingagents.dataflows.fred._make_fred_request') as mock_request:
yield mock_request
@pytest.fixture
def sample_interest_rate_df():
"""Create sample interest rate DataFrame."""
return pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=5, freq='D'),
'value': [5.33, 5.35, 5.37, 5.39, 5.40],
})
@pytest.fixture
def sample_treasury_rate_df():
"""Create sample treasury rate DataFrame."""
return pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=5, freq='D'),
'value': [4.25, 4.27, 4.29, 4.31, 4.33],
})
@pytest.fixture
def sample_money_supply_df():
"""Create sample M2 money supply DataFrame (billions)."""
return pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=5, freq='ME'),
'value': [21000.0, 21050.0, 21100.0, 21150.0, 21200.0],
})
@pytest.fixture
def sample_gdp_df():
"""Create sample GDP DataFrame (billions)."""
return pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=4, freq='QE'),
'value': [27000.0, 27200.0, 27400.0, 27600.0],
})
@pytest.fixture
def sample_cpi_df():
"""Create sample CPI DataFrame."""
return pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=5, freq='ME'),
'value': [308.0, 309.0, 310.0, 311.0, 312.0],
})
@pytest.fixture
def sample_unemployment_df():
"""Create sample unemployment rate DataFrame."""
return pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=5, freq='ME'),
'value': [3.7, 3.8, 3.9, 4.0, 4.1],
})
# ============================================================================
# Test get_interest_rates()
# ============================================================================
class TestGetInterestRates:
"""Test federal funds rate retrieval."""
def test_get_interest_rates_success_default(self, mock_fred_request, sample_interest_rate_df):
"""Test successful retrieval with default parameters (FEDFUNDS)."""
from tradingagents.dataflows.fred import get_interest_rates
mock_fred_request.return_value = sample_interest_rate_df
result = get_interest_rates()
assert isinstance(result, pd.DataFrame)
assert len(result) == 5
assert 'date' in result.columns
assert 'value' in result.columns
mock_fred_request.assert_called_once_with('FEDFUNDS', start_date=None, end_date=None)
def test_get_interest_rates_with_date_range(self, mock_fred_request, sample_interest_rate_df):
"""Test retrieval with custom date range."""
from tradingagents.dataflows.fred import get_interest_rates
mock_fred_request.return_value = sample_interest_rate_df
result = get_interest_rates(start_date='2024-01-01', end_date='2024-12-31')
mock_fred_request.assert_called_once_with('FEDFUNDS', start_date='2024-01-01', end_date='2024-12-31')
def test_get_interest_rates_custom_series(self, mock_fred_request, sample_interest_rate_df):
"""Test retrieval with custom series ID."""
from tradingagents.dataflows.fred import get_interest_rates
mock_fred_request.return_value = sample_interest_rate_df
result = get_interest_rates(series_id='DFF')
mock_fred_request.assert_called_once_with('DFF', start_date=None, end_date=None)
def test_get_interest_rates_returns_error_string_on_failure(self, mock_fred_request):
"""Test that errors return error string, not exception."""
from tradingagents.dataflows.fred import get_interest_rates
from tradingagents.dataflows.fred_common import FredRateLimitError
mock_fred_request.side_effect = FredRateLimitError("Rate limit exceeded")
result = get_interest_rates()
assert isinstance(result, str)
assert "error" in result.lower() or "failed" in result.lower()
assert "rate limit" in result.lower()
def test_get_interest_rates_empty_dataframe(self, mock_fred_request):
"""Test handling of empty DataFrame response."""
from tradingagents.dataflows.fred import get_interest_rates
mock_fred_request.return_value = pd.DataFrame()
result = get_interest_rates()
assert isinstance(result, pd.DataFrame)
assert len(result) == 0
def test_get_interest_rates_datetime_objects(self, mock_fred_request, sample_interest_rate_df):
"""Test retrieval with datetime objects instead of strings."""
from tradingagents.dataflows.fred import get_interest_rates
mock_fred_request.return_value = sample_interest_rate_df
start = datetime(2024, 1, 1)
end = datetime(2024, 12, 31)
result = get_interest_rates(start_date=start, end_date=end)
# Should convert to string format
assert mock_fred_request.called
# ============================================================================
# Test get_treasury_rates()
# ============================================================================
class TestGetTreasuryRates:
"""Test treasury yield retrieval."""
def test_get_treasury_rates_default_10y(self, mock_fred_request, sample_treasury_rate_df):
"""Test successful retrieval of 10-year treasury (default)."""
from tradingagents.dataflows.fred import get_treasury_rates
mock_fred_request.return_value = sample_treasury_rate_df
result = get_treasury_rates()
assert isinstance(result, pd.DataFrame)
assert len(result) == 5
mock_fred_request.assert_called_once_with('DGS10', start_date=None, end_date=None)
def test_get_treasury_rates_2y(self, mock_fred_request, sample_treasury_rate_df):
"""Test retrieval of 2-year treasury."""
from tradingagents.dataflows.fred import get_treasury_rates
mock_fred_request.return_value = sample_treasury_rate_df
result = get_treasury_rates(maturity='2Y')
mock_fred_request.assert_called_once_with('DGS2', start_date=None, end_date=None)
def test_get_treasury_rates_5y(self, mock_fred_request, sample_treasury_rate_df):
"""Test retrieval of 5-year treasury."""
from tradingagents.dataflows.fred import get_treasury_rates
mock_fred_request.return_value = sample_treasury_rate_df
result = get_treasury_rates(maturity='5Y')
mock_fred_request.assert_called_once_with('DGS5', start_date=None, end_date=None)
def test_get_treasury_rates_30y(self, mock_fred_request, sample_treasury_rate_df):
"""Test retrieval of 30-year treasury."""
from tradingagents.dataflows.fred import get_treasury_rates
mock_fred_request.return_value = sample_treasury_rate_df
result = get_treasury_rates(maturity='30Y')
mock_fred_request.assert_called_once_with('DGS30', start_date=None, end_date=None)
def test_get_treasury_rates_invalid_maturity(self, mock_fred_request):
"""Test invalid maturity returns error string."""
from tradingagents.dataflows.fred import get_treasury_rates
result = get_treasury_rates(maturity='15Y')
assert isinstance(result, str)
assert "error" in result.lower() or "invalid" in result.lower()
def test_get_treasury_rates_with_date_range(self, mock_fred_request, sample_treasury_rate_df):
"""Test retrieval with date range."""
from tradingagents.dataflows.fred import get_treasury_rates
mock_fred_request.return_value = sample_treasury_rate_df
result = get_treasury_rates(maturity='10Y', start_date='2024-01-01', end_date='2024-12-31')
mock_fred_request.assert_called_once_with('DGS10', start_date='2024-01-01', end_date='2024-12-31')
def test_get_treasury_rates_returns_error_string_on_failure(self, mock_fred_request):
"""Test that errors return error string."""
from tradingagents.dataflows.fred import get_treasury_rates
from tradingagents.dataflows.fred_common import FredInvalidSeriesError
mock_fred_request.side_effect = FredInvalidSeriesError("Invalid series")
result = get_treasury_rates()
assert isinstance(result, str)
assert "error" in result.lower()
# ============================================================================
# Test get_money_supply()
# ============================================================================
class TestGetMoneySupply:
"""Test money supply (M2) retrieval."""
def test_get_money_supply_default_m2(self, mock_fred_request, sample_money_supply_df):
"""Test successful M2 money supply retrieval (default)."""
from tradingagents.dataflows.fred import get_money_supply
mock_fred_request.return_value = sample_money_supply_df
result = get_money_supply()
assert isinstance(result, pd.DataFrame)
assert len(result) == 5
mock_fred_request.assert_called_once_with('M2SL', start_date=None, end_date=None)
def test_get_money_supply_m1(self, mock_fred_request, sample_money_supply_df):
"""Test M1 money supply retrieval."""
from tradingagents.dataflows.fred import get_money_supply
mock_fred_request.return_value = sample_money_supply_df
result = get_money_supply(measure='M1')
mock_fred_request.assert_called_once_with('M1SL', start_date=None, end_date=None)
def test_get_money_supply_with_date_range(self, mock_fred_request, sample_money_supply_df):
"""Test money supply with date range."""
from tradingagents.dataflows.fred import get_money_supply
mock_fred_request.return_value = sample_money_supply_df
result = get_money_supply(start_date='2020-01-01', end_date='2024-12-31')
mock_fred_request.assert_called_once_with('M2SL', start_date='2020-01-01', end_date='2024-12-31')
def test_get_money_supply_invalid_measure(self, mock_fred_request):
"""Test invalid measure returns error string."""
from tradingagents.dataflows.fred import get_money_supply
result = get_money_supply(measure='M5')
assert isinstance(result, str)
assert "error" in result.lower() or "invalid" in result.lower()
def test_get_money_supply_returns_error_string_on_failure(self, mock_fred_request):
"""Test that errors return error string."""
from tradingagents.dataflows.fred import get_money_supply
mock_fred_request.side_effect = Exception("Network error")
result = get_money_supply()
assert isinstance(result, str)
assert "error" in result.lower()
# ============================================================================
# Test get_gdp()
# ============================================================================
class TestGetGDP:
"""Test GDP data retrieval."""
def test_get_gdp_default_quarterly(self, mock_fred_request, sample_gdp_df):
"""Test successful quarterly GDP retrieval (default)."""
from tradingagents.dataflows.fred import get_gdp
mock_fred_request.return_value = sample_gdp_df
result = get_gdp()
assert isinstance(result, pd.DataFrame)
assert len(result) == 4
mock_fred_request.assert_called_once_with('GDP', start_date=None, end_date=None)
def test_get_gdp_annual(self, mock_fred_request, sample_gdp_df):
"""Test annual GDP retrieval."""
from tradingagents.dataflows.fred import get_gdp
mock_fred_request.return_value = sample_gdp_df
result = get_gdp(frequency='annual')
mock_fred_request.assert_called_once_with('GDPA', start_date=None, end_date=None)
def test_get_gdp_real(self, mock_fred_request, sample_gdp_df):
"""Test real GDP retrieval."""
from tradingagents.dataflows.fred import get_gdp
mock_fred_request.return_value = sample_gdp_df
result = get_gdp(frequency='real')
mock_fred_request.assert_called_once_with('GDPC1', start_date=None, end_date=None)
def test_get_gdp_with_date_range(self, mock_fred_request, sample_gdp_df):
"""Test GDP with date range."""
from tradingagents.dataflows.fred import get_gdp
mock_fred_request.return_value = sample_gdp_df
result = get_gdp(start_date='2020-01-01', end_date='2024-12-31')
mock_fred_request.assert_called_once_with('GDP', start_date='2020-01-01', end_date='2024-12-31')
def test_get_gdp_invalid_frequency(self, mock_fred_request):
"""Test invalid frequency returns error string."""
from tradingagents.dataflows.fred import get_gdp
result = get_gdp(frequency='weekly')
assert isinstance(result, str)
assert "error" in result.lower() or "invalid" in result.lower()
def test_get_gdp_returns_error_string_on_failure(self, mock_fred_request):
"""Test that errors return error string."""
from tradingagents.dataflows.fred import get_gdp
mock_fred_request.side_effect = Exception("API error")
result = get_gdp()
assert isinstance(result, str)
assert "error" in result.lower()
# ============================================================================
# Test get_inflation()
# ============================================================================
class TestGetInflation:
"""Test inflation/CPI data retrieval."""
def test_get_inflation_default_cpi(self, mock_fred_request, sample_cpi_df):
"""Test successful CPI retrieval (default)."""
from tradingagents.dataflows.fred import get_inflation
mock_fred_request.return_value = sample_cpi_df
result = get_inflation()
assert isinstance(result, pd.DataFrame)
assert len(result) == 5
mock_fred_request.assert_called_once_with('CPIAUCSL', start_date=None, end_date=None)
def test_get_inflation_core_cpi(self, mock_fred_request, sample_cpi_df):
"""Test core CPI retrieval (excluding food and energy)."""
from tradingagents.dataflows.fred import get_inflation
mock_fred_request.return_value = sample_cpi_df
result = get_inflation(measure='CORE')
mock_fred_request.assert_called_once_with('CPILFESL', start_date=None, end_date=None)
def test_get_inflation_pce(self, mock_fred_request, sample_cpi_df):
"""Test PCE price index retrieval."""
from tradingagents.dataflows.fred import get_inflation
mock_fred_request.return_value = sample_cpi_df
result = get_inflation(measure='PCE')
mock_fred_request.assert_called_once_with('PCEPI', start_date=None, end_date=None)
def test_get_inflation_with_date_range(self, mock_fred_request, sample_cpi_df):
"""Test inflation with date range."""
from tradingagents.dataflows.fred import get_inflation
mock_fred_request.return_value = sample_cpi_df
result = get_inflation(start_date='2020-01-01', end_date='2024-12-31')
mock_fred_request.assert_called_once_with('CPIAUCSL', start_date='2020-01-01', end_date='2024-12-31')
def test_get_inflation_invalid_measure(self, mock_fred_request):
"""Test invalid measure returns error string."""
from tradingagents.dataflows.fred import get_inflation
result = get_inflation(measure='INVALID')
assert isinstance(result, str)
assert "error" in result.lower() or "invalid" in result.lower()
def test_get_inflation_returns_error_string_on_failure(self, mock_fred_request):
"""Test that errors return error string."""
from tradingagents.dataflows.fred import get_inflation
mock_fred_request.side_effect = Exception("API error")
result = get_inflation()
assert isinstance(result, str)
assert "error" in result.lower()
# ============================================================================
# Test get_unemployment()
# ============================================================================
class TestGetUnemployment:
"""Test unemployment rate retrieval."""
def test_get_unemployment_default_unrate(self, mock_fred_request, sample_unemployment_df):
"""Test successful unemployment rate retrieval (default)."""
from tradingagents.dataflows.fred import get_unemployment
mock_fred_request.return_value = sample_unemployment_df
result = get_unemployment()
assert isinstance(result, pd.DataFrame)
assert len(result) == 5
mock_fred_request.assert_called_once_with('UNRATE', start_date=None, end_date=None)
def test_get_unemployment_custom_series(self, mock_fred_request, sample_unemployment_df):
"""Test unemployment with custom series ID."""
from tradingagents.dataflows.fred import get_unemployment
mock_fred_request.return_value = sample_unemployment_df
result = get_unemployment(series_id='U6RATE')
mock_fred_request.assert_called_once_with('U6RATE', start_date=None, end_date=None)
def test_get_unemployment_with_date_range(self, mock_fred_request, sample_unemployment_df):
"""Test unemployment with date range."""
from tradingagents.dataflows.fred import get_unemployment
mock_fred_request.return_value = sample_unemployment_df
result = get_unemployment(start_date='2020-01-01', end_date='2024-12-31')
mock_fred_request.assert_called_once_with('UNRATE', start_date='2020-01-01', end_date='2024-12-31')
def test_get_unemployment_returns_error_string_on_failure(self, mock_fred_request):
"""Test that errors return error string."""
from tradingagents.dataflows.fred import get_unemployment
mock_fred_request.side_effect = Exception("API error")
result = get_unemployment()
assert isinstance(result, str)
assert "error" in result.lower()
# ============================================================================
# Test get_fred_series() - Generic Function
# ============================================================================
class TestGetFredSeries:
"""Test generic FRED series retrieval."""
def test_get_fred_series_success(self, mock_fred_request, sample_interest_rate_df):
"""Test successful generic series retrieval."""
from tradingagents.dataflows.fred import get_fred_series
mock_fred_request.return_value = sample_interest_rate_df
result = get_fred_series('FEDFUNDS')
assert isinstance(result, pd.DataFrame)
assert len(result) == 5
mock_fred_request.assert_called_once_with('FEDFUNDS', start_date=None, end_date=None)
def test_get_fred_series_with_date_range(self, mock_fred_request, sample_interest_rate_df):
"""Test generic series with date range."""
from tradingagents.dataflows.fred import get_fred_series
mock_fred_request.return_value = sample_interest_rate_df
result = get_fred_series('FEDFUNDS', start_date='2024-01-01', end_date='2024-12-31')
mock_fred_request.assert_called_once_with('FEDFUNDS', start_date='2024-01-01', end_date='2024-12-31')
def test_get_fred_series_custom_series_id(self, mock_fred_request, sample_interest_rate_df):
"""Test generic series with any custom series ID."""
from tradingagents.dataflows.fred import get_fred_series
mock_fred_request.return_value = sample_interest_rate_df
result = get_fred_series('SP500')
mock_fred_request.assert_called_once_with('SP500', start_date=None, end_date=None)
def test_get_fred_series_returns_error_string_on_failure(self, mock_fred_request):
"""Test that errors return error string."""
from tradingagents.dataflows.fred import get_fred_series
mock_fred_request.side_effect = Exception("API error")
result = get_fred_series('INVALID')
assert isinstance(result, str)
assert "error" in result.lower()
def test_get_fred_series_invalid_series_id(self, mock_fred_request):
"""Test generic series with invalid series ID."""
from tradingagents.dataflows.fred import get_fred_series
from tradingagents.dataflows.fred_common import FredInvalidSeriesError
mock_fred_request.side_effect = FredInvalidSeriesError("Series not found", series_id='INVALID')
result = get_fred_series('INVALID')
assert isinstance(result, str)
assert "error" in result.lower()
assert "INVALID" in result or "series" in result.lower()
# ============================================================================
# Edge Case Tests
# ============================================================================
class TestEdgeCases:
"""Test edge cases across all functions."""
def test_all_functions_handle_none_dates(self, mock_fred_request, sample_interest_rate_df):
"""Test that all functions handle None dates properly."""
from tradingagents.dataflows.fred import (
get_interest_rates, get_treasury_rates, get_money_supply,
get_gdp, get_inflation, get_unemployment, get_fred_series
)
mock_fred_request.return_value = sample_interest_rate_df
# Should all succeed with None dates
functions = [
lambda: get_interest_rates(start_date=None, end_date=None),
lambda: get_treasury_rates(start_date=None, end_date=None),
lambda: get_money_supply(start_date=None, end_date=None),
lambda: get_gdp(start_date=None, end_date=None),
lambda: get_inflation(start_date=None, end_date=None),
lambda: get_unemployment(start_date=None, end_date=None),
lambda: get_fred_series('TEST', start_date=None, end_date=None),
]
for func in functions:
result = func()
assert isinstance(result, pd.DataFrame)
def test_all_functions_return_strings_on_error(self, mock_fred_request):
"""Test that all functions return error strings, not exceptions."""
from tradingagents.dataflows.fred import (
get_interest_rates, get_treasury_rates, get_money_supply,
get_gdp, get_inflation, get_unemployment, get_fred_series
)
mock_fred_request.side_effect = Exception("Test error")
functions = [
get_interest_rates,
get_treasury_rates,
get_money_supply,
get_gdp,
get_inflation,
get_unemployment,
lambda: get_fred_series('TEST'),
]
for func in functions:
result = func()
assert isinstance(result, str), f"{func.__name__} should return string on error"
assert "error" in result.lower() or "failed" in result.lower()
def test_empty_series_id_handled(self, mock_fred_request):
"""Test handling of empty series ID."""
from tradingagents.dataflows.fred import get_fred_series
result = get_fred_series('')
assert isinstance(result, str)
assert "error" in result.lower()