{"record":{"id":"4f97ab32c7171884","repo":"ZhuLinsen/daily_stock_analysis","slug":"etf-stock-code","errorCode":null,"errorMessage":"无法识别 ETF 代码 {stock_code}","messagePattern":"无法识别 ETF 代码 (.+?)","errorType":"exception","errorClass":"DataFetchError","httpStatus":null,"severity":"error","filePath":"data_provider/efinance_fetcher.py","lineNumber":173,"sourceCode":"    \n    ETF 代码规则：\n    - 上交所 ETF: 51xxxx, 52xxxx, 56xxxx, 58xxxx\n    - 深交所 ETF: 15xxxx, 16xxxx, 18xxxx\n    \n    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","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/data_provider/efinance_fetcher.py#L155-L191","documentation":"Raised by _build_eastmoney_etf_secid in EfinanceFetcher: after normalize_stock_code, the code does not satisfy _is_a_share_etf_code, so no Eastmoney ETF secid can be built. It is a routing/classification error — a non-A-share-ETF code reached the A-share ETF K-line path.","triggerScenarios":"Calling EfinanceFetcher._fetch_etf_data (or get_daily_data routed to the ETF branch because _is_etf_code matched) with a code that normalize_stock_code mangles or that matches the outer ETF heuristic but not _is_a_share_etf_code — e.g. LOF/funds with unexpected prefixes, codes with typos, or non-CN fund identifiers.","commonSituations":"User passes a mutual-fund or bond code (e.g. 5-digit odd prefix) that the generic ETF heuristic accepts but the A-share ETF prefix list rejects; code normalization strips or keeps a suffix inconsistently.","solutions":["Verify the code is a genuine A-share ETF (SH 51/56/58/60 prefixes, SZ 15/16/18 prefixes per _ETF_SH_PREFIXES/_ETF_SZ_PREFIXES).","Check what normalize_stock_code does to the input — ensure you pass the bare 6-digit code without exchange suffix.","If you support new ETF prefixes, extend _ETF_SH_PREFIXES/_ETF_SZ_PREFIXES rather than bypassing the check."],"exampleFix":"# before\nfetcher.get_daily_data('510300.SH', ...)  # suffix may break classification\n# after\nfetcher.get_daily_data('510300', ...)","handlingStrategy":"validation","validationCode":"from data_provider.efinance_fetcher import _is_etf_code\ncode = normalize_stock_code(user_input)\nif not _is_etf_code(code):\n    raise ValueError(f'{user_input} is not an A-share ETF; route to stock path')","typeGuard":"def is_a_share_etf(code: str) -> bool:\n    c = normalize_stock_code(code)\n    return _is_etf_code(c)","tryCatchPattern":"try:\n    df = fetcher.get_daily_data(code, ...)\nexcept DataFetchError as e:\n    if '无法识别 ETF' in str(e):\n        df = fetcher._fetch_stock_data(code, ...)  # or route via manager","preventionTips":["Normalize codes (strip exchange suffixes) before passing to fetchers.","Maintain the ETF prefix whitelist against exchange fund lists.","Add unit tests covering LOF/odd-prefix codes for the classifier."],"tags":["efinance","etf","code-classification","validation"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}