mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-06-16 21:06:15 +03:00
feat(data): add FRED macro indicators as an optional vendor
Surface Federal Reserve Economic Data (rates, inflation, labor, growth) to the news analyst via a new get_macro_indicators tool and a macro_data vendor category. Friendly aliases (cpi, unemployment, fed_funds_rate, 10y_treasury, yield_curve, ...) map to FRED series IDs; raw series IDs are accepted too. The report gives the latest value, change over the window, and a recent observation table. Windowing is lookahead-safe (observation_end = curr_date), missing values are skipped, and a missing FRED_API_KEY surfaces as a clear not-configured condition through the vendor router rather than a crash.
This commit is contained in:
@@ -16,6 +16,9 @@ MOONSHOT_API_KEY=
|
|||||||
GROQ_API_KEY=
|
GROQ_API_KEY=
|
||||||
NVIDIA_API_KEY=
|
NVIDIA_API_KEY=
|
||||||
|
|
||||||
|
# FRED (Federal Reserve macro data: rates, inflation, labor, growth). Free key: https://fred.stlouisfed.org/docs/api/api_key.html
|
||||||
|
#FRED_API_KEY=
|
||||||
|
|
||||||
# Optional: a custom OpenAI-compatible endpoint (vLLM, LM Studio, llama.cpp,
|
# Optional: a custom OpenAI-compatible endpoint (vLLM, LM Studio, llama.cpp,
|
||||||
# relay). Select provider "openai_compatible" and set the base URL; the key is
|
# relay). Select provider "openai_compatible" and set the base URL; the key is
|
||||||
# optional (local servers need none).
|
# optional (local servers need none).
|
||||||
|
|||||||
177
tests/test_fred.py
Normal file
177
tests/test_fred.py
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
"""FRED macro vendor: alias resolution, configuration errors, output formatting,
|
||||||
|
missing-value handling, lookahead-safe windowing, and router integration.
|
||||||
|
|
||||||
|
All API access is mocked, so these run without a network connection or a key.
|
||||||
|
"""
|
||||||
|
import copy
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import tradingagents.dataflows.config as config_module
|
||||||
|
import tradingagents.default_config as default_config
|
||||||
|
from tradingagents.dataflows import fred, interface
|
||||||
|
from tradingagents.dataflows.config import set_config
|
||||||
|
|
||||||
|
# A small, stable set of observations to format against.
|
||||||
|
_META = {
|
||||||
|
"seriess": [
|
||||||
|
{
|
||||||
|
"title": "Unemployment Rate",
|
||||||
|
"units_short": "%",
|
||||||
|
"frequency": "Monthly",
|
||||||
|
"seasonal_adjustment_short": "SA",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
_OBS = {
|
||||||
|
"observations": [
|
||||||
|
{"date": "2025-06-01", "value": "4.1"},
|
||||||
|
{"date": "2025-07-01", "value": "4.3"},
|
||||||
|
{"date": "2025-08-01", "value": "."}, # missing -> skipped
|
||||||
|
{"date": "2025-09-01", "value": "4.4"},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _request_stub(meta=_META, obs=_OBS):
|
||||||
|
"""Build a _request replacement that dispatches on the endpoint path."""
|
||||||
|
def _impl(path, params):
|
||||||
|
if path == "series":
|
||||||
|
return meta
|
||||||
|
if path == "series/observations":
|
||||||
|
return obs
|
||||||
|
raise AssertionError(f"unexpected FRED path: {path}")
|
||||||
|
return _impl
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
class FredResolutionTests(unittest.TestCase):
|
||||||
|
def test_alias_maps_to_series_id(self):
|
||||||
|
self.assertEqual(fred._resolve_series_id("cpi"), "CPIAUCSL")
|
||||||
|
self.assertEqual(fred._resolve_series_id("unemployment"), "UNRATE")
|
||||||
|
|
||||||
|
def test_alias_is_case_and_separator_insensitive(self):
|
||||||
|
self.assertEqual(fred._resolve_series_id("Fed Funds Rate"), "FEDFUNDS")
|
||||||
|
self.assertEqual(fred._resolve_series_id("10y-treasury"), "DGS10")
|
||||||
|
|
||||||
|
def test_unknown_alias_is_treated_as_raw_series_id(self):
|
||||||
|
# Power users can pass any FRED series ID; we uppercase by convention.
|
||||||
|
self.assertEqual(fred._resolve_series_id("dgs30"), "DGS30")
|
||||||
|
self.assertEqual(fred._resolve_series_id("MyCustomSeries"), "MYCUSTOMSERIES")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
class FredConfigTests(unittest.TestCase):
|
||||||
|
def test_missing_key_raises_not_configured(self):
|
||||||
|
with mock.patch.dict("os.environ", {}, clear=True), \
|
||||||
|
self.assertRaises(fred.FredNotConfiguredError):
|
||||||
|
fred.get_api_key()
|
||||||
|
|
||||||
|
def test_not_configured_is_a_value_error(self):
|
||||||
|
# Routing relies on this subclassing for "vendor unavailable" handling.
|
||||||
|
self.assertTrue(issubclass(fred.FredNotConfiguredError, ValueError))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
class FredFormattingTests(unittest.TestCase):
|
||||||
|
def test_report_has_header_latest_change_and_table(self):
|
||||||
|
with mock.patch.object(fred, "_request", side_effect=_request_stub()):
|
||||||
|
out = fred.get_macro_data("unemployment", "2025-09-30", 365)
|
||||||
|
self.assertIn("## FRED: Unemployment Rate (UNRATE)", out)
|
||||||
|
self.assertIn("Units: %", out)
|
||||||
|
self.assertIn("Frequency: Monthly (SA)", out)
|
||||||
|
self.assertIn("**Latest:** 4.4 (2025-09-01)", out)
|
||||||
|
# change over the window: 4.4 - 4.1 = +0.30
|
||||||
|
self.assertIn("+0.30", out)
|
||||||
|
self.assertIn("| 2025-06-01 | 4.1 |", out)
|
||||||
|
|
||||||
|
def test_missing_value_is_skipped(self):
|
||||||
|
with mock.patch.object(fred, "_request", side_effect=_request_stub()):
|
||||||
|
out = fred.get_macro_data("unemployment", "2025-09-30", 365)
|
||||||
|
# the "." observation must not appear as a row
|
||||||
|
self.assertNotIn("2025-08-01", out)
|
||||||
|
|
||||||
|
def test_empty_window_reports_no_observations(self):
|
||||||
|
empty = {"observations": []}
|
||||||
|
with mock.patch.object(fred, "_request", side_effect=_request_stub(obs=empty)):
|
||||||
|
out = fred.get_macro_data("unemployment", "2025-09-30", 30)
|
||||||
|
self.assertIn("No observations", out)
|
||||||
|
|
||||||
|
def test_unknown_series_raises(self):
|
||||||
|
no_series = {"seriess": []}
|
||||||
|
with mock.patch.object(fred, "_request", side_effect=_request_stub(meta=no_series)), \
|
||||||
|
self.assertRaises(ValueError):
|
||||||
|
fred.get_macro_data("totally_unknown_xyz", "2025-09-30", 30)
|
||||||
|
|
||||||
|
def test_long_series_is_truncated_but_change_uses_full_range(self):
|
||||||
|
# Build > MAX_ROWS observations deterministically.
|
||||||
|
obs = {
|
||||||
|
"observations": [
|
||||||
|
{"date": f"2025-01-{(i % 28) + 1:02d}", "value": str(i)}
|
||||||
|
for i in range(fred.MAX_ROWS + 10)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
with mock.patch.object(fred, "_request", side_effect=_request_stub(obs=obs)):
|
||||||
|
out = fred.get_macro_data("unemployment", "2025-12-31", 365)
|
||||||
|
self.assertIn(f"most recent {fred.MAX_ROWS}", out)
|
||||||
|
# change-over-window must reference the true first (0) and last value
|
||||||
|
self.assertIn("from 0 ", out)
|
||||||
|
body_rows = [ln for ln in out.splitlines() if ln.startswith("| 2025")]
|
||||||
|
self.assertEqual(len(body_rows), fred.MAX_ROWS)
|
||||||
|
|
||||||
|
def test_window_is_lookahead_safe(self):
|
||||||
|
# observation_end must equal curr_date so a past date never pulls future data.
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
def _capture(path, params):
|
||||||
|
captured[path] = params
|
||||||
|
return _META if path == "series" else _OBS
|
||||||
|
|
||||||
|
with mock.patch.object(fred, "_request", side_effect=_capture):
|
||||||
|
fred.get_macro_data("unemployment", "2025-09-30", 90)
|
||||||
|
obs_params = captured["series/observations"]
|
||||||
|
self.assertEqual(obs_params["observation_end"], "2025-09-30")
|
||||||
|
self.assertEqual(obs_params["observation_start"], "2025-07-02") # 90d back
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
class FredRoutingTests(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
config_module._config = copy.deepcopy(default_config.DEFAULT_CONFIG)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
config_module._config = copy.deepcopy(default_config.DEFAULT_CONFIG)
|
||||||
|
|
||||||
|
def test_macro_category_routes_to_fred(self):
|
||||||
|
self.assertEqual(
|
||||||
|
interface.get_category_for_method("get_macro_indicators"), "macro_data"
|
||||||
|
)
|
||||||
|
set_config({"data_vendors": {"macro_data": "fred"}})
|
||||||
|
with mock.patch.dict(
|
||||||
|
interface.VENDOR_METHODS,
|
||||||
|
{"get_macro_indicators": {"fred": lambda *a, **k: "MACRO_OK"}},
|
||||||
|
clear=False,
|
||||||
|
):
|
||||||
|
out = interface.route_to_vendor("get_macro_indicators", "cpi", "2026-06-01", 365)
|
||||||
|
self.assertEqual(out, "MACRO_OK")
|
||||||
|
|
||||||
|
def test_not_configured_surfaces_through_router(self):
|
||||||
|
# With only fred and no key, the router has no fallback and must surface
|
||||||
|
# the real "not configured" failure rather than masking it.
|
||||||
|
set_config({"data_vendors": {"macro_data": "fred"}})
|
||||||
|
|
||||||
|
def _unconfigured(*a, **k):
|
||||||
|
raise fred.FredNotConfiguredError("FRED_API_KEY not set")
|
||||||
|
|
||||||
|
with mock.patch.dict(
|
||||||
|
interface.VENDOR_METHODS,
|
||||||
|
{"get_macro_indicators": {"fred": _unconfigured}},
|
||||||
|
clear=False,
|
||||||
|
), self.assertRaises(fred.FredNotConfiguredError):
|
||||||
|
interface.route_to_vendor("get_macro_indicators", "cpi", "2026-06-01", 365)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -3,6 +3,7 @@ from tradingagents.agents.utils.agent_utils import (
|
|||||||
get_instrument_context_from_state,
|
get_instrument_context_from_state,
|
||||||
get_global_news,
|
get_global_news,
|
||||||
get_language_instruction,
|
get_language_instruction,
|
||||||
|
get_macro_indicators,
|
||||||
get_news,
|
get_news,
|
||||||
)
|
)
|
||||||
from tradingagents.dataflows.config import get_config
|
from tradingagents.dataflows.config import get_config
|
||||||
@@ -18,10 +19,11 @@ def create_news_analyst(llm):
|
|||||||
tools = [
|
tools = [
|
||||||
get_news,
|
get_news,
|
||||||
get_global_news,
|
get_global_news,
|
||||||
|
get_macro_indicators,
|
||||||
]
|
]
|
||||||
|
|
||||||
system_message = (
|
system_message = (
|
||||||
f"You are a news researcher tasked with analyzing recent news and trends over the past week. Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. Use the available tools: get_news(query, start_date, end_date) for {asset_label}-specific or targeted news searches, and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news. Provide specific, actionable insights with supporting evidence to help traders make informed decisions."
|
f"You are a news researcher tasked with analyzing recent news and trends over the past week. Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. Use the available tools: get_news(query, start_date, end_date) for {asset_label}-specific or targeted news searches, get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news, and get_macro_indicators(indicator, curr_date, look_back_days) to ground macro commentary in actual data from FRED (e.g. 'cpi', 'core_pce', 'unemployment', 'fed_funds_rate', '10y_treasury', 'yield_curve'). Provide specific, actionable insights with supporting evidence to help traders make informed decisions."
|
||||||
+ """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read."""
|
+ """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read."""
|
||||||
+ get_language_instruction()
|
+ get_language_instruction()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ from tradingagents.agents.utils.news_data_tools import (
|
|||||||
get_insider_transactions,
|
get_insider_transactions,
|
||||||
get_global_news
|
get_global_news
|
||||||
)
|
)
|
||||||
|
from tradingagents.agents.utils.macro_data_tools import (
|
||||||
|
get_macro_indicators
|
||||||
|
)
|
||||||
from tradingagents.agents.utils.market_data_validation_tools import (
|
from tradingagents.agents.utils.market_data_validation_tools import (
|
||||||
get_verified_market_snapshot
|
get_verified_market_snapshot
|
||||||
)
|
)
|
||||||
|
|||||||
36
tradingagents/agents/utils/macro_data_tools.py
Normal file
36
tradingagents/agents/utils/macro_data_tools.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from langchain_core.tools import tool
|
||||||
|
|
||||||
|
from tradingagents.dataflows.interface import route_to_vendor
|
||||||
|
|
||||||
|
|
||||||
|
@tool
|
||||||
|
def get_macro_indicators(
|
||||||
|
indicator: Annotated[
|
||||||
|
str,
|
||||||
|
"Macro indicator: a friendly alias such as 'cpi', 'core_pce', "
|
||||||
|
"'unemployment', 'fed_funds_rate', '10y_treasury', 'yield_curve', "
|
||||||
|
"'real_gdp', 'vix', or a raw FRED series ID such as 'CPIAUCSL'.",
|
||||||
|
],
|
||||||
|
curr_date: Annotated[str, "Current date in yyyy-mm-dd format; the end of the window"],
|
||||||
|
look_back_days: Annotated[
|
||||||
|
int | None, "Trailing window length in days; omit for a 1-year window"
|
||||||
|
] = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Retrieve a macroeconomic indicator time series from FRED (Federal Reserve
|
||||||
|
Economic Data): policy rates, Treasury yields, inflation, labor, and growth.
|
||||||
|
Returns the series title, units, frequency, the latest value, the change
|
||||||
|
over the window, and a recent observation table. Uses the configured
|
||||||
|
macro_data vendor.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
indicator (str): Friendly alias or raw FRED series ID
|
||||||
|
curr_date (str): Current date in yyyy-mm-dd format
|
||||||
|
look_back_days (int): Trailing window length; omit for a 1-year window
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A formatted markdown report of the macro series
|
||||||
|
"""
|
||||||
|
return route_to_vendor("get_macro_indicators", indicator, curr_date, look_back_days)
|
||||||
215
tradingagents/dataflows/fred.py
Normal file
215
tradingagents/dataflows/fred.py
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
"""FRED (Federal Reserve Economic Data) macro vendor.
|
||||||
|
|
||||||
|
Fetches macroeconomic time series — policy rates, Treasury yields, inflation,
|
||||||
|
labor, growth — from the St. Louis Fed's free API. Used by the news analyst to
|
||||||
|
ground macro commentary in actual numbers rather than headlines alone.
|
||||||
|
|
||||||
|
A free API key (https://fred.stlouisfed.org/docs/api/api_key.html) is read from
|
||||||
|
``FRED_API_KEY``; if it is unset the vendor raises ``FredNotConfiguredError`` so
|
||||||
|
the routing layer treats it as "unavailable" rather than a hard crash.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
FRED_API_BASE = "https://api.stlouisfed.org/fred"
|
||||||
|
|
||||||
|
# Network timeout (seconds) so a stalled request can't hang the agents,
|
||||||
|
# mirroring the Alpha Vantage client.
|
||||||
|
REQUEST_TIMEOUT = 30
|
||||||
|
|
||||||
|
# Default trailing window when the caller does not specify one. A year captures
|
||||||
|
# the trend and the year-over-year base for most monthly/quarterly series.
|
||||||
|
DEFAULT_LOOKBACK_DAYS = 365
|
||||||
|
|
||||||
|
# Rows cap for the rendered table: recent values matter most for a decision, and
|
||||||
|
# daily series (yields, VIX) over a long window would otherwise flood context.
|
||||||
|
MAX_ROWS = 40
|
||||||
|
|
||||||
|
# Curated human-friendly aliases -> FRED series IDs. Anything not listed is used
|
||||||
|
# verbatim as a raw FRED series ID, so power users are never limited to this set.
|
||||||
|
MACRO_SERIES = {
|
||||||
|
# Policy rate & Treasury yields
|
||||||
|
"fed_funds_rate": "FEDFUNDS",
|
||||||
|
"federal_funds_rate": "FEDFUNDS",
|
||||||
|
"fed_funds": "FEDFUNDS",
|
||||||
|
"2y_treasury": "DGS2",
|
||||||
|
"10y_treasury": "DGS10",
|
||||||
|
"30y_treasury": "DGS30",
|
||||||
|
"10y_2y_spread": "T10Y2Y",
|
||||||
|
"yield_curve": "T10Y2Y",
|
||||||
|
# Inflation
|
||||||
|
"cpi": "CPIAUCSL",
|
||||||
|
"core_cpi": "CPILFESL",
|
||||||
|
"pce": "PCEPI",
|
||||||
|
"core_pce": "PCEPILFE",
|
||||||
|
"inflation_expectations": "T10YIE",
|
||||||
|
# Growth & output
|
||||||
|
"real_gdp": "GDPC1",
|
||||||
|
"gdp": "GDP",
|
||||||
|
"industrial_production": "INDPRO",
|
||||||
|
# Labor
|
||||||
|
"unemployment_rate": "UNRATE",
|
||||||
|
"unemployment": "UNRATE",
|
||||||
|
"nonfarm_payrolls": "PAYEMS",
|
||||||
|
"payrolls": "PAYEMS",
|
||||||
|
"initial_claims": "ICSA",
|
||||||
|
# Money & markets
|
||||||
|
"m2": "M2SL",
|
||||||
|
"money_supply": "M2SL",
|
||||||
|
"vix": "VIXCLS",
|
||||||
|
"dollar_index": "DTWEXBGS",
|
||||||
|
# Sentiment & housing
|
||||||
|
"consumer_sentiment": "UMCSENT",
|
||||||
|
"housing_starts": "HOUST",
|
||||||
|
"retail_sales": "RSAFS",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FredNotConfiguredError(ValueError):
|
||||||
|
"""Raised when FRED is selected but no API key is configured.
|
||||||
|
|
||||||
|
Subclasses ValueError so callers already catching ValueError keep working,
|
||||||
|
while the routing layer can distinguish a "vendor unavailable" condition
|
||||||
|
from a genuine data error (same contract as AlphaVantageNotConfiguredError).
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def get_api_key() -> str:
|
||||||
|
"""Retrieve the FRED API key from the environment."""
|
||||||
|
api_key = os.getenv("FRED_API_KEY")
|
||||||
|
if not api_key:
|
||||||
|
raise FredNotConfiguredError(
|
||||||
|
"FRED_API_KEY environment variable is not set. Get a free key at "
|
||||||
|
"https://fred.stlouisfed.org/docs/api/api_key.html."
|
||||||
|
)
|
||||||
|
return api_key
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_series_id(indicator: str) -> str:
|
||||||
|
"""Map a friendly alias to a FRED series ID, or pass a raw ID through."""
|
||||||
|
key = indicator.strip().lower().replace(" ", "_").replace("-", "_")
|
||||||
|
if key in MACRO_SERIES:
|
||||||
|
return MACRO_SERIES[key]
|
||||||
|
# Not a known alias: treat the input as a raw FRED series ID (FRED IDs are
|
||||||
|
# conventionally uppercase, e.g. "DGS10", "CPIAUCSL").
|
||||||
|
return indicator.strip().upper()
|
||||||
|
|
||||||
|
|
||||||
|
def _request(path: str, params: dict) -> dict:
|
||||||
|
"""GET a FRED endpoint, surfacing FRED's JSON error body on a bad request."""
|
||||||
|
api_params = {**params, "api_key": get_api_key(), "file_type": "json"}
|
||||||
|
response = requests.get(
|
||||||
|
f"{FRED_API_BASE}/{path}", params=api_params, timeout=REQUEST_TIMEOUT
|
||||||
|
)
|
||||||
|
# FRED returns 400 with a JSON {"error_message": ...} for unknown series IDs
|
||||||
|
# or malformed params; turn that into a clear, actionable error.
|
||||||
|
if response.status_code == 400:
|
||||||
|
try:
|
||||||
|
message = response.json().get("error_message", response.text)
|
||||||
|
except ValueError:
|
||||||
|
message = response.text
|
||||||
|
raise ValueError(f"FRED request failed: {message}")
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
def get_macro_data(
|
||||||
|
indicator: str,
|
||||||
|
curr_date: str,
|
||||||
|
look_back_days: int | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Fetch a FRED macroeconomic series as a formatted markdown report.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
indicator: A friendly alias (e.g. "cpi", "unemployment", "10y_treasury")
|
||||||
|
or a raw FRED series ID (e.g. "CPIAUCSL", "DGS10").
|
||||||
|
curr_date: End of the window (yyyy-mm-dd); no later observations are
|
||||||
|
returned, so a past date never leaks future data.
|
||||||
|
look_back_days: Trailing window length; ``None`` uses DEFAULT_LOOKBACK_DAYS.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A markdown report with the series title, units, frequency, the latest
|
||||||
|
value, the change over the window, and a recent observation table.
|
||||||
|
"""
|
||||||
|
if look_back_days is None:
|
||||||
|
look_back_days = DEFAULT_LOOKBACK_DAYS
|
||||||
|
|
||||||
|
end_dt = datetime.strptime(curr_date, "%Y-%m-%d")
|
||||||
|
start_date = (end_dt - timedelta(days=look_back_days)).strftime("%Y-%m-%d")
|
||||||
|
series_id = _resolve_series_id(indicator)
|
||||||
|
|
||||||
|
meta = _request("series", {"series_id": series_id}).get("seriess") or []
|
||||||
|
if not meta:
|
||||||
|
raise ValueError(
|
||||||
|
f"FRED series '{series_id}' not found. Pass a known alias "
|
||||||
|
f"(e.g. 'cpi', 'unemployment') or a valid FRED series ID."
|
||||||
|
)
|
||||||
|
info = meta[0]
|
||||||
|
title = info.get("title", series_id)
|
||||||
|
units = info.get("units_short") or info.get("units", "")
|
||||||
|
frequency = info.get("frequency", "")
|
||||||
|
seasonal = info.get("seasonal_adjustment_short", "")
|
||||||
|
|
||||||
|
observations = _request(
|
||||||
|
"series/observations",
|
||||||
|
{
|
||||||
|
"series_id": series_id,
|
||||||
|
"observation_start": start_date,
|
||||||
|
"observation_end": curr_date,
|
||||||
|
"sort_order": "asc",
|
||||||
|
},
|
||||||
|
).get("observations", [])
|
||||||
|
|
||||||
|
# FRED encodes a missing observation as ".".
|
||||||
|
points = [
|
||||||
|
(o["date"], o["value"])
|
||||||
|
for o in observations
|
||||||
|
if o.get("value") not in (".", None, "")
|
||||||
|
]
|
||||||
|
|
||||||
|
header = (
|
||||||
|
f"## FRED: {title} ({series_id})\n"
|
||||||
|
f"- Units: {units}\n"
|
||||||
|
f"- Frequency: {frequency}"
|
||||||
|
f"{f' ({seasonal})' if seasonal else ''}\n"
|
||||||
|
f"- Window: {start_date} to {curr_date}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not points:
|
||||||
|
return header + (
|
||||||
|
f"\nNo observations for {series_id} in this window. The series may "
|
||||||
|
f"report less frequently than the window length; widen look_back_days."
|
||||||
|
)
|
||||||
|
|
||||||
|
first_date, first_val = points[0]
|
||||||
|
last_date, last_val = points[-1]
|
||||||
|
try:
|
||||||
|
delta = float(last_val) - float(first_val)
|
||||||
|
base = float(first_val)
|
||||||
|
pct = f" ({delta / base * 100:+.2f}%)" if base != 0 else ""
|
||||||
|
summary = (
|
||||||
|
f"\n**Latest:** {last_val} ({last_date}) | "
|
||||||
|
f"**Change over window:** {delta:+.2f}{pct} "
|
||||||
|
f"from {first_val} ({first_date})\n"
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
summary = f"\n**Latest:** {last_val} ({last_date})\n"
|
||||||
|
|
||||||
|
shown = points
|
||||||
|
note = ""
|
||||||
|
if len(points) > MAX_ROWS:
|
||||||
|
shown = points[-MAX_ROWS:]
|
||||||
|
note = f"\n_(showing the most recent {MAX_ROWS} of {len(points)} observations)_\n"
|
||||||
|
|
||||||
|
table = (
|
||||||
|
"\n| Date | Value |\n| --- | --- |\n"
|
||||||
|
+ "\n".join(f"| {d} | {v} |" for d, v in shown)
|
||||||
|
+ "\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
return header + summary + note + table
|
||||||
@@ -24,6 +24,7 @@ from .alpha_vantage import (
|
|||||||
get_global_news as get_alpha_vantage_global_news,
|
get_global_news as get_alpha_vantage_global_news,
|
||||||
)
|
)
|
||||||
from .alpha_vantage_common import AlphaVantageRateLimitError
|
from .alpha_vantage_common import AlphaVantageRateLimitError
|
||||||
|
from .fred import get_macro_data as get_fred_macro_data
|
||||||
from .symbol_utils import NoMarketDataError
|
from .symbol_utils import NoMarketDataError
|
||||||
|
|
||||||
# Configuration and routing logic
|
# Configuration and routing logic
|
||||||
@@ -61,11 +62,18 @@ TOOLS_CATEGORIES = {
|
|||||||
"get_global_news",
|
"get_global_news",
|
||||||
"get_insider_transactions",
|
"get_insider_transactions",
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"macro_data": {
|
||||||
|
"description": "Macroeconomic indicators (rates, inflation, labor, growth)",
|
||||||
|
"tools": [
|
||||||
|
"get_macro_indicators",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VENDOR_LIST = [
|
VENDOR_LIST = [
|
||||||
"yfinance",
|
"yfinance",
|
||||||
|
"fred",
|
||||||
"alpha_vantage",
|
"alpha_vantage",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -111,6 +119,10 @@ VENDOR_METHODS = {
|
|||||||
"alpha_vantage": get_alpha_vantage_insider_transactions,
|
"alpha_vantage": get_alpha_vantage_insider_transactions,
|
||||||
"yfinance": get_yfinance_insider_transactions,
|
"yfinance": get_yfinance_insider_transactions,
|
||||||
},
|
},
|
||||||
|
# macro_data
|
||||||
|
"get_macro_indicators": {
|
||||||
|
"fred": get_fred_macro_data,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_category_for_method(method: str) -> str:
|
def get_category_for_method(method: str) -> str:
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ DEFAULT_CONFIG = _apply_env_overrides({
|
|||||||
"technical_indicators": "yfinance", # Options: alpha_vantage, yfinance
|
"technical_indicators": "yfinance", # Options: alpha_vantage, yfinance
|
||||||
"fundamental_data": "yfinance", # Options: alpha_vantage, yfinance
|
"fundamental_data": "yfinance", # Options: alpha_vantage, yfinance
|
||||||
"news_data": "yfinance", # Options: alpha_vantage, yfinance
|
"news_data": "yfinance", # Options: alpha_vantage, yfinance
|
||||||
|
"macro_data": "fred", # Options: fred (needs FRED_API_KEY)
|
||||||
},
|
},
|
||||||
# Tool-level configuration (takes precedence over category-level)
|
# Tool-level configuration (takes precedence over category-level)
|
||||||
"tool_vendors": {
|
"tool_vendors": {
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ from tradingagents.agents.utils.agent_utils import (
|
|||||||
get_income_statement,
|
get_income_statement,
|
||||||
get_news,
|
get_news,
|
||||||
get_insider_transactions,
|
get_insider_transactions,
|
||||||
get_global_news
|
get_global_news,
|
||||||
|
get_macro_indicators
|
||||||
)
|
)
|
||||||
|
|
||||||
from .checkpointer import checkpoint_step, clear_checkpoint, get_checkpointer, thread_id
|
from .checkpointer import checkpoint_step, clear_checkpoint, get_checkpointer, thread_id
|
||||||
@@ -192,6 +193,7 @@ class TradingAgentsGraph:
|
|||||||
get_news,
|
get_news,
|
||||||
get_global_news,
|
get_global_news,
|
||||||
get_insider_transactions,
|
get_insider_transactions,
|
||||||
|
get_macro_indicators,
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
"fundamentals": ToolNode(
|
"fundamentals": ToolNode(
|
||||||
|
|||||||
Reference in New Issue
Block a user