refactor: name the data layer's date argument as_of_date

- vendor functions and date_window take as_of_date, the date data is served as of; the model-facing tool arguments are unchanged
- build_instrument_context and resolve_instrument_context take trade_date, which is what they receive
This commit is contained in:
Yijia-Xiao
2026-09-24 19:38:41 +00:00
parent 5ac5786d0d
commit 674f1087d1
19 changed files with 165 additions and 165 deletions
+2 -2
View File
@@ -82,7 +82,7 @@ def test_fundamentals_look_ahead_filter_runs_on_json_string(monkeypatch):
# #1115: the payload arrives as a JSON *string*; the old dict-only guard let
# future-dated fiscal periods leak into historical runs.
monkeypatch.setattr(avf, "_make_api_request", lambda fn, params: _FUNDAMENTALS_JSON)
out = avf.get_balance_sheet("AAPL", curr_date="2024-01-01")
out = avf.get_balance_sheet("AAPL", as_of_date="2024-01-01")
assert isinstance(out, str) # callers still receive a str
parsed = json.loads(out)
assert [r["fiscalDateEnding"] for r in parsed["annualReports"]] == ["2023-12-31"]
@@ -98,7 +98,7 @@ def test_fundamentals_no_curr_date_passes_through(monkeypatch):
@pytest.mark.unit
def test_fundamentals_non_json_body_unchanged(monkeypatch):
monkeypatch.setattr(avf, "_make_api_request", lambda fn, params: "not-json")
assert avf.get_cashflow("AAPL", curr_date="2024-01-01") == "not-json"
assert avf.get_cashflow("AAPL", as_of_date="2024-01-01") == "not-json"
# ---------------------------------------------------------------------------
+6 -6
View File
@@ -141,7 +141,7 @@ class FredFormattingTests(unittest.TestCase):
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.
# observation_end must equal as_of_date so a past date never pulls future data.
captured = {}
def _capture(path, params):
@@ -156,9 +156,9 @@ class FredFormattingTests(unittest.TestCase):
def test_requests_pin_the_data_vintage(self):
# #1275: both the metadata and observations requests must pin the vintage
# to curr_date (clamped to FRED's today), or FRED serves the latest
# to as_of_date (clamped to FRED's today), or FRED serves the latest
# revision and revision-prone series leak future information. A past
# curr_date sits below FRED's today, so it pins through unchanged.
# as_of_date sits below FRED's today, so it pins through unchanged.
captured = {}
def _capture(path, params):
@@ -174,11 +174,11 @@ class FredFormattingTests(unittest.TestCase):
self.assertEqual(captured[path]["realtime_end"], "2025-09-30", path)
def test_future_curr_date_clamps_vintage_to_fred_today(self):
# #1275 regression: on a live run curr_date is the caller's LOCAL date,
# #1275 regression: on a live run as_of_date is the caller's LOCAL date,
# which can be a day ahead of FRED's US-Central clock. Pinning the vintage
# to that future date 400s, and the routing layer then drops macro data
# silently. The pin must clamp to FRED's today; the observation window
# (future bars can't exist yet) stays at curr_date.
# (future bars can't exist yet) stays at as_of_date.
captured = {}
def _capture(path, params):
@@ -192,7 +192,7 @@ class FredFormattingTests(unittest.TestCase):
for path in ("series", "series/observations"):
self.assertEqual(captured[path]["realtime_start"], "2026-08-31", path)
self.assertEqual(captured[path]["realtime_end"], "2026-08-31", path)
# the observation window still tracks curr_date, not the clamped vintage
# the observation window still tracks as_of_date, not the clamped vintage
self.assertEqual(captured["series/observations"]["observation_end"], "2026-09-01")
+5 -5
View File
@@ -10,7 +10,7 @@ FRED (#1275), social (#1220) and memory (#1251) leaks.
Both vendors withhold on one shared rule (``date_window.withhold_live_profile``)
so switching ``fundamental_data`` between them cannot reintroduce the leak. The
statement tools stay point-in-time by filtering on ``curr_date``, and a live run
statement tools stay point-in-time by filtering on ``as_of_date``, and a live run
is unchanged. All API access is mocked.
"""
from __future__ import annotations
@@ -45,19 +45,19 @@ _LEAKY = ("3500000000000", "34.2", "260.1", "391000000000",
"Apple Inc.", "Technology", "Consumer Electronics")
def _yf(curr_date, info=_INFO, today=_TODAY):
def _yf(as_of_date, info=_INFO, today=_TODAY):
with mock.patch.object(date_window, "get_current_date", return_value=today), \
mock.patch.object(yahoo_fundamentals, "yf_retry", lambda fn: info), \
mock.patch.object(yahoo_market.yf, "Ticker"):
return yahoo_fundamentals.get_fundamentals("AAPL", curr_date)
return yahoo_fundamentals.get_fundamentals("AAPL", as_of_date)
def _av(curr_date, today=_TODAY):
def _av(as_of_date, today=_TODAY):
"""Alpha Vantage path; the API call is mocked so a leak would be visible."""
with mock.patch.object(date_window, "get_current_date", return_value=today), \
mock.patch.object(av, "_make_api_request",
return_value="MarketCapitalization: 3500000000000") as req:
return av.get_fundamentals("AAPL", curr_date), req
return av.get_fundamentals("AAPL", as_of_date), req
@pytest.mark.unit
+1 -1
View File
@@ -102,4 +102,4 @@ def test_an_unreachable_yahoo_is_not_reported_as_a_symbol_without_insider_data()
with mock.patch.object(fundamentals.yf, "Ticker", return_value=ticker), \
mock.patch.object(fundamentals, "vendor_reachable", return_value=False), \
pytest.raises(VendorRateLimitError):
fundamentals.get_insider_transactions("AAPL", curr_date="2026-09-21")
fundamentals.get_insider_transactions("AAPL", as_of_date="2026-09-21")
+5 -5
View File
@@ -70,14 +70,14 @@ def test_alpha_vantage_insider_filings_after_the_date_are_dropped():
@pytest.mark.unit
def test_polymarket_withholds_live_odds_from_a_historical_run():
with mock.patch.object(polymarket, "_request", side_effect=AssertionError("must not fetch")):
out = polymarket.get_prediction_markets("Fed rate cut", curr_date="2025-06-01")
out = polymarket.get_prediction_markets("Fed rate cut", as_of_date="2025-06-01")
assert "withheld" in out
@pytest.mark.unit
def test_polymarket_serves_a_current_run():
with mock.patch.object(polymarket, "_request", return_value={"events": []}) as req:
polymarket.get_prediction_markets("Fed rate cut", curr_date=polymarket.get_current_date())
polymarket.get_prediction_markets("Fed rate cut", as_of_date=polymarket.get_current_date())
req.assert_called_once()
@@ -103,7 +103,7 @@ def test_a_historical_run_is_told_the_identity_is_current(monkeypatch):
identity = {"company_name": "Example Corp", "sector": "Technology",
"industry": "Software", "exchange": "NMS"}
historical = build_instrument_context("EXMP", "stock", identity, curr_date="2024-03-14")
historical = build_instrument_context("EXMP", "stock", identity, trade_date="2024-03-14")
assert "Example Corp" in historical
assert "2024-03-14" in historical and "today" in historical.lower()
@@ -114,7 +114,7 @@ def test_a_current_run_is_not_cluttered_with_a_vintage_note(monkeypatch):
from tradingagents.dataflows.date_window import get_current_date
today = build_instrument_context("EXMP", "stock", {"company_name": "Example Corp"},
curr_date=get_current_date())
trade_date=get_current_date())
assert "Example Corp" in today
assert "resolved today" not in today.lower()
@@ -297,7 +297,7 @@ def test_an_unavailable_notice_names_no_date_after_the_run():
coverage_gap([pd.Timestamp(today, tz="UTC")], "2025-01-01", "2025-01-07", "Feed", "news"),
withhold_live_profile("2025-01-07", "AAPL"),
_yf_insider(_insider_frame(today), "2025-01-07"),
build_instrument_context("EXMP", "stock", {"company_name": "Example"}, curr_date="2025-01-07"),
build_instrument_context("EXMP", "stock", {"company_name": "Example"}, trade_date="2025-01-07"),
]
for notice in notices:
assert _dates_after(notice, "2025-01-07") == [], notice
+3 -3
View File
@@ -100,7 +100,7 @@ def build_instrument_context(
ticker: str,
asset_type: str = "stock",
identity: Mapping[str, str] | None = None,
curr_date: str | None = None,
trade_date: str | None = None,
) -> str:
"""Describe the exact instrument so agents preserve identity and ticker.
@@ -144,10 +144,10 @@ def build_instrument_context(
"result explicitly disproves this resolved identity."
)
today = get_current_date()
if curr_date and str(curr_date) < today:
if trade_date and str(trade_date) < today:
context += (
f" This identity is how the vendor describes the instrument today, "
f"not necessarily on {curr_date}: a name or classification changed "
f"not necessarily on {trade_date}: a name or classification changed "
f"since then would read as the current one."
)
+7 -7
View File
@@ -95,7 +95,7 @@ def as_of_window(start_date: str, end_date: str, trade_date: str) -> tuple[str,
return f"{_parse(end) - span:%Y-%m-%d}", end
def withhold_live_profile(curr_date: str | None, label: str) -> str | None:
def withhold_live_profile(as_of_date: str | None, label: str) -> str | None:
"""Notice to serve instead of a live-only company profile, or None to serve it.
Vendor "company overview" endpoints (yfinance ``Ticker.info``, Alpha Vantage
@@ -105,20 +105,20 @@ def withhold_live_profile(curr_date: str | None, label: str) -> str | None:
Every fundamentals vendor withholds on this rule, so switching between them
cannot reintroduce the leak.
"""
if not curr_date:
if not as_of_date:
return None
today = get_current_date()
if curr_date >= today:
if as_of_date >= today:
return None
return (
f"# Company Fundamentals for {label}\n"
f"# Point-in-time as of: {curr_date}\n\n"
f"# Point-in-time as of: {as_of_date}\n\n"
f"Profile fundamentals are withheld for this date. This vendor serves "
f"only present-day values with no historical vintage: market "
f"cap, valuation multiples, the 52-week range and TTM income move with "
f"today's quote, and even the name, sector and industry reflect today "
f"rather than {curr_date} (companies rename and get reclassified). "
f"Serving them would put post-decision information into a {curr_date} "
f"analysis. Point-in-time fundamentals for {curr_date} are available "
f"rather than {as_of_date} (companies rename and get reclassified). "
f"Serving them would put post-decision information into a {as_of_date} "
f"analysis. Point-in-time fundamentals for {as_of_date} are available "
f"from the balance sheet, income statement, and cash flow tools."
)
+15 -15
View File
@@ -4,14 +4,14 @@ from tradingagents.dataflows.date_window import withhold_live_profile
from tradingagents.dataflows.vendors.alpha_vantage.common import _make_api_request
def _filter_reports_by_date(result, curr_date: str):
"""Drop annual/quarterly reports dated after curr_date to prevent look-ahead.
def _filter_reports_by_date(result, as_of_date: str):
"""Drop annual/quarterly reports dated after as_of_date to prevent look-ahead.
``_make_api_request`` returns the fundamentals payload as a JSON string, so
parse, filter, and re-serialize. A non-JSON body or an unset ``curr_date`` is
parse, filter, and re-serialize. A non-JSON body or an unset ``as_of_date`` is
returned unchanged.
"""
if not curr_date or not isinstance(result, str):
if not as_of_date or not isinstance(result, str):
return result
try:
payload = json.loads(result)
@@ -23,28 +23,28 @@ def _filter_reports_by_date(result, curr_date: str):
if isinstance(payload.get(key), list):
payload[key] = [
r for r in payload[key]
if r.get("fiscalDateEnding", "") <= curr_date
if r.get("fiscalDateEnding", "") <= as_of_date
]
return json.dumps(payload)
def get_fundamentals(ticker: str, curr_date: str = None) -> str:
def get_fundamentals(ticker: str, as_of_date: str = None) -> str:
"""
Retrieve comprehensive fundamental data for a given ticker symbol using Alpha Vantage.
OVERVIEW serves only present-day values and carries no historical vintage, so
a past ``curr_date`` withholds it rather than leaking post-decision figures
a past ``as_of_date`` withholds it rather than leaking post-decision figures
into a backtest (#1300); the statement endpoints below stay point-in-time via
``_filter_reports_by_date``.
Args:
ticker (str): Ticker symbol of the company
curr_date (str): Analysis date, yyyy-mm-dd
as_of_date (str): Analysis date, yyyy-mm-dd
Returns:
str: Company overview data including financial ratios and key metrics
"""
withheld = withhold_live_profile(curr_date, ticker)
withheld = withhold_live_profile(as_of_date, ticker)
if withheld:
return withheld
@@ -55,20 +55,20 @@ def get_fundamentals(ticker: str, curr_date: str = None) -> str:
return _make_api_request("OVERVIEW", params)
def get_balance_sheet(ticker: str, freq: str = "quarterly", curr_date: str = None):
def get_balance_sheet(ticker: str, freq: str = "quarterly", as_of_date: str = None):
"""Retrieve balance sheet data for a given ticker symbol using Alpha Vantage."""
result = _make_api_request("BALANCE_SHEET", {"symbol": ticker})
return _filter_reports_by_date(result, curr_date)
return _filter_reports_by_date(result, as_of_date)
def get_cashflow(ticker: str, freq: str = "quarterly", curr_date: str = None):
def get_cashflow(ticker: str, freq: str = "quarterly", as_of_date: str = None):
"""Retrieve cash flow statement data for a given ticker symbol using Alpha Vantage."""
result = _make_api_request("CASH_FLOW", {"symbol": ticker})
return _filter_reports_by_date(result, curr_date)
return _filter_reports_by_date(result, as_of_date)
def get_income_statement(ticker: str, freq: str = "quarterly", curr_date: str = None):
def get_income_statement(ticker: str, freq: str = "quarterly", as_of_date: str = None):
"""Retrieve income statement data for a given ticker symbol using Alpha Vantage."""
result = _make_api_request("INCOME_STATEMENT", {"symbol": ticker})
return _filter_reports_by_date(result, curr_date)
return _filter_reports_by_date(result, as_of_date)
+6 -6
View File
@@ -9,7 +9,7 @@ logger = logging.getLogger(__name__)
def get_indicator(
symbol: str,
indicator: str,
curr_date: str,
as_of_date: str,
look_back_days: int,
interval: str = "daily",
time_period: int = 14,
@@ -21,7 +21,7 @@ def get_indicator(
Args:
symbol: ticker symbol of the company
indicator: technical indicator to get the analysis and report of
curr_date: The current trading date you are trading on, YYYY-mm-dd
as_of_date: The current trading date you are trading on, YYYY-mm-dd
look_back_days: how many days to look back
interval: Time interval (daily, weekly, monthly)
time_period: Number of data points for calculation
@@ -72,8 +72,8 @@ def get_indicator(
f"Alpha Vantage does not serve {indicator}; it serves {list(supported_indicators)}"
)
curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d")
before = curr_date_dt - relativedelta(days=look_back_days)
as_of_dt = datetime.strptime(as_of_date, "%Y-%m-%d")
before = as_of_dt - relativedelta(days=look_back_days)
# Get the full data for the period instead of making individual calls
_, required_series_type = supported_indicators[indicator]
@@ -184,7 +184,7 @@ def get_indicator(
date_str = values[date_col_idx].strip()
date_dt = datetime.strptime(date_str, "%Y-%m-%d")
if before <= date_dt <= curr_date_dt:
if before <= date_dt <= as_of_dt:
value = values[value_col_idx].strip()
result_data.append((date_dt, value))
except (ValueError, IndexError):
@@ -201,7 +201,7 @@ def get_indicator(
ind_string = "No data available for the specified date range.\n"
result_str = (
f"## {indicator.upper()} values from {before.strftime('%Y-%m-%d')} to {curr_date}:\n\n"
f"## {indicator.upper()} values from {before.strftime('%Y-%m-%d')} to {as_of_date}:\n\n"
+ ind_string
+ "\n\n"
+ indicator_descriptions.get(indicator, "No description available.")
+8 -8
View File
@@ -33,13 +33,13 @@ def get_news(ticker, start_date, end_date) -> dict[str, str] | str:
return _make_api_request("NEWS_SENTIMENT", params)
def get_global_news(curr_date, look_back_days: int | None = None, limit: int | None = None) -> dict[str, str] | str:
def get_global_news(as_of_date, look_back_days: int | None = None, limit: int | None = None) -> dict[str, str] | str:
"""Returns global market news & sentiment data without ticker-specific filtering.
Covers broad market topics like financial markets, economy, and more.
Args:
curr_date: Current date in yyyy-mm-dd format.
as_of_date: Current date in yyyy-mm-dd format.
look_back_days: Number of days to look back; ``None`` uses
``global_news_lookback_days`` from the active config.
limit: Maximum number of articles; ``None`` uses
@@ -56,28 +56,28 @@ def get_global_news(curr_date, look_back_days: int | None = None, limit: int | N
if limit is None:
limit = config["global_news_article_limit"]
curr_dt = datetime.strptime(curr_date, "%Y-%m-%d")
curr_dt = datetime.strptime(as_of_date, "%Y-%m-%d")
start_dt = curr_dt - timedelta(days=look_back_days)
start_date = start_dt.strftime("%Y-%m-%d")
params = {
"topics": "financial_markets,economy_macro,economy_monetary",
"time_from": format_datetime_for_api(start_date),
"time_to": format_datetime_for_api(curr_date, end_of_day=True),
"time_to": format_datetime_for_api(as_of_date, end_of_day=True),
"limit": str(limit),
}
return _make_api_request("NEWS_SENTIMENT", params)
def get_insider_transactions(symbol: str, curr_date: str | None = None) -> dict[str, str] | str:
def get_insider_transactions(symbol: str, as_of_date: str | None = None) -> dict[str, str] | str:
"""Returns latest and historical insider transactions by key stakeholders.
Covers transactions by founders, executives, board members, etc.
Args:
symbol: Ticker symbol. Example: "IBM".
curr_date: When given, only transactions on or before it (yyyy-mm-dd).
as_of_date: When given, only transactions on or before it (yyyy-mm-dd).
Returns:
Dictionary containing insider transaction data or JSON string.
@@ -88,8 +88,8 @@ def get_insider_transactions(symbol: str, curr_date: str | None = None) -> dict[
}
response = _make_api_request("INSIDER_TRANSACTIONS", params)
if not curr_date:
if not as_of_date:
return response
payload = json.loads(response)
payload["data"] = [t for t in payload["data"] if t["transaction_date"] <= curr_date]
payload["data"] = [t for t in payload["data"] if t["transaction_date"] <= as_of_date]
return json.dumps(payload)
+10 -10
View File
@@ -126,7 +126,7 @@ def _fred_today() -> str:
"""FRED's current calendar date (US Central) as ``yyyy-mm-dd``.
The vintage pin is clamped to this: FRED rejects a ``realtime_start`` after
its own today with a 400, and ``curr_date`` on a live run comes from the
its own today with a 400, and ``as_of_date`` on a live run comes from the
caller's local clock, which can already be tomorrow in Chicago.
"""
return datetime.now(FRED_TZ).strftime("%Y-%m-%d")
@@ -155,7 +155,7 @@ def _request(path: str, params: dict) -> dict:
def get_macro_data(
indicator: str,
curr_date: str,
as_of_date: str,
look_back_days: int | None = None,
) -> str:
"""Fetch a FRED macroeconomic series as a formatted markdown report.
@@ -163,9 +163,9 @@ 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: The as-of date (yyyy-mm-dd). It bounds the observation window
as_of_date: The as-of date (yyyy-mm-dd). It bounds the observation window
AND pins the data vintage: FRED is queried with the realtime bounds
set to ``curr_date`` (clamped to FRED's own today) so a historical
set to ``as_of_date`` (clamped to FRED's own today) 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).
@@ -178,18 +178,18 @@ def get_macro_data(
if look_back_days is None:
look_back_days = DEFAULT_LOOKBACK_DAYS
end_dt = datetime.strptime(curr_date, "%Y-%m-%d")
end_dt = datetime.strptime(as_of_date, "%Y-%m-%d")
start_date = (end_dt - timedelta(days=look_back_days)).strftime("%Y-%m-%d")
# Pin the data vintage. FRED defaults both realtime bounds to today, serving
# the LATEST revision of every observation; a single-day realtime interval
# asks for the values known as of the pin instead, on both the metadata and
# observations requests (#1275). Clamp to FRED's today: on a live run
# curr_date is the caller's local date, which can be a day ahead of Chicago,
# as_of_date is the caller's local date, which can be a day ahead of Chicago,
# and a realtime date in FRED's future 400s -> the routing layer would then
# drop macro data silently. A past curr_date is unaffected, so historical
# drop macro data silently. A past as_of_date is unaffected, so historical
# point-in-time behaviour is preserved.
pit = min(curr_date, _fred_today())
pit = min(as_of_date, _fred_today())
realtime = {"realtime_start": pit, "realtime_end": pit}
# Invalid LLM-supplied indicator: return guidance rather than raising, so a
@@ -217,7 +217,7 @@ def get_macro_data(
{
"series_id": series_id,
"observation_start": start_date,
"observation_end": curr_date,
"observation_end": as_of_date,
"sort_order": "asc",
**realtime,
},
@@ -235,7 +235,7 @@ def get_macro_data(
f"- Units: {units}\n"
f"- Frequency: {frequency}"
f"{f' ({seasonal})' if seasonal else ''}\n"
f"- Window: {start_date} to {curr_date}\n"
f"- Window: {start_date} to {as_of_date}\n"
)
if not points:
+5 -5
View File
@@ -67,7 +67,7 @@ def _is_forward_looking(market: dict, now: datetime) -> bool:
)
def get_prediction_markets(topic: str, limit: int | None = None, curr_date: str | None = None) -> str:
def get_prediction_markets(topic: str, limit: int | None = None, as_of_date: str | None = None) -> str:
"""Return live prediction-market probabilities for an event topic.
Args:
@@ -75,7 +75,7 @@ def get_prediction_markets(topic: str, limit: int | None = None, curr_date: str
"US election", or a sector/company event.
limit: Max markets to return (ranked by traded volume); ``None`` uses
DEFAULT_LIMIT.
curr_date: The analysis date. Polymarket serves only live odds, so a
as_of_date: The analysis date. Polymarket serves only live odds, so a
date before today withholds them.
Returns:
@@ -83,11 +83,11 @@ def get_prediction_markets(topic: str, limit: int | None = None, curr_date: str
each with its implied probability, traded volume, resolution date, and
recent (1-week) move.
"""
if curr_date and curr_date < get_current_date():
if as_of_date and as_of_date < get_current_date():
return (
f"Prediction-market odds are withheld for {curr_date}. Polymarket serves "
f"Prediction-market odds are withheld for {as_of_date}. Polymarket serves "
f"only live odds on open markets, with no historical vintage, so serving "
f"them would put post-decision information into a {curr_date} analysis."
f"them would put post-decision information into a {as_of_date} analysis."
)
if limit is None:
limit = DEFAULT_LIMIT
+17 -17
View File
@@ -5,7 +5,7 @@ statement at the fiscal period end. That is two claims a run should not make: a
period that has ended is not public until the company files, weeks later, and a
figure that was later restated is not what investors saw at the time.
EDGAR reports every fact with the date it was filed, so a run dated ``curr_date``
EDGAR reports every fact with the date it was filed, so a run dated ``as_of_date``
serves exactly what was on file by then, restatements included at the vintage
that was current: Apple's 2008 total assets read 39.6B until the 2010 amendment
restated them to 36.2B.
@@ -144,7 +144,7 @@ def cik_for(ticker: str) -> str | None:
return None
def _as_of(facts: dict, tags: tuple[str, ...], curr_date: str, span: tuple[int, int],
def _as_of(facts: dict, tags: tuple[str, ...], as_of_date: str, span: tuple[int, int],
forms: tuple[str, ...] = ()) -> tuple[dict, str]:
"""({period end: value}, unit) for the first tag the filer reports, as known then.
@@ -164,7 +164,7 @@ def _as_of(facts: dict, tags: tuple[str, ...], curr_date: str, span: tuple[int,
latest: dict[str, dict] = {}
covered: set[str] = set() # period ends a filing of ``forms`` reports
for fact in unit_values:
if fact["filed"] > curr_date or fact["end"] in values:
if fact["filed"] > as_of_date or fact["end"] in values:
continue
# A duration fact (revenue, cash flow) must cover the span asked
# for. An instant fact (a balance) has no span and serves both.
@@ -184,8 +184,8 @@ def _as_of(facts: dict, tags: tuple[str, ...], curr_date: str, span: tuple[int,
return dict(sorted(values.items())), chosen_unit
def _statement(kind: str, ticker: str, freq: str, curr_date: str, title: str) -> str:
curr_date = curr_date or datetime.now().strftime("%Y-%m-%d")
def _statement(kind: str, ticker: str, freq: str, as_of_date: str, title: str) -> str:
as_of_date = as_of_date or datetime.now().strftime("%Y-%m-%d")
cik = cik_for(ticker)
if cik is None:
raise NoMarketDataError(ticker, ticker, "not a US SEC filer")
@@ -198,14 +198,14 @@ def _statement(kind: str, ticker: str, freq: str, curr_date: str, title: str) ->
quarterly = freq.lower() == "quarterly"
span = _SPANS["quarterly" if quarterly else "annual"]
forms = () if quarterly else _ANNUAL_FORMS
lines = {label: _as_of(us_gaap, tags, curr_date, span, forms) for label, tags in _STATEMENTS[kind]}
lines = {label: _as_of(us_gaap, tags, as_of_date, span, forms) for label, tags in _STATEMENTS[kind]}
periods = sorted({end for values, _ in lines.values() for end in values})
if not periods:
raise NoMarketDataError(ticker, ticker, f"no {freq} {title.lower()} filed by {curr_date}")
raise NoMarketDataError(ticker, ticker, f"no {freq} {title.lower()} filed by {as_of_date}")
header = (
f"# {title} for {ticker.upper()} ({freq}), USD in millions unless the row says otherwise\n"
f"# SEC EDGAR facts filed on or before {curr_date}, at the values filed then\n\n"
f"# SEC EDGAR facts filed on or before {as_of_date}, at the values filed then\n\n"
)
rows = [",".join([""] + periods)]
for label, (values, unit) in lines.items():
@@ -223,21 +223,21 @@ def _statement(kind: str, ticker: str, freq: str, curr_date: str, title: str) ->
return header + "\n".join(rows) + "\n"
def get_balance_sheet(ticker: str, freq: str = "quarterly", curr_date: str | None = None) -> str:
"""Balance sheet as filed on or before ``curr_date``."""
return _statement("balance_sheet", ticker, freq, curr_date, "Balance Sheet")
def get_balance_sheet(ticker: str, freq: str = "quarterly", as_of_date: str | None = None) -> str:
"""Balance sheet as filed on or before ``as_of_date``."""
return _statement("balance_sheet", ticker, freq, as_of_date, "Balance Sheet")
def get_income_statement(ticker: str, freq: str = "quarterly", curr_date: str | None = None) -> str:
"""Income statement as filed on or before ``curr_date``.
def get_income_statement(ticker: str, freq: str = "quarterly", as_of_date: str | None = None) -> str:
"""Income statement as filed on or before ``as_of_date``.
A fourth quarter is never derived: filers report it only inside the annual
figure, and subtracting three separately filed quarters would invent a number
with no filing date behind it.
"""
return _statement("income_statement", ticker, freq, curr_date, "Income Statement")
return _statement("income_statement", ticker, freq, as_of_date, "Income Statement")
def get_cashflow(ticker: str, freq: str = "quarterly", curr_date: str | None = None) -> str:
"""Cash flow statement as filed on or before ``curr_date``."""
return _statement("cashflow", ticker, freq, curr_date, "Cash Flow Statement")
def get_cashflow(ticker: str, freq: str = "quarterly", as_of_date: str | None = None) -> str:
"""Cash flow statement as filed on or before ``as_of_date``."""
return _statement("cashflow", ticker, freq, as_of_date, "Cash Flow Statement")
+21 -21
View File
@@ -16,19 +16,19 @@ from tradingagents.dataflows.vendors.yahoo.ohlcv import (
def get_fundamentals(
ticker: Annotated[str, "ticker symbol of the company"],
curr_date: Annotated[str, "analysis date in YYYY-MM-DD format"] = None
as_of_date: Annotated[str, "analysis date in YYYY-MM-DD format"] = None
):
"""Get company fundamentals overview from yfinance.
``Ticker.info`` is a present-day snapshot with no historical vintage, so a
past ``curr_date`` withholds it through the shared point-in-time guard
past ``as_of_date`` withholds it through the shared point-in-time guard
(``date_window.withhold_live_profile``, #1300).
"""
canonical = normalize_symbol(ticker)
# Guard before the request: the response would only be discarded, and the
# answer does not depend on it.
withheld = withhold_live_profile(curr_date, canonical)
withheld = withhold_live_profile(as_of_date, canonical)
if withheld:
return withheld
@@ -99,14 +99,14 @@ _PERIOD_END_VINTAGE = (
)
def _statement(ticker, freq, curr_date, title, quarterly_attr, annual_attr) -> str:
"""One financial statement as CSV, cut at ``curr_date`` by period end."""
def _statement(ticker, freq, as_of_date, title, quarterly_attr, annual_attr) -> str:
"""One financial statement as CSV, cut at ``as_of_date`` by period end."""
canonical = normalize_symbol(ticker)
what = title.lower()
try:
ticker_obj = yf.Ticker(canonical)
attr = quarterly_attr if freq.lower() == "quarterly" else annual_attr
data = filter_financials_by_date(yf_retry(lambda: getattr(ticker_obj, attr)), curr_date)
data = filter_financials_by_date(yf_retry(lambda: getattr(ticker_obj, attr)), as_of_date)
if data.empty:
raise_for_empty(ticker, canonical, f"{what} data")
return f"# {title} data for {canonical} ({freq})\n" + _PERIOD_END_VINTAGE + data.to_csv()
@@ -119,28 +119,28 @@ def _statement(ticker, freq, curr_date, title, quarterly_attr, annual_attr) -> s
def get_balance_sheet(
ticker: Annotated[str, "ticker symbol of the company"],
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
curr_date: Annotated[str, "current date in YYYY-MM-DD format"] = None
as_of_date: Annotated[str, "current date in YYYY-MM-DD format"] = None
):
"""Get balance sheet data from yfinance."""
return _statement(ticker, freq, curr_date, "Balance Sheet", "quarterly_balance_sheet", "balance_sheet")
return _statement(ticker, freq, as_of_date, "Balance Sheet", "quarterly_balance_sheet", "balance_sheet")
def get_cashflow(
ticker: Annotated[str, "ticker symbol of the company"],
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
curr_date: Annotated[str, "current date in YYYY-MM-DD format"] = None
as_of_date: Annotated[str, "current date in YYYY-MM-DD format"] = None
):
"""Get cash flow data from yfinance."""
return _statement(ticker, freq, curr_date, "Cash Flow", "quarterly_cashflow", "cashflow")
return _statement(ticker, freq, as_of_date, "Cash Flow", "quarterly_cashflow", "cashflow")
def get_income_statement(
ticker: Annotated[str, "ticker symbol of the company"],
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
curr_date: Annotated[str, "current date in YYYY-MM-DD format"] = None
as_of_date: Annotated[str, "current date in YYYY-MM-DD format"] = None
):
"""Get income statement data from yfinance."""
return _statement(ticker, freq, curr_date, "Income Statement", "quarterly_income_stmt", "income_stmt")
return _statement(ticker, freq, as_of_date, "Income Statement", "quarterly_income_stmt", "income_stmt")
# Rows are dated by the transaction, which is when the insider traded, not when
@@ -156,7 +156,7 @@ _TRANSACTION_DATE_VINTAGE = (
def get_insider_transactions(
ticker: Annotated[str, "ticker symbol of the company"],
curr_date: Annotated[str | None, "only transactions on or before this date, yyyy-mm-dd"] = None,
as_of_date: Annotated[str | None, "only transactions on or before this date, yyyy-mm-dd"] = None,
):
"""Get insider transactions data from yfinance."""
canonical = normalize_symbol(ticker)
@@ -171,12 +171,12 @@ def get_insider_transactions(
raise VendorRateLimitError("Yahoo Finance is unreachable; insider filings were not retrieved")
return f"No insider transactions reported for symbol '{canonical}'"
if curr_date:
if as_of_date:
traded = data["Start Date"]
kept = data[traded <= pd.Timestamp(curr_date)]
kept = data[traded <= pd.Timestamp(as_of_date)]
if kept.empty:
return (
f"<insider transactions unavailable for {canonical} as of {curr_date}: "
f"<insider transactions unavailable for {canonical} as of {as_of_date}: "
"Yahoo serves recent transactions only>"
)
data = kept
@@ -198,15 +198,15 @@ def get_company_profile(ticker: str) -> dict:
raise NoMarketDataError(ticker, canonical, f"profile unavailable: {e}") from e
def filter_financials_by_date(data: pd.DataFrame, curr_date: str) -> pd.DataFrame:
"""Drop financial statement columns (fiscal period timestamps) after curr_date.
def filter_financials_by_date(data: pd.DataFrame, as_of_date: str) -> pd.DataFrame:
"""Drop financial statement columns (fiscal period timestamps) after as_of_date.
yfinance financial statements use fiscal period end dates as columns.
Columns after curr_date represent future data and are removed to
Columns after as_of_date represent future data and are removed to
prevent look-ahead bias.
"""
if not curr_date or data.empty:
if not as_of_date or data.empty:
return data
cutoff = pd.Timestamp(curr_date)
cutoff = pd.Timestamp(as_of_date)
mask = pd.to_datetime(data.columns, errors="coerce") <= cutoff
return data.loc[:, mask]
+22 -22
View File
@@ -73,7 +73,7 @@ def get_YFin_data_online(
def get_stock_stats_indicators_window(
symbol: Annotated[str, "ticker symbol of the company"],
indicator: Annotated[str, "technical indicator to get the analysis and report of"],
curr_date: Annotated[
as_of_date: Annotated[
str, "The current trading date you are trading on, YYYY-mm-dd"
],
look_back_days: Annotated[int, "how many days to look back"],
@@ -157,16 +157,16 @@ def get_stock_stats_indicators_window(
f"Indicator {indicator} is not supported. Please choose from: {list(best_ind_params.keys())}"
)
end_date = curr_date
curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d")
before = curr_date_dt - relativedelta(days=look_back_days)
end_date = as_of_date
as_of_dt = datetime.strptime(as_of_date, "%Y-%m-%d")
before = as_of_dt - relativedelta(days=look_back_days)
# Optimized: Get stock data once and calculate indicators for all dates
try:
indicator_data = _get_stock_stats_bulk(symbol, indicator, curr_date)
indicator_data = _get_stock_stats_bulk(symbol, indicator, as_of_date)
# Generate the date range we need
current_dt = curr_date_dt
current_dt = as_of_dt
date_values = []
while current_dt >= before:
@@ -191,13 +191,13 @@ def get_stock_stats_indicators_window(
logger.warning("Bulk stockstats fetch failed, falling back per-day: %s", e)
# Fallback to original implementation if bulk method fails
ind_string = ""
curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d")
while curr_date_dt >= before:
as_of_dt = datetime.strptime(as_of_date, "%Y-%m-%d")
while as_of_dt >= before:
indicator_value = get_stockstats_indicator(
symbol, indicator, curr_date_dt.strftime("%Y-%m-%d")
symbol, indicator, as_of_dt.strftime("%Y-%m-%d")
)
ind_string += f"{curr_date_dt.strftime('%Y-%m-%d')}: {indicator_value}\n"
curr_date_dt = curr_date_dt - relativedelta(days=1)
ind_string += f"{as_of_dt.strftime('%Y-%m-%d')}: {indicator_value}\n"
as_of_dt = as_of_dt - relativedelta(days=1)
result_str = (
f"## {indicator} values from {before.strftime('%Y-%m-%d')} to {end_date}:\n\n"
@@ -212,7 +212,7 @@ def get_stock_stats_indicators_window(
def _get_stock_stats_bulk(
symbol: Annotated[str, "ticker symbol of the company"],
indicator: Annotated[str, "technical indicator to calculate"],
curr_date: Annotated[str, "current date for reference"]
as_of_date: Annotated[str, "current date for reference"]
) -> dict:
"""
Optimized bulk calculation of stock stats indicators.
@@ -221,7 +221,7 @@ def _get_stock_stats_bulk(
"""
from stockstats import wrap
data = load_ohlcv(symbol, curr_date)
data = load_ohlcv(symbol, as_of_date)
df = wrap(data)
df["Date"] = df["Date"].dt.strftime("%Y-%m-%d")
@@ -243,19 +243,19 @@ def _get_stock_stats_bulk(
def get_stockstats_indicator(
symbol: Annotated[str, "ticker symbol of the company"],
indicator: Annotated[str, "technical indicator to get the analysis and report of"],
curr_date: Annotated[
as_of_date: Annotated[
str, "The current trading date you are trading on, YYYY-mm-dd"
],
) -> str:
curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d")
curr_date = curr_date_dt.strftime("%Y-%m-%d")
as_of_dt = datetime.strptime(as_of_date, "%Y-%m-%d")
as_of_date = as_of_dt.strftime("%Y-%m-%d")
try:
indicator_value = get_stock_stats(
symbol,
indicator,
curr_date,
as_of_date,
)
except VendorError:
raise # Unknown/delisted symbol — let the router emit the sentinel
@@ -264,7 +264,7 @@ def get_stockstats_indicator(
# reads as no value that day rather than a read that failed. Raise so the
# router can try the next vendor or report the series unavailable.
raise NoMarketDataError(
symbol, symbol, f"{indicator} could not be read for {curr_date}: {e}"
symbol, symbol, f"{indicator} could not be read for {as_of_date}: {e}"
) from e
return str(indicator_value)
@@ -285,17 +285,17 @@ def get_stock_stats(
indicator: Annotated[
str, "quantitative indicators based off of the stock data for the company"
],
curr_date: Annotated[
as_of_date: Annotated[
str, "curr date for retrieving stock price data, YYYY-mm-dd"
],
):
data = load_ohlcv(symbol, curr_date)
data = load_ohlcv(symbol, as_of_date)
df = wrap(data)
df["Date"] = df["Date"].dt.strftime("%Y-%m-%d")
curr_date_str = pd.to_datetime(curr_date).strftime("%Y-%m-%d")
as_of_str = pd.to_datetime(as_of_date).strftime("%Y-%m-%d")
df[indicator] # trigger stockstats to calculate the indicator
matching_rows = df[df["Date"].str.startswith(curr_date_str)]
matching_rows = df[df["Date"].str.startswith(as_of_str)]
if not matching_rows.empty:
indicator_value = matching_rows[indicator].values[0]
+6 -6
View File
@@ -121,7 +121,7 @@ def get_news_yfinance(
def get_global_news_yfinance(
curr_date: str,
as_of_date: str,
look_back_days: int | None = None,
limit: int | None = None,
) -> str:
@@ -129,7 +129,7 @@ def get_global_news_yfinance(
Retrieve global/macro economic news using yfinance Search.
Args:
curr_date: Current date in yyyy-mm-dd format
as_of_date: Current date in yyyy-mm-dd format
look_back_days: Number of days to look back. ``None`` falls back to
``global_news_lookback_days`` from the active config.
limit: Maximum number of articles to return. ``None`` falls back to
@@ -145,7 +145,7 @@ def get_global_news_yfinance(
limit = config["global_news_article_limit"]
search_queries = config["global_news_queries"]
curr_dt = datetime.strptime(curr_date, "%Y-%m-%d")
curr_dt = datetime.strptime(as_of_date, "%Y-%m-%d")
start_dt = curr_dt - relativedelta(days=look_back_days)
start_date = start_dt.strftime("%Y-%m-%d")
@@ -189,10 +189,10 @@ def get_global_news_yfinance(
if not news_str:
# Results merge several fuzzy searches, so their timestamps prove no
# continuous coverage; judge the window against the present only.
gap = coverage_gap((), start_date, curr_date, "Yahoo Finance global news", "market news")
return gap or f"No global news found between {start_date} and {curr_date}"
gap = coverage_gap((), start_date, as_of_date, "Yahoo Finance global news", "market news")
return gap or f"No global news found between {start_date} and {as_of_date}"
return f"## Global Market News, from {start_date} to {curr_date}:\n\n{news_str}"
return f"## Global Market News, from {start_date} to {as_of_date}:\n\n{news_str}"
except VendorError:
raise
+16 -16
View File
@@ -93,7 +93,7 @@ def _local_midnight(value) -> pd.Timestamp:
def _normalize_dates(dates) -> pd.Series:
"""Parse to naive, midnight-normalized dates so tz-aware or intraday
timestamps compare correctly against the naive ``curr_date`` cutoff (#1201).
timestamps compare correctly against the naive ``as_of_date`` cutoff (#1201).
Normalized per element: 5 years of yfinance bars span daylight-saving
changes (and cache CSVs round-trip the offsets as strings), so the series can
@@ -147,13 +147,13 @@ def _coerce_ohlcv_dates(data: pd.DataFrame) -> pd.Series:
def _assert_ohlcv_not_stale(
data: pd.DataFrame,
curr_date: str,
as_of_date: str,
symbol: str,
canonical: str | None = None,
*,
max_stale_days: int = MAX_OHLCV_STALE_DAYS,
) -> None:
"""Reject OHLCV whose latest row is far older than curr_date.
"""Reject OHLCV whose latest row is far older than as_of_date.
Raises NoMarketDataError (with a stale-specific detail) so the router treats
it like any other "no usable data from this vendor" — try the next vendor,
@@ -164,7 +164,7 @@ def _assert_ohlcv_not_stale(
"""
if data is None or data.empty:
return
requested = pd.to_datetime(curr_date, errors="coerce")
requested = pd.to_datetime(as_of_date, errors="coerce")
if pd.isna(requested):
return
requested = requested.normalize()
@@ -182,7 +182,7 @@ def _assert_ohlcv_not_stale(
)
def _cache_is_fresh(data_file, curr_date_dt, now) -> bool:
def _cache_is_fresh(data_file, as_of_dt, now) -> bool:
"""Whether the symbol's cached download can serve this request.
The file holds the download made on the day it was written, so it serves
@@ -194,14 +194,14 @@ def _cache_is_fresh(data_file, curr_date_dt, now) -> bool:
written = pd.Timestamp.fromtimestamp(os.path.getmtime(data_file))
if written.date() != now.date():
return False
return curr_date_dt.date() < now.date() or (now - written).total_seconds() <= OHLCV_CACHE_TTL_SECONDS
return as_of_dt.date() < now.date() or (now - written).total_seconds() <= OHLCV_CACHE_TTL_SECONDS
def load_ohlcv(symbol: str, curr_date: str, fill_gaps: bool = True) -> pd.DataFrame:
def load_ohlcv(symbol: str, as_of_date: str, fill_gaps: bool = True) -> pd.DataFrame:
"""Fetch OHLCV data with caching, filtered to prevent look-ahead bias.
Downloads 5 years of data up to today and caches per symbol. On
subsequent calls the cache is reused. Rows after curr_date are
subsequent calls the cache is reused. Rows after as_of_date are
filtered out so backtests never see future prices.
``fill_gaps`` carries prices forward over gaps so indicators compute on a
@@ -215,15 +215,15 @@ def load_ohlcv(symbol: str, curr_date: str, fill_gaps: bool = True) -> pd.DataFr
safe_symbol = safe_ticker_component(canonical)
config = get_config()
curr_date_dt = pd.to_datetime(curr_date).normalize()
as_of_dt = pd.to_datetime(as_of_date).normalize()
# One cache file per symbol, holding the latest 5y-to-today download.
now = pd.Timestamp.today()
start_date = now - pd.DateOffset(years=5)
start_str = start_date.strftime("%Y-%m-%d")
# yfinance ``end`` is EXCLUSIVE; request tomorrow so today's row is included
# when curr_date is the current day (#986). Look-ahead is still prevented by
# the curr_date filter below.
# when as_of_date is the current day (#986). Look-ahead is still prevented by
# the as_of_date filter below.
end_str = (now + pd.Timedelta(days=1)).strftime("%Y-%m-%d")
os.makedirs(config["data_cache_dir"], exist_ok=True)
@@ -241,7 +241,7 @@ def load_ohlcv(symbol: str, curr_date: str, fill_gaps: bool = True) -> pd.DataFr
if (
not cached.empty
and "Close" in cached.columns
and _cache_is_fresh(data_file, curr_date_dt, now)
and _cache_is_fresh(data_file, as_of_dt, now)
):
data = cached
@@ -271,8 +271,8 @@ def load_ohlcv(symbol: str, curr_date: str, fill_gaps: bool = True) -> pd.DataFr
data = _clean_dataframe(data)
# Filter to curr_date to prevent look-ahead bias in backtesting.
data = data[data["Date"] <= curr_date_dt]
# Filter to as_of_date to prevent look-ahead bias in backtesting.
data = data[data["Date"] <= as_of_dt]
# A closeless newest bar is an unsettled session, not a symbol without data.
# _fill_price_gaps below drops it, here and mid-series alike, so the frame
@@ -295,9 +295,9 @@ def load_ohlcv(symbol: str, curr_date: str, fill_gaps: bool = True) -> pd.DataFr
# a filled cell is the previous session's price under this session's date.
data = _fill_price_gaps(data) if fill_gaps else data.dropna(subset=["Close"]).copy()
# Reject a stale frame (latest row far older than curr_date) rather than
# Reject a stale frame (latest row far older than as_of_date) rather than
# feeding year-old prices into indicators (#1021).
_assert_ohlcv_not_stale(data, curr_date, symbol, canonical)
_assert_ohlcv_not_stale(data, as_of_date, symbol, canonical)
return data
+8 -8
View File
@@ -25,8 +25,8 @@ DEFAULT_SNAPSHOT_INDICATORS: tuple[str, ...] = (
)
def _verified_rows(symbol: str, curr_date: str) -> pd.DataFrame:
"""OHLCV on or before curr_date, date-sorted. Raises if nothing usable.
def _verified_rows(symbol: str, as_of_date: str) -> pd.DataFrame:
"""OHLCV on or before as_of_date, date-sorted. Raises if nothing usable.
``load_ohlcv`` already normalizes the Date column and filters out
look-ahead rows, but we re-apply the cutoff defensively — this is a
@@ -34,16 +34,16 @@ def _verified_rows(symbol: str, curr_date: str) -> pd.DataFrame:
"""
# As reported: this snapshot is quoted by the agents as exact prices, so a
# gap-filled cell would put the previous session's number under this date.
data = load_ohlcv(symbol, curr_date, fill_gaps=False)
data = load_ohlcv(symbol, as_of_date, fill_gaps=False)
if data is None or data.empty:
raise ValueError(f"No OHLCV data available for {symbol}.")
df = data.copy()
df["Date"] = pd.to_datetime(df["Date"], errors="coerce")
df = df.dropna(subset=["Date"])
df = df[df["Date"] <= pd.to_datetime(curr_date)].sort_values("Date")
df = df[df["Date"] <= pd.to_datetime(as_of_date)].sort_values("Date")
if df.empty:
raise ValueError(f"No OHLCV rows on or before {curr_date} for {symbol}.")
raise ValueError(f"No OHLCV rows on or before {as_of_date} for {symbol}.")
return df
@@ -63,7 +63,7 @@ def _fmt(value) -> str:
def build_verified_market_snapshot(
symbol: str,
curr_date: str,
as_of_date: str,
look_back_days: int = 30,
indicators: Iterable[str] | None = None,
) -> str:
@@ -71,7 +71,7 @@ def build_verified_market_snapshot(
# `df` keeps the original capitalized OHLCV columns (Open/High/Low/Close/
# Volume); stockstats `wrap()` lowercases columns and adds indicator
# columns, so read raw prices from `df` and indicators from `stock_df`.
df = _verified_rows(symbol, curr_date)
df = _verified_rows(symbol, as_of_date)
stock_df = wrap(df.copy())
selected = tuple(indicators or DEFAULT_SNAPSHOT_INDICATORS)
@@ -91,7 +91,7 @@ def build_verified_market_snapshot(
lines = [
f"## Verified market data snapshot for {symbol.upper()}",
"",
f"- Requested analysis date: {curr_date}",
f"- Requested analysis date: {as_of_date}",
f"- Latest trading row used: {latest_date}",
"- Rows after the requested analysis date are excluded before verification.",
"",
+2 -2
View File
@@ -114,7 +114,7 @@ class TradingAgentsGraph:
self._resuming = False
def resolve_instrument_context(self, ticker: str, asset_type: str = "stock",
curr_date: str | None = None) -> str:
trade_date: str | None = None) -> str:
"""Resolve ticker identity once and return the full instrument context.
Deterministic yfinance lookup (cached, fail-open) injected into a
@@ -124,7 +124,7 @@ class TradingAgentsGraph:
graph regardless of entry point.
"""
identity = resolve_instrument_identity(ticker)
return build_instrument_context(ticker, asset_type, identity, curr_date)
return build_instrument_context(ticker, asset_type, identity, trade_date)
def _memory_as_of(self, trade_date) -> str | None:
"""Point-in-time cutoff for past-context lessons (#1251).