From 9ad98c55c5bee4b6cd142e9783c6d52783048c62 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 21 Jun 2026 21:28:59 +0000 Subject: [PATCH] fix(data): normalize ticker on the news path The yfinance news fetch queried the raw ticker while every other path uses the canonical symbol, so broker/forex/crypto aliases silently returned no news. Normalize it (XAUUSD -> GC=F) and keep the user's ticker in the report header. --- tests/test_symbol_normalization_paths.py | 29 ++++++++++++++++++++---- tradingagents/dataflows/yfinance_news.py | 14 ++++++++---- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/tests/test_symbol_normalization_paths.py b/tests/test_symbol_normalization_paths.py index a023d24a6..1f4d9c88d 100644 --- a/tests/test_symbol_normalization_paths.py +++ b/tests/test_symbol_normalization_paths.py @@ -1,13 +1,14 @@ """Symbol normalization must apply on every yfinance path, not just price fetch. -Regression tests for #983 (instrument identity) and #984 (reflection returns): -a broker symbol like XAUUSD must resolve to the same Yahoo symbol (GC=F) that -the price path uses, so identity and realized-return lookups hit the right -instrument instead of failing/mismatching. +Regression tests for #983 (instrument identity), #984 (reflection returns), and +the news path: a broker symbol like XAUUSD must resolve to the same Yahoo symbol +(GC=F) that the price path uses, so identity, realized-return, and news lookups +hit the right instrument instead of failing/mismatching. """ import pandas as pd import tradingagents.agents.utils.agent_utils as au +import tradingagents.dataflows.yfinance_news as ynews import tradingagents.graph.trading_graph as tg from tradingagents.graph.trading_graph import TradingAgentsGraph @@ -52,3 +53,23 @@ def test_fetch_returns_normalizes_symbol(monkeypatch): assert queried[0] == "GC=F" # stock symbol normalized (#984) assert queried[1] == "SPY" # benchmark left as the canonical symbol assert raw is not None and days is not None + + +def test_news_lookup_normalizes_symbol(monkeypatch): + seen = {} + + class FakeTicker: + def __init__(self, symbol): + seen["symbol"] = symbol + + def get_news(self, count): + return [] + + monkeypatch.setattr(ynews.yf, "Ticker", FakeTicker) + monkeypatch.setattr(ynews, "yf_retry", lambda fn: fn()) + + out = ynews.get_news_yfinance("XAUUSD", "2025-01-01", "2025-01-10") + + assert seen["symbol"] == "GC=F" # news queried with the canonical symbol + assert "XAUUSD" in out # the user's ticker stays in the report + assert "GC=F" in out # provenance noted diff --git a/tradingagents/dataflows/yfinance_news.py b/tradingagents/dataflows/yfinance_news.py index 3c8e4ba6f..832e3991a 100644 --- a/tradingagents/dataflows/yfinance_news.py +++ b/tradingagents/dataflows/yfinance_news.py @@ -8,6 +8,7 @@ from dateutil.relativedelta import relativedelta from .config import get_config from .stockstats_utils import yf_retry +from .symbol_utils import normalize_symbol def _extract_article_data(article: dict) -> dict: @@ -87,12 +88,17 @@ def get_news_yfinance( Formatted string containing news articles """ article_limit = get_config()["news_article_limit"] + # Query Yahoo with the canonical symbol, like every other yfinance path — + # a raw broker/forex/crypto alias (XAUUSD, BTCUSD) otherwise silently + # returns no news. Keep the user's ticker in the report header. + canonical = normalize_symbol(ticker) + resolved = "" if canonical == ticker else f" (resolved to {canonical})" try: - stock = yf.Ticker(ticker) + stock = yf.Ticker(canonical) news = yf_retry(lambda: stock.get_news(count=article_limit)) if not news: - return f"No news found for {ticker}" + return f"No news found for {ticker}{resolved}" # Parse date range for filtering start_dt = datetime.strptime(start_date, "%Y-%m-%d") @@ -117,9 +123,9 @@ def get_news_yfinance( filtered_count += 1 if filtered_count == 0: - return f"No news found for {ticker} between {start_date} and {end_date}" + return f"No news found for {ticker}{resolved} between {start_date} and {end_date}" - return f"## {ticker} News, from {start_date} to {end_date}:\n\n{news_str}" + return f"## {ticker}{resolved} News, from {start_date} to {end_date}:\n\n{news_str}" except Exception as e: return f"Error fetching news for {ticker}: {str(e)}"