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)
This commit is contained in:
Yijia-Xiao
2026-09-14 23:37:48 +00:00
parent d04693a208
commit 99313bfdbc
2 changed files with 26 additions and 1 deletions

View File

@@ -52,6 +52,23 @@ class TestNormalizeSymbol(unittest.TestCase):
def test_empty_input_passthrough(self): def test_empty_input_passthrough(self):
self.assertEqual(normalize_symbol(""), "") 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 @pytest.mark.unit
class TestNoMarketDataError(unittest.TestCase): class TestNoMarketDataError(unittest.TestCase):

View File

@@ -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 EURUSD EURUSD=X spot forex pairs take a ``=X`` suffix
BTCUSD BTC-USD crypto pairs use a ``-`` separator BTCUSD BTC-USD crypto pairs use a ``-`` separator
SPX500, US500 ^GSPC index CFDs map to Yahoo index symbols 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 Passing the raw broker symbol to Yahoo returns an empty result, which the
agents previously received as free text and could hallucinate a price 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 symbols may contain letters, digits, and these structural characters.
_YAHOO_SAFE = re.compile(r"^[A-Za-z0-9._\-\^=]+$") _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 # Crypto quote currencies that all map to Yahoo's USD pair. Yahoo lists only
# ``<BASE>-USD`` (not the USDT/USDC stablecoin pairs), so a broker symbol quoted # ``<BASE>-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 2. Crypto rule: a known crypto base quoted in USD/USDT/USDC (dashed or
not) -> ``BASE-USD``. not) -> ``BASE-USD``.
3. Forex rule: six letters that are two ISO currency codes -> ``PAIR=X``. 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``). equities, ETFs, Yahoo-native symbols like ``GC=F`` or ``^GSPC``).
A trailing ``+`` (broker CFD marker, e.g. ``XAUUSD+``) is stripped before A trailing ``+`` (broker CFD marker, e.g. ``XAUUSD+``) is stripped before
@@ -130,6 +136,8 @@ def normalize_symbol(raw: str) -> str:
canonical = crypto canonical = crypto
elif len(s) == 6 and s[:3] in _FOREX_CURRENCIES and s[3:] in _FOREX_CURRENCIES: elif len(s) == 6 and s[:3] in _FOREX_CURRENCIES and s[3:] in _FOREX_CURRENCIES:
canonical = f"{s}=X" canonical = f"{s}=X"
elif hk := _HK_CODE.match(s):
canonical = f"{int(hk.group(1)):04d}.HK"
else: else:
canonical = s canonical = s