Skip to main content

Stanford CS224R — 用 GRPO 训 LLM 做股票表现预测

原文:Reinforcement Learning for Predicting Future Stock Performance | 作者:Jonathan Larkin, Ram Komarraju, Tamika Bassman (Stanford)

这篇文章基于 Stanford CS224R 课程项目报告深度扩写。我以第一视角重新梳理了他们用 GRPO + 合成推理链训练 Qwen3-1.7B 做股票预测的完整方法——数据 pipeline、两阶段推理链生成(Rejection Sampling + STaR)、SFT → RLVR 渐进训练、以及在 TF-IDF 基线面前的诚实失败。


这篇文章说了什么

Stanford 的一个课程项目,目标是让 LLM 读财报电话会议文本(earnings call transcript),预测这个股票下个月会涨还是跌

核心挑战:

  • 财报 transcript 非常长(几千到上万 token)
  • 真正需要的推理链复杂(需要理解财务指标、行业趋势、管理层语气)
  • 单张 H100 的算力预算

团队设计了一条三阶段 pipeline:

  1. SFT 基线:用 Qwen3-1.7B 直接做分类
  2. SFT on Reasoning Traces:用 Gemini Pro 2.5 生成合成推理链,两种策略(Rejection Sampling + STaR),SFT 模型学会"先推理再预测"
  3. RLVR + GRPO:用可验证的奖励信号(预测对没对)做强化学习

结果比较尴尬:每个阶段都在进步,但最终没能超过一个简单的 TF-IDF + 线性模型。

但这篇报告的价值恰恰在这个"诚实失败"里——它展示了用 RL 做金融预测的真实挑战

核心结论:文本里确实有强信号(TF-IDF 证明了这一点),但 LLM + RL 的 pipeline 当前对数据量和算力的需求远超你能提供的。先用简单基线证明数据有信号,再上 RL。


有效的实验就这几招

方法做了什么效果
SWE 渐进训练Baseline → SFT on labels → SFT on traces → RLVR每步都稳步提升,pipeline 本身是靠谱的
两种推理链生成策略Rejection Sampling(生成 1600 条只保留正确的)+ STaR(先告诉答案再让它解释)STaR 生成的推理链更连贯,Rejection Sampling 更自然
GRPO + 二元正确性奖励预测对了给 1,错了给 0,加上格式奖励虽然没超过 TF-IDF,但 RL 阶段确实有提升
TF-IDF 基线最简单的 n-gram 特征 + 线性分类器这本应是所有 NLP 项目的第一步——结果它成了最强的
同业百分位排名标签不是用绝对涨幅标 Buy/Sell,而是按行业内百分位排名去掉了行业整体波动的影响,更干净

一、先搞懂这些概念

1.1 为什么股票预测是 RL 的天然场景

股票预测有一个特点让它极其适合 RLVR(Reinforcement Learning with Verifiable Rewards):

预测对没对,一个月后就知道。

你说这个股票下个月会涨(STRONG BUY),一个月后真的涨了→ reward=1。 你说会跌(STRONG SELL),一个月后真的跌了→ reward=1。 你说会涨但跌了→ reward=0。

这个标签是天然的、完美的、无可争议的奖励信号。不需要人工标注,不需要 Reward Model,不需要偏好对——市场帮你打了分。

但问题是:虽然标签无争议,但拿到标签的时间是"一个月后",不是训练时。 这个问题怎么解?等一下再聊。

1.2 什么是 Earnings Call Transcript

美国上市公司每个季度要公开财报,并召开电话会议——CEO 和 CFO 会介绍业绩、展望未来,然后接受专业分析师的提问。会议全程记录为文字,就是 earnings call transcript。

这些 transcript 里有大量的前瞻性信息

  • 管理层对未来增长的预期
  • 对新产品的态度(乐观/谨慎)
  • 对竞争威胁的回应
  • 财务指标的解读和趋势

人类分析师花大量时间看这些 transcript 来修改他们的 Buy/Sell/Hold 评级。这个项目的问题就是:LLM 能不能做同样的事?

1.3 标签是怎么构造的

股票预测里最坑的就是标签定义。如果直接说"涨了就是 Buy",那在 2021 年大牛市里几乎所有股票都是 Buy——模型不需要看文本,只需要知道"现在是大牛市"就能拿高分。

团队的做法:同业百分位排名

具体来说:看一只股票未来一个月的回报,把它跟同行业其他股票的回报放在一起排名——

  • 前 20% → STRONG BUY
  • 20%-40% → BUY
  • 40%-60% → HOLD
  • 60%-80% → SELL
  • 80%-100% → STRONG SELL

这就把"行业整体是牛是熊"的影响去掉了,你只需要判断"同一行业里,这只比其他强还是弱"。


二、数据:12 万份财报 Transcript

2.1 数据规模

  • 来源:Financial Modeling Prep
  • 覆盖:3000 只美股,20 年跨度
  • 总量:~120,000 条 transcript
  • 训练/测试划分:以 2023 年 1 月 1 日为时间截点(之前训练、之后测试),~90,000 训练 + ~20,000 测试

为什么用时间截点而不是随机划分? 因为金融数据有很强的时间序列特性——用历史数据预测未来数据,这是唯一诚实的评估方式。随机划分会导致未来信息泄漏到训练集(时间穿越),让结果虚假偏高。

2.2 TF-IDF 基线

在动手训练 LLM 之前,团队先做了一个最简单的基线:

TF-IDF 向量化 → 线性分类器 → 5 分类(Strong Buy ~ Strong Sell)

结果:F1 = 0.2587

这不是一个"好"分数(比瞎猜 0.20 好一点),但关键在对比:

  • 基座 Qwen3-1.7B zero-shot:F1 = 0.0847(比瞎猜还差)
  • 人类分析师 consensus:F1 = 0.1741(分析师也没比 TF-IDF 好)

这不是说 TF-IDF 很强——而是说 transcript 文本里确实有信号,但 LLM(至少 1.7B 这个量级)不会自动提取出来。


三、推理链生成:两种策略的对比

3.1 Rejection Sampling

思路:让 Gemini Pro 2.5 读 transcript → 预测股票表现。生成 1600 条推理链,只保留预测正确的那部分

问题:很多推理链是"先给结论再编理由"——模型其实是在做完预测后往回调。这种推理链对训练没用,因为学到的不是"怎么推理到结论",而是"怎么给结论找借口"。

3.2 STaR(Self-Taught Reasoner)

思路:先告诉 Gemini 正确答案是什么(比如"这只股票下个月表现是 STRONG BUY"),然后让它反推——"如果我知道答案是这样,我会怎么推理?"

这听着像作弊,但实际上是一种高效的冷启动策略:

  • 模型需要构建一个从 transcript 文本到结论的连贯推理
  • 推理必须是"一步步推导"而不是"结论先行"
  • 有逻辑漏洞的推理链被过滤掉

团队筛选只保留:

  • 推理链是"构建型"的(逐步推导结论,不是先声明结论再解释)
  • 推理链没有暴露"成事后诸葛亮"(given the hindsight that... 这类表达)

最终只留下了 414 条高质量推理链

3.3 为什么只有 414 条

因为生成和筛选的成本很高:

  • Gemini Pro 2.5 的推理链一次生成可能要 500-1000 token
  • 每人手动检查推理质量
  • STaR pruning 要逐条判断"是否构建型""是否暴露了 hindsight"

414 条对于 SFT 够用,但对于 RL 来说少得可怜——后面会看到这直接导致了问题。


四、三阶段训练 Pipeline

4.1 阶段一:SFT on Labels Only

用 5,000 条(transcript, label)对训练 Qwen3-1.7B,只教模型输出标签(不加推理链)。

目的:让模型先熟悉这个分类任务的格式和标签分布。

4.2 阶段二:SFT on Full Reasoning Traces

用 414 条(transcript, reasoning_trace, label)对训练。

目的:教模型"先推理、再预测"的模式。

4.3 阶段三:RLVR + GRPO

用 1,000 条数据进行 GRPO 训练。每步训练:

  1. 取一个 transcript
  2. 模型生成多个候选(推理链 + 预测标签)
  3. 奖励函数打分:
    • 预测标签正确 → 1.0(最终结果奖励)
    • 格式正确(有推理 + 有答案)→ +0.1
  4. GRPO 组内比较,更新模型

但总共只用了 1,000 条数据做 RL——因为算力限制(单 H100),而全部训练数据有 90,000 条。


五、结果:诚实的失败

模型F1 Score
TF-IDF + Linear(在全部 90K 训练数据上训练)0.2587
Human Analyst Consensus0.1741
Qwen3-1.7B + SFT on labels + SFT on traces + RLVR0.1569
Qwen3-1.7B + SFT on labels + RLVR(跳过 traces SFT)0.1152
Qwen3-1.7B + SFT on traces + RLVR(跳过 labels SFT)0.1384
Qwen3-1.7B Zero-shot0.0847

几个关键的发现:

  1. 每一步都在进步:Zero-shot(0.0847) → SFT(0.1152) → SFT+traces+RLVR(0.1569)。Pipeline 本身是正确的。
  2. RLVR 确实有帮助:加了 RLVR 的版本(0.1569)比没加的(0.1384)略有提升。
  3. 但没超过 TF-IDF 基线:0.1569 < 0.2587。差距不小。
  4. 也没超过人类分析师:0.1569 < 0.1741。但差距很小(~1.7 个百分点)。
  5. SFT on traces 可能有负面效果:SFT on labels + RLVR(0.1152) vs SFT on traces + RLVR(0.1384) → traces 有帮助,但 traces → labels → RLVR(0.1569) 的组合才最优。

为什么没超过 TF-IDF

团队自己分析的原因:

  1. 数据量严重不足:414 条 SFT 推理链 + 1,000 条 RL 训练数据,而 TF-IDF 在 90,000 条上训练。LLM pipeline 用不到 2% 的数据量就在跟 100% 数据量的 TF-IDF 比
  2. 模型太小:Qwen3-1.7B 的推理能力本身就有限,加上金融领域的专业术语和长文本,1.7B 容量捉襟见肘
  3. 推理链质量:414 条合成推理链的质量未知——是 Gemini 真实推理出来的,还是看似合理但实际有逻辑漏洞?

六、工程启示

6.1 永远先跑一个简单基线

TF-IDF 这么简单的 baseline,F1=0.2587 就超过了人类分析师(0.1741)。如果团队直接跳进去训练 LLM,可能永远不知道 transcript 文本里到底有没有信号。

教训:在花几个月训练 LLM Agent 之前,先用 TF-IDF/Logistic Regression/Boosting 跑一遍——这是 1 小时的工作,能省你几个月。

6.2 金融数据里的时间穿越陷阱

用时间截点(而不是随机 shuffle)划分训练/测试集。如果随机划分,模型可能"看到"了未来的 transcript 来预测过去的价格——结果虚假偏高,上线后全废。

6.3 同业百分位排名是干净的标签

比绝对涨幅更好的标签构造方式——去掉了行业波动、市场牛熊的影响。这个技巧适用于所有需要做对比预测的场景(不限于股票)。

6.4 RL 的提升是有上限的

RLVR 能在 SFT 基础上进一步提升,但提升幅度受限于数据量和模型容量。1000 条数据 + 1.7B 模型 + 单 H100 的组合,天花板就在那里。

但 pipeline 是正确的:如果团队有足够的算力跑 90,000 条 + 更大的模型(比如 8B 或 32B),结果可能完全不同。这也是为什么工业界的 RL 训练动不动就几十台 H100——不是炫富,是真实需要。


七、跟其他 GRPO 项目的对比

维度本文 (Stanford 股票预测)基金助手投标 AgentART·E
场景金融预测(分类)基金 API Agent电力设备 Agent邮件搜索 Agent
RL 类型RLVR + GRPO纯 GRPOSFT → DPO → RL纯 GRPO
奖励信号实际股价涨跌(延迟 1 月)规则奖励函数(即时)规则奖励函数(即时)LLM Judge + 多目标
数据量414 SFT + 1000 RL~1300~40,000~4000
结果没超过 TF-IDF 基线97% 工具准确率成本降 15 倍超越 o3
独特性唯一一篇讲"诚实失败"的30+ 版本迭代8 Agent 全链路开源模型+代码

这篇最大的价值不在于"成功案例",而在于"如何正确地失败"——先跑简单基线、时间截点划分、诚实地报告 LLM pipeline 不如传统方法。这对任何一个想用 RL 做金融预测的团队来说,都是真实而重要的参考。