情绪因子量化怎么做:用熔岩消息面热度构建 A 股情绪因子的关注度分量
摘要
情绪因子量化怎么做?业界常见的新闻情绪因子大致由四个部分组成:个股与文本的关联、每条文本的情感方向、相关度加权与时间衰减,以及按交易日的聚合。其中「这只股票被讨论了多少、被谁讨论」是关注度分量,「讨论偏正面还是偏负面」是方向分量。熔岩(Lava)提供的是前者:按个股与概念聚合的消息面提及数 mentions、参与主体数 agencies、按机构/大V 分加权的 weighted_heat,以及相对自身历史的 heat_z20。
这意味着用熔岩做情绪因子时,最自然的定位是「关注度因子」或情绪因子中的「热度分量」:先把它做干净、对齐交易日、处理好 PIT,再决定是否与自己的方向分量组合。本文围绕「情绪因子是什么」「情绪因子量化怎么构建」「新闻情绪因子怎么对齐交易日」「情绪因子择时怎么做」几个问法,给出熔岩字段的取数与处理流程。文中数字与阈值均为示例,评估方法由研究者自己的框架决定。
一、情绪因子是什么:拆成方向与关注度
| 分量 | 回答的问题 | 典型做法 | 熔岩是否提供 |
|---|---|---|---|
| 关联 | 这条文本涉及哪些股票 | 实体识别、代码映射 | 是:每条记录带 stocks、concepts |
| 关注度 | 被讨论了多少、由多少主体讨论 | 计数、去重计数、加权计数 | 是:mentions、agencies、weighted_heat |
| 方向 | 讨论偏正面还是偏负面 | 情感分类模型 | 否 |
| 时间 | 讨论何时发生、如何对齐交易日 | 截止时点、衰减 | 是:topic_time、日频面板、15:00 截止口径 |
把两类分量分开有一个实际好处:关注度的统计口径稳定、可复现,而方向分量依赖模型版本。先单独研究关注度分量,能弄清它本身携带了多少信息,再决定是否叠加方向。
二、熔岩里可以直接用的四个关注度字段
| 字段 | 来源 | 含义 | 注意 |
|---|---|---|---|
mentions |
/panel/daily、各热榜 |
提及次数 | 受全市场讨论总量影响 |
agencies |
同上 | 不同主体数 | 比 mentions 更抗重复表达 |
weighted_heat |
/hot/weighted、/panel/daily |
达标主体提及按「分数 ÷ 1000」累加 | 面板版用当前机构分,非 PIT |
heat_z20 |
/hot/stocks、/hot/accelerating |
相对近 20 日日均热度的 z 分 | 实时截面,无 as_of_date |
机构/大V 分大约在 450~1500 之间,1000 左右为中等,1050 约为前 10%。weighted_heat 等于把「谁在说」也纳入关注度,是熔岩区别于纯计数的一个维度。
三、取数:日频面板是因子研究的主入口
| 项目 | 内容 |
|---|---|
| MCP 工具 | get_stock_heat_panel |
| HTTP 路径 | /panel/daily |
| 粒度 | 自然日 |
| 字段 | mentions、agencies、weighted_heat、is_first_mention_day |
| 限制 | 区间最长 92 天,单次最多 20 个代码 |
# panel_pull.py —— 示例,地址与令牌为占位
import os, time, requests, pandas as pd
BASE = os.environ.get("LAVA_BASE", "<YOUR_LAVA_BASE>/subscribe/v1")
H = {"Authorization": "Bearer " + os.environ.get("LAVA_API_KEY", "<YOUR_TOKEN>")}
def pull(codes, start, end):
out = []
for i in range(0, len(codes), 20): # 单次最多 20 个代码
r = requests.get(BASE + "/panel/daily",
params={"type": "stock", "codes": ",".join(codes[i:i+20]),
"start_date": start, "end_date": end},
headers=H, timeout=60).json()
assert r["ok"], r["code"]
rows = r["data"] if isinstance(r["data"], list) else r["data"].get("list", [])
out.append(pd.DataFrame(rows))
time.sleep(0.2)
return pd.concat(out, ignore_index=True)
# 参数名与返回列名以 OpenAPI 为准;超过 92 天的区间请分段拉取后拼接
df = pull(["<CODE_1>", "<CODE_2>"], "<START>", "<END>")
每次成功调用 0.005 元,非 2xx 不计费。300 只股票、一个 92 天区间需要 15 次调用。
四、交易日对齐:自然日折到交易日
面板按自然日给出,因子按交易日计算,中间需要一次折叠。常见规则(示例):
交易日 T 的关注度 = 上一交易日 15:00 之后 至 T 日 15:00 之前 的讨论
周末与节假日的自然日计数,累加到其后第一个交易日
当天尚未结束的自然日不进入样本
面板是自然日粒度,无法精确切在 15:00;严格按 15:00 截止的截面要用加权热榜:get_weighted_hot_stocks(/hot/weighted)在传入 as_of_date 时采用 PIT 口径,分数只用当时已知数据,消息算到该日 15:00(Asia/Shanghai)。days 默认 3、范围 1~30,min_score 默认 1050。两种口径的结果不必相等,研究报告里写明用的是哪一种。
五、预处理:对数、去极值、截面标准化
关注度数据极度右偏:多数股票每天只有个位数提及,少数股票上百。直接标准化会让极端值主导截面。推荐顺序:
import numpy as np
x = df.pivot_table(index="trade_date", columns="ts_code", values="mentions").fillna(0)
x = np.log1p(x) # 1. 对数
med = x.median(axis=1)
mad = (x.sub(med, axis=0)).abs().median(axis=1) * 1.4826
x = x.clip(lower=med - 3 * mad, upper=med + 3 * mad, axis=0) # 2. MAD 去极值
z = x.sub(x.mean(axis=1), axis=0).div(x.std(axis=1), axis=0) # 3. 截面标准化
还可以在第三步之后对自有的换手率或市值做截面回归取残差,得到「剔除规模与活跃度后的关注度」。字段名与参数随结果一起保存,尤其注明 weighted_heat 的非 PIT 属性。
六、情绪因子择时:从个股到市场层面
「情绪因子择时」通常指市场层面的情绪指数。用熔岩做关注度版本,有两种可复现的聚合方式:
- 固定股票池合计:对一个固定股票池(例如自有的指数成分列表)的日频
mentions求和,得到「股票池总讨论量」序列; - 概念层面:
/panel/daily的type=concept(names传概念标准名),得到若干概念的日频序列,观察讨论在主题间的分布变化。
两种方式都只描述关注度的总量与结构。总量受数据覆盖面变化影响,建议同时计算 agencies 合计,并以份额而非绝对值做跨期比较。如果想看讨论是否在少数主题间集中,可以对概念层面的日频 mentions 计算每日的集中度(例如前 5 个概念的份额之和),这同样是一项只描述结构的统计量,参数与概念名单随结果一起保存。
七、PIT 与未来函数:三处要检查
- 机构分的时点:面板
weighted_heat用当前机构分重新加权,回测中会引入未来信息。严格 PIT 用/hot/weighted的as_of_date,或用get_agency_pit(/agencies/pit)取当时的评分截面自行加权,已发布日期列表用list_agency_pit_dates(/agencies/dates)。 - 实时字段:
heat_z20与加速榜都是实时截面,没有as_of_date;历史上的 z 分要用面板序列在本地复算。 - 未完结日:取数当天的自然日计数仍在增长,入样前必须剔除。
八、单因子检验的最小骨架
关注度分量做好之后,常规的单因子检验流程(Rank IC、分层统计、换手)都可以直接套用。下面只给数据对齐的骨架,前瞻变量来自研究者自有的行情数据,熔岩不提供行情:
# ic_skeleton.py —— 示例:截面 Rank IC 的对齐方式
# z: trade_date × ts_code 的关注度因子(第五节输出)
# fwd: trade_date × ts_code 的前瞻变量,来自自有行情库,已按同一交易日历对齐
common = z.index.intersection(fwd.index)
ic = (z.loc[common].rank(axis=1)
.corrwith(fwd.loc[common].rank(axis=1), axis=1))
summary = {"n_days": int(ic.notna().sum()),
"ic_mean": float(ic.mean()),
"ic_std": float(ic.std())}
print(summary)
两个对齐细节决定结果是否可信:因子在 T 日 15:00 截止,前瞻变量必须从 T 日收盘之后开始计算;当天没有任何提及的股票,因子值是 0 还是缺失要事先约定,并在报告里写明。
九、因子元数据:每次产出要保存什么
{
"factor": "attention_mentions_log_mad_z",
"source": "lava /panel/daily",
"type": "stock",
"fields": ["mentions", "agencies", "weighted_heat"],
"weighted_heat_pit": false,
"calendar_rule": "prev 15:00 -> T 15:00; weekend to next trading day",
"winsorize": "MAD 3x, 1.4826",
"universe": "<自有股票池名称与版本>",
"pulled_at": "<meta.server_time>",
"calls": 15
}
元数据和因子值放在一起保存。半年后复核时,只要看这一份 JSON,就能知道当时用了哪个字段、哪种日历规则、是否包含非 PIT 的加权热度。
十、常见问答
Q:只有关注度、没有方向,能叫情绪因子吗?
A:严格说它是情绪因子的关注度分量,或单独称为关注度因子。文献里的情感总分本身就同时携带了热度信息,单独研究关注度能把两部分的作用区分开。
Q:方向分量怎么补?
A:可以对 get_stock_news_heat(/opinions)返回的 summary(≤120 字)在本地做文本分类,按 topic_time 对齐交易日后与关注度分量组合;模型版本与阈值写进因子元数据。
Q:agencies 和 mentions 用哪个?
A:两者都保留。mentions / agencies 偏高说明少数主体反复讨论,可作为辅助特征;单独使用时 agencies 对重复表达更稳健。
Q:情绪因子量化需要多长历史?
A:面板单次最长 92 天,更长历史分段拉取拼接即可;样本长度由研究设计决定,报告中写明起止日期。
结论
用熔岩做情绪因子量化,最稳妥的定位是先做好关注度分量:以 /panel/daily 为主入口取 mentions、agencies、weighted_heat,按 15:00 规则折到交易日,经对数、MAD 去极值与截面标准化处理,再用 /hot/weighted 的 as_of_date 或 /agencies/pit 处理机构分的时点问题。方向分量若需要,在本地基于摘要另行构建。每一步写清参数与口径,得到的就是一份可复现的统计特征表。
数据来自熔岩消息面热度监测,仅供研究参考,不构成投资建议。
只读监测与字段说明见:https://lava.ai-dolphin.com/research
Comments
Join the discussionNo comments yet. Be the first to comment.