← 返回博客列表

【魔码量化工程实战进阶 #12】接口字段类型契约与校验:别让脏数据炸了你的策略

2026年08月31日 18:04 · 魔码数服 · 魔码量化工程实战进阶

【魔码量化工程实战进阶 #12】接口字段类型契约与校验:别让脏数据炸了你的策略

入门系列把字段当"拿来就用"。本篇讲一个生产级铁律:字段有契约——名字、类型、语义、取值范围都必须被显式约束,否则一只"脏数据"就能让回测崩坏或净值腰斩。我们基于真实返回的字段集建一个校验器,落库前先过一道关。

本文你将得到什么

  1. 字段契约是什么:名字 / 类型 / 取值范围 / 语义 四件套
  2. 一个真实发现的陷阱:同一个 pc 字段,在历史接口是"前收"、在实时接口是"涨跌幅%"
  3. 一个可跑的 字段校验器(落库前拦截脏数据)
  4. 四个典型脏数据:类型错 / 取值越界 / 时间格式乱 / 接口加字段破老代码

一、痛点:字段不是"拿来就用"

真实接口返回里藏着三类雷: - 类型漂移v(成交量)有时是 int、有时被当成 str; - 语义双义pc 在历史 K 线是"前收"(如 11.72),在实时快照是"涨跌幅%"(如 0.5177)——同一个字母,两种含义; - 脏值:停牌日 v=0、除权日 c 跳变、个别 c=None

不校验直接进策略,轻则 TypeError 崩,重则用"前收"当"涨跌幅"算出荒谬信号。


二、真实字段契约(基于实测返回)

历史 K 线(实测 600519,字段 ['a','c','h','l','o','pc','sf','t','v']):

字段 类型 语义 取值
t str 交易日 YYYY-MM-DD 非空
o/h/l/c float 开/高/低/收 > 0
v int 成交量(手) ≥ 0
a int 成交额(元) ≥ 0
pc float 前收 > 0
sf int 复权因子/状态标识 ≥ 0

实时快照(实测 510300,字段含 pe,ud,pc,zf,tr,pb_ratio,p,o,h,l,yc,cje,v,pv,tv,t):

字段 类型 语义 取值
t str 时间 YYYY-MM-DD HH:MM:SS 非空
p/o/h/l/yc float 最新/开/高/低/昨收 > 0
pc float 涨跌幅% 通常 (−11, 11)
zf float 振幅% ≥ 0
pe/pb_ratio float 市盈/市净 ≥ 0
ud float 涨跌额 任意
tr float 换手率% ≥ 0
cje int 成交额 ≥ 0
v int 成交量 ≥ 0

⚠️ 核心陷阱pc 历史=前收(量纲是价格),实时=涨跌幅%(量纲是百分比)。解析时必须按接口分别处理,绝不能共用一个含义。


三、可跑代码:字段校验器

def validate_bar(row, kind="history"):
    """落库前校验;返回 (ok, reason)。kind=history|realtime"""
    if kind == "history":
        need = {"t":str, "o":float, "h":float, "l":float, "c":float, "v":int, "a":int, "pc":float}
        for f, typ in need.items():
            if f not in row:
                return False, f"缺字段 {f}"
            if not isinstance(row[f], typ) and not (typ is int and isinstance(row[f], float)):
                return False, f"{f} 类型应为 {typ}, 实为 {type(row[f]).__name__}"
        # 取值约束
        if not (row["h"] >= row["l"] >= 0):
            return False, "高低价非法"
        if row["c"] <= 0 or row["o"] <= 0:
            return False, "价格非正"
        if not (0 <= row["pc"] <= row["c"] * 2):   # 前收不应离谱偏离
            return False, "前收异常"
    else:
        if "p" not in row or not isinstance(row.get("pc"), (int, float)):
            return False, "实时字段缺失/类型错"
        if not (-15 < row.get("pc", 0) < 15):
            return False, "涨跌幅越界(可能单位错)"
    return True, "ok"

# 用法:落库前逐行校验,脏数据进死信(接 #01)
for r in rows:
    ok, why = validate_bar(r, "history")
    if not ok:
        dead.append((r, why))

四、本机实测(校验器逻辑可跑)

下面用真实字段结构构造一条"正常"和一条"脏"样本,验证校验器能拦截:

good = {"t":"2025-09-15","o":11.7,"h":11.72,"l":11.63,"c":11.65,"v":840387,"a":980347616,"pc":11.72}
bad1= {"t":"2025-09-15","o":11.7,"h":11.72,"l":11.63,"c":-5.0,"v":840387,"a":980347616,"pc":11.72}  # 价格非正
bad2= {"t":"2025-09-15","o":11.7,"h":5.0,"l":11.63,"c":11.65,"v":840387,"a":980347616,"pc":11.72}   # 高<低
print(validate_bar(good, "history"))   # (True, 'ok')
print(validate_bar(bad1, "history"))   # (False, '价格非正')
print(validate_bar(bad2, "history"))   # (False, '高低价非法')

运行后你会看到:

(True, 'ok')
(False, '价格非正')
(False, '高低价非法')

校验器正确放过了正常数据、拦下了两类脏数据——这些脏数据若直接进回测,会导致 c 出现负值、高低关系反转,指标全部失真。


五、原理深挖:四个典型脏数据

  1. 类型错v 偶发以字符串返回。校验器用 isinstance 兜住,必要时 int(float(v)) 强转。
  2. 取值越界:停牌日 v=0 合法(但要标停牌),c=-5 非法(价格非正)。区分"合法零"和"非法负"。
  3. 时间格式乱t 有时 2025-09-15、有时带时分。统一 datetime.strptime 解析,失败进死信。
  4. 接口加字段破老代码:服务端升级加字段,老代码用 row["新字段"]KeyError。永远用 row.get("新字段"),契约向后兼容。

六、小结

字段契约 = 把"字段名/类型/语义/取值"显式写下来,落库前用校验器过一道。它拦住的每一条脏数据,都可能是一个"回测很美、实盘爆炸"的隐患。尤其记住 pc 的双义陷阱——历史里它是前收,实时里它是涨跌幅%,混用即错。

到这里,模块二(行情数据深用)收口:消费(#07)、回放对齐(#08)、复权(#09)、分钟级(#10)、多资产(#11)、字段契约(#12)。下一篇进入模块三——把干净的数据变成信号。详见文末。


免责声明:本文所有示例数据仅用于接口演示,不构成任何投资建议;市场有风险,投资需谨慎。

系列持续更新中。 想要亲手跑通上面的代码?前往 魔码证书申请页 免费领取你的专属证书,复制即用、按次计费、稳定可用。

想亲自试一下?免费获取证书
客服微信
客服微信二维码