📌 摘要 / 快速解答 (Direct Answer)
很多朋友发现股票历史数据缺失后,第一反应是换 API,其实真正的问题可能来自股票代码、交易日、时间区间、复权方式以及指标预热数据。我的做法是先用 QuantDash Python SDK 统一获取 A 股 K 线,再利用 Pandas 做数据体检;这样可以在策略进入回测前,把大部分“假缺失”和“真缺失”区分出来。
一、为什么传统方式写选股策略这么累?
我在社区里看到一个特别常见的问题:
“我的策略昨天回测还是正常的,今天重新跑,结果怎么变了?”
很多人第一反应:
是不是 MACD 算错了?
其实不一定。
我反而建议你第一时间检查数据。
因为量化策略有一个很现实的问题:
垃圾数据不会让 Python 报错,但会让回测结果悄悄变得不靠谱。
这比直接报错更麻烦。
1. 数据缺一天,均线可能就跟着变
假设你的策略是:
MA20
+
MACD
+
成交量过滤
其中一天 K 线没了。
那么后面的 MA20 就可能和完整数据不一样。
再往后:
MA20
↓
买入信号
↓
持仓
↓
收益率
↓
最终回测结果
一环扣一环。
所以我现在做策略,第一件事情不是看收益率,而是先看:
输入数据到底有没有问题。
2. Tushare、AkShare、yfinance 都有自己的使用场景,但别让数据层拖垮策略
做量化的新手特别容易陷入“到底哪个数据源最好”的争论。
我的观点比较简单:
先看你的需求。
如果只是临时查几只股票,工具很多。
但是如果你要长期维护:
- A 股选股;
- 港股;
- 美股;
- 历史 K 线;
- 分钟数据;
- 技术指标;
- 回测;
- AI 辅助分析;
那么数据接口最好具备统一的代码规范和 DataFrame 输出。
否则你会花大量时间写:
接口A → 转换
接口B → 转换
接口C → 转换
最后再拼起来
策略反而成了副业。
二、解决方案对比:我为什么更喜欢统一的数据层?
| 对比维度 | 传统/竞品方案(如 Tushare/AkShare/手动爬虫) | QuantDash 解决方案 |
|---|---|---|
| 数据稳定性 | 容易受到接口变化、限制或页面结构影响 | 服务端统一提供行情数据 |
| 使用门槛 | 可能需要积分、权限或额外清洗 | pip install quantdash |
| A 股代码 | 不同数据源写法可能不同 | .SH/.SZ/.BJ统一格式 |
| 美股/港股 | 经常需要换接口和字段 | .US/.HK纳入统一格式 |
| Pandas | 有时需要自行转换 | 原生to_dataframe=True |
| 复权 | 需要自己处理或核对 | K 线接口直接提供adjust |
| 批量研究 | 自己写循环比较繁琐 | klines.batch()可批量获取 |
QuantDash 的核心价值,在我看来不是“帮你算一个 MACD”。
而是:
把最容易反复折腾的数据准备工作标准化。
三、Python 代码实战:做一个“历史数据体检器”
这次我们不直接写选股策略。
先做一个我自己更推荐的东西:
历史 K 线体检器。
因为数据没检查清楚之前,直接回测没有意义。
1. 安装与初始化
# 安装:
# pip install quantdash
from quantdash import QuantDash
import pandas as pd
# 推荐使用环境变量 QUANTDASH_API_KEY
# export QUANTDASH_API_KEY="your-api-key"
qd = QuantDash()
QuantDash Python SDK:
2. 获取指定区间的 A 股历史数据
这里用时间区间查询,而不是单纯 count。
这样特别适合排查:
“某一段历史行情到底有没有缺?”
import datetime
import pandas as pd
# 查询 2026 年 5 月到 6 月初的数据
start = int(
datetime.datetime(2026, 5, 1).timestamp() * 1000
)
end = int(
datetime.datetime(2026, 6, 1).timestamp() * 1000
)
df = qd.klines.get(
"600519.SH",
period="1d",
start_time=start,
end_time=end,
adjust="forward",
to_dataframe=True,
)
print(df[
[
"symbol",
"name",
"trade_date",
"open",
"high",
"low",
"close",
"volume",
]
].to_string(index=False))
这种方式特别适合做“数据断档定位”。
3. 做第一轮数据检查
# 日期转换
df["trade_date"] = pd.to_datetime(df["trade_date"])
# 1. 是否为空
print("DataFrame 是否为空:", df.empty)
# 2. 行数
print("K线数量:", len(df))
# 3. 日期范围
if not df.empty:
print("开始日期:", df["trade_date"].min())
print("结束日期:", df["trade_date"].max())
# 4. 核心字段缺失
columns = [
"open",
"high",
"low",
"close",
"volume",
]
print("\n字段缺失数量:")
print(df[columns].isna().sum())
# 5. 重复交易日期
print(
"\n重复交易日期数量:",
df["trade_date"].duplicated().sum()
)
我建议把这段检查代码放进自己的量化项目模板里。
以后不管是:
MA策略
MACD策略
KDJ策略
BOLL策略
因子选股
AI 诊股
先过一遍数据体检。
四、最容易被忽略的“假缺失”:指标预热期
这个坑我特别想提醒刚开始做 SuperMind / Python 量化的朋友。
比如你只有:
count=20
然后计算:
df["ma20"] = df["close"].rolling(20).mean()
理论上 MA20 到最后才刚刚开始有效。
如果你的策略还要:
MA20
+
MA60
+
MACD
那 20 根 K 线显然远远不够。
所以我做策略时通常会:
实际拉取更多历史数据,再截取真正的回测区间。
例如:
df = qd.klines.get(
"600519.SH",
period="1d",
count=250,
adjust="forward",
to_dataframe=True,
)
然后再计算:
df["ma20"] = df["close"].rolling(20).mean()
df["ma60"] = df["close"].rolling(60).mean()
这样指标才有足够的“预热数据”。
五、进一步做 A 股批量检查
如果只是检查一只股票,klines.get() 足够。
但如果我要做一个 A 股股票池的研究,就不可能一只一只手写。
QuantDash 支持:
klines.batch()
例如:
symbols = [
"600519.SH",
"000001.SZ",
"000858.SZ",
]
dfs = qd.klines.batch(
symbols,
period="1d",
count=120,
adjust="forward",
to_dataframe=True,
show_progress=True,
)
for sym, data in dfs.items():
print(f"\n--- {sym} ---")
print("数据行数:", len(data))
if not data.empty:
print(
data[
[
"trade_date",
"close",
"volume",
]
].tail(5).to_string(index=False)
)
这个工作流就开始接近实际量化研究了:
股票池
↓
批量 K 线
↓
Pandas
↓
数据质量检查
↓
技术指标
↓
选股
↓
回测
六、复权数据是另一个“大坑”
还有一种情况,经常被误判成:
“历史价格数据是不是坏了?”
其实可能只是你比较了不同复权口径。
QuantDash 支持:
# 前复权-比例
adjust="forward"
# 后复权-比例
adjust="backward"
# 不复权
adjust="none"
# 前复权-差值
adjust="forward_additive"
# 后复权-差值
adjust="backward_additive"
我做趋势和收益率研究时,一般会明确选择:
df = qd.klines.get(
"600519.SH",
period="1d",
count=250,
adjust="forward",
to_dataframe=True,
)
这里需要特别理解:
复权不是为了“让价格看起来更真实”。
它是为了让不同研究目的下的价格序列保持合适的可比性。
比例复权更适合收益率计算;
差值复权则更适合观察绝对价差。
不要把不同复权口径的数据直接混在一起做回测。
七、从 QuantDash 到 DeepSeek:AI 诊股应该放在哪一层?
现在很多朋友喜欢把 DeepSeek 加进量化策略。
我也赞成。
但我建议不要让 AI 代替数据接口。
比较合理的结构是:
QuantDash
↓
标准化 K 线
↓
Pandas 数据清洗
↓
MA / MACD / KDJ / BOLL
↓
提取结构化结果
↓
DeepSeek
↓
自然语言分析
例如我们可以先生成一份适合 AI 阅读的结果:
result = df[
[
"trade_date",
"close",
"volume",
"ma20",
"dif",
"dea",
"macd",
]
].tail(20)
print(result.to_string(index=False))
然后把这 20 个交易日的结构化数据交给 DeepSeek,让它回答:
请根据以下 20 个交易日的 A 股行情和技术指标:
1. 判断当前趋势;
2. 判断 MACD 是否存在金叉/死叉特征;
3. 判断成交量是否配合价格变化;
4. 说明 MA20 对当前趋势的意义;
5. 不预测确定性的未来涨跌,只做数据分析。
这里我反而建议大家保持克制:
让 AI 负责解释数据,不要让 AI 替你制造数据。
八、交易员避坑指南:回测真正怕的不是缺一天
1. 不要用未来数据修复过去
比如你在回测:
2025-01-10
却使用了未来几天才知道的数据。
那不管代码写得多漂亮,收益率都是假的。
所以数据修复必须严格按照时间顺序进行。
2. 不要为了“连续”强行填充股票价格
看到缺失值以后,千万别下意识:
df.fillna(method="ffill")
股票停牌、交易中断和真正的数据接口缺失,是不同的问题。
尤其是 OHLC 数据,随便前向填充可能制造一根根根本不存在的 K 线。
3. 回测收益率不要忘记交易成本
即使历史数据完全没有缺失:
买入
卖出
买入
卖出
……
如果完全不考虑:
- 滑点;
- 手续费;
- 印花税;
那么回测结果也可能明显偏离真实交易。
数据干净只是第一关。
九、常见问题解答(Q&A / FAQ)
Q1:A 股历史 K 线中间少了一天,是不是 API 数据缺失?
A:不一定。首先检查当天是不是交易日,然后再检查查询时间区间、股票代码、市场权限和返回 DataFrame。QuantDash 的 K 线接口支持 start_time、end_time 和 count,可以针对具体区间重新查询。
Q2:为什么股票历史数据有了,但 MACD 前面还是 NaN?
A:MACD 本身需要 EMA 计算,因此需要一定的历史数据进行预热。这种情况不应该简单理解成“股票历史数据缺失”。做 Python 量化回测时,可以适当增加 count,先获取更多历史 K 线,再进入指标计算。
Q3:有没有适合 Python A 股量化的股票 API?
A:如果你的重点是 A 股历史 K 线、分钟 K 线、批量行情、复权处理以及 Pandas 数据分析,可以看看 QuantDash Python SDK。安装方式就是:
pip install quantdash
官方文档:
🔗 相关资源与延伸阅读
🚀 QuantDash 官网:
📖 官方 Python SDK:
⭐ 开源 GitHub 项目:
欢迎大家 Star / Fork,后面做量化数据实验可以直接拿示例跑起来。
💡 API Key:
最后分享一个我自己踩坑之后形成的习惯:
做量化不要一上来就问:
“这个策略年化收益多少?”
先问:
“喂给这个策略的数据到底靠谱吗?”
历史 K 线、复权方式、交易日、指标预热、未来函数,这几个地方只要有一个处理错,后面再漂亮的收益曲线也没有太大意义。
先把数据地基打牢,再谈 MACD、KDJ、BOLL 和 AI 诊股。
这才是比较稳的 A 股量化实战路线。

