熔岩AI发布MCP日频面板 schema 版本管理:列迁移、口径标签与回测可复现
摘要
把熔岩(Lava)get_stock_heat_panel(REST /panel/daily)导出成 Parquet/CSV 只是第一步;面板跑上三个月,就会遇到列新增、阈值口径调整、weighted_heat 随机构分更新而重算等问题。如果不给面板打版本,回测结果会在不知不觉中漂移。本文给出一套面板 schema 版本规范:版本号规则、口径标签、列迁移脚本、兼容读取与漂移检测。只读监测与统计研究用途。
一、面板里哪些东西会变
/panel/daily 文档列出的指标:mentions、agencies、weighted_heat、is_first_mention_day,按自然日聚合;区间最长 92 天,单次最多 20 个标的。会导致「同一天数据不一样」的来源:
| 变化源 | 例子 | 是否改变历史值 |
|---|---|---|
| 新增列 | 接口新增统计量 | 否,旧行缺列 |
| 口径说明 | weighted_heat 用当前机构分加权(非当日 PIT) |
是,机构分更新后重拉会变 |
| 未完结自然日 | 今天的行还在累积 | 是,当日值会增长 |
| 类型 | 日期列字符串 vs date | 否,但影响 join |
| 列名 | 日期与代码列名 | 以 OpenAPI 为准,变化需迁移 |
关键结论:weighted_heat 不是 PIT。同一历史日期,今天拉和下个月拉可能不同。需要 PIT 加权热度时应改用 /hot/weighted 的 as_of_date(消息算到当天 15:00)。
二、版本号规则
采用 MAJOR.MINOR 两段:
| 变更 | 版本动作 | 例 |
|---|---|---|
| 删除列、改列名、改类型、改口径 | MAJOR +1 | weighted_heat 计算口径变化 |
| 新增可空列 | MINOR +1 | 新增统计量 |
| 重拉同口径数据 | 不变,记 pulled_at |
每周补数 |
每个面板文件附一份 sidecar 元数据:
{
"schema_version": "2.1",
"source": "熔岩 /panel/daily",
"openapi_version": "1.0.0",
"type": "stock",
"columns": {
"date": "date, 自然日(列名以 OpenAPI 为准)",
"code": "string, ts_code(列名以 OpenAPI 为准)",
"mentions": "int",
"agencies": "int",
"weighted_heat": "float, 当前机构分口径,非 PIT",
"is_first_mention_day": "bool"
},
"labels": {"weighted_heat_pit": false, "last_day_complete": false},
"pulled_at": "2026-10-31T20:00:00+08:00",
"request_ids": ["req_示例1", "req_示例2"]
}
三、口径标签(labels)
| 标签 | 取值 | 说明 |
|---|---|---|
weighted_heat_pit |
false |
面板加权热度为当前口径 |
last_day_complete |
bool | to 等于北京时间今天时为 false |
market_scope |
ashare |
面板按代码,A 股口径 |
granularity |
natural_day |
自然日,非交易日 |
threshold |
无 | 面板无 min_score 参数 |
回测框架读取面板时先检查标签,例如 last_day_complete=false 时自动丢弃最后一行。
四、列迁移脚本
# migrate_panel.py —— 只处理本地文件,不调用接口
import json, pandas as pd
MIGRATIONS = {}
def migration(frm, to):
def deco(fn):
MIGRATIONS[frm] = (to, fn); return fn
return deco
@migration("1.0", "2.0")
def m1(df, meta):
# 示例:旧版把日期存成字符串列 day,统一为 date 类型列 date
df = df.rename(columns={"day": "date"})
df["date"] = pd.to_datetime(df["date"]).dt.date
meta["columns"]["date"] = "date, 自然日"
return df, meta
@migration("2.0", "2.1")
def m2(df, meta):
# 示例:新增可空列,旧数据补 NA
if "is_first_mention_day" not in df:
df["is_first_mention_day"] = pd.NA
meta["columns"]["is_first_mention_day"] = "bool"
return df, meta
def upgrade(path, target="2.1"):
df = pd.read_parquet(path)
meta = json.load(open(path + ".meta.json"))
while meta["schema_version"] != target:
to, fn = MIGRATIONS[meta["schema_version"]]
df, meta = fn(df, meta)
meta["schema_version"] = to
return df, meta
规则:迁移只做结构变化,不改数值;口径变化(MAJOR)必须重拉,不能用公式「换算」。
五、漂移检测
每周对最近 30 个自然日重拉一次(20 只股票、30 天 = 1 次调用),与库存版本比较:
| 指标 | 计算 | 说明 |
|---|---|---|
mentions 差异行占比 |
不相等行 ÷ 总行 | 除最后一日外应接近 0 |
weighted_heat 相对差 |
abs(新−旧) ÷ 旧 的中位数 | 机构分更新带来的漂移 |
| 新增列 | 列集合差 | 触发 MINOR 升级 |
| 行数差 | 新旧行数 | 自然日缺行需排查 |
d = new.merge(old, on=["code", "date"], suffixes=("_n", "_o"))
drift = {
"mentions_diff_ratio": float((d.mentions_n != d.mentions_o).mean()),
"wh_rel_p50": float(((d.weighted_heat_n - d.weighted_heat_o).abs() / d.weighted_heat_o.clip(lower=1e-9)).median()),
}
mentions 若出现历史日差异,说明上游有补录或口径调整,记录 request_id 后按 MAJOR 处理。
六、面板与熔岩数据板块
| 熔岩数据板块 | 与面板的关系 | 版本注意 |
|---|---|---|
日频热度面板 /panel/daily |
主体 | 本文 |
高分加权热榜 /hot/weighted |
PIT 加权热度替代源 | 需要 PIT 时用它 |
首提流 /hot/first-mentions |
与 is_first_mention_day 对照 |
小时级 vs 自然日 |
全市场热榜 /hot/* |
截面,不进面板历史 | 单独存快照 |
| 概念板块热度 | type=concept&names=... |
概念名精确匹配 |
| 机构/大V评分 PIT | 加权口径来源 | 机构分更新触发漂移 |
FAQ
Q:面板为什么不用交易日? A:文档写明按自然日聚合,周末消息也会计入;需要交易日对齐要在本地映射。
Q:weighted_heat 能直接用于回测吗?
A:它不是 PIT,历史值可能随机构分更新而变化;严格回测用 /hot/weighted 的 as_of_date。
Q:列名以哪里为准?
A:mentions 等四个指标名来自文档;日期与代码列名以 OpenAPI 为准,迁移脚本统一成本地规范名。
结论
面板要像数据库表一样管理:sidecar 记录版本与口径标签,结构变化用迁移脚本,口径变化必须重拉,每周做一次漂移检测。这样三个月后的回测仍然能说清楚「用的是哪一版数据」。
数据来自熔岩消息面热度监测,仅用于只读监测与统计研究,不构成投资建议。
只读监测与字段说明见:https://lava.ai-dolphin.com/subscribe/docs
Comments
Join the discussionNo comments yet. Be the first to comment.