【魔码量化工程实战进阶 #12】接口字段类型契约与校验:别让脏数据炸了你的策略
【魔码量化工程实战进阶 #12】接口字段类型契约与校验:别让脏数据炸了你的策略
入门系列把字段当"拿来就用"。本篇讲一个生产级铁律:字段有契约——名字、类型、语义、取值范围都必须被显式约束,否则一只"脏数据"就能让回测崩坏或净值腰斩。我们基于真实返回的字段集建一个校验器,落库前先过一道关。
本文你将得到什么
- 字段契约是什么:名字 / 类型 / 取值范围 / 语义 四件套
- 一个真实发现的陷阱:同一个
pc字段,在历史接口是"前收"、在实时接口是"涨跌幅%" - 一个可跑的 字段校验器(落库前拦截脏数据)
- 四个典型脏数据:类型错 / 取值越界 / 时间格式乱 / 接口加字段破老代码
一、痛点:字段不是"拿来就用"
真实接口返回里藏着三类雷:
- 类型漂移: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 出现负值、高低关系反转,指标全部失真。
五、原理深挖:四个典型脏数据
- 类型错:
v偶发以字符串返回。校验器用isinstance兜住,必要时int(float(v))强转。 - 取值越界:停牌日
v=0合法(但要标停牌),c=-5非法(价格非正)。区分"合法零"和"非法负"。 - 时间格式乱:
t有时2025-09-15、有时带时分。统一datetime.strptime解析,失败进死信。 - 接口加字段破老代码:服务端升级加字段,老代码用
row["新字段"]会KeyError。永远用row.get("新字段"),契约向后兼容。
六、小结
字段契约 = 把"字段名/类型/语义/取值"显式写下来,落库前用校验器过一道。它拦住的每一条脏数据,都可能是一个"回测很美、实盘爆炸"的隐患。尤其记住 pc 的双义陷阱——历史里它是前收,实时里它是涨跌幅%,混用即错。
到这里,模块二(行情数据深用)收口:消费(#07)、回放对齐(#08)、复权(#09)、分钟级(#10)、多资产(#11)、字段契约(#12)。下一篇进入模块三——把干净的数据变成信号。详见文末。
免责声明:本文所有示例数据仅用于接口演示,不构成任何投资建议;市场有风险,投资需谨慎。
系列持续更新中。 想要亲手跑通上面的代码?前往 魔码证书申请页 免费领取你的专属证书,复制即用、按次计费、稳定可用。
