Skip to content

TradingAgents 深度剖析:多智能体 LLM 金融交易框架

本文不满足于"它做了什么",而是顺着代码回答三个问题:为什么这么设计、怎么实现的、能迁移什么。分析基于 v0.3.1 源码。


1. 它是什么:一句话定位

TradingAgents 是 TauricResearch 开源的多智能体金融交易框架(arXiv 2412.20138)。它用 12 个分工不同的 LLM Agent 复刻了一家真实交易公司从研究到下单的完整流程:四个分析师先各自产出研究报告,多空研究员就这些报告展开对抗式辩论,研究经理把辩论收敛成一份投资计划,交易员把计划落成具体的买卖提案,最后由三个风险辩论手和组合经理做终裁,输出一档 Buy / Overweight / Hold / Underweight / Sell 评级。

关键点在于:它不把"决策"委托给一个强模型,而是把"决策"拆解成一条有向图上的流水线。每个节点职责单一,节点之间的边由共享状态和条件逻辑驱动。这样一来,单次决策的复杂度被摊薄到每个小 Agent 上,而整个过程的每一步都有记录、可审计、可配置、可中断恢复。

2. 为什么是多智能体:设计动机

先看它要解决的问题。直接让一个 LLM 输出"这只股票该不该买",会踩四个坑:

  1. 幻觉。模型会一本正经地编造财报数字、历史价格和利好新闻,因为它"不知道"真实世界,只能编。
  2. 单一视角。一个模型只有一种倾向,给出结论后没人反驳,盲区就是盲区。
  3. 无制衡。模型既出观点又自我论证,逻辑链再站不住脚也能自圆其说。
  4. 不可审计。没有过程只有结果,复盘无据,责任无主。

TradingAgents 的解法很朴素:向真实交易公司抄作业。真实投研从来不是一个人的事——分析师分工研究、研究员多空交锋、交易台落单、风控委员会制衡、组合经理拍板。这条流程经过几十年演化,本身就是一套成熟的"认知纠偏与风险控制机制"。项目只是把它逐节点复刻成代码:每个角色一个 Agent,用 LangGraph 把节点连成图,用全局状态在节点间传递信息。

这个选择背后是一条值得记住的原则:当单点模型能力不足以可靠完成复杂任务时,与其无限堆叠模型能力,不如用"流程 + 分工 + 结构"来降低对单点能力的依赖。 分工让每个 Agent 只做一件简单的事,辩论让盲区互相暴露,结构化输出让模糊的结果可裁决。

3. 整体架构:一条有向图上的投研流水线

实现上,这是 LangGraph 的 StateGraph。核心是 tradingagents/graph/setup.py 里的一段声明式代码:把节点 add_node 进图,用 add_edge / add_conditional_edges 把它们连起来。有三个细节值得展开。

唯一的共享状态。 整个图跑在同一份 AgentState 上(agents/utils/agent_states.py)。它继承 LangGraph 的 MessagesState,额外携带:四份分析师报告、两套辩论状态(investment_debate_staterisk_debate_state)、交易员提案、最终决策,以及记忆注入的 past_context。节点之间没有私有变量,一切靠这份状态传递。这带来两个直接好处:一是任何节点都能读到前置节点的完整产出,上下文天然完备;二是整份状态可以被序列化落盘——这正是后面"checkpoint 恢复"和"决策日志"能实现的原因。

条件边 = 业务规则。 图的边不是全部静态的。conditional_logic.py 里定义了一组路由函数,读状态字段决定下一条边:投资辩论是否继续,取决于 current_response 的前缀(Bull 还是 Bear)和累计轮次 count;风控辩论的发言顺序取决于 latest_speaker。这等于把"业务流程"写成了数据驱动逻辑,而不是散落在每个 Agent 的 prompt 里。改流程 = 改路由条件,而不是改提示词。

分析师可插拔。 分析师节点不是硬编码的,而是由 analyst_execution.py 的执行计划决定跑哪些分析师、按什么顺序,默认是 (market, social, news, fundamentals),可以任意裁剪。这是"流程结构"与"具体逻辑"解耦的示范:你想让系统只做技术面+情绪面,改一行配置即可,图的边会自动重排。

4. 一个 Agent 是怎么工作的:ReAct 循环 + 工具调用

每个分析师本质上是一个带工具的 LLM。以市场分析师为例(agents/analysts/market_analyst.py),它的节点逻辑是:把系统提示词、当前日期、工具清单拼成 prompt,用 llm.bind_tools([...]) 让模型能发起工具调用,然后在一个循环里反复执行"模型生成 → 工具执行 → 结果回填"直到模型不再请求工具。这是标准的 ReAct 模式(推理 + 行动):

有意思的是 prompt 的设计。系统给模型一张"技能清单",让模型自己选择最多 8 个互补的技术指标(MACD、RSI、布林、ATR、VWMA 等),再调用工具去取这些指标的真实数值。注意这个分工:模型负责判断"该看什么",工具负责"给出真实数字"。 判断可以错,但数字不能假。

另一个值得注意的设计是分析师与决策者的输出形态不同。分析师写自由文本长报告,因为它的读者是下游 Agent,需要信息量大、允许自由发挥;而研究经理、交易员、组合经理走结构化输出,因为它们的产出要被人读、被解析、被存档,格式必须稳定。这一层区分是整个系统"可信度"的来源之一,详见第 7 节。

5. 辩论机制:用结构化的对抗对冲偏差

这是整个系统最核心、也最值得借鉴的设计。

投资辩论(Bull ⇄ Bear)。 四份分析师报告汇齐后,先由 Bull 研究员发言立多面证据,接着 Bear 研究员必须针对上一位的论证反驳而非自说自话,然后 Bull 再回应……如此交替,直到轮次耗尽(count >= 2 * max_debate_rounds),最后由研究经理读完整段历史,输出一份带五档评级的投资计划。研究经理既是评委又是收敛点——它把多空交锋压缩成一个明确结论。

风控辩论(激进 ⇄ 保守 ⇄ 中性)。 交易员拿出提案后,三个风险辩手按固定轮次(激进→保守→中性→激进)轮番质询这笔交易,组合经理在轮次耗尽后读完全部质询历史做出最终裁决。注意轮次顺序是状态驱动的(latest_speaker 字段),不靠模型自己记得"该谁说了"——这是防止流程失控的可靠做法。

两段辩论的流转关系:

为什么辩论有用?可以从三个层面理解:

  • 认知层面:一个模型自说自话时很难推翻自己;但"让对方反驳"强迫它看到反面的证据。对抗把单点的认知盲区变成了公开的争论。
  • 结构层面:轮次、顺序、裁判都是图里的条件逻辑,可配置(调 max_debate_rounds 就能改辩论深度)。辩论不是玄学,是一条有边界的循环。
  • 审计层面:完整历史都记录在状态里,每次发言都能回溯——"这条结论是拿哪几条论据换来的"一目了然。

一个容易被忽略的细节:辩论 prompt 要求"以对话方式直接反驳对方上轮论点",而不是罗列自己的数据。这是刻意为之——只有针对性回应,辩论才产生新的信息增量,否则只是两份自说自话的报告并排摆着。

6. 数据层:工具作为"确定性锚点"

金融决策里最不能容忍的就是模型编造数字。TradingAgents 的数据层(dataflows/interface.py)是专门为"防编造"设计的。

接口与实现分离。 每个数据方法(get_stock_dataget_indicatorsget_newsget_fundamentals…)都对应一套 vendor 实现:yfinance、Alpha Vantage、FRED、Polymarket。运行时按配置路由到具体 vendor,配置里还能写降级链(如 "yfinance,alpha_vantage"),一个 vendor 失败自动换下一个。

失败要"大声"。 系统对失败的处理很讲究:拿不到数据时返回显式哨兵 NO_DATA_AVAILABLE,并在返回文本里写明原因(无效代码、退市、数据陈旧),同时 prompt 明确禁止模型估算或编造。也就是说,模型宁可说"我不知道",也不许"圆一个答案"——把幻觉堵死在源头。

确定性快照做"真相源"。 市场分析师被要求在写报告前调用 get_verified_market_snapshot,取一份确定性的 OHLCV 快照作为精确价格数字的唯一权威;如果其他工具的输出和快照冲突,必须标记不一致,而不是自创一个调和值。这个"锚定真相源"的思路,任何依赖 LLM 生成精确数字的系统都值得照抄。

7. 结构化输出:从自由文本到可信决策

分析师写长文报告(人读),但三个决策节点用 with_structured_output(Pydantic schema) 输出(机器读):研究经理产出 ResearchPlan(评级/理由/行动),交易员产出 TraderProposal(动作/入场价/止损/仓位),组合经理产出 PortfolioDecision(评级/概要/论点/目标价/期限)。这套设计有几个巧思。

Schema 即指令。 每个字段的 description 就是给模型的输出约束(例如"评级必须是五档之一,Hold 仅当双方证据确实均衡时使用")。prompt 正文于是只需要给上下文,输出规格全在 schema 里。字段描述和提示词互为补充,而不是重复。

一次调用完成"决策 + 结构化"。 模型的一次主调用就返回 Pydantic 实例,不需要额外的解析或抽取环节;再用 render 函数渲染回统一的 Markdown 形状,让下游(记忆日志、报告、CLI)保持原格式不变。这避免了"先写散文、再硬抽字段"这种既慢又脆弱的常见做法。

降级不阻塞。 当弱模型或供应商不支持结构化输出时,自动回退到自由文本(structured.py),管线永不卡死——结构化是增强,不是单点依赖。

信号提取零成本。 最终评级用确定性正则从 Markdown 里解析(rating.py),而不是再调一次 LLM。"5 档评级"这套统一裁决语言被研究经理和组合经理共用,全链路一致。

这里体现了一条重要的分层原则:自由文本用于"思考的载体",结构化数据用于"决策的交接",两者用 render 函数桥接,谁也不必迁就谁。 这也解释了为什么系统主要产物仍是散文报告——结构化只加在真正需要机器消费的决策点上。

8. 记忆与反思:让系统"变聪明"

单次运行的 Agent 是没有记忆的,但 TradingAgents 用一套很朴素的双阶段记忆日志实现了"从历史中学习"。

Phase A(写):每次 propagate() 跑完,就把最终决策以 pending 状态追加写入一个 append-only 的 Markdown 日志(trading_memory.md),零 LLM 调用,开销几乎可以忽略。

Phase B(补):下次运行同一标的时,先把上次标记 pending 的决策"兑现"——拉取这段持有期的真实收益和相对基准(SPY 或区域指数,如 ^N225)的 Alpha,然后让一个 Reflector LLM 读着真实结果做复盘(方向对没对、论点哪里成立/失败、给下次留一条具体教训),把反思写回日志。

这些历史教训在每次运行开始时被读出来,注入到组合经理的 prompt(past_context),于是上一次的成败会真实影响下一次的决策。虽然是极简的 append-only 文件 + 确定性解析,但它闭环了:决策 → 结果 → 反思 → 影响未来决策。对大多数 Agent 应用来说,这个闭环的收益/成本比远高于贸然接一套向量数据库。

9. LLM 抽象与工程韧性

多供应商工厂。 create_llm_client() 按 provider 懒加载:OpenAI、Gemini、Claude、Bedrock、Azure、DeepSeek、Qwen、Ollama、任意 OpenAI 兼容端点。换模型只改配置,不动业务代码;provider 的思考参数(reasoning_effort / thinking_level / effort)也按厂商注入。

双模型分层。 系统同时持有两个 LLM:deep_think_llm(推理强、贵)用于裁判和终裁这类"高杠杆"节点,quick_think_llm(快、省)用于分析师和辩手这类"高调用量"节点。这是对成本与质量最朴素的显式取舍——不是所有节点都值得用最贵的模型。

工程韧性。 它把很多"生产级"细节做足了:LangGraph SqliteSaver 支持崩溃后从上一节点续跑(checkpoint/resume);LLM 重试预算可配(扛 429 限流);日志写入用 temp+replace 原子替换防损坏、同天重复运行幂等去重;Alpha Vantage 数据按交易日切分防 lookahead(未来数据泄漏是回测的隐形杀手);ticker 拼路径前做安全化防路径穿越;所有配置可被 TRADINGAGENTS_* 环境变量统一覆盖。这些单个看都不起眼,合起来就是"demo 和产品之间的距离"。

10. 一次完整运行走查

ta.propagate("NVDA", "2024-05-10") 会发生什么(main.pygraph/trading_graph.py):

  1. 解析记忆:把 NVDA 之前 pending 的决策兑现——拉真实收益、算 Alpha、生成反思、回填日志。
  2. 初始化状态:构造 AgentState,注入解析出的标的身份上下文和 past_context 历史教训。
  3. 图流式执行:从 START 进入分析师链,四份报告依次产出;进入投资辩论循环,Bull/Bear 交替若干轮,研究经理收敛出投资计划;交易员把它落成 BUY/HOLD/SELL 提案;进入风控辩论循环,三辩手质询,组合经理终裁出五档评级。
  4. 落盘:完整状态写入 JSON,决策追加进记忆日志。
  5. 提取信号:从终裁文本确定性解析出 Buy / Overweight / Hold / Underweight / Sell 之一,作为对外信号。

debug 模式下每一步的节点输出都会被打印;reflect_and_remember() 则提供了对整个持仓周期做深度复盘的入口。

11. 可复用的设计范式(总结)

剥掉金融外壳,这个项目留下了六条可迁移到任何复杂 Agent 系统的范式:

  1. 图编排状态机:把业务流程建模成"有向图 + 一份共享状态",节点可增删、流程可中断恢复。别把流程硬编码进 if-else 或单个 prompt。
  2. 角色化单一职责:每个 Agent 只做一件事。复杂任务拆成可单测的小单元,比一个"全能大 Agent"可靠得多。
  3. 对抗辩论降偏差:让立场对立的 Agent 互相反驳,第三方裁决。用结构化的"多智能体分歧"对冲单模型幻觉。
  4. 工具是确定性锚点:LLM 只推理,数字和事实全来自工具;拿不到数据就明说,绝不编造。
  5. 结构化输出分层:自由文本承载思考、Pydantic Schema 承载交接,用 render 函数统一;决策提取不花第二次 LLM 调用。
  6. 记忆形成闭环:append-only 日志 + 事后反思 + 下次注入,用极低成本让系统"变聪明"。

一句话总结:TradingAgents 的价值不在于"几个 LLM 开了个会",而在于它把分工、辩论、锚定、结构化、记忆这五件事都做成了有状态、可路由、可配置、可审计的结构——这是它区别于一堆玩具 demo 的地方。