mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-09-19 11:15:24 +03:00
fix(dataflows): pin the FRED data vintage to the as-of date
- FRED defaults both realtime bounds to today, so historical macro requests served the latest revision and leaked future information into backtests - set realtime_start=realtime_end=curr_date on both the metadata and observations requests #1275
This commit is contained in:
@@ -150,6 +150,23 @@ class FredFormattingTests(unittest.TestCase):
|
||||
self.assertEqual(obs_params["observation_end"], "2025-09-30")
|
||||
self.assertEqual(obs_params["observation_start"], "2025-07-02") # 90d back
|
||||
|
||||
def test_requests_pin_the_data_vintage(self):
|
||||
# #1275: both the metadata and observations requests must set
|
||||
# realtime_start=realtime_end=curr_date, or FRED serves the latest
|
||||
# revision and revision-prone series leak future information.
|
||||
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("cpi", "2025-09-30", 90)
|
||||
|
||||
for path in ("series", "series/observations"):
|
||||
self.assertEqual(captured[path]["realtime_start"], "2025-09-30", path)
|
||||
self.assertEqual(captured[path]["realtime_end"], "2025-09-30", path)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class FredRoutingTests(unittest.TestCase):
|
||||
|
||||
@@ -143,8 +143,12 @@ def get_macro_data(
|
||||
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.
|
||||
curr_date: The as-of date (yyyy-mm-dd). It bounds the observation window
|
||||
AND pins the data vintage: FRED is queried with
|
||||
``realtime_start = realtime_end = curr_date`` so a historical run sees
|
||||
the values that were actually published by that date, not later
|
||||
revisions. Without this, revision-prone series (CPI, GDP, ...) would
|
||||
leak future information into a backtest (#1275).
|
||||
look_back_days: Trailing window length; ``None`` uses DEFAULT_LOOKBACK_DAYS.
|
||||
|
||||
Returns:
|
||||
@@ -157,6 +161,12 @@ def get_macro_data(
|
||||
end_dt = datetime.strptime(curr_date, "%Y-%m-%d")
|
||||
start_date = (end_dt - timedelta(days=look_back_days)).strftime("%Y-%m-%d")
|
||||
|
||||
# Pin the data vintage to curr_date. FRED defaults both realtime bounds to
|
||||
# today, which serves the LATEST revision of every observation; a single-day
|
||||
# realtime interval asks for the values known as of curr_date instead. This
|
||||
# is applied to both the metadata and observations requests (#1275).
|
||||
realtime = {"realtime_start": curr_date, "realtime_end": curr_date}
|
||||
|
||||
# Invalid LLM-supplied indicator: return guidance rather than raising, so a
|
||||
# bad argument doesn't abort the run (the routing layer also degrades macro
|
||||
# data, but a specific message is more useful to the analyst).
|
||||
@@ -165,7 +175,7 @@ def get_macro_data(
|
||||
except ValueError as e:
|
||||
return f"FRED: {e}"
|
||||
|
||||
meta = _request("series", {"series_id": series_id}).get("seriess") or []
|
||||
meta = _request("series", {"series_id": series_id, **realtime}).get("seriess") or []
|
||||
if not meta:
|
||||
return (
|
||||
f"FRED series '{series_id}' not found. Pass a known alias "
|
||||
@@ -184,6 +194,7 @@ def get_macro_data(
|
||||
"observation_start": start_date,
|
||||
"observation_end": curr_date,
|
||||
"sort_order": "asc",
|
||||
**realtime,
|
||||
},
|
||||
).get("observations", [])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user