{"record":{"id":"160e773dfb62171b","repo":"ZhuLinsen/daily_stock_analysis","slug":"tushare-e","errorCode":null,"errorMessage":"Tushare 配额超限: {e}","messagePattern":"Tushare 配额超限: (.+?)","errorType":"exception","errorClass":"RateLimitError","httpStatus":null,"severity":"error","filePath":"data_provider/tushare_fetcher.py","lineNumber":544,"sourceCode":"                    end_date=ts_end,\n                )\n            else:\n                # Regular A-share stocks use daily interface\n                df = self._api.daily(\n                    ts_code=ts_code,\n                    start_date=ts_start,\n                    end_date=ts_end,\n                )\n            \n            return df\n            \n        except Exception as e:\n            error_msg = str(e).lower()\n            \n            # 检测配额超限\n            if any(keyword in error_msg for keyword in ['quota', '配额', 'limit', '权限']):\n                logger.warning(f\"Tushare 配额可能超限: {e}\")\n                raise RateLimitError(f\"Tushare 配额超限: {e}\") from e\n            \n            raise DataFetchError(f\"Tushare 获取数据失败: {e}\") from e\n    \n    def _normalize_data(self, df: pd.DataFrame, stock_code: str) -> pd.DataFrame:\n        \"\"\"\n        标准化 Tushare 数据\n        \n        Tushare daily / fund_daily 返回的列名：\n        ts_code, trade_date, open, high, low, close, pre_close, change, pct_chg, vol, amount\n        \n        需要映射到标准列名：\n        date, open, high, low, close, volume, amount, pct_chg\n\n        单位缩放仅适用于 A 股（及 ETF 等使用同一套单位的接口）：\n        - vol 按「手」计，乘以 100 转为「股」\n        - amount 按「千元」计，乘以 1000 转为「元」\n\n        港股 hk_daily 返回的 vol / amount 已是可直接使用的量级，不做上述缩放。","sourceCodeStart":526,"sourceCodeEnd":562,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/data_provider/tushare_fetcher.py#L526-L562","documentation":"Raised as RateLimitError when the underlying Tushare pro call throws an exception whose message contains 'quota', '配额', 'limit', or '权限'. Tushare enforces per-integration credit limits (e.g. daily() needs 120 points, hk_daily needs higher tier); exceeding your account tier or calling an API you have no permission for produces these messages. The fetcher re-classifies the generic failure as RateLimitError so upstream logic can back off or switch sources.","triggerScenarios":"Calling daily()/fund_daily()/hk_daily more times or at a lower credit tier than the account allows: e.g. free 120-point account calling daily() repeatedly across a large stock list, or calling hk_daily without HK-quote permission. Any exception string containing 'limit' (including some network-layer 'connection limit' texts) also matches the keyword filter.","commonSituations":"Batch analysis of many stocks exhausting the daily call quota; new code path using an ETF/HK interface the token's tier does not cover; free-tier token used in scheduled GitHub Actions runs.","solutions":["Check your points/quota on tushare.pro and wait for daily quota reset (2000 calls/day at base tier) or upgrade the tier for the specific interface.","Reduce Tushare call volume: cache results, widen fetch intervals, batch by trade_date instead of per-symbol where the API allows.","Let the RateLimitError trigger the existing fallback chain to Akshare/Yfinance for the remaining symbols.","If the underlying message is a false positive (e.g. a network error containing 'limit'), fix the keyword classification or the network issue rather than treating it as quota."],"exampleFix":"# before\nfor code in watchlist:  # 500 symbols on a free token\n    tushare_fetcher.fetch_stock_data(code)  # -> RateLimitError: 配额超限\n\n# after\ntry:\n    df = tushare_fetcher.fetch_stock_data(code)\nexcept RateLimitError:\n    df = akshare_fetcher.fetch_stock_data(code)  # fallback source","handlingStrategy":"fallback","validationCode":"# No pre-call API to query quota; mitigate by budgeting calls before the batch\nmax_tushare_calls = 480  # stay under the free-tier daily limit\ncount = cached_tushare_call_count()\nuse_tushare = count < max_tushare_calls","typeGuard":null,"tryCatchPattern":"from data_provider.base import RateLimitError, DataFetchError\n\ntry:\n    df = tushare_fetcher.fetch_stock_data(code)\nexcept RateLimitError:\n    df = akshare_fetcher.fetch_stock_data(code)  # quota exhausted -> switch source\nexcept DataFetchError:\n    df = yfinance_fetcher.fetch_stock_data(code)","preventionTips":["Cache Tushare responses; do not re-fetch unchanged symbols within a day.","Batch by trade_date where the API allows one call for many symbols.","Track call counts per day and stop before hitting the tier limit.","Catch RateLimitError specifically so quota exhaustion switches sources instead of retrying."],"tags":["rate-limit","tushare","quota","data-provider"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}