{"record":{"id":"8138bef8707ba2c9","repo":"ZhuLinsen/daily_stock_analysis","slug":"tickflow-daily-k-line-response-may-be-truncated-by","errorCode":null,"errorMessage":"TickFlow daily K-line response may be truncated by count: symbol={symbol} start={start_date} end={end_date} rows={len(frame)} count={count}","messagePattern":"TickFlow daily K-line response may be truncated by count: symbol=(.+?) start=(.+?) end=(.+?) rows=(.+?) count=(.+?)","errorType":"exception","errorClass":"DataFetchError","httpStatus":null,"severity":"error","filePath":"data_provider/tickflow_fetcher.py","lineNumber":495,"sourceCode":"            end_date=end_date,\n            count=count,\n            returned_rows=len(frame),\n        ):\n            first_date = valid_dates.min().strftime(\"%Y-%m-%d\")\n            last_date = valid_dates.max().strftime(\"%Y-%m-%d\")\n            logger.warning(\n                \"[TickFlowFetcher] reject incomplete daily K-line response: symbol=%s context=%s \"\n                \"start=%s end=%s first=%s last=%s rows=%d count=%d reason=count_cap\",\n                symbol,\n                context,\n                start_date,\n                end_date,\n                first_date,\n                last_date,\n                len(frame),\n                count,\n            )\n            raise DataFetchError(\n                \"TickFlow daily K-line response may be truncated by count: \"\n                f\"symbol={symbol} start={start_date} end={end_date} rows={len(frame)} count={count}\"\n            )\n\n        start = pd.Timestamp(start_date).normalize()\n        end = pd.Timestamp(end_date).normalize()\n        in_range = dates.notna() & (dates >= start) & (dates <= end)\n        if not in_range.any():\n            return pd.DataFrame(columns=frame.columns)\n        return frame.loc[in_range].reset_index(drop=True)\n\n    @classmethod\n    def _is_daily_frame_truncated(\n        cls,\n        *,\n        dates: pd.Series,\n        start_date: str,\n        end_date: str,","sourceCodeStart":477,"sourceCodeEnd":513,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/data_provider/tickflow_fetcher.py#L477-L513","documentation":"DataFetchError raised by TickFlowFetcher._prepare_daily_frame when the response's row count equals the requested count cap — the response may have been truncated by the 'count' parameter, so continuing would silently produce incomplete history. The fetcher deliberately rejects the response (integrity guard) after logging a detailed warning.","triggerScenarios":"Requesting a date range whose trading-day count (computed by _daily_kline_count) reaches the count limit sent to client.klines.get; the API returns exactly `count` rows, so it is impossible to tell whether more data existed beyond the cap.","commonSituations":"Long lookback windows (multi-year backtests) exceeding the per-request cap; count estimation too tight for ranges with many trading days; API changing pagination semantics so count no longer caps server-side.","solutions":["Split the request into smaller date chunks so each chunk's trading-day count stays below the cap.","Raise the count cap in _daily_kline_count / the request if the API allows larger pages.","If chunking is already correct and this persists, compare rows against an independent calendar to detect API-side truncation and report it."],"exampleFix":"# before\n# single request over 5 years -> rows == count -> DataFetchError\nrows = tickflow_fetcher.get_stock_history_with_limit(\"600519\", start=\"2020-01-01\", end=\"2026-01-01\")\n\n# after\n# chunk by year so each request is far below the count cap\nframes = []\nfor (s, e) in split_year_ranges(\"2020-01-01\", \"2026-01-01\"):\n    frames.append(tickflow_fetcher.get_stock_history_with_limit(\"600519\", start=s, end=e))\ndf = pd.concat(frames, ignore_index=True).drop_duplicates(subset=\"date\")","handlingStrategy":"validation","validationCode":"# keep requested trading-day count safely below the cap before calling\ntrading_days = estimate_trading_days(start_date, end_date)\ncap = tickflow_fetcher._daily_kline_count(start_date, end_date)\nassert trading_days < cap, f\"range too wide ({trading_days} days); split into chunks\"","typeGuard":null,"tryCatchPattern":"try:\n    df = tickflow_fetcher.get_stock_data(code, start, end)\nexcept DataFetchError as e:\n    if \"truncated by count\" in str(e):\n        df = pd.concat(\n            [tickflow_fetcher.get_stock_data(code, s, en) for (s, en) in split_ranges(start_date, end_date, months=12)],\n            ignore_index=True,\n        ).drop_duplicates(subset=\"date\")\n    else:\n        raise","preventionTips":["Cap per-request date windows (e.g. <= 1 year) so responses cannot hit the count limit.","Never ignore this error — truncated history silently corrupts indicators and backtests.","After chunked fetches, verify continuity against a trading calendar."],"tags":["tickflow","data-integrity","pagination","truncation"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}