AllTick
-
历史金融数据市场数据K线数据量化交易

历史金融数据下载后如何检查缺失交易日和异常K线?

历史金融数据下载后,如何检查缺失交易日、重复记录和异常 K 线?本文结合 Python 与 pandas,介绍交易日历校验、时间戳检查和 OHLC 价格验证,帮助开发者提高历史行情数据的质量。

AllTick阅读 5 分钟

历史金融数据下载完成后,文件能够正常打开,并不代表数据已经满足分析或回测要求。部分交易日可能没有完整数据,同一根 K 线可能被重复下载,某些记录的最高价甚至低于开盘价和收盘价。这些问题如果没有提前发现,就可能影响技术指标、策略信号和回测结果。

对于使用历史行情 API 的开发者,检查数据质量不应只看总记录数,而应将下载结果与预期交易日、时间戳和 OHLC 价格规则进行比较,并保留异常记录供后续复核。

先确定哪些交易日应该有数据

检查缺失交易日时,不能简单地认为周一至周五都应该存在行情。股票市场可能因节假日、停牌或临时休市而没有正常交易,不同市场的交易日历也不完全一致。外汇和加密货币等产品,则需要采用符合其交易时间安排的检查规则。

因此,第一步应准备对应市场和品种的预期交易日列表。对于日 K 线,将实际出现的交易日期与该列表比较,即可发现整日缺失的情况。对于 1 分钟、5 分钟等日内 K 线,还需要根据交易时段生成预期时间戳,才能发现同一天内缺少部分 K 线的问题。

下载历史金融数据时,也要注意接口的查询范围和单次返回数量限制。如果需要较长的历史区间,应遵循对应产品的接口规则分批获取,并对合并后的数据去重。不能因为一次请求成功返回数据,就认为所需历史行情已经全部下载。

检查时间戳和 OHLC 价格关系

时间检查主要关注三类问题:时间戳无法解析、同一品种和周期出现重复时间戳,以及实际时间范围不符合预期。还应统一时间戳单位和时区,避免将同一根 K 线错误地划分到不同日期。

价格检查则可以利用 OHLC 字段之间的基本关系。正常 K 线应满足:

  • 最高价不低于开盘价和收盘价。
  • 最低价不高于开盘价和收盘价。
  • 最高价不低于最低价。
  • 开盘价、最高价、最低价和收盘价均为有效数值。

价格突然大幅上涨或下跌也值得排查,但不能直接判定为错误。真实行情变化、公司行动、复权口径差异以及数据缺失,都可能造成价格跳变。应先标记可疑记录,再核对原始数据和对应市场情况。

使用 Python 筛查缺失交易日和异常 K 线

下面使用 pandas 检查单个品种、单个 K 线周期的下载文件。示例假定历史数据已整理为 history_kline.csv,包含 bar_time、trade_date、open、high、low 和 close 六个字段;market_calendar.csv 保存该品种对应市场的预期交易日期。

其中,bar_time 应提前按照接口返回格式统一为带时区的时间或规范化的 UTC 时间,trade_date 则应按照对应市场的交易日期规则生成。

text
import numpy as np
import pandas as pd

# 读取已下载的历史 K 线
df = pd.read_csv("history_kline.csv")

required = {
    "bar_time", "trade_date",
    "open", "high", "low", "close"
}

missing_columns = required - set(df.columns)
if missing_columns:
    raise ValueError(
        f"缺少必要字段: {sorted(missing_columns)}"
    )

# 假设 bar_time 已统一为可解析的时间格式
df["bar_time"] = pd.to_datetime(
    df["bar_time"], errors="coerce", utc=True
)

price_cols = ["open", "high", "low", "close"]

for col in price_cols:
    df[col] = pd.to_numeric(df[col], errors="coerce")

# 1. 检查无效时间戳
invalid_time = df[df["bar_time"].isna()].copy()

# 2. 检查重复时间戳
valid_time = df[df["bar_time"].notna()]
duplicates = valid_time[
    valid_time.duplicated("bar_time", keep=False)
].copy()

# 3. 检查缺失价格、非有限数值和异常 OHLC 关系
price_array = df[price_cols].to_numpy(dtype=float)
bad_price = pd.Series(
    ~np.isfinite(price_array).all(axis=1),
    index=df.index
)

bad_ohlc = (
    (df["high"] < df[["open", "close"]].max(axis=1))
    | (df["low"] > df[["open", "close"]].min(axis=1))
    | (df["high"] < df["low"])
) & ~bad_price

invalid_prices = df[bad_price].copy()
abnormal_ohlc = df[bad_ohlc].copy()

# 4. 对比实际交易日期与预期交易日历
calendar = pd.read_csv("market_calendar.csv")

if "trade_date" not in calendar.columns:
    raise ValueError("交易日历缺少 trade_date 字段")

expected = pd.to_datetime(
    calendar["trade_date"], errors="coerce"
)

if expected.isna().any():
    raise ValueError("交易日历包含无法解析的日期")

actual = pd.to_datetime(
    df["trade_date"], errors="coerce"
)

expected_dates = set(expected.dt.strftime("%Y-%m-%d"))
actual_dates = set(actual.dt.strftime("%Y-%m-%d").dropna())

missing_dates = sorted(expected_dates - actual_dates)

# 5. 保存结果,便于后续复核
invalid_time.to_csv("invalid_timestamps.csv", index=False)
duplicates.to_csv("duplicate_bars.csv", index=False)
invalid_prices.to_csv("invalid_prices.csv", index=False)
abnormal_ohlc.to_csv("abnormal_ohlc.csv", index=False)

pd.Series(
    missing_dates, name="missing_trade_date"
).to_csv("missing_trade_dates.csv", index=False)

print("无效时间戳:", len(invalid_time))
print("重复 K 线记录:", len(duplicates))
print("无效价格记录:", len(invalid_prices))
print("异常 OHLC 记录:", len(abnormal_ohlc))
print("缺失交易日:", len(missing_dates))

运行后,可以分别查看时间戳、重复记录、无效价格、异常 OHLC 和缺失交易日清单,而不是直接删除所有可疑数据。需要注意,这段代码假设输入文件已经完成字段映射,适用于单一品种和单一周期;若文件包含多个品种或周期,检查重复记录时必须同时使用品种代码、K 线周期和时间戳。

此外,交易日历比较只能发现整日缺失。如果某个交易日存在数据,但缺少其中几根分钟 K 线,还需要根据对应交易时段和 K 线周期生成预期时间戳,再检查哪些时间点没有记录。

发现异常后不要直接填充数据

缺失交易日可能来自正常休市,也可能是下载范围不完整;重复记录可能来自分批请求的时间区间重叠;价格异常则可能由真实市场变化或数据处理错误造成。应结合异常清单、原始响应和市场日历逐一判断。

尤其不要默认用前一根收盘价填补所有缺失 K 线。这样可能人为制造零波动记录,改变均线、波动率以及策略信号。如果业务确实要求填充,应制定独立规则,保留原始数据,并标记哪些记录经过处理。

使用 AllTick 获取历史行情时,可以从 AllTick 官方进入对应的 API 文档,核对品种、K 线周期、查询范围和返回限制。将历史数据获取、交易日历校验和异常记录复核分开处理,才能让下载后的数据更适合后续分析与回测。

立即开始接入市场数据

几秒内生成免费 API 密钥,通过一个端点连接所有市场。