{"record":{"id":"9f5536ea0c88de0b","repo":"ZhuLinsen/daily_stock_analysis","slug":"etf-stock-code-eastmoney","errorCode":null,"errorMessage":"无法确定 ETF {stock_code} 的 Eastmoney 市场前缀","messagePattern":"无法确定 ETF (.+?) 的 Eastmoney 市场前缀","errorType":"exception","errorClass":"DataFetchError","httpStatus":null,"severity":"error","filePath":"data_provider/efinance_fetcher.py","lineNumber":178,"sourceCode":"    Args:\n        stock_code: 股票/基金代码\n        \n    Returns:\n        True 表示是 ETF 代码，False 表示是普通股票代码\n    \"\"\"\n    return _is_a_share_etf_code(stock_code)\n\n\ndef _build_eastmoney_etf_secid(stock_code: str) -> str:\n    \"\"\"Build Eastmoney secid for A-share ETF historical K-line queries.\"\"\"\n    code = normalize_stock_code(stock_code)\n    if not _is_etf_code(code):\n        raise DataFetchError(f\"无法识别 ETF 代码 {stock_code}\")\n    if code.startswith(_ETF_SH_PREFIXES):\n        return f\"1.{code}\"\n    if code.startswith(_ETF_SZ_PREFIXES):\n        return f\"0.{code}\"\n    raise DataFetchError(f\"无法确定 ETF {stock_code} 的 Eastmoney 市场前缀\")\n\n\ndef _is_us_code(stock_code: str) -> bool:\n    \"\"\"\n    判断代码是否为美股\n    \n    美股代码规则：\n    - 1-5个大写字母，如 'AAPL', 'TSLA'\n    - 可能包含 '.'，如 'BRK.B'\n    \"\"\"\n    code = stock_code.strip().upper()\n    return bool(re.match(r'^[A-Z]{1,5}(\\.[A-Z])?$', code))\n\n\ndef _ef_call_with_timeout(func, *args, timeout=None, **kwargs):\n    \"\"\"Run an efinance library call in a thread with a timeout.\n\n    efinance internally uses requests/urllib3 with no timeout, so when","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/data_provider/efinance_fetcher.py#L160-L196","documentation":"Raised by _build_eastmoney_etf_secid when the code passes _is_etf_code but starts with neither _ETF_SH_PREFIXES nor _ETF_SZ_PREFIXES, so the Eastmoney market digit ('1.' for SH, '0.' for SZ) cannot be chosen. Means the ETF classifier and the prefix whitelist disagree.","triggerScenarios":"An A-share ETF code whose first digits fall outside the known SH/SZ ETF prefix ranges reaches the secid builder — new exchange prefix, LOF (16 is SZ so fine, but e.g. 50xxxx SH LOF variants), or an outdated prefix list.","commonSituations":"Exchange lists a new ETF series with an unseen prefix; the repo's prefix constants lag behind the market; user-supplied code from another market slipped through classification.","solutions":["Confirm the code's actual exchange (SSE vs SZSE) from the fund's official page, then check it against _ETF_SH_PREFIXES/_ETF_SZ_PREFIXES in efinance_fetcher.py.","Add the missing prefix to the correct tuple (with a regression test using the real code).","If the code is not A-share, route it to the correct market fetcher instead of EfinanceFetcher's ETF path."],"exampleFix":"# before\n_ETF_SH_PREFIXES = ('51', '56', '58')\n# after (new SSE ETF series)\n_ETF_SH_PREFIXES = ('51', '56', '58', '60')","handlingStrategy":"validation","validationCode":"from data_provider.efinance_fetcher import _ETF_SH_PREFIXES, _ETF_SZ_PREFIXES\ncode = normalize_stock_code(code)\nif not (code.startswith(_ETF_SH_PREFIXES) or code.startswith(_ETF_SZ_PREFIXES)):\n    raise ValueError(f'{code}: unknown ETF market prefix — extend prefix lists')","typeGuard":"def has_known_etf_prefix(code: str) -> bool:\n    c = normalize_stock_code(code)\n    return c.startswith(_ETF_SH_PREFIXES) or c.startswith(_ETF_SZ_PREFIXES)","tryCatchPattern":null,"preventionTips":["Keep _ETF_SH_PREFIXES/_ETF_SZ_PREFIXES in one place and test against real listed ETF codes.","When an exchange announces a new ETF series, add the prefix with a regression test.","Log unknown prefixes with the raw code so gaps surface in operations."],"tags":["efinance","etf","prefix-mapping","configuration"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}