一句话版本
大模型 Agent 非常擅长"讲故事":给它一段数据,它能流畅地解释"因为新闻情绪转好,所以股价上涨"。问题在于,金融数据里到处是共同原因造成的相关性,一个听起来合理的因果故事,可能只是两个变量同时被第三个因素驱动。 这类"金融幻觉"比编造事实更隐蔽:数据是真的,相关也是真的,只有因果解释是错的。本文给 Agent 配备三件因果工具:画出因果图、控制混杂变量、做安慰剂与负对照检验,并说明它们各自能排除什么、不能排除什么。
免责
本文仅为技术科普,不构成任何投资建议。文中代码使用模拟数据。
第一层:通俗理解
1.1 一个很有说服力的错误
Agent 分析了三年数据,得出结论:
"新闻情绪指数与次日收益显著正相关(t = 13)。情绪改善会推动投资者买入,从而推高股价。建议构建情绪因子。"
听起来无懈可击。但实际情况可能是:市场整体的风险偏好同时推高了新闻情绪(记者更乐观)和股价,情绪本身对股价毫无作用。一旦风险偏好的驱动方式变了,这个"因子"就会失效。
1.2 两种幻觉
| 类型 | 例子 | 检测难度 |
|---|---|---|
| 事实幻觉 | 编造一个不存在的财报数字 | 较容易:核对数据源 |
| 因果幻觉 | 数据和相关都是真的,但因果解释错了 | 难:需要因果推理 |
大模型从大量财经文本中学会了"因为……所以……"的叙事方式,而财经媒体本身就充满事后归因。Agent 的因果幻觉,很大程度上是继承来的。
1.3 三层防线
第二层:三件工具
2.1 因果图(DAG)
要求 Agent 在给出结论前,先画出相关变量之间可能的因果关系:
一旦画出来,"情绪 → 收益"之外的其他路径就一目了然。Pearl 的因果推断理论给出了判断"需要控制哪些变量"的规则(后门准则)。
2.2 控制混杂变量
在回归中加入混杂变量(或它的代理变量),看原来的效应是否还在:
- 效应大幅减小甚至消失 → 原来的相关主要来自混杂;
- 效应基本不变 → 至少排除了这个混杂(但可能还有别的)。
2.3 安慰剂检验与负对照
| 方法 | 做法 | 能排除什么 |
|---|---|---|
| 安慰剂(置换)检验 | 把因子的时间顺序打乱,重复多次,得到"没有任何关系时"的分布 | 排除"纯属偶然" |
| 负对照 | 找一个不可能是原因的变量(比如在收益之后才公布的数据),看它是否也和收益"相关" | 如果它也相关,说明相关来自共同原因或数据问题 |
| 时间上的负对照 | 检验因子和过去收益的关系;因子不可能影响过去 | 排除反向因果和前视偏差 |
关键区分:置换检验只能证明"不是偶然",不能证明"是因果"。一个由混杂造成的相关,在置换检验下照样"显著"。
第三层:代码演示
3.1 模拟:收益只由风险偏好驱动,情绪毫无作用
import numpy as np, pandas as pd, statsmodels.api as sm
rng = np.random.default_rng(21)
T = 1000
# 混杂变量:市场风险偏好(不可直接观测,但可以用代理变量)
risk_on = rng.normal(size=T)
sentiment = 0.8 * risk_on + rng.normal(scale=0.6, size=T) # Agent 发现的"新闻情绪因子"
ret = 0.5 * risk_on + rng.normal(scale=1.0, size=T) # 收益真正只由风险偏好驱动
df = pd.DataFrame({"sentiment": sentiment, "ret": ret,
"risk_proxy": risk_on + rng.normal(scale=0.3, size=T)})
naive = sm.OLS(df["ret"], sm.add_constant(df[["sentiment"]])).fit()
print("朴素回归:情绪系数", round(naive.params["sentiment"], 3), " t =", round(naive.tvalues["sentiment"], 1))
ctrl = sm.OLS(df["ret"], sm.add_constant(df[["sentiment", "risk_proxy"]])).fit()
print("控制混杂后:情绪系数", round(ctrl.params["sentiment"], 3), " t =", round(ctrl.tvalues["sentiment"], 1))
null = []
for _ in range(500):
s = rng.permutation(df["sentiment"].values)
null.append(np.corrcoef(s, df["ret"])[0, 1])
obs = df["sentiment"].corr(df["ret"])
print("观测相关", round(obs, 3), " 安慰剂 95% 分位", round(np.quantile(np.abs(null), 0.95), 3))
future_only = 0.8 * risk_on + rng.normal(scale=0.6, size=T) # 和情绪同源但时间上不可能是原因
print("负对照变量与收益的相关", round(np.corrcoef(future_only, ret)[0, 1], 3))
输出:
朴素回归:情绪系数 0.45 t = 13.0
控制混杂后:情绪系数 0.099 t = 1.8
观测相关 0.381 安慰剂 95% 分位 0.06
负对照变量与收益的相关 0.384
3.2 解读
| 检验 | 结果 | 结论 |
|---|---|---|
| 朴素回归 | t = 13,非常"显著" | Agent 的原始结论就是这么来的 |
| 控制混杂 | 系数从 0.45 降到 0.10,t = 1.8,不再显著 | 绝大部分效应来自风险偏好 |
| 置换检验 | 观测相关 0.38 远超安慰剂分布 | 只说明"不是偶然",不能证明因果 |
| 负对照 | 一个不可能是原因的变量,相关同样是 0.38 | 强烈提示相关来自共同原因 |
控制后系数没有完全归零(0.10),是因为代理变量 risk_proxy 带有测量误差,没能完全吸收混杂。这也是现实中的常态:控制混杂只能做到"尽量"。
3.3 把它变成 Agent 的工作规范
给研究 Agent 的系统提示词和工具中加入以下要求:
- 结论必须附因果图:列出至少三个可能的混杂变量;
- 必须运行的检验:控制主要混杂(市场、行业、市值、风险偏好代理)、置换检验、至少一个负对照;
- 结论分级:
| 等级 | 条件 | 措辞 |
|---|---|---|
| 相关 | 只通过置换检验 | "X 与 Y 相关,原因不明" |
| 稳健相关 | 控制主要混杂后仍显著,负对照不显著 | "控制已知因素后,X 仍与 Y 相关" |
| 因果证据 | 有自然实验、外生冲击或可干预的证据 | "有证据表明 X 影响 Y" |
- 禁止的措辞:在只有相关证据时使用"导致""推动""因为";
- 反方 Agent:由另一个 Agent 专门提出替代解释。
3.4 局限
- 未观测到的混杂永远无法完全排除;
- 金融市场是反身的:一个因子被广泛使用后,本身就会改变市场;
- 最强的因果证据来自干预(见《干预式训练》),而在金融市场中,真正的随机实验几乎不可能。可以利用的是"自然实验":政策变化、指数调整、交易规则变更等外生事件。
总结
| 层次 | 要点 |
|---|---|
| 问题 | 金融幻觉:数据真、相关真、因果解释错 |
| 工具 | 因果图、控制混杂、置换检验、负对照 |
| 关键区分 | 置换检验排除偶然,不排除混杂;负对照可以暴露共同原因 |
| 规范 | 结论附因果图、强制检验、结论分级、禁止因果措辞、反方 Agent |