From 99313bfdbc8f8a5da07733f620cf6abe9a24709d Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Mon, 14 Sep 2026 23:37:48 +0000 Subject: [PATCH] fix(dataflows): zero-pad HK ticker codes to Yahoo's 4-digit form (#1342) - normalize_symbol maps 09992.HK to 9992.HK and 700.HK to 0700.HK (#957) --- tests/test_symbol_utils.py | 17 +++++++++++++++++ tradingagents/dataflows/symbol_utils.py | 10 +++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/test_symbol_utils.py b/tests/test_symbol_utils.py index 9db3894ff..f87ca393a 100644 --- a/tests/test_symbol_utils.py +++ b/tests/test_symbol_utils.py @@ -52,6 +52,23 @@ class TestNormalizeSymbol(unittest.TestCase): def test_empty_input_passthrough(self): self.assertEqual(normalize_symbol(""), "") + def test_hk_five_digit_code_repadded_to_four(self): + # HKEX lists up to 5-digit codes; Yahoo only accepts 4 (#957). + self.assertEqual(normalize_symbol("09992.HK"), "9992.HK") + self.assertEqual(normalize_symbol("00700.HK"), "0700.HK") + self.assertEqual(normalize_symbol("00001.HK"), "0001.HK") + + def test_hk_four_digit_code_unchanged(self): + self.assertEqual(normalize_symbol("0700.HK"), "0700.HK") + self.assertEqual(normalize_symbol("9992.HK"), "9992.HK") + self.assertEqual(normalize_symbol("80737.HK"), "80737.HK") + + def test_hk_short_code_padded_to_four(self): + self.assertEqual(normalize_symbol("700.HK"), "0700.HK") + + def test_hk_code_case_insensitive_suffix(self): + self.assertEqual(normalize_symbol("09992.hk"), "9992.HK") + @pytest.mark.unit class TestNoMarketDataError(unittest.TestCase): diff --git a/tradingagents/dataflows/symbol_utils.py b/tradingagents/dataflows/symbol_utils.py index f91b4e149..274cc0889 100644 --- a/tradingagents/dataflows/symbol_utils.py +++ b/tradingagents/dataflows/symbol_utils.py @@ -10,6 +10,7 @@ differ from the broker / TradingView / MT5 style symbols users often type: EURUSD EURUSD=X spot forex pairs take a ``=X`` suffix 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 Passing the raw broker symbol to Yahoo returns an empty result, which the agents previously received as free text and could hallucinate a price @@ -72,6 +73,9 @@ _ALIASES = { # Yahoo symbols may contain letters, digits, and these structural characters. _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$") + # Crypto quote currencies that all map to Yahoo's USD pair. Yahoo lists only # ``-USD`` (not the USDT/USDC stablecoin pairs), so a broker symbol quoted @@ -109,7 +113,9 @@ def normalize_symbol(raw: str) -> str: 2. Crypto rule: a known crypto base quoted in USD/USDT/USDC (dashed or not) -> ``BASE-USD``. 3. Forex rule: six letters that are two ISO currency codes -> ``PAIR=X``. - 4. Otherwise the upper-cased symbol is returned unchanged (plain + 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 equities, ETFs, Yahoo-native symbols like ``GC=F`` or ``^GSPC``). A trailing ``+`` (broker CFD marker, e.g. ``XAUUSD+``) is stripped before @@ -130,6 +136,8 @@ def normalize_symbol(raw: str) -> str: canonical = crypto elif len(s) == 6 and s[:3] in _FOREX_CURRENCIES and s[3:] in _FOREX_CURRENCIES: canonical = f"{s}=X" + elif hk := _HK_CODE.match(s): + canonical = f"{int(hk.group(1)):04d}.HK" else: canonical = s