mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-06-16 21:06:15 +03:00
fix(data): normalize symbols on the identity and reflection paths
resolve_instrument_identity and the reflection return lookup queried Yahoo with the raw ticker, so broker/forex/commodity symbols (XAUUSD, BTCUSD, EURUSD) failed identity or could mismatch the priced instrument even though the price path already normalized them. Route both through normalize_symbol (#983, #984).
This commit is contained in:
54
tests/test_symbol_normalization_paths.py
Normal file
54
tests/test_symbol_normalization_paths.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
"""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.
|
||||||
|
"""
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
import tradingagents.agents.utils.agent_utils as au
|
||||||
|
import tradingagents.graph.trading_graph as tg
|
||||||
|
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||||
|
|
||||||
|
|
||||||
|
def test_identity_lookup_normalizes_symbol(monkeypatch):
|
||||||
|
seen = {}
|
||||||
|
|
||||||
|
class FakeTicker:
|
||||||
|
def __init__(self, symbol):
|
||||||
|
seen["symbol"] = symbol
|
||||||
|
|
||||||
|
@property
|
||||||
|
def info(self):
|
||||||
|
return {"longName": "Gold Futures", "quoteType": "FUTURE"}
|
||||||
|
|
||||||
|
monkeypatch.setattr(au.yf, "Ticker", FakeTicker)
|
||||||
|
au.resolve_instrument_identity.cache_clear()
|
||||||
|
|
||||||
|
identity = au.resolve_instrument_identity("XAUUSD")
|
||||||
|
|
||||||
|
assert seen["symbol"] == "GC=F" # normalized, not the raw broker symbol
|
||||||
|
assert identity.get("company_name") == "Gold Futures"
|
||||||
|
|
||||||
|
|
||||||
|
def test_fetch_returns_normalizes_symbol(monkeypatch):
|
||||||
|
queried = []
|
||||||
|
|
||||||
|
class FakeTicker:
|
||||||
|
def __init__(self, symbol):
|
||||||
|
queried.append(symbol)
|
||||||
|
|
||||||
|
def history(self, *args, **kwargs):
|
||||||
|
return pd.DataFrame({"Close": [100.0, 101.0, 102.0, 103.0, 104.0, 105.0, 106.0]})
|
||||||
|
|
||||||
|
monkeypatch.setattr(tg.yf, "Ticker", FakeTicker)
|
||||||
|
|
||||||
|
# _fetch_returns does not use ``self``; call unbound to avoid building the graph.
|
||||||
|
raw, alpha, days = TradingAgentsGraph._fetch_returns(
|
||||||
|
None, "XAUUSD", "2025-01-02", holding_days=5, benchmark="SPY"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
@@ -70,9 +70,14 @@ def resolve_instrument_identity(ticker: str) -> dict:
|
|||||||
recognise the ticker, we return ``{}`` and the caller falls back to
|
recognise the ticker, we return ``{}`` and the caller falls back to
|
||||||
ticker-only context rather than failing before analysis starts. Cached so
|
ticker-only context rather than failing before analysis starts. Cached so
|
||||||
the lookup happens at most once per ticker per process.
|
the lookup happens at most once per ticker per process.
|
||||||
|
|
||||||
|
The symbol is normalized first (e.g. ``XAUUSD`` -> ``GC=F``) so identity
|
||||||
|
resolves for the same instrument the price path actually fetches (#983).
|
||||||
"""
|
"""
|
||||||
|
from tradingagents.dataflows.symbol_utils import normalize_symbol
|
||||||
|
|
||||||
try:
|
try:
|
||||||
info = yf.Ticker(ticker.upper()).info or {}
|
info = yf.Ticker(normalize_symbol(ticker)).info or {}
|
||||||
except Exception as exc: # noqa: BLE001 — fail open, never block the run
|
except Exception as exc: # noqa: BLE001 — fail open, never block the run
|
||||||
logger.debug("Could not resolve instrument identity for %s: %s", ticker, exc)
|
logger.debug("Could not resolve instrument identity for %s: %s", ticker, exc)
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -232,12 +232,17 @@ class TradingAgentsGraph:
|
|||||||
actual_holding_days)`` or ``(None, None, None)`` if price data is
|
actual_holding_days)`` or ``(None, None, None)`` if price data is
|
||||||
unavailable (too recent, delisted, or network error).
|
unavailable (too recent, delisted, or network error).
|
||||||
"""
|
"""
|
||||||
|
from tradingagents.dataflows.symbol_utils import normalize_symbol
|
||||||
|
|
||||||
try:
|
try:
|
||||||
start = datetime.strptime(trade_date, "%Y-%m-%d")
|
start = datetime.strptime(trade_date, "%Y-%m-%d")
|
||||||
end = start + timedelta(days=holding_days + 7) # buffer for weekends/holidays
|
end = start + timedelta(days=holding_days + 7) # buffer for weekends/holidays
|
||||||
end_str = end.strftime("%Y-%m-%d")
|
end_str = end.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
stock = yf.Ticker(ticker).history(start=trade_date, end=end_str)
|
# Normalize so the realized-return lookup hits the same instrument
|
||||||
|
# the analysis priced (e.g. XAUUSD -> GC=F) (#984). The benchmark is
|
||||||
|
# already a canonical Yahoo symbol from ``_resolve_benchmark``.
|
||||||
|
stock = yf.Ticker(normalize_symbol(ticker)).history(start=trade_date, end=end_str)
|
||||||
bench = yf.Ticker(benchmark).history(start=trade_date, end=end_str)
|
bench = yf.Ticker(benchmark).history(start=trade_date, end=end_str)
|
||||||
|
|
||||||
if len(stock) < 2 or len(bench) < 2:
|
if len(stock) < 2 or len(bench) < 2:
|
||||||
|
|||||||
Reference in New Issue
Block a user