From 2942655f703b45a67026e740684ab5a7b5410022 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Tue, 15 Sep 2026 00:04:25 +0000 Subject: [PATCH] fix(dataflows): map Shanghai .SH tickers to Yahoo's .SS (#1260) - normalize_symbol maps 600519.SH to 600519.SS - the alpha benchmark is resolved from the normalized symbol --- tests/test_memory_log.py | 2 ++ tests/test_symbol_utils.py | 4 ++++ tradingagents/dataflows/symbol_utils.py | 7 ++++++- tradingagents/graph/trading_graph.py | 4 +++- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/test_memory_log.py b/tests/test_memory_log.py index 77629fb4d..28a943498 100644 --- a/tests/test_memory_log.py +++ b/tests/test_memory_log.py @@ -598,6 +598,8 @@ class TestDeferredReflection: "benchmark_map": DEFAULT_CONFIG["benchmark_map"]} assert TradingAgentsGraph._resolve_benchmark(mock_graph, "600519.SS") == "000001.SS" assert TradingAgentsGraph._resolve_benchmark(mock_graph, "000001.SZ") == "399001.SZ" + # .SH is the exchange's own suffix; Yahoo spells Shanghai .SS (#1260) + assert TradingAgentsGraph._resolve_benchmark(mock_graph, "600519.SH") == "000001.SS" def test_resolve_benchmark_us_ticker_defaults_to_spy(self): """US tickers (no dotted suffix) take the empty-suffix entry.""" diff --git a/tests/test_symbol_utils.py b/tests/test_symbol_utils.py index f87ca393a..8ddf877c8 100644 --- a/tests/test_symbol_utils.py +++ b/tests/test_symbol_utils.py @@ -69,6 +69,10 @@ class TestNormalizeSymbol(unittest.TestCase): def test_hk_code_case_insensitive_suffix(self): self.assertEqual(normalize_symbol("09992.hk"), "9992.HK") + def test_shanghai_sh_suffix_maps_to_yahoo_ss(self): + self.assertEqual(normalize_symbol("600519.sh"), "600519.SS") + self.assertEqual(normalize_symbol("600519.SS"), "600519.SS") + @pytest.mark.unit class TestNoMarketDataError(unittest.TestCase): diff --git a/tradingagents/dataflows/symbol_utils.py b/tradingagents/dataflows/symbol_utils.py index 274cc0889..647f50444 100644 --- a/tradingagents/dataflows/symbol_utils.py +++ b/tradingagents/dataflows/symbol_utils.py @@ -11,6 +11,7 @@ differ from the broker / TradingView / MT5 style symbols users often type: BTCUSD BTC-USD crypto pairs use a ``-`` separator SPX500, US500 ^GSPC index CFDs map to Yahoo index symbols 09992.HK, 700.HK 9992.HK, 0700.HK HK codes are zero-padded to 4 digits + 600519.SH 600519.SS Yahoo spells Shanghai ``.SS`` Passing the raw broker symbol to Yahoo returns an empty result, which the agents previously received as free text and could hallucinate a price @@ -75,6 +76,7 @@ _YAHOO_SAFE = re.compile(r"^[A-Za-z0-9._\-\^=]+$") # HKEX codes as Yahoo spells them: the number zero-padded to 4 digits (#957). _HK_CODE = re.compile(r"^(\d{1,5})\.HK$") +_SHANGHAI_SH = re.compile(r"^(\d{6})\.SH$") # Crypto quote currencies that all map to Yahoo's USD pair. Yahoo lists only @@ -115,7 +117,8 @@ def normalize_symbol(raw: str) -> str: 3. Forex rule: six letters that are two ISO currency codes -> ``PAIR=X``. 4. HK rule: a numeric ``.HK`` code -> Yahoo's 4-digit padding (``09992.HK`` -> ``9992.HK``, ``700.HK`` -> ``0700.HK``). - 5. Otherwise the upper-cased symbol is returned unchanged (plain + 5. Shanghai rule: ``600519.SH`` -> ``600519.SS``. + 6. Otherwise the upper-cased symbol is returned unchanged (plain equities, ETFs, Yahoo-native symbols like ``GC=F`` or ``^GSPC``). A trailing ``+`` (broker CFD marker, e.g. ``XAUUSD+``) is stripped before @@ -138,6 +141,8 @@ def normalize_symbol(raw: str) -> str: canonical = f"{s}=X" elif hk := _HK_CODE.match(s): canonical = f"{int(hk.group(1)):04d}.HK" + elif sh := _SHANGHAI_SH.match(s): + canonical = f"{sh.group(1)}.SS" else: canonical = s diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 4d732644b..900266535 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -274,11 +274,13 @@ class TradingAgentsGraph: entry, which is the right default because the alpha calculation works in USD. """ + from tradingagents.dataflows.symbol_utils import normalize_symbol + explicit = self.config.get("benchmark_ticker") if explicit: return explicit benchmark_map = self.config.get("benchmark_map", {}) - ticker_upper = ticker.upper() + ticker_upper = normalize_symbol(ticker) for suffix, benchmark in benchmark_map.items(): if suffix and ticker_upper.endswith(suffix.upper()): return benchmark