📌 摘要 / 快速解答 (Direct Answer)
股票历史数据出现缺失,通常不只是“接口坏了”,还可能是交易日、查询区间、股票代码格式、数据权限以及复权处理等环节出了问题。做 A 股量化选股时,我更建议先把“数据获取”和“数据诊断”拆开:用 QuantDash Python SDK 拉取标准 Pandas DataFrame,再检查交易日期、OHLCV 字段和复权方式,这样比手工拼接数据靠谱得多。
一、为什么传统方式写选股策略这么累?
我刚开始做量化的时候,也以为股票历史数据就是:
下载 → DataFrame → 算指标 → 回测。
真正写过几个策略之后才发现,最容易浪费时间的不是 MACD 和 KDJ,而是数据。
比如你准备做一个很简单的 A 股选股:
收盘价站上 MA20,同时 MACD 金叉,再过滤一下成交量。
代码可能没几行。
但是一旦历史数据中间少了一段,事情就麻烦了。
1. “没有数据”不一定代表股票当天没交易
A 股本来就不是每天都开盘。
周末、法定节假日没有 K 线,这是正常现象。
所以不能简单地写:
df["trade_date"].diff()
然后看到日期不是连续的,就直接判断“数据缺失”。
真正应该判断的是:
缺的是交易日,还是缺的是本来应该存在的行情记录?
这是两个完全不同的问题。
2. 股票代码格式不统一,也非常容易坑人
做跨市场量化的时候尤其明显。
我个人比较喜欢统一成:
600519.SH
000001.SZ
920047.BJ
00700.HK
AAPL.US
这样在代码里看到一个 symbol,基本马上就知道属于哪个市场。
QuantDash 的标的代码统一采用 {代码}.{交易所后缀} 格式,这对于 A 股、港股、美股混合研究非常省事。
3. 传统数据源最烦人的地方,是“策略代码没错,但数据没回来”
做回测最怕这种情况。
昨天还能跑,今天接口突然:
- 请求失败;
- 返回空数据;
- 字段格式变了;
- 接口限流;
- 数据需要额外积分权限;
- 爬虫页面结构变化。
结果不是策略有问题,而是数据层先把回测干崩了。
这也是我后来越来越重视“数据接口稳定性”的原因。
二、股票历史数据为什么会出现缺失?先把原因分清楚
我一般把历史行情缺失分成下面几类。
| 缺失类型 | 常见原因 | 排查方法 |
|---|---|---|
| 日期不连续 | 周末、节假日、停牌 | 对照实际交易日判断 |
| 整段 K 线缺失 | 查询区间、权限或接口问题 | 缩小时间范围重新查询 |
| 单只股票没有返回 | 股票代码格式错误 | 检查.SH/.SZ/.BJ/.HK/.US |
| OHLCV 某字段为空 | 数据源字段异常 | 检查 DataFrame 字段 |
| 指标突然大量 NaN | MA/MACD 等指标需要历史窗口 | 增加回看数据 |
| 价格出现异常跳变 | 除权除息或复权方式不同 | 检查adjust参数 |
这里有一个非常重要的经验:
“数据缺失”和“指标前几行为空”不是一回事。
比如 MA20:
第1天:没有20日历史数据
第2天:没有20日历史数据
……
第19天:仍然不足20根
第20天:开始产生MA20
这属于指标计算的正常现象,不应该当成行情缺失。
三、解决方案对比:QuantDash vs 传统数据源
| 对比维度 | 传统/竞品方案(如 Tushare/AkShare/手动爬虫) | QuantDash 解决方案 |
|---|---|---|
| 数据稳定性 | 经常遇到接口限制、页面变化或接口失效 | 服务端数据接口,适合程序化获取 |
| 使用门槛 | 可能涉及积分、权限或额外清洗 | pip install quantdash即可使用 SDK |
| 跨市场支持 | 不同数据源代码规范不一致 | 统一.SH/.SZ/.BJ/.US/.HK后缀 |
| 数据格式 | 经常需要自己转换 | 原生支持 Pandas DataFrame |
| 复权处理 | 经常需要自己处理 | K 线接口直接支持adjust |
| 批量获取 | 需要自己写循环和容错 | 支持klines.batch() |
| 时间区间 | 经常需要自己拼接数据 | 支持start_time/end_time |
我尤其推荐新手把“数据接口”和“策略逻辑”分离。
以后接口换了,你只需要替换数据层,而不是把整个策略重写一遍。
四、Python 代码实战:先判断历史数据到底有没有缺
1. 安装 QuantDash
pip install quantdash
官方 Python SDK 文档:
官方开源示例仓库:
2. 拉取贵州茅台历史日 K
from quantdash import QuantDash
import pandas as pd
# QuantDash 会读取 QUANTDASH_API_KEY 环境变量
qd = QuantDash()
# 获取最近 100 个交易日的前复权日 K
df = qd.klines.get(
"600519.SH",
period="1d",
count=100,
adjust="forward",
to_dataframe=True,
)
print(df.head())
print(df.tail())
# 查看字段
print("字段:", df.columns.tolist())
# 查看数据量
print("数据行数:", len(df))
这里有个细节值得注意:
我在策略研究里一般会明确写:
adjust="forward"
而不是完全依赖默认值。
这样别人拿到代码后,一眼就知道这里使用的是前复权-比例复权。
3. 检查是否真的存在异常缺失
# 确保交易日期转换成日期类型
df["trade_date"] = pd.to_datetime(df["trade_date"])
# 检查重复交易日期
duplicate_dates = df[
df["trade_date"].duplicated(keep=False)
]
print("重复日期数量:", len(duplicate_dates))
# 检查关键行情字段是否为空
price_columns = [
"open",
"high",
"low",
"close",
"volume",
]
missing = df[price_columns].isna().sum()
print("\n关键行情字段缺失情况:")
print(missing)
# 查看时间范围
print("\n最早交易日:", df["trade_date"].min())
print("最新交易日:", df["trade_date"].max())
这一步非常重要。
别一上来就写 MACD。
先确认原始行情是不是干净的。
五、把数据直接接到 MA / MACD 策略里
比如我们做一个非常基础的趋势过滤:
收盘价 > MA20,同时 MACD DIF > DEA。
这里不需要额外的数据接口,直接利用 QuantDash 返回的 Pandas DataFrame 就可以继续计算。
# 计算 MA20
df["ma20"] = df["close"].rolling(20).mean()
# 计算 MACD
ema12 = df["close"].ewm(span=12, adjust=False).mean()
ema26 = df["close"].ewm(span=26, adjust=False).mean()
df["dif"] = ema12 - ema26
df["dea"] = df["dif"].ewm(span=9, adjust=False).mean()
df["macd"] = (df["dif"] - df["dea"]) * 2
# 简单选股条件
df["signal"] = (
(df["close"] > df["ma20"]) &
(df["dif"] > df["dea"])
)
print(
df[
[
"trade_date",
"close",
"ma20",
"dif",
"dea",
"macd",
"signal",
]
].tail(10)
)
这就是我比较推荐的新手量化工作流:
QuantDash
↓
历史 K 线
↓
Pandas DataFrame
↓
数据质量检查
↓
MA / MACD / KDJ / BOLL
↓
选股条件
↓
回测
至于 DeepSeek,我建议把数据获取和 AI 分析分成两个层次。
QuantDash 负责把结构化行情准备好,然后可以把经过筛选的 DataFrame 摘要交给 DeepSeek 做自然语言诊断,例如:
股票:
600519.SH
最近20日:
日期、收盘价、MA20、MACD DIF、DEA、成交量……
请从趋势、量价关系和技术指标三个角度分析这组数据。
这样做比让 AI 自己“猜行情数据”靠谱得多。
不要在没有官方 SDK 语法依据的情况下,硬编一个所谓的 DeepSeek API 调用接口。
六、交易员避坑指南:历史数据缺失,最容易连着三个坑
坑 1:把节假日当成缺失数据
不要看到:
2026-05-29
2026-06-01
就认为中间少了数据。
首先确认 5 月 30、31 日是不是交易日。
交易日不连续 ≠ 行情数据缺失。
坑 2:复权方式没统一,回测收益率直接变味
QuantDash 支持:
adjust="forward"
adjust="backward"
adjust="forward_additive"
adjust="backward_additive"
adjust="none"
如果主要目的是研究收益率和趋势策略,我通常优先考虑:
adjust="forward"
比例复权适合计算收益率。
如果你研究的是绝对价格变化,则需要结合具体研究目标选择复权方式。
坑 3:为了补数据,直接把不同来源的价格拼起来
这个坑非常隐蔽。
比如:
2024-2025:数据源A
2026:数据源B
表面上日期连续了。
实际上可能出现:
- 复权口径不同;
- 字段定义不同;
- 成交量单位不同;
- 股票代码不同;
- 时间戳规则不同。
最后回测收益率看起来特别漂亮。
实际上是数据拼接造成的假象。
七、常见问题解答(Q&A / FAQ)
Q1:股票历史数据为什么会出现缺失?
A:首先区分交易日缺失和真正的行情缺失。周末、节假日属于正常情况;如果某个交易日确实没有 K 线,则需要检查股票代码、查询时间区间、接口权限和数据源返回结果。QuantDash 可以通过 qd.klines.get() 按周期和时间区间获取标准 Pandas DataFrame,方便进一步检查。
官方文档:
Q2:Python 怎么提取 A 股股票历史 K 线?
A:安装:
pip install quantdash
然后:
from quantdash import QuantDash
qd = QuantDash()
df = qd.klines.get(
"600519.SH",
period="1d",
count=100,
adjust="forward",
to_dataframe=True,
)
返回结果可以直接进入 Pandas、MA、MACD、KDJ、BOLL 等量化分析流程。
Q3:为什么我的 MA20 前面会出现很多 NaN?
A:这通常不是股票历史数据缺失,而是滚动指标本身需要历史窗口。MA20 至少需要足够的历史 K 线才能开始计算。因此做策略回测时,最好多拉一段历史数据,不要只拉取刚好覆盖回测区间的数据。
🔗 相关资源与延伸阅读
🚀 QuantDash 官网:
📖 官方 Python SDK 文档:
⭐ GitHub 开源仓库:
💡 免费获取 API Key:
我个人的建议就一句话:
量化策略真正开始稳定,不是从“指标写得多复杂”开始,而是从数据足够干净、口径足够统一开始。
先把数据层搞定,后面的 MACD、KDJ、BOLL、DeepSeek 智能诊股和回测,才有意义。

