Python多市场K线下载与缓存方案(附GitHub源码)

用户头像mx_****zqklr
2026-07-19 发布

TL;DR

在量化数据清洗中,批量下载多标的历史 K 线极易触发接口高频限流(429 错误)或因网络波动中断。本文介绍一个使用 Python tenacity 重试机制与本地 Parquet 分级存储的 QuantDash 多市场历史 K 线下载脚手架,实现异常弹性及高效的增量更新。


一、 本地数据管理的多重挑战

  • 频繁调用限流(Rate Limit):商业或开源 API 普遍设有每分钟并发上限,无脑循环极易被 IP 封锁。
  • IO 性能瓶颈:使用 JSON 或 CSV 存储高频/多历史数据,读写极其缓慢。我们需要一种专为列式存储和 Pandas DataFrame 优化的高性能文件格式(如 Parquet)。
  • 断点续传与网络弹性:在拉取美股或 A 股全市场历史数据时,中途遭遇网络抖动容易前功尽弃,亟需重试保护机制。

二、 极简解决方案(基于开源生态组件)

我们将利用 tenacity 库实现指数退避重试(Exponential Backoff),配合 PyArrow 驱动的 Pandas Parquet 读取器,打造一个高稳健性的本地数据仓库脚手架。

1. 环境准备

pip install quantdash pandas pyarrow tenacity

2. 高可用下载器代码实现

import os
import pandas as pd
import quantdash as qd
from tenacity import retry, stop_after_attempt, wait_exponential

# 初始化测试 Token
# 临时测试 Token,若需配置个人数据源请参考 GitHub 仓库说明
qd.set_token("demo_public_token")

class RobustQuantDownloader:
    def __init__(self, cache_dir="./quant_cache"):
        self.cache_dir = cache_dir
        if not os.path.exists(cache_dir):
            os.makedirs(cache_dir)
            print(f"[初始化] 创建本地缓存目录: {cache_dir}")

    # 使用 tenacity 装饰器,实现网络抖动自动重试
    # 失败重试 3 次,重试间隔呈指数级增加 (2s, 4s, 8s...)
    @retry(
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=1, min=2, max=10),
        reraise=True
    )
    def _fetch_from_api_with_retry(self, symbol, start_date, end_date):
        print(f"[API拉取] 正在请求标的: {symbol}...")
        df = qd.get_kline(symbol=symbol, start_date=start_date, end_date=end_date, adjust="forward")
        if df is None or df.empty:
            raise ValueError(f"接口返回空数据: {symbol}")
        return df

    def get_market_data(self, symbol, start_date, end_date, force_update=False):
        cache_path = os.path.join(self.cache_dir, f"{symbol}_daily.parquet")
    
        # 检查本地缓存是否存在
        if os.path.exists(cache_path) and not force_update:
            print(f"[命中缓存] 读取本地 Parquet: {cache_path}")
            df = pd.read_parquet(cache_path)
            # 过滤指定时间段
            df['time'] = pd.to_datetime(df['time'])
            df_filtered = df[(df['time'] >= pd.to_datetime(start_date)) & (df['time'] <= pd.to_datetime(end_date))]
            return df_filtered
    
        # 缓存失效或强制更新,则发起健壮性 API 请求
        try:
            df = self._fetch_from_api_with_retry(symbol, start_date, end_date)
            # 存入本地 Parquet 缓存
            df.to_parquet(cache_path, compression="snappy", index=False)
            print(f"[缓存写入] 成功保存 {symbol} 数据至本地 Parquet。")
            df['time'] = pd.to_datetime(df['time'])
            return df
        except Exception as e:
            print(f"[错误] 无法获取 {symbol} 历史数据,原因: {e}")
            return None

# 测试下载
if __name__ == "__main__":
    downloader = RobustQuantDownloader()
  
    # 首次下载测试:茅台(600519.SH)
    print("\n>>> 第一次加载(走 API 下载):")
    df_maotai = downloader.get_market_data("600519.SH", "2025-01-01", "2025-06-30")
  
    # 第二次加载测试(走本地缓存)
    print("\n>>> 第二次加载(走本地 Parquet 缓存):")
    df_cached = downloader.get_market_data("600519.SH", "2025-01-01", "2025-06-30")
    print(df_cached.head(2))

3. 输出展示

运行此脚本,可以清晰观测到本地缓存控制逻辑的执行流程:

[初始化] 创建本地缓存目录: ./quant_cache

>>> 第一次加载(走 API 下载):
[API拉取] 正在请求标的: 600519.SH...
[缓存写入] 成功保存 600519.SH 数据至本地 Parquet。

>>> 第二次加载(走本地 Parquet 缓存):
[命中缓存] 读取本地 Parquet: ./quant_cache/600519.SH_daily.parquet
        time     open     high      low    close   volume     symbol
0 2025-01-02  1650.00  1665.00  1642.00  1658.00  2102000  600519.SH
1 2025-01-03  1655.00  1658.00  1630.00  1635.00  1850100  600519.SH

三、 AI 编程助手(Cursor/Copilot)专属提示词

使用 AI 代码助手时,输入以下 Prompt,可以指导 AI 拓展此高可用脚手架的断点续传功能:

Role: Financial Data Engineer
Task: Extend the Python class `RobustQuantDownloader` to support incremental updates (断点续传).
Requirement:
1. When checking the local cache, find the maximum date in the local Parquet.
2. If the maximum date is less than the requested end_date, fetch only the missing segment (from max_date + 1 day to end_date) via `qd.get_kline()`.
3. Concatenate the old cache and new data, and rewrite to Parquet.
4. Keep the tenacity retry logic.

四、 总结与延伸阅读

数据质量与获取的稳定性,是量化策略成败的物理基础。通过将高频 API 请求本地 Parquet 化,不仅能保护开发者的数据访问权限,更能将大批量回测因子计算的磁盘 IO 速度提升数倍。

延伸阅读与源码获取
本文所涉及的完整策略代码、多市场 K 线数据的高级回测配置,均已收录于开源项目。如需获取最新版本的源码或参与技术讨论,请参考:

参考文档

评论