TradingAgents
己所不欲,勿施于人。——孔子
一家交易公司的组织结构,被拆成了几个 agent
先说一件常被忽略的事
交易公司不是一个人在做决定。
一个像样的机构里,看财报的人和看图的人不在同一张桌子上;有人专门读新闻,有人专门去社交媒体上闻情绪;然后有多头和空头互相拆台,有一个交易员负责把所有人的话压缩成一个动作,最后还有一道风控,站在旁边说「这个仓不能这么大」。
这条链条之所以长,不是因为流程官僚,而是因为它用分歧来对冲判断错误。每个人的偏见不一样,凑在一起,偏见会互相抵消一部分。
TradingAgents 做的事,就是把这套组织结构原样搬进代码里:把分析师、研究员、交易员、风控和组合经理,各自变成一个由大模型驱动的角色,让它们在同一个流程里互相质询,最后给出一个动作。
项目地址:https://github.com/TauricResearch/TradingAgents
它的自我介绍只有一句:一个多 agent 的 LLM 金融交易框架。
而框架的第一段描述里,有一个词用得很准:mirror,镜像。它没有说自己比人更聪明,它说自己模仿的是真实交易公司的运作方式。
四个分析师,同时开工
分析师团队有四个人,各管一种「看市场的方式」。
基本面分析师看公司财报和经营指标,试图找出内在价值,以及那些藏在数字里的红灯。
情绪分析师把新闻标题、StockTwits 和 Reddit 上的碎嘴子聚成一份情绪读数,用来判断短期市场的心情。
新闻分析师盯全球新闻和宏观指标,判断事件会怎么传导到市场。
技术分析师用 MACD、RSI 这类指标找形态,预测价格往哪走。
这四个人有一个很关键的运行方式:同时开工,各用各的工具,等所有人的报告都到齐,研究辩论才开始。
这句话里的时序安排,比很多人想的要重要。如果分析师是一个接一个跑的,后一个人就会看见前一个人的结论,然后不自觉地被带跑——这叫锚定,是人类会议里最常见的污染源。让它们并行、独立、互不知情,等于把四份互不污染的判断放进同一个房间。
多空研究员:把分歧做成流程
分析师给的是事实和读法,研究员给的是立场。
研究团队里同时有多头和空头。他们要做的不是「汇总」,而是批判性地评估分析师给的洞察,然后进入结构化的辩论:一边尽量把潜在的收益说足,一边尽量把内在的风险挖出来。
这段设计里最漂亮的地方,是它把「有人唱反调」这件事制度化了。
现实中的投研会上,唱反调的人往往不受欢迎,因为他拖慢决策、让领导不舒服。而在这里,唱反调是一个岗位,有明确的职责,有必须完成的回合数——配置里那个 max_debate_rounds 就是给它准备的。
一个机构能不能做出好判断,很大程度上取决于它有没有给「反对」留出预算。这里的预算是可以调的,从一轮到多轮。
交易员、风控、组合经理:一条有否决权的链
辩论结束,进入执行侧。
交易员把分析师和研究员产出的报告编成一份判断,决定什么时候交易、交易多少。注意是两件事:时点和规模。前者是「现在动不动」,后者是「动多大」——这两件事分开问,比合成一个「买/卖」要诚实得多。
风控团队持续评估组合风险:市场波动、流动性,以及其他风险因子。它不只给意见,它会评估并调整交易策略,然后把评估报告交给组合经理。
组合经理做最后一道决定:通过,或者否决。通过了,订单才会被送到模拟交易所执行。
这条链条里有三个细节值得停一下。
第一,风控不是「给建议」,是「调整策略」。它有实际的修改权。
第二,最终决定权在一个单独的岗位上,而不是投票产生。多 agent 系统最容易犯的错,是让所有 agent 投票,最后得到一个谁都不负责的平均值。
第三,执行落在模拟交易所上。这个选择把整件事的性质定死了:它是一个研究装置,不是一个下单接口。
两种模型,两种脑子
框架用 LangGraph 搭起来,配置里有两个模型槽位:quick_think_llm 和 deep_think_llm。
分工是这样的:快模型服务分析师、研究员、辩论者和交易员;深模型服务研究经理和组合经理。
这个分配很有意思。干活的人和拍板的人,用的不是同一个脑子。分析师要读大量材料、做大量归纳,需要吞吐;经理要在互相矛盾的报告之间做取舍,需要推理深度。
更进一步,两个槽位可以各自挂不同的厂商:经理跑 Claude,其余跑 OpenAI,改两行配置就行。文档还给了对应的环境变量,CLI 里也能这么配。
一个模型全包所有角色,是最省事的做法,也是最容易让整条链条产生同质化偏见——同一个模型在四个位置上说四种话,本质上还是一个人在自言自语。让不同模型坐在不同位置,分歧才是真的。
如果不想走 CLI,包也能直接用。构造一个图对象,把配置递进去,调一次就能拿到决定:
1 | from tradingagents.graph.trading_graph import TradingAgentsGraph |
propagate 返回的两样东西对应两种用途:decision 是这一轮的动作,state 是整棵报告树——CLI 存下来的那份报告,就是从这个 state 里长出来的。
时间旅行:这个项目最硬的一块
如果要在这份文档里挑一节最值得读的,我会挑「Fundamentals as filed」。
回测一个策略最容易骗自己的方式,是用今天的知识去解释昨天的决定。你知道某家公司后来暴雷了,于是「当时就该看空」显得理所当然。这种事后聪明,能让任何一个策略在回测里显得英明。
TradingAgents 在这件事上做了相当彻底的工程处理。
美国公司的财报来自 SEC EDGAR,而 EDGAR 记录了每一个数字被提交的日期。所以一次设定在过去某天的运行,读到的是那天实际存在的报表版本:一个已经结束但还没提交的财年,不会被提供;一个后来被重述的数字,仍然按最初报告的样子呈现。
文档给的例子很具体。苹果 2008 年的总资产,最初申报是 396 亿美元,2010 年被重述为 362 亿。那么一次设定在这两个时点之间的运行,读到的是 396 亿。
这就是 point-in-time 的意义:它不让未来的修正渗透进过去的判断。
对其他市场的公司,报表来自 Yahoo Finance。而 Yahoo 按「报表覆盖的期间」而不是「发布的时间」来标日期,所以今天的运行能读到,但设定在过去的运行会被明确告知「这些数据被扣留了」——因为 Yahoo 说不清当时哪些数字已经公开。说不清,就不给。 这个处理比假装知道要老实得多。
内部人交易同理:按发生时间而不是申报时间定日期,所以过去日期的运行也会被告知数据被扣留。
还有一个很细的地方:SEC 要求调用方表明身份,没有联系地址的请求会被拒绝,所以框架会带一个默认的。文档建议你换成自己的,这样 SEC 能找到你,而不是找到项目。
甚至还有一条关于第四季度的说明:EDGAR 的机器可读申报从 2009 年开始,而第四季度被标为「不可得」而不是推导出来——因为申报方只把 Q4 放在年报里公布。
一个研究工具愿不愿意在「数据拿不到」的地方留白,比它声称自己多准更能说明问题。
记忆:每一个决定都要结账
大多数分析工具跑完就结束了。这一份不是。
它有一个永远开着的记忆日志:每次运行完成,决定会被追加到 ~/.tradingagents/memory/trading_memory.md。
真正有意思的是后面那一步。当下一次运行的分析师还在干活的时候,它会去结算所有持仓期已经过去的旧决定:抓取实现的收益,既给原始收益,也给相对该标的所在市场基准的超额收益,然后生成一段反思。
之后组合经理会读同一标的最近的几次决定,加上其他标的最近的教训。
这套机制把「复盘」变成了流程的一部分,而不是人的自觉。而且它结账的方式很讲究——用 alpha,不用绝对收益。涨了不算本事,跑赢它所在市场的基准才算。
文档还留了一句很实在的兜底:如果结算失败,运行照常继续,报告里会写明这件事。
回测:一次决定说明不了什么
文档里有一句话,可以说是整份文档里最清醒的一句:
一次运行只给一个决定,而一个决定无法告诉你这个系统决定得好不好。
所以有了 run_backtest:在标的和日期组成的网格上跑同一套流程,写一份自己的记忆日志,然后给那些持仓窗口已经走完的决定打分。
评分用的是相对该市场基准的实现 alpha,按评级分组。
有一个细节必须在做任何回测之前知道:决定按它日期之后的第一个收盘价入场。理由是,一次运行会读到那天收盘后发布的新闻——如果按当天收盘价成交,你就用到了当时还不可能知道的信息。
这句话是整份文档里技术含量最高的一句,也是最能看出作者被现实教育过的一句。
回测还有两个贴心之处:它从不写你自己的记忆日志;用同一个 run_id 重跑同一个网格,会跳过已经跑过的格子,所以中断的扫描可以从断点接着走。
复现性:哪些会变,哪些已经不变
这一节写得像一份事故报告。
先说会变的。语言模型采样本身就不确定,即使固定温度,厂商也不保证跨调用字节一致;而推理模型变化最大,因为它的内部推理过程本身就是采样出来的。另外数据会动:新闻、StockTwits、Reddit 的内容随时间变化,所以今天跑和上周跑,即使交易日期一样,输入也不一样。
想收紧,可以调低温度。但文档立刻补了一句实话:当前精选的模型都是推理优先的,基本不理温度;想要更稳,得在配置里点名一个非推理模型。
再说已经不变的。分析的公司身份在任何一个 agent 跑起来之前,就由标的代码确定性地解析出来了;市场分析师对具体价格和指标的说法,被钉在一份经过核验的数据快照上。
文档还补了一句:早前出现的「不同运行分析成了不同公司」和「编造价格水平」的问题,就是靠这两个机制解决的。
把曾经的 bug 写进文档,是一种很稀缺的坦白。它同时说明了两件事:这些问题真实存在过,以及它们现在有了具体的防线。
装起来
Python 3.11 或更高。用 conda 或 uv 建环境都行,然后装包:
1 | conda create -n tradingagents python=3.13 |
也可以走 Docker:把 .env.example 复制成 .env 填上 key,然后 docker compose run --rm tradingagents。结果、报告、记忆日志和缓存住在 tradingagents_data 这个卷里;想放到宿主机目录,把 TRADINGAGENTS_DATA_DIR 指过去就行。本地模型另有 Ollama 的 profile。
跑起来的入口有两个:装好后的 tradingagents 命令,或者从源码跑 python -m cli.main。它会让你选标的、分析日期、模型提供方、研究深度,而上一次运行的答案会变成默认值,直接回车就沿用。
如果要放进定时任务,就用参数把问题一个个答掉:
1 | export TRADINGAGENTS_LLM_PROVIDER=openai TRADINGAGENTS_QUICK_THINK_LLM=gpt-6-luna TRADINGAGENTS_DEEP_THINK_LLM=gpt-6-sol |
每个参数只跳过它对应的那一个问题。而没有终端时,缺哪个答案,程序会在开始之前就停下,并告诉你该设哪个参数或哪条变量——不会跑到一半卡在等输入上。
接模型的方式,比想象的多
API key 那一串很长:OpenAI、Google、Anthropic、xAI、DeepSeek、Qwen 的国际与中国端点、GLM 的国际与中国端点、MiniMax 的全球与中国端点、OpenRouter、Mistral、Kimi、Groq、NVIDIA NIM。
除了 key,还有几条别的路。
用自己的 ChatGPT 订阅。 在 CLI 的提供方菜单里选「Subscription」,或者把 llm_provider 设成 chatgpt,然后 tradingagents login chatgpt。它会打印一个地址让你在浏览器里打开,登录后回落到这台机器的 127.0.0.1;如果浏览器在另一台机器上,或者跑在 Docker 里,就把浏览器最终停留的地址粘回来。凭证存在 ~/.tradingagents/chatgpt-auth.json,logout 可以吊销。文档也提醒了两件事:用量算进你的套餐额度;套餐不接受 temperature 和 max_tokens。
AWS Bedrock。 装 .[bedrock] 这个额外依赖,配好 AWS 凭证和区域,然后用 Bedrock 的模型 ID。
本地模型。 Ollama 的默认端点指向本机的 11434 端口,可以用 OLLAMA_BASE_URL 指向远端的 ollama-serve。
任何 OpenAI 兼容的服务。 vLLM、LM Studio、llama.cpp,或者自建的中转,把 llm_provider 设成 openai_compatible,用 backend_url 指端点。本地服务不需要 key。
还有一条容易被忽略:如果设了 TYPESAFE_API_KEY,情绪分析师会先用 Jev 筛一遍 StockTwits 和 Reddit 的帖子再读——跟公司无关的帖子会被丢掉,每类来源开头会给一个按立场分组的计数:看多、看空、中性、说不清。没设 key,帖子就原样过去,不筛。
市场与代码
框架能处理 Yahoo Finance 覆盖的任何市场,用带交易所后缀的标的代码,公司身份和 alpha 基准会按市场自动解析。
美股就是 AAPL、SPY;港股 0700.HK;东京 7203.T;伦敦 AZN.L;印度 RELIANCE.NS 或 .BO;加拿大 .TO;澳洲 .AX;A 股用上交所的 .SS 和深交所的 .SZ,比如 600519.SS 就是贵州茅台;加密是 BTC-USD、ETH-USD。
一个框架愿意把 A 股的代码后缀单列出来,通常意味着它真的有人在那儿用。
组合:它默认不知道你手里有什么
这是个很小但很重要的默认值。
如果不传组合,agent 给出的建议是写给「一个会把它用在自己仓位上」的读者——它不知道你的成本、你的现金、你已经压了多少在这只票上。
传了组合,交易员、风险分析师和组合经理就会对着你真实的账面工作。Python 里传一个 PortfolioContext,CLI 里传一个 JSON 文件:tradingagents --portfolio my_book.json。
还有一个必须说清的区别:空的持仓列表意味着空仓,这和「什么都没传」不是一回事。没传组合的运行,永远不会被当作空仓处理。
这类边界条件,往往是最容易在文档里被含糊过去的地方。这里没有含糊。
它自己怎么定位
文档里有一段话,位置很靠前,措辞也不绕:
这个框架是为研究目的设计的。交易表现会因很多因素而不同,包括所选的基座模型、模型温度、交易时段、数据质量,以及其他非确定性因素。它不构成财务、投资或交易方面的建议。
这段话不是免责声明式的敷衍,因为它后面跟着整篇文档里最诚实的一句总结:把框架当成一个研究多 agent 分析的脚手架,而不是一个有着固定、可复现收益的策略。
回测结果也不保证与任何已公布数字一致。
一个做交易的框架,主动说「别把我的回测数字当业绩」,这需要的不只是诚实,还需要清楚自己在做什么。
最后
回到开头那张组织结构图。
把一家交易公司拆成 agent,技术上最难的部分其实不是让模型说话,而是让这套流程不敢骗自己。所以这份文档里最重的几节,都不是关于模型的:一节讲怎么让过去的分析只能看见过去的数据,一节讲怎么在持仓期结束后给每个决定结账并用 alpha 而不是涨幅来评分,一节讲回测必须按决定之后第一个收盘价入场。
这三节加在一起,构成了一个很朴素的立场:判断可以错,但不能作弊。
至于那些 agent——基本面、情绪、新闻、技术、多头、空头、交易员、风控、组合经理——它们像一支真正的小团队那样开会、吵架、否决、拍板。
一家公司最值钱的东西,从来不是某个人的判断,而是那套让判断互相校验的结构。这套结构,现在可以下载下来了。
