{"record":{"id":"679c5e62a7246edc","repo":"ZhuLinsen/daily_stock_analysis","slug":"self-name-stock-code","errorCode":null,"errorMessage":"[{self.name}] 未获取到 {stock_code} 的数据","messagePattern":"\\[(.+?)\\] 未获取到 (.+?) 的数据","errorType":"exception","errorClass":"DataFetchError","httpStatus":null,"severity":"error","filePath":"data_provider/base.py","lineNumber":494,"sourceCode":"        # 计算日期范围\n        if end_date is None:\n            end_date = datetime.now().strftime('%Y-%m-%d')\n        \n        if start_date is None:\n            # 默认获取最近 30 个交易日（按日历日估算，多取一些）\n            from datetime import timedelta\n            start_dt = datetime.strptime(end_date, '%Y-%m-%d') - timedelta(days=days * 2)\n            start_date = start_dt.strftime('%Y-%m-%d')\n\n        request_start = time.time()\n        logger.info(f\"[{self.name}] 开始获取 {stock_code} 日线数据: 范围={start_date} ~ {end_date}\")\n        \n        try:\n            # Step 1: 获取原始数据\n            raw_df = self._fetch_raw_data(stock_code, start_date, end_date)\n            \n            if raw_df is None:\n                raise DataFetchError(f\"[{self.name}] 未获取到 {stock_code} 的数据\")\n            if raw_df.empty:\n                elapsed = time.time() - request_start\n                logger.info(\n                    f\"[{self.name}] {stock_code} 返回空日线结果: 范围={start_date} ~ {end_date}, \"\n                    f\"elapsed={elapsed:.2f}s\"\n                )\n                if self.allow_empty_daily_data:\n                    return pd.DataFrame(columns=STANDARD_COLUMNS)\n                raise DataFetchError(f\"[{self.name}] 未获取到 {stock_code} 的数据\")\n            \n            # Step 2: 标准化列名\n            df = self._normalize_data(raw_df, stock_code)\n            \n            # Step 3: 数据清洗\n            df = self._clean_data(df)\n            \n            # Step 4: 计算技术指标\n            df = self._calculate_indicators(df)","sourceCodeStart":476,"sourceCodeEnd":512,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/data_provider/base.py#L476-L512","documentation":"Raised by BaseFetcher.get_daily_data when the concrete fetcher's _fetch_raw_data returns None instead of a DataFrame. The base class treats None as 'provider produced nothing' and immediately converts it into DataFetchError so the DataFetcherManager failover chain can try the next source.","triggerScenarios":"Calling get_daily_data(stock_code, days/end_date) on any fetcher subclass whose _fetch_raw_data hits a code path that returns None (e.g. a provider response parsed to nothing, a guarded except that swallows the error and returns None) instead of raising.","commonSituations":"Upstream API changed response shape so parsing silently yields None; a fetcher override added a defensive 'return None' branch; monkeypatched/mocked _fetch_raw_data in tests returning None.","solutions":["Check the specific fetcher's _fetch_raw_data implementation for branches that return None and make them raise DataFetchError with the real cause instead.","Inspect the logged line '[name] 开始获取 ... 日线数据' just before the raise to identify which fetcher and date range produced None.","If the provider legitimately has no data, return an empty DataFrame so the allow_empty_daily_data path (base.py:501) applies instead of None."],"exampleFix":"// before (in a fetcher)\nexcept Exception:\n    return None\n// after\nexcept Exception as e:\n    raise DataFetchError(f\"[{self.name}] upstream failed: {e}\") from e","handlingStrategy":"validation","validationCode":"raw = fetcher._fetch_raw_data(code, start, end)\nif raw is None:\n    raise DataFetchError(f'[{fetcher.name}] returned None for {code}; fix fetcher')\ndf = fetcher.get_daily_data(code, ...)","typeGuard":null,"tryCatchPattern":"try:\n    df = fetcher.get_daily_data(code, ...)\nexcept DataFetchError as e:\n    log_and_failover(e)  # manager handles; direct callers should move to next source","preventionTips":["Never return None from _fetch_raw_data overrides — raise DataFetchError with the cause.","In tests, assert fetcher overrides never hit the None branch with real provider payloads.","Let DataFetcherManager own failover instead of calling single fetchers directly."],"tags":["data-provider","daily-data","failover","python"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}