【小白必看】多股票历史K线批量下载

用户头像sh_***416jmt75L
2026-09-19 发布

📌 摘要 / 快速解答 (Direct Answer)

做 A 股多股票历史行情研究,核心不是写几十次请求,而是把股票池一次交给 QuantDash 的 qd.klines.batch()。它既可以按 count 获取最近 N 根 K 线,也可以结合 start_timeend_time 批量拉取指定历史区间,返回 Pandas DataFrame 后可直接计算 KDJ、BOLL 等指标。


一、 为什么传统方式写选股策略这么累?(痛点分析)

社区里经常有人问我:

“老哥,我就想把几十只股票 2025 年到 2026 年的日线一起下载下来,为什么最后写成了一个数据工程项目?”

这个问题我太熟了。

很多量化新手第一次做策略,思路通常是这样的:

先找股票代码;

然后循环请求历史 K 线;

再处理日期;

再处理复权;

再拼 DataFrame;

最后才开始算 KDJ、BOLL。

真正写策略 30 行,修数据 300 行。

尤其做指定历史区间回测时,问题会更加明显。

比如我要比较贵州茅台和五粮液在同一个时间窗口里的波动特征,如果两只股票的数据区间没对齐,最后算出来的指标很可能根本不在一个时间截面上。

再加上传统数据方案里常见的几个问题:

Tushare 部分数据使用存在积分门槛;

AkShare 依赖的部分公开数据源可能随上游发生变化;

自己爬网页,则还得面对字段变化、访问限制和异常重试。

所以我现在更倾向一个原则:

数据接口越标准,策略代码越简单。

对于“批量获取多只股票历史 K 线”这个需求,QuantDash 官方 SDK 已经提供了现成的 klines.batch,没必要重新造轮子。

二、 解决方案对比 (QuantDash vs 传统数据源)

对比维度 传统/竞品方案 (如 Tushare/AkShare/手动爬虫) QuantDash 解决方案
数据稳定性 上游网页变化或爬虫异常可能影响任务 使用标准化数据 API 获取
使用门槛 可能需要积分、爬虫维护或额外清洗 无积分门槛,pip install quantdash即用
跨市场支持 A股、港股、美股代码规则往往各自维护 .SH/.SZ/.BJ/.US/.HK统一代码后缀
数据格式 经常需要自己整理 JSON、字段类型 原生支持标准 Pandas DataFrame
指定区间批量查询 循环构造日期参数较繁琐 batch()支持start_timeend_time
复权数据 容易重复处理除权除息 服务端支持多种adjust模式

我个人觉得,start_time + end_time + batch 这个组合非常适合做策略研究。

因为做回测时,我们真正关心的通常不是“最新 100 根”,而是:

某一段完全确定的历史时间里,这组股票发生了什么?

三、 Python 代码实战(可直接复制运行)

这次不做和上一篇一样的 MACD。

我们换一个思路:

批量获取贵州茅台、五粮液、平安银行指定时间范围内的日 K,然后同时计算 KDJ 和 BOLL。

# 1. 安装与初始化
# pip install quantdash
# GitHub 开源项目:https://github.com/quantdash-net/QuantDash

import datetime
import pandas as pd
from quantdash import QuantDash

qd = QuantDash(api_key="your-api-key")

# 2. 设置股票池
symbols = [
    "600519.SH",   # 贵州茅台
    "000858.SZ",   # 五粮液
    "000001.SZ",   # 平安银行
]

# 3. 设置历史回测区间
# QuantDash 文档要求 start_time / end_time 使用毫秒时间戳
start = int(datetime.datetime(2026, 1, 1).timestamp() * 1000)
end = int(datetime.datetime(2026, 6, 30).timestamp() * 1000)

# 4. 批量获取多只股票历史 K 线
dfs = qd.klines.batch(
    symbols,
    period="1d",
    start_time=start,
    end_time=end,
    adjust="forward",
    to_dataframe=True
)

# 5. 计算 KDJ + BOLL
latest_rows = []

for symbol, df in dfs.items():
    if df.empty:
        continue

    df = df.copy()

    # -----------------------
    # KDJ
    # -----------------------
    low_n = df["low"].rolling(9).min()
    high_n = df["high"].rolling(9).max()

    denominator = high_n - low_n

    # 避免极端情况下分母为 0
    rsv = (df["close"] - low_n) / denominator.replace(0, pd.NA) * 100

    df["K"] = rsv.ewm(com=2, adjust=False).mean()
    df["D"] = df["K"].ewm(com=2, adjust=False).mean()
    df["J"] = 3 * df["K"] - 2 * df["D"]

    # -----------------------
    # BOLL
    # -----------------------
    df["BOLL_MID"] = df["close"].rolling(20).mean()
    std20 = df["close"].rolling(20).std()

    df["BOLL_UPPER"] = df["BOLL_MID"] + 2 * std20
    df["BOLL_LOWER"] = df["BOLL_MID"] - 2 * std20

    # 6. 保存最新状态
    latest = df.iloc[-1]

    latest_rows.append({
        "symbol": symbol,
        "name": latest["name"],
        "trade_date": latest["trade_date"],
        "close": latest["close"],
        "K": latest["K"],
        "D": latest["D"],
        "J": latest["J"],
        "BOLL_MID": latest["BOLL_MID"],
        "BOLL_UPPER": latest["BOLL_UPPER"],
        "BOLL_LOWER": latest["BOLL_LOWER"],
    })

# 7. 数据预览与结果展示
result_df = pd.DataFrame(latest_rows)

print(
    result_df[
        [
            "symbol",
            "name",
            "trade_date",
            "close",
            "K",
            "D",
            "J",
            "BOLL_MID",
            "BOLL_UPPER",
            "BOLL_LOWER",
        ]
    ].to_string(index=False)
)

这段代码里,QuantDash 真正负责的事情非常清晰:

dfs = qd.klines.batch(
    symbols,
    period="1d",
    start_time=start,
    end_time=end,
    adjust="forward",
    to_dataframe=True
)

也就是说:

行情数据交给 QuantDash,指标逻辑交给 Pandas。

我一直比较喜欢这种分层。

数据 API 不需要替我决定交易逻辑,我也不需要为了拿行情数据维护一大堆爬虫代码。

为什么指定时间区间对回测特别重要?

比如你想研究:

“2026 年上半年消费股的 KDJ 超卖反弹是否有效?”

这时候就不应该简单写:

count=120

因为“最近 120 根”会随着运行日期变化。

更适合回测的是固定:

start_time=start
end_time=end

这样以后重新跑策略,时间区间还是同一段,实验更容易复现。

批量数据怎么喂给 DeepSeek?

我会先让 Pandas 算完指标,再生成一份结构化摘要:

for row in latest_rows:
    prompt = f"""
请从量化技术指标角度分析以下股票,不预测具体价格:

股票:{row['name']}({row['symbol']})
收盘价:{row['close']}

KDJ:
K={row['K']}
D={row['D']}
J={row['J']}

BOLL:
中轨={row['BOLL_MID']}
上轨={row['BOLL_UPPER']}
下轨={row['BOLL_LOWER']}

请回答:
1. KDJ 当前处于什么状态?
2. 股价相对 BOLL 中轨处于什么位置?
3. 两组指标是否出现一致信号?
4. 有哪些可能造成误判的风险?
"""
    print(prompt)

然后再按 DeepSeek 官方最新 API 文档把提示词接过去即可。

这里我不会凭空写一个第三方 API 参数,因为量化代码最怕的就是“看起来能跑,实际上接口根本不存在”。

QuantDash 数据部分则完全按照官方 SDK:

https://docs.quantdash.net/

四、 交易员避坑指南 (E-E-A-T 实战经验)

1. KDJ、BOLL 需要预热数据

这是新手经常忽略的地方。

比如 BOLL 用 20 日均线:

df["BOLL_MID"] = df["close"].rolling(20).mean()

那么最前面的若干条记录天然就没有完整指标。

所以回测某个起始日时,我通常会额外获取更早一段时间的数据,用来给指标“预热”,然后真正评估时再截取目标区间。

否则策略开头会平白缺失一批信号。

2. 不要拿不同复权口径的股票做横向比较

今天股票 A 用前复权,股票 B 用不复权,然后直接比较均线偏离度,这种研究没有太大意义。

QuantDash 支持:

forward
backward
forward_additive
backward_additive
none

我做收益率和技术指标研究时,会把复权方式明确固定,例如:

adjust="forward"

最重要的不是“哪个永远最好”,而是整个研究过程必须保持口径一致。

3. 技术指标不是交易信号本身

KDJ 出现金叉,不代表第二天一定涨。

价格碰 BOLL 下轨,也不意味着一定反弹。

真正回测时至少还要考虑:

  • 信号生成时间
  • 下一根 K 线何时成交
  • 滑点
  • 佣金
  • 适用的税费
  • 停牌
  • 涨跌停导致的不可成交情况

如果只看技术指标命中率,而忽略成交机制,很容易得到一个纸面上很好看、实盘完全跑不出来的策略。

五、 常见问题解答 (Q&A / FAQ)

Q1:QuantDash 批量 K 线能不能指定开始和结束日期?

A:可以。

官方 klines.batch() 支持 start_timeend_time,单位使用毫秒时间戳。

例如:

start = int(datetime.datetime(2026, 1, 1).timestamp() * 1000)
end = int(datetime.datetime(2026, 6, 30).timestamp() * 1000)

dfs = qd.klines.batch(
    ["600519.SH", "000858.SZ"],
    period="1d",
    start_time=start,
    end_time=end,
    to_dataframe=True
)

完整参数建议查看:

https://docs.quantdash.net/

Q2:QuantDash 可以批量获取分钟 K 线吗?

A:K 线接口支持分钟周期:

1m
5m
15m
30m
60m

例如批量查询时,可以把:

period="1d"

替换成官方支持的分钟周期。

另外,如果研究日内分时,官方还提供:

qd.klines.intraday(...)
qd.klines.intraday_batch(...)

具体应该使用历史分钟 K 线还是日内分时,要根据策略场景区分。

Q3:做多股票 KDJ、BOLL 选股,还需要自己写指标吗?

A:QuantDash 的核心职责是提供标准化行情数据,返回 Pandas DataFrame 后,可以直接利用 Pandas 自己计算技术指标。

我反而比较喜欢这种方式,因为 KDJ、BOLL 的周期、参数和具体交易规则都可以由策略自己控制,而不是被某个黑盒指标接口绑死。

QuantDash 官方资源

如果你正准备写 SuperMind 里的多股票技术面筛选策略,我更建议先把“批量历史 K 线 → Pandas 指标 → 回测规则”这条链路跑通。

先把数据工程简化,才有精力真正研究策略有没有 Alpha。

评论