{"record":{"id":"d63d9da71c7ec023","repo":"ZhuLinsen/daily_stock_analysis","slug":"efinance-etf-failure-message","errorCode":null,"errorMessage":"efinance 获取 ETF 数据失败: {failure_message}","messagePattern":"efinance 获取 ETF 数据失败: (.+?)","errorType":"exception","errorClass":"DataFetchError","httpStatus":null,"severity":"error","filePath":"data_provider/efinance_fetcher.py","lineNumber":568,"sourceCode":"            return df\n\n        except Exception as e:\n            api_elapsed = time.time() - api_start\n            category, failure_message = self._build_history_failure_message(\n                stock_code=stock_code,\n                beg_date=beg_date,\n                end_date=end_date_fmt,\n                exc=e,\n                elapsed=api_elapsed,\n                is_etf=True,\n            )\n\n            if category == \"rate_limit_or_anti_bot\":\n                logger.warning(failure_message)\n                raise RateLimitError(f\"efinance 可能被限流: {failure_message}\") from e\n\n            logger.error(failure_message)\n            raise DataFetchError(f\"efinance 获取 ETF 数据失败: {failure_message}\") from e\n    \n    def _normalize_data(self, df: pd.DataFrame, stock_code: str) -> pd.DataFrame:\n        \"\"\"\n        标准化 efinance 数据\n        \n        efinance 返回的列名（中文）：\n        股票名称, 股票代码, 日期, 开盘, 收盘, 最高, 最低, 成交量, 成交额, 振幅, 涨跌幅, 涨跌额, 换手率\n        \n        需要映射到标准列名：\n        date, open, high, low, close, volume, amount, pct_chg\n        \"\"\"\n        df = df.copy()\n        \n        # Column mapping (efinance Chinese column names -> standard English column names)\n        column_mapping = {\n            '日期': 'date',\n            '开盘': 'open',\n            '收盘': 'close',","sourceCodeStart":550,"sourceCodeEnd":586,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/data_provider/efinance_fetcher.py#L550-L586","documentation":"Generic terminal failure of EfinanceFetcher._fetch_etf_data: the exception from the ETF history call was classified as non-rate-limit (parse/schema/network), re-raised as DataFetchError with the built failure message. ETF twin of error 151.","triggerScenarios":"ETF history response shape unexpected (column rename, missing 单位净值-era fields); efinance version regression on the stock-K-line-for-ETF path (the code historically had Issue #541 with ef.fund.get_quote_history kwargs); non-throttle network faults.","commonSituations":"Upgrading efinance after the ETF path switched from fund API to stock API; ETF with tiny history returning degenerate frames.","solutions":["Read failure_message for the underlying exception and elapsed time to classify parse vs network.","Verify the efinance version supports the stock-K-line call signature used in _fetch_etf_data (beg/end kwargs — see Issue #541 note).","Fail over to akshare/tushare ETF daily data via the manager."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"# verify efinance supports the stock-K-line kwargs used for ETFs\nimport efinance as ef, inspect\nsig = inspect.signature(ef.stock.get_quote_history)\nassert {'beg', 'end'} <= set(sig.parameters), 'efinance version incompatible with ETF path'","typeGuard":null,"tryCatchPattern":"try:\n    df = efinance_fetcher.get_daily_data(etf_code, ...)\nexcept DataFetchError as e:\n    df = manager.get_daily_data(etf_code, ...)  # akshare ETF fallback","preventionTips":["Check efinance version compatibility after any upgrade (see Issue #541 history).","Test the ETF path with a real SH and SZ ETF on every dependency bump.","Log e.__cause__ to distinguish schema drift from network faults."],"tags":["efinance","etf","schema-drift","parse-error"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}