同一只股票,两个数据源给出的「成交量」差 100 倍——字段校准 6 个校准点

同一只股票,两个数据源给出的「成交量」差 100 倍——字段校准 6 个校准点

作者:量化专家 | 迪雅数据量化学院 适合读者:同时接了两个以上数据源、或准备迁移数据源的个人开发者

上一篇讲集合竞价的"装档",提到一个细节:同一个站内的两个接口,symbol 格式都不一样。这篇展开讲这件事——它属于一类更普遍的问题:字段校准


一、先看一个真实的对照

同一只股票,同一个时刻,从两个接口拿到的代码格式:

接口symbol 字段的值
实时行情sz000001
集合竞价(历史)000001.SZ
集合竞价(当日)000001.SZ

在同一个数据平台上,同一个标的,有三种写法中的两种。

这不是谁写错了。前面是"市场前缀式",后面是"交易所后缀式",两种都是行业里通用的写法。差别只在于:不同接口往往由不同的数据管线维护,管线之间没有强制统一格式。

如果你写的是 if symbol == "000001.SZ" 这样的判断,它在这三个接口上只有一个能过。 而它不会抛异常,只会静默地匹配不到,然后你的程序安静地什么都不做。

再放大一点看,整个市场的写法差异是这样的:

写法示例常见于
市场前缀sz000001 / sh600519新浪、腾讯行情
交易所后缀000001.SZ / 600519.SHWind、聚宽、多数研究型平台
纯数字000001 / 600519通达信、部分看板

没有一种写法是"标准"。你唯一能控制的,是自己代码里有一个统一的归一化函数。


二、为什么字段校准是选型的必做项

假设你只接一个数据源。你永远不会发现口径差异——因为差异需要比较才存在

问题在于,你迟早会接第二个源:可能是为了补数据、可能是为了做交叉验证、可能是原来那家涨价了。这时候你已有的全部数据都成了"未标定"状态。

差异的来源有三类,每一类都会造成静默错误:

来源 1:单位不一致(最常见)

这是最经典的坑。同一只股票的"成交量":

  • 有的源单位是
  • 有的源单位是 (1 手 = 100 股)

差 100 倍。 如果你的策略里有"成交量突破均量 N 倍"的条件,这个条件在两种口径下的触发时机会完全不同——而且不会报错。

判别方法:找一只你熟悉的股票,用一个你知道大致量级的交易日去核对。平安银行(000001.SZ)日常成交量在百万股量级;如果你看到的数字是几万,那多半是"手"。

更可靠的方法:看字段名。有些接口会把单位直接写进字段名里,比如 集合竞价成交量(股)成交均价(元)流通股本(万股)字段名带单位是最好的一种设计——它把歧义在设计阶段就消掉了。

来源 2:时间基准不一致

timestamp 字段可能是:

  • 交易所时间:这笔成交在交易所撮合的时间
  • 服务器时间:数据源服务器推送给你时打的时间戳

两者的差异在正常行情下是几十毫秒,在开盘瞬间、极端行情、跨日切换时可能放大到秒级。如果你的策略对时序敏感(比如判断"9:25:00 那笔竞价是否算入开盘"),这个差异是致命的。

判别方法:看返回的时间戳,和你本地时钟对比。如果长期稳定偏移一个固定值,那是服务器时钟不准;如果偏移量随网络状况浮动,那是"推送时刻"而非"交易所时刻"。

来源 3:复权处理管线不同

这个上一篇讲过,这里补充它和校准的关系:

同样的 close 字段,可能是未复权前复权后复权中的任何一种。而且——

前复权数据是"时变"的:每次除权除息,全部历史会被重算。这意味着你今天拉的前复权数据和上个月拉的不一样,且这是正确行为

所以"我上次拉的数据对不上"这件事,在复权场景下不一定代表出错。 你得先确认两次拉取用的是同一个复权口径。


三、6 个校准点清单

拿一只你熟悉的股票(建议用大盘股,流动性好、异常少),对每个候选源逐一核对这 6 项:

#校准点为什么重要真实踩过的坑
1symbol 格式决定你的匹配逻辑是否生效三套写法混用,if 判断静默失效
2last_price行情的基准值,错一个点后面全错集合竞价期返回 0 或昨收
3timestamp策略要知道"这个价格是几点的"有的给交易所时间,有的给服务器时间
4volume 单位量纲是手还是股把"手"当"股",成交量差 100 倍
5pre_close涨跌幅的计算基准复权处理不当,昨收被改,涨跌幅全歪
6非交易时段行为监控脚本半夜会不会误报收盘后返回 500 或假数据

跑完这 6 项,任何两项之间有系统性差异的源,就必须在你的代码里做显式转换。


四、一个可复用的归一化写法

校准的产出不该是一堆笔记,而应该是一段只写一次、全项目复用的代码。

第一步:symbol 归一化

import re

def normalize_symbol(s: str) -> str:
    """把各种写法统一成 '000001.SZ' 形式(交易所后缀式)
    
    支持输入:
      sz000001 / SH600519 / 000001.SZ / 600519.SH / 000001 / 600519
    """
    s = s.strip().upper()

    # 已是后缀式:000001.SZ
    m = re.match(r'^(\d{6})\.(SH|SZ|BJ)$', s)
    if m:
        return f"{m.group(1)}.{m.group(2)}"

    # 前缀式:SZ000001
    m = re.match(r'^(SH|SZ|BJ)(\d{6})$', s)
    if m:
        return f"{m.group(2)}.{m.group(1)}"

    # 纯数字:按交易品种规则推断交易所
    m = re.match(r'^(\d{6})$', s)
    if m:
        code = m.group(1)
        if code.startswith(('60', '68', '5', '11', '13')):   # 沪市主板/科创/基金/债
            return f"{code}.SH"
        if code.startswith(('00', '30', '12', '15')):        # 深市主板/创业板/债/基金
            return f"{code}.SZ"
        if code.startswith(('43', '83', '87', '92')):        # 北交所
            return f"{code}.BJ"
        return f"{code}.UNKNOWN"    # 明确标出未识别,而不是猜
    return s

注意最后那个 UNKNOWN——这比默认归到某个交易所要好。未识别就明确说未识别,让问题暴露出来,而不是猜一个可能错的值然后静默运行。

第二步:成交量单位归一化

def normalize_volume(raw, unit_hint: str = "") -> int:
    """把成交量统一成「股」
    
    unit_hint: 从字段名或文档里读到的单位标识
    """
    if unit_hint and '手' in unit_hint:
        return int(raw * 100)
    return int(raw)

关键点:这个函数必须接收一个显式的 unit_hint 参数,而不是自己猜。 猜出来的单位,等于给未来的自己埋雷。

第三步:把校准结果写成配置

不要把这些差异散落在业务代码里。集中成一个配置表:

SOURCES = {
    "source_a": {
        "symbol_style": "prefix",      # sz000001
        "volume_unit": "股",
        "adjust_supported": ["qfq", "hfq", "none"],
        "realtime_offhours": "empty",  # 非交易时段返回空
    },
    "source_b": {
        "symbol_style": "suffix",      # 000001.SZ
        "volume_unit": "手",
        "adjust_supported": ["none"],
        "realtime_offhours": "zero",   # 非交易时段返回 0 ← 危险
    },
}

def is_trustworthy(src_name: str) -> bool:
    """非交易时段返回 0 的源,需要额外防御"""
    return SOURCES[src_name]["realtime_offhours"] != "zero"

这张配置表本身就是文档。 半年后你加了第三个源,照着这个结构补一行就行,不用重新踩一遍。


五、怎么判断一个源"值不值得校准"

校准是要花时间的。什么时候值得做?

值得做

  • 你准备用它跑回测或实盘 —— 错误成本高,必须校准
  • 你要用它和另一个源交叉验证 —— 不校准就没法比
  • 数据要长期留存 —— 半年后你自己都记不清当初的口径

不值得做

  • 只是临时看一眼某个数据 —— 用现成的可视化工具更快
  • 这个源你只用它的一个简单指标,且不参与任何计算

判断标准很实际:这段数据会不会进入你的计算链路? 会,就校准;只是给人看的,就不必。


六、写在最后

字段校准最麻烦的地方在于:它不出错的时候没有任何反馈。

symbol 匹配不上,程序不报错,只是返回空结果。成交量差 100 倍,策略照常运行,只是信号全歪。你会在两周后回测结果异常时,才回头怀疑数据。

而校准的成本是多少?一个下午,6 个校准点,一段归一化函数。这笔投入只在一种情况下会显得浪费:你从始至终只用一个数据源,且永远不换。

数据源这件事,几乎没人能一直用一个。所以校准这件事,早做比晚做便宜得多。

本文为个人量化研究经验分享,数据接口信息以各平台官网最新公布为准。市场有风险,回测结果不代表实盘收益,不构成任何投资建议。


关于本文用到的数据

文中的字段示例来自迪雅数据(diyadata.cn)的实际接口返回,主要包括:

接口说明与校准相关的特点
A 股实时交易数据 ssjy实时行情快照symbol 为市场前缀式sz000001
A 股历史交易数据 lsjy日频历史行情返回 date/open/close/high/low/amount支持前复权、后复权、不复权三种口径
当日/历史集合竞价竞价数据symbol 为交易所后缀式000001.SZ

注意上表中三行的 symbol 格式并不统一——这不是缺陷披露,而是本文想说明的现实:任何多接口的数据平台都存在这种差异,校准是使用者的必修课,而不是某一家的问题。

迪雅数据目前提供 50+ 个 A 股数据接口,具体字段与调用方式见官网接口文档。

下一篇预告:《免费数据源最危险的地方不是不能用,而是错了你不知道》

评分:
在线客服
工作日 9:00 - 18:00 在线回复

产品使用、订单、售后问题
点击下方按钮在线沟通

点击开始 QQ 咨询

客服QQ:64935542

官方微信自助客服

扫一扫添加微信客服
获取专属技术支持