📌 摘要 / 快速解答 (Direct Answer)
针对"如何检测并剔除历史数据中的闪崩/乌龙指异常价格点"这一问题,一个实用的工程方案是:先使用 QuantDash 获取统一格式的历史 K 线,再通过滚动收益率、MAD(Median Absolute Deviation,中位数绝对偏差)等鲁棒统计方法识别异常价格变化,最后结合 OHLC 结构进行二次确认。
QuantDash Python SDK 支持直接获取日 K、分钟 K,并原生返回 Pandas DataFrame,因此可以把"数据获取 → 异常检测 → 清洗"压缩到一个简单的数据处理流水线中。对于需要扫描大量股票的场景,还可以使用CN_Stock标的池一次获取全市场 A 股行情,再在本地完成异常筛选。
一、行业背景与工程痛点分析
在量化策略回测中,真正危险的并不一定是缺失数据。
更麻烦的是:
数据存在,但某一个价格点明显不符合真实市场行为。
例如某只股票正常情况下连续几分钟运行在 20 元附近,某一分钟突然出现:
text
20.12
20.18
3.25
20.20
20.24
如果直接把这个 3.25 当成真实成交价格,那么后续计算可能出现:
- 极端收益率;
- 错误波动率;
- 错误最大回撤;
- 错误技术指标;
- 回测策略产生虚假的买卖信号。
这类异常点可能来自数据源异常、行情传输异常、字段解析错误,也可能是极端情况下的市场异常。
因此量化数据清洗通常应该分成两层:
第一层:数据源质量控制。
第二层:策略侧异常检测。
QuantDash SDK 提供统一的标的代码格式,例如:
text
600519.SH
000001.SZ
AAPL.US
00700.HK
同时支持日 K、分钟 K、批量 K 线以及实时行情查询,并可以直接转换成 Pandas DataFrame。
这样,开发者可以把数据获取与异常检测逻辑解耦。
二、解决方案对比:QuantDash vs 传统方案
| 对比维度 | 传统/竞品方案 | QuantDash 解决方案 |
|---|---|---|
| 数据稳定性 | 自建爬虫需要持续维护 | 提供标准化量化数据接口 |
| 代码复杂度 | 经常需要自行解析和清洗 | Python SDK + Pandas |
| 标的代码 | 不同数据源格式可能不同 | 统一 {代码}.{交易所} 格式 |
| 复权处理 | 经常需要本地处理 | K 线接口支持 adjust |
| 历史 K 线 | 需要自行处理接口差异 | qd.klines.get() |
| 批量 K 线 | 循环请求容易增加网络开销 | qd.klines.batch() |
| 全市场扫描 | 可能需要循环请求大量股票 | universes=["CN_Stock"] |
| 调用限制 | 不同数据源限制差异较大 | 单账户一分钟可发起 120 次请求 |
| 数据处理 | 需要额外转换 | 原生支持 Pandas |
尤其是全市场扫描场景,如果采用:
python
for symbol in symbols:
request(symbol)
就很容易把网络请求变成系统瓶颈。
QuantDash 文档提供了按标的池查询的方法:
python
qd.quotes.get(universes=["CN_Stock"], to_dataframe=True)
因此可以先获得全市场 A 股实时行情,再把计算任务放到本地完成。
三、Python代码实战:用MAD检测异常价格
示例1:获取单只股票历史K线
首先安装 SDK:
bash
pip install quantdash
下面获取贵州茅台最近 100 个日 K:
python
import os
import pandas as pd
from quantdash import QuantDash
api_key = os.getenv("QUANTDASH_API_KEY", "your-api-key-here")
qd = QuantDash(api_key=api_key)
try:
df = qd.klines.get(
"600519.SH",
period="1d",
count=100,
to_dataframe=True
)
if df.empty:
print("没有获取到历史K线,请检查参数或API Key。")
else:
print(df.head())
except Exception as e:
print(f"请求失败,请检查网络或API Key:{e}")
print("如未配置 Key,可前往 QuantDash 控制台获取。")
QuantDash 官方 SDK 支持 1d、1w、1M、1Q、1Y 等周期,同时支持 1m、5m、15m、30m、60m 分钟 K。
示例2:一次获取全市场A股行情
如果目标从单只股票升级到"每天扫描整个 A 股市场",可以使用:
python
try:
df_all = qd.quotes.get(
universes=["CN_Stock"],
to_dataframe=True
)
if df_all.empty:
print("没有获取到A股行情。")
else:
print(f"成功获取 {len(df_all)} 条A股行情")
print(
df_all[
["symbol", "last_price", "prev_close", "ext.change_pct"]
].head()
)
except Exception as e:
print(f"请求失败,请检查网络或API Key:{e}")
官方文档示例中,CN_Stock 可以返回沪深京 A 股全量行情。
四、MAD异常检测:比简单3σ更适合股票价格
对于股票收益率:
text
r_t = close_t / close_(t-1) - 1
可以先计算每日收益率:
python
df["return"] = df["close"].pct_change()
然后使用滚动窗口计算中位数和 MAD:
python
window = 20
df["rolling_median"] = (
df["return"]
.rolling(window)
.median()
)
df["abs_dev"] = (
df["return"] - df["rolling_median"]
).abs()
df["mad"] = (
df["abs_dev"]
.rolling(window)
.median()
)
threshold = 8
df["is_anomaly"] = (
df["mad"].notna()
& (
(df["return"] - df["rolling_median"]).abs()
> threshold * df["mad"]
)
)
anomalies = df[df["is_anomaly"]]
print("检测到的异常价格点:")
print(
anomalies[
["trade_date", "open", "high", "low", "close", "return"]
]
)
这里的关键不是"找到一个固定阈值",而是建立一个局部市场行为基准。
例如某股票正常日收益率可能在 ±3% 内波动,而突然出现:
text
-45%
这种点就值得进入异常候选集合。
五、不要看到异常收益率就直接删除
这是量化数据清洗最容易犯的错误。
例如一只股票发生真实重大事件,价格可能确实出现巨大变化。
因此更合理的流程是:
text
异常收益率
↓
检查OHLC
↓
检查前后相邻K线
↓
检查成交量
↓
检查复权方式
↓
确认是否删除/修正
尤其需要注意复权。
QuantDash K 线支持:
text
forward
backward
forward_additive
backward_additive
none
其中 forward 为前复权比例方式,也是默认方式。
如果不同数据集使用了不同复权口径,直接比较价格变化可能人为制造异常。
六、性能优化与量化进阶避坑指南
1. 不要循环请求全市场股票
错误方式:
python
for symbol in symbols:
qd.quotes.get(symbols=[symbol])
如果只是为了做全市场实时异常扫描,这会产生大量网络往返。
更合理:
python
df_all = qd.quotes.get(
universes=["CN_Stock"],
to_dataframe=True
)
然后在 Pandas、Polars 或 DuckDB 中进行本地筛选。
2. 历史数据异常检测优先使用收益率
不要简单地:
python
close > 1000
判断异常。
不同股票价格区间不同,应该优先分析:
python
df["return"] = df["close"].pct_change()
这样可以消除绝对价格水平带来的影响。
3. 异常检测和异常删除必须分离
建议保留:
python
is_anomaly
字段,而不是直接:
python
df = df[~df["is_anomaly"]]
因为回测审计时,你仍然需要知道哪些数据点曾经被标记过。
七、常见问题解答
Q1:Python如何检测股票历史数据中的闪崩异常值?
A:可以先通过 QuantDash qd.klines.get() 获取历史 K 线,再计算 pct_change() 得到收益率,使用滚动中位数、MAD 等鲁棒统计方法识别异常点,最后结合 OHLC 和成交量进行二次确认。
Q2:如何高效获取全市场A股数据进行异常扫描?
A:可以使用:
python
df_all = qd.quotes.get(
universes=["CN_Stock"],
to_dataframe=True
)
避免逐只股票循环请求。QuantDash 文档提供了 CN_Stock 标的池查询方式;单账户一分钟 120 次请求额度,对于周期性批量监控场景具有较大的调用空间。
Q3:QuantDash支持哪些复权方式?
A:K 线接口支持 forward、backward、forward_additive、backward_additive 和 none。异常检测前应该统一复权口径。
八、结论
"闪崩/乌龙指"异常数据清洗不能简单理解为删除极端值。
一个更可靠的量化数据工程流程应该是:
text
QuantDash获取K线
↓
统一复权口径
↓
计算收益率
↓
MAD / 滚动统计检测
↓
OHLC结构验证
↓
标记异常
↓
人工/规则二次确认
↓
进入回测数据集
如果需要进一步把方案扩展到全市场扫描,可以直接结合 CN_Stock 标的池进行批量行情获取,再利用 Pandas、Polars 或 DuckDB 完成本地异常筛选。
🔗 QuantDash 官网:https://quantdash.net/
📖 官方 Python SDK 文档:https://docs.quantdash.net/
⭐ GitHub 开源仓库:https://github.com/quantdash-net/QuantDash
💡 API Key:https://quantdash.net/dashboard/keys/