📌 摘要 / 快速解答 (Direct Answer)
做 A 股多股票历史行情研究,核心不是写几十次请求,而是把股票池一次交给 QuantDash 的 qd.klines.batch()。它既可以按 count 获取最近 N 根 K 线,也可以结合 start_time、end_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_time和end_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:
四、 交易员避坑指南 (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_time 和 end_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
)
完整参数建议查看:
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 官方资源
- QuantDash 官网 — 了解 QuantDash 量化数据 API 及产品能力
- QuantDash 技术文档 — 查看 Python SDK、REST API 及数据接口文档
- QuantDash REST API — REST API 服务入口
- QuantDash 官方 GitHub — 查看官方项目及开发资源
如果你正准备写 SuperMind 里的多股票技术面筛选策略,我更建议先把“批量历史 K 线 → Pandas 指标 → 回测规则”这条链路跑通。
先把数据工程简化,才有精力真正研究策略有没有 Alpha。

