自选股票列表接入行情 API 时,如何避免漏查、重复查和查错标的?

一份包含 200 只股票的自选列表,经过 Python 程序处理后,最终生成了 198 个行情查询任务。

这是否意味着程序漏掉了两只股票?

不一定。

原始列表可能本来就存在重复代码,也可能包含空白记录、格式错误的标的,或者同一证券在不同数据源中使用了不同的代码表示方式。

真正需要回答的问题是:程序能否解释每一条原始记录最终去了哪里,以及为什么被保留、合并或拒绝?

对于量化开发而言,自选股票列表不仅是一个输入文件,也是一组需要经过验证的数据记录。如果只使用 dropna()、strip() 和 drop_duplicates() 等操作快速生成代码列表,虽然能够减少一些明显的输入问题,却可能同时掩盖值得关注的数据质量异常。

本文从数据质量检查的角度,设计一套更适合实际行情接入流程的处理方法。

一、不要把所有异常都当作可以删除的垃圾数据

考虑下面这份原始列表:

python 复制代码
raw_symbols = [
    "600519.SH",
    "000001.SZ",
    "600519.SH",
    " AAPL.US ",
    "",
    None,
    "00700.HK",
    "600519",
]

如果直接清洗:

python 复制代码
symbols = list(
    dict.fromkeys(
        str(symbol).strip()
        for symbol in raw_symbols
        if symbol
    )
)

结果看起来已经比较整齐。

但这段代码仍然存在几个问题。

首先,None、空字符串和格式不完整的代码被采用了不同的处理方式。其次,重复记录被自动合并,但程序没有留下重复发生的记录。最后,600519 可能因为缺少市场后缀而无法按照项目约定进行查询,却仍然进入了结果列表。

这里最重要的区别是:

数据清洗的目标不是让数据看起来整齐,而是让每一次数据转换都有明确的规则和可追溯的结果。

因此,不建议将所有异常直接删除。更好的方式是先识别异常类型,再决定是否保留。

二、为每条原始记录建立处理状态

在处理复杂自选列表时,可以为每条记录建立独立的状态,例如:

  • valid_candidate:通过初步格式检查。
  • empty:代码为空。
  • invalid_format:不符合项目规定的代码格式。
  • duplicate:与此前记录重复。
  • needs_verification:格式看似合理,但仍需确认标的有效性。

这些状态是本文设计的内部分类,不是 QuantDash 或其他行情 API 的官方状态码。

实现时,可以保留原始行号、原始代码和标准化后的代码:

python 复制代码
import pandas as pd


def inspect_symbols(
    values: list,
) -> pd.DataFrame:
    records = []

    for row_number, value in enumerate(
        values,
        start=1,
    ):
        original = value

        if value is None or pd.isna(value):
            records.append({
                "row_number": row_number,
                "original_symbol": original,
                "symbol": None,
                "status": "empty",
            })
            continue

        symbol = str(value).strip()

        if not symbol:
            status = "empty"
        elif "." not in symbol:
            status = "invalid_format"
        else:
            status = "valid_candidate"

        records.append({
            "row_number": row_number,
            "original_symbol": original,
            "symbol": symbol or None,
            "status": status,
        })

    return pd.DataFrame(records)

使用示例:

python 复制代码
raw_symbols = [
    "600519.SH",
    "000001.SZ",
    "600519.SH",
    " AAPL.US ",
    "",
    None,
    "00700.HK",
    "600519",
]

audit = inspect_symbols(raw_symbols)

print(audit)

这段代码只演示最基础的检查流程。

其中,是否包含句点只是一个简化的格式判断条件,不能用来证明代码有效。正式项目应根据实际支持的市场和代码规范,建立更严格的验证规则。

另外,如果数据来自 Excel,最好在读取阶段就保留原始行号或唯一记录 ID。否则,当转换结果发生变化时,可能很难追溯原始记录。

三、重复记录不一定意味着重复查询

重复代码是自选列表中常见的问题,但需要区分两种情况。

情况一:同一标的被重复加入

例如:

text 复制代码
600519.SH
000001.SZ
600519.SH

如果任务是获取每只股票的一份日线数据,那么相同代码通常只需要进入一次查询清单。

情况二:同一标的对应不同业务任务

例如,同一只股票可能同时属于:

  • 长期投资观察列表。
  • 日内策略监控列表。
  • 财报事件研究列表。

这时,直接按照 symbol 去重,可能会丢失分组或策略信息。

如果原始记录代表不同业务关系,可以采用复合键进行判断:

python 复制代码
unique_tasks = watchlist.drop_duplicates(
    subset=["symbol", "group"],
    keep="first",
)

这段代码要求数据中存在 symbol 和 group 两列。

它表达的规则是:同一标的在同一分组中只保留一条记录,但允许它出现在不同分组中。

真正应该使用什么去重规则,取决于数据最终服务的任务,而不是取决于哪个 Pandas 函数最方便。

四、如何发现标准化过程造成的漏查?

有些问题不是原始数据错误,而是转换规则本身造成的。

例如,假设项目决定将标的代码中的英文字母统一转换为大写:

python 复制代码
symbol = symbol.strip().upper()

这对符合项目约定的代码通常是合理的。

但是,如果程序进一步擅自添加市场后缀、删除特殊字符,或者根据代码长度推测市场,就可能改变原始记录的含义。

例如:

text 复制代码
600519

不能仅凭字符串长度就确定它应该对应哪个市场。

更可靠的方式是建立显式的映射规则,并将无法判断的记录交由验证流程处理。

对于原始代码和标准化代码不一致的情况,也可以单独记录:

python 复制代码
audit["changed"] = (
    audit["original_symbol"].astype("string")
    != audit["symbol"].astype("string")
)

这项比较可以帮助发现标准化过程中发生的变化,但不等于变化一定错误。

例如,去除前后空格属于合理清洗,而自动补充市场后缀则需要明确的业务依据。

建议为以下情况建立专门的检查:

  1. 原始代码为空或缺失。
  2. 标准化后代码发生变化。
  3. 同一个代码出现多次。
  4. 代码不符合项目支持的格式。
  5. 代码格式正确,但尚未通过标的有效性验证。
  6. 原始记录在最终查询清单中没有对应结果。

这样才能区分"按规则排除"和"意外丢失"。

五、查询完成后,为什么还要检查结果覆盖率?

即使生成的查询清单没有问题,行情请求也可能因为网络错误、权限问题、数据缺失或其他原因而无法完成。

因此,不能将查询清单中的标的数量直接当作成功获取数据的标的数量。

建议至少区分以下三个集合:

  • input_symbols:原始输入中涉及的标的。
  • query_symbols:通过本地规则并进入查询任务的标的。
  • success_symbols:经过结果验证后,确实获得预期数据的标的。

可以用集合差异定位部分问题:

python 复制代码
input_symbols = {
    "600519.SH",
    "000001.SZ",
    "AAPL.US",
}

query_symbols = {
    "600519.SH",
    "AAPL.US",
}

success_symbols = {
    "600519.SH",
}

not_scheduled = (
    input_symbols - query_symbols
)

not_successful = (
    query_symbols - success_symbols
)

print("未进入查询任务:", not_scheduled)
print("查询后未成功:", not_successful)

这里的集合都是示意数据,不代表实际 API 调用结果。

在正式程序中,集合比较适合检查唯一标的,但如果需要追踪重复记录、不同分组或每次请求的状态,就应该使用包含任务 ID 的结构化记录,而不是只依赖集合。

另外,成功获取数据也不一定意味着数据完整。

例如,某只股票成功返回了日线记录,但返回的时间区间不符合预期。程序如果只判断请求是否报错,就可能将不完整的数据错误地标记为成功。

因此,应根据具体查询任务定义验证条件,例如:

  • 是否返回了预期的数据结构。
  • 返回的标的是否与请求目标一致。
  • 时间范围是否符合任务要求。
  • 必要字段是否存在。
  • 数据是否存在明显的重复记录。
  • 预期周期内是否存在需要进一步调查的缺口。

并非每种缺口都意味着错误。停牌、上市时间和交易日历都会影响数据的合理性,不能简单地把所有缺失日期都视为 API 故障。

六、使用行情数据服务时,如何划清输入问题与接口问题的边界?

在接入行情 API 时,建议将故障分为三个层次。

第一层:本地数据问题。

例如,代码为空、格式不符合要求、标的重复,或者市场映射缺失。

这类问题通常可以在发送请求之前发现。

第二层:请求与权限问题。

例如,认证信息错误、权限不足或请求受到限流。

QuantDash 的公开资料列出了 REST API 的 401、403 和 429 等 HTTP 错误状态。这些状态应结合实际响应和官方文档解释,而不能简单地统一归结为股票代码错误。

第三层:返回数据问题。

例如,请求已完成,但返回数据不满足当前任务的时间范围、字段或完整性要求。

这时需要检查返回结果,而不能仅依据 HTTP 请求是否成功作出判断。

QuantDash 提供金融行情数据 API 和 Python SDK,可用于相关行情数据接入工作。其具体接口的请求参数、返回结构和错误处理方式,应以对应的官方技术文档为准。

需要特别强调的是,行情数据接口可以帮助完成数据获取,但无法自动替代应用程序的输入审计、业务规则检查和数据质量验证。

七、建立一份可以长期使用的输入审计报告

如果自选列表会定期变化,建议每次转换都生成一份审计结果。

例如:

python 复制代码
summary = {
    "input_rows": len(audit),
    "empty_rows": int(
        (audit["status"] == "empty").sum()
    ),
    "invalid_format_rows": int(
        (
            audit["status"]
            == "invalid_format"
        ).sum()
    ),
    "candidate_rows": int(
        (
            audit["status"]
            == "valid_candidate"
        ).sum()
    ),
}

print(summary)

这个统计只反映示例代码定义的状态,不代表已经完成标的有效性验证。

如果需要更完整的审计,还可以记录:

  • 原始记录数。
  • 标准化后的记录数。
  • 去重后的唯一标的数。
  • 被排除记录及原因。
  • 尚未完成有效性验证的标的。
  • 实际进入查询任务的标的数。
  • 查询成功与失败的任务数。
  • 返回数据未通过质量检查的任务数。

这些指标可以帮助开发者判断问题究竟发生在输入转换、请求执行还是结果验证阶段。

最终,自选股票列表接入行情 API 时,最重要的不是尽可能多地删除异常记录,而是建立一套可解释的处理规则。每个标的为什么被查询、为什么没有被查询,以及查询后为什么没有获得预期数据,都应该有明确的答案。

只有把这些问题分开处理,才能避免一个看似简单的股票列表转换步骤,成为后续回测、监控和策略运行中难以追踪的数据质量隐患。

QuantDash 官方文档

相关推荐
miofly37 分钟前
GitHub 日榜趋势速报 | 2026-10-20
开源·github
高频因子挖掘机1 小时前
量化项目的数据架构怎么设计?从第一份行情到可扩展的 Python 研究环境
后端·github·api
考虑考虑9 小时前
JDK26中的Map.ofLazy
java·后端·java ee
云浪11 小时前
Go 并发实战:从零实现一个高并发安全的任务调度器
javascript·后端·go
苏三的开发日记11 小时前
Docker 启动顺序优化(CentOS / Ubuntu)
后端
高频因子挖掘机12 小时前
同一交易日出现重复 K 线:去重前先确认时间、来源和粒度
后端·github·api
feely020813 小时前
《我写了 31 个静态扫描插件,踩了 7 个「假绿」》
后端
橙子家14 小时前
参数校验类异常(Argument Exception)【C# Exception 详解系列一】
后端