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:
- SFT 基线:用 Qwen3-1.7B 直接做分类
- SFT on Reasoning Traces:用 Gemini Pro 2.5 生成合成推理链,两种策略(Rejection Sampling + STaR),SFT 模型学会"先推理再预测"
- 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 训练。每步训练:
- 取一个 transcript
- 模型生成多个候选(推理链 + 预测标签)
- 奖励函数打分:
- 预测标签正确 → 1.0(最终结果奖励)
- 格式正确(有推理 + 有答案)→ +0.1
- GRPO 组内比较,更新模型
但总共只用了 1,000 条数据做 RL——因为算力限制(单 H100),而全部训练数据有 90,000 条。
五、结果:诚实的失败
| 模型 | F1 Score |
|---|---|
| TF-IDF + Linear(在全部 90K 训练数据上训练) | 0.2587 |
| Human Analyst Consensus | 0.1741 |
| Qwen3-1.7B + SFT on labels + SFT on traces + RLVR | 0.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-shot | 0.0847 |
几个关键的发现:
- 每一步都在进步:Zero-shot(0.0847) → SFT(0.1152) → SFT+traces+RLVR(0.1569)。Pipeline 本身是正确的。
- RLVR 确实有帮助:加了 RLVR 的版本(0.1569)比没加的(0.1384)略有提升。
- 但没超过 TF-IDF 基线:0.1569 < 0.2587。差距不小。
- 也没超过人类分析师:0.1569 < 0.1741。但差距很小(~1.7 个百分点)。
- SFT on traces 可能有负面效果:SFT on labels + RLVR(0.1152) vs SFT on traces + RLVR(0.1384) → traces 有帮助,但 traces → labels → RLVR(0.1569) 的组合才最优。
为什么没超过 TF-IDF
团队自己分析的原因:
- 数据量严重不足:414 条 SFT 推理链 + 1,000 条 RL 训练数据,而 TF-IDF 在 90,000 条上训练。LLM pipeline 用不到 2% 的数据量就在跟 100% 数据量的 TF-IDF 比
- 模型太小:Qwen3-1.7B 的推理能力本身就有限,加上金融领域的专业术语和长文本,1.7B 容量捉襟见肘
- 推理链质量: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 股票预测) | 基金助手 | 投标 Agent | ART·E |
|---|---|---|---|---|
| 场景 | 金融预测(分类) | 基金 API Agent | 电力设备 Agent | 邮件搜索 Agent |
| RL 类型 | RLVR + GRPO | 纯 GRPO | SFT → 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 做金融预测的团队来说,都是真实而重要的参考。