☰
多Agent股票分析模拟交易系统搭建:从架构设计到风控实践
2026/10/13 16:20:56 网站建设 项目流程

最近我一直在折腾一个比较“费钱费时间”的方向:用 AI Agent 搭一套完整的股票分析模拟交易系统。起因很简单——我发现市面上的 AI 炒股助手大多是“聊天窗口 + 行情接口”的缝合怪,你问一句它答一句,既不会主动盯盘,也没有完整的决策闭环。与其天天羡慕别人的 Agent 项目能跑通,不如自己从零搭一套:从多 Agent 生成研报,到带硬性风控的模拟自主交易,把整条链路走完。

这篇文章就把完整的搭建过程、架构设计、关键代码逻辑,以及三周模拟盘运行里翻车的真实案例都写出来。如果你是正在折腾 AI Agent 应用、又想了解金融场景落地的开发者,这篇文章应该能省你不少试错时间。先说清楚:整套系统跑在模拟资金池上,所有数据和分析只用于技术验证,不构成投资建议。

1. 为什么我要用多 Agent 拆解,而不是让一个大模型全干

1.1 股票研报分析的本质是一条决策链

最开始我偷懒,试着把整份研报任务丢给一个超长上下文的单一大模型,结果惨不忍睹。事后我才想明白一个道理:股票分析根本不是“一次问答”,而是一条需要不同视角交叉验证的决策链。

基本面要看财务报表的利润、营收、负债,还要理解行业格局;技术面要看均线趋势、成交量、MACD 这些完全不同的数据形态;消息面要读公告、新闻、政策,判断情绪方向;最后还要把这些互相矛盾的信息合成一个动作建议。

这些环节的“信息类型”完全不同。基本面用的数据是季度级的报表,技术面用的是分钟级的行情,舆情面用的是一堆非结构化文本。它们的时间窗口不同、量化口径不同、甚至需要调用的工具都不同。让一个 Agent 在同一上下文里同时处理这些,等于让一个人既当会计又当时盘操盘手还兼着新闻编辑,上下文一长必然出问题。

1.2 单 Agent 的典型翻车现场

我最早那个单 Agent 方案,最常犯的错是“角色混乱”。比如我让它同时分析市盈率和 MACD 金叉,它居然能把两套系统混在一起,输出类似“该股票基本面良好,技术面出现金叉,MACD 代表长期盈利增长强劲”这种话——MACD 是什么?它是个动量指标,跟长期盈利没有半毛钱关系。

更常见的翻车是上下文污染。让它分析 A 公司的财报,再紧接着让它看 B 公司的技术图形,它经常会把两家公司的数据混着用,甚至出现“该股票连续三年营收增长,同时近期成交量放大”这种张冠李戴的结论。

这个问题的本质不是模型不够聪明,而是单一 Agent 的任务边界太宽,超出它的有效注意力范围。我试过用更强的模型、更长的 prompt、更详细的 few-shot,效果都很有限。

1.3 多 Agent 的真实优势在于工程可控

后来我换成了多 Agent 分工,四个角色各干一摊事:数据 Agent 负责把原始数据整理成干净的结构化数据;研报 Agent 负责基于结构化数据生成观点;风控 Agent 负责检查硬性约束;交易 Agent 负责最后决策和动作生成。

好处不是“更智能”,而是工程上完全可控。每个 Agent 都只面对一类数据、只回答一个问题,prompt 可以针对性优化;输出格式可以用统一的 JSON Schema 约束;一个环节出了问题,重跑这个 Agent 就行,不用整条链路全部重来。比如技术面 Agent 的模型偶尔输出不稳定,我只换这一个 Agent 的模型,不用动其他环节。

这套拆法还有一个隐性收益:调试体验好。单 Agent 方案里你不知道是 prompt 的问题还是上下文的问题,多 Agent 方案里每个环节的输入输出都可单独检查,出 bug 半小时内能定位到具体角色。

2. 整体骨架:四类 Agent 加一个调度中枢

2.1 系统组成与数据流

整套系统我取名“模拟项目 X”,结构不复杂,就是一个管道式的多 Agent 协作流:

数据 Agent 先从数据源抓取行情、财报、新闻,做完清洗和复权后落库。然后研报 Agent 基于数据库里的结构化数据并行做基本面分析和技术面分析,分别产出两份独立观点。这两份观点交给汇总 Agent 合并成一份研报,包含综合评级和风险提示。交易决策 Agent 拿到研报后,结合当前持仓和账户状态给出建议动作。风控 Agent 对动作做硬性校验,通过的订单才交给模拟执行器去成交。

我整理了一张表,把这套流程的输入输出和职责说清楚:

环节输入输出核心职责
数据 Agent原始接口数据清洗后的结构化数据复权、去重、格式统一
基本面 Agent财务数据 + 公告基本面评分和观点分析盈利、成长、估值
技术面 AgentOHLCV 行情数据技术面评分和观点分析趋势、量能、均线
汇总 Agent两份观点综合研报观点冲突仲裁、生成评级
交易 Agent研报 + 持仓 + 风控状态带理由的交易动作最终决策和下单意图
风控 Agent交易动作 + 账户状态放行/拦截硬性风控规则检查
模拟执行器通过风控的订单成交回报模拟撮合、手续费、滑点

这个流程看起来是串行的,但实际运行中,基本面 Agent 和技术面 Agent 是并行跑的,因为它们的数据源互不依赖,并行能把单次全链路分析的时间从 3 分钟压缩到 40 秒左右。

2.2 调度中枢:用事件驱动替代链式调用

一开始我写的是最简单的链式调用,A Agent 跑完传给 B Agent。跑了几次就发现问题:任何一个环节超时或者报错,整条链路就断了,而且断点恢复很麻烦。

后来我改成了事件驱动的调度方式。每个 Agent 完成工作后,把自己的产出写入中间存储并发布一个事件;下游 Agent 订阅到自己关注的事件类型后,从存储里取数据继续处理。这样每个环节都是独立的,失败可以单独重放,上游数据和下游状态完全解耦。

调度状态机的核心逻辑很简单,核心思想就是维护一个任务状态字典:

TASK_STATES = { "data_ingest": {"next": ["fundamental", "technical"], "retry": 3}, "fundamental": {"next": ["merge"], "retry": 2}, "technical": {"next": ["merge"], "retry": 2}, "merge": {"next": ["decision"], "retry": 2}, "decision": {"next": ["risk_check"], "retry": 2}, "risk_check": {"next": ["executor"], "retry": 0}, "executor": {"next": [], "retry": 0}, }

每个任务节点定义清楚下游节点和重试次数,调度器根据状态流转执行。失败的任务如果超过最大重试次数,就进入死信队列,人工介入检查。这套机制在很多现成的 Agent 框架里都有概念,但自研的好处是我能看到每一步到底发生了什么,而不是把整个链路的控制权交给一个黑盒。

2.3 技术栈选型:为什么不用重型 Agent 框架

技术栈我用的是 Python 3.11,数据存 SQLite 和 PostgreSQL(SQLite 负责本地缓存,PostgreSQL 负责需要并发读写的核心数据)。调度器是自研的,写了几百行代码,没有用现成的 Agent 编排框架。

不加重型框架的原因很简单:这类框架对单 Agent 的编排确实方便,但金融链路里每个节点都需要精细控制,包括输出校验、幂等处理、数据快照回放。框架封装层级越深,排查问题的成本越高。我自己写调度器,每个状态流转都有日志,出了问题直接查状态机就能定位。

Docker Compose 把整套系统编排起来,定时任务由 Cron 触发,每天收盘后自动跑一次全链路分析,盘中每 15 分钟跑一次轻量巡检。这个节奏是我调出来的,全量分析太频繁的话,大模型 API 成本吃不消;太稀疏的话,又容易错过行情拐点。

3. 研报 Agent 的落地细节:数据清洗与结构化输出

3.1 数据层建设:接口、缓存、清洗

研报能不能用,一半取决于数据管道干不干净。这一层我踩的坑最多,起初从财经数据接口拿到的数据直接灌给 Agent,结果各种奇葩问题都来了。

第一个坑是除权除息。某只股票 10 股派 8 元现金,除息日当天开盘价直接跳空低开,但技术指标不会自动处理这种跳空。如果用不复权的数据算均线,就会得出假突破、假大跌的结论。后来我统一对所有历史行情做了前复权处理,复权因子在数据入库的时候就算好,指标计算全部基于复权后的价格序列。

第二个坑是财报数据的发布延迟。财务数据在季报发布前是旧的,发布后突然跳变,Agent 可能拿旧数据做出错误判断。我的做法是给每项财务数据加一个“报告期”字段,在 prompt 里明确让 Agent 只使用最近一期的报告期数据,并且把财报发布日期和抓取日期都打印在研报脚注里。

第三个坑是数据源偶尔断更。某个股票停牌之后,行情接口可能返回全零数据,如果不做过滤直接喂给 Agent,它会一本正经地分析出一堆假信号。清洗规则里我加了一条:当日成交量为零、连续两个交易日价格不变的股票直接跳过,技术面 Agent 不给评分。

3.2 研报 Agent 的 Prompt 设计:角色、任务、三明治约束

Prompt 设计是这套系统的核心竞争力。我给每个 Agent 写的是“角色定义 + 一段话任务 + 严格输出格式”的结构,行业里管这个叫角色约束。

以基本面 Agent 为例,核心角色定义是这样一段话:

你是一名专注上市公司基本面研究的分析师。你只基于给定数据说话,不猜测、不脑补。 你的输入包括:最近完整报告期的资产负债表、利润表、现金流量表摘要,以及近两年的营收和利润趋势。 你的任务:评估这家公司的盈利能力变化、成长性趋势、财务健康度,并给出 0-100 的基本面评分。 严格约束: 1. 你不得讨论技术面、量价和 K 线信号; 2. 你不得使用超出现有输入数据范围的信息; 3. 输出必须是一个 JSON 对象,包含评分以及得分因子的说明。

这段 prompt 里有三层设计:第一层明确角色身份来防止串台;第二层限定输入范围来防止幻觉;第三层约束输出格式来保证下游可解析。我还加了一个三明治技巧——在最开头和最结尾都重复一遍“只基于给定数据说话”,实测下来对模型输出稳定性的提升比想象中大。

3.3 Agent 的数值能力差,计算交给代码

这是整篇文章里我最想说的一条经验:千万不要让大模型做算术。

起初我偷懒,让基本面 Agent 直接从营收和净利润数字里“算出”同比增长率,结果它三次里有两次算错,而且错得毫无规律,有时候差 0.5 个百分点,有时候差 5 个百分点。

后来我把所有数值计算全部挪到数据层完成:同比增长率、毛利率变化、ROE 的变化量,都是用代码算好后塞进输入数据里,Agent 只负责看趋势做判断。这样一来 Agent 的任务变成了对比“增长率从 18% 下降到 6%”这类文本描述,这正好是语言模型擅长的工作。现在整套系统的数值计算误差,比原来低了一个数量级。

3.4 多 Agent 研报合并:观点冲突怎么仲裁

基本面 Agent 和技术面 Agent 经常打架,一个说买入一个说卖出。最简单的做法是加权平均,但我试下来发现直接把两份评分加权求平均有个大问题:基本面 Agent 用的是季度数据,反应慢,技术面 Agent 用的是日线数据,反应快,在基本面平稳但技术面剧烈波动的时候,加权平均会给出一个“在下跌趋势里接飞刀”的错误信号。

我的解决方案是分层仲裁。先比较两份观点的方向一致度,如果一致,直接取加权分;如果不一致,就先检查技术面的趋势强度,如果趋势信号特别强(比如放量突破年线且均线多头发散),暂以技术面信号为主,但把基本面观点作为风险提示并列写进研报。也就是说,仲裁的结果不是吞掉一方观点,而是让研报同时呈现“短期信号指向”和“中长期基本面判断”两个维度,把决策复杂性留给交易 Agent 去处理。

4. 交易决策与风控模块:把 Agent 关进笼子里

4.1 交易决策 Agent 的输入输出:决策留痕是关键

交易决策 Agent 是整个链路里输出最复杂的角色。它的输入是汇总研报、当前持仓、账户可用资金、以及当天已经发生的交易记录。输出是带理由的动作建议,我给它的输出定义了一套严格 JSON Schema:

{ "action": "buy|sell|hold", "symbol": "字符串股票代号", "quantity": 0, "price_limit": 0, "confidence": 0.0, "reason": "不超过200字的判断理由" }

这个 Schema 里最有用的是 confidence 字段。当研报两个维度观点冲突时,决策 Agent 往往给出 50 分上下的低置信度判断。我在调度器里加了规则:置信度低于 55 的动作,默认降级为“只观察不入场”,避免 Agent 在状态不明确时强行交易。

决策留痕是我从头坚持到尾的工程原则。每一步 Agent 的判断都要有完整日志,包括输入数据摘要、输出结果、引用的关键因子。因为模拟盘出亏损的时候,如果没有留痕根本不知道是哪个判断错了,只能蒙着改 prompt,很容易陷入“越调越乱”的坑。

4.2 硬性风控规则在 Agent 之上:Agent 不能推翻规则

关于风控,我的实践结论是:LLM 可以参与风控分析,但不能作为唯一风控手段。Agent 的文本输出天然不稳定,同一个输入跑两次可能有不同结果,如果用大模型做最终风控,相当于把安全底线建立在一个概率系统上。

所以我设计了双层风控结构。第一层是代码实现的硬规则,在 Agent 之上强制执行,Agent 无论如何都无法覆盖;第二层才是让风控 Agent 对交易动作做合理性分析,比如检查决策理由是否和研报观点自洽。

硬规则我用一套白名单式代码实现,主要包括几条:

def check_risk(order, account): # 1. 单票仓位不超过账户总资产的 15% if order.value > account.total_assets * 0.15: return False # 2. 单行业暴露度不超过 25% if account.get_industry_exposure(order.symbol) > 0.25: return False # 3. 个股相对建仓价的回撤超过 8% 必须强制止损 position = account.get_position(order.symbol) if position and position.pnl_ratio < -0.08: order.force_close = True # 4. 账户整体回撤超过 5% 时暂停开新仓 if account.total_drawdown > 0.05 and order.action == "buy": return False # 5. 当日已买入的持仓不允许当天卖出(模拟 T+1) if order.action == "sell" and order.symbol in account.bought_today: return False return True

这套规则看起来简单,但真正的难点是怎么让它“不可绕过”。我的做法是:风控检查放在交易 Agent 和模拟执行器之间,执行器只认经过风控校验且校验通过的订单对象。调度器层面也做了防护,如果某个 Agent 因为输出格式问题导致订单字段缺失,执行器会拒绝执行而不是自动补默认值。

风控 Agent 的职责不在这里,它拿到的输入是“已经通过硬规则校验的订单”,用来做更细的分析——比如检查交易理由是否前后矛盾、是否在事件窗口期内有重大未释放风险。也就是说,硬规则负责“底线安全”,风控 Agent 负责“语义安全”,一个靠代码,一个靠模型。

4.3 模拟执行器:订单管理、手续费、滑点模型

模拟执行器负责最后一公里。它维护一个虚拟资金池,初始资金设定为 100 万模拟金额,用来跑验证。每笔交易按真实市场规则计算成本:佣金双边收万分之三点五,印花税卖出收千分之一,另外我还加了一个滑点模型,模拟盘限价单如果和市场价差距过大,就直接按市场价打折成交,防止 Agent 设定一个永远不可能成交的极端价格。

订单管理模块的职责是防止重复和错乱。比如交易 Agent 重复生成同方向订单,执行器会做去重;账户资金不够买入,执行器会拒绝而不是按比例缩量;停牌股票不允许交易,黑名单股票不允许交易,这些规则全部写在执行器层面,不依赖大模型判断。模拟盘跑起来之后,这块逻辑给我省了不少心。

5. 三周模拟盘的运行结果与几个翻车案例

5.1 整体运行数据:先看数字,再看问题

系统跑了一个完整的验证周期,数据样本不大但足够说明问题。整体区间是窄幅震荡市,模拟账户最终的区间收益约一个多点,最大回撤约三个多点,交易 20 多次,胜率在五成上下,单笔最大亏损控制在两个点以内。单看收益不算亮眼,但考虑到全程没有人为干预,这个表现至少证明了整套链路能稳定跑通。

收益贡献里有一个值得注意的现象:大部分盈利来自少数几笔交易,其余很多笔交易是赚赚亏亏打平。这和真实市场里的盈利分布很像。系统最大的问题不在选股——研报判断的质量其实还可以,问题出在“结合市场节奏执行”的环节。趋势跟踪类的策略在震荡市里天然吃亏,技术面 Agent 经常刚发出买入信号市场就熄火,刚止损就反弹。

这张表是模拟验证期的整体表现记录:

指标模拟验证结果
区间收益率略正(约 +1% 级别)
最大回撤约 3% 级别
总交易次数20 余次
胜率约 50%
单笔最大亏损控制在 2% 以内
单笔最大盈利约 4%

5.2 翻车案例一:新闻分析 Agent 把利好当利空

这套系统上线后的第一个低级错误来自舆情分析环节。某公司发布了一份净利润大幅增长的公告,结果新闻分析 Agent 把这个消息判定成了利空,理由是“净利润增长幅度低于市场预期”。但实际上这家公司当时的市场预期并没有那么高,Agent 是根据自己训练数据里的“常识”脑补出了一个预期值,而完全忽略了输入公告里“创历史新高”的关键词。

排查链路是这样的:先看决策 Agent 为什么给出卖出信号,日志显示它引用了舆情 Agent 的“利空”结论;再查舆情 Agent 的输入,原始公告文本里没有任何利空表述;问题定位在舆情分析环节对“增长”语义的极性判断上。修复方式是在舆情 Agent 的 prompt 里加了一条硬约束:除非输入文本明确包含下滑、亏损、下调指引等词汇,否则默认将业绩增长判定为正面信号,同时把“对比市场预期”的判断从 Agent 任务中移除。

这个案例给我的教训很直接:舆情 Agent 的分析一定要基于输入文本的显式表达,不能让它自己脑补市场预期。语言模型对“语境外补充”的倾向极强,不加约束就会用训练数据里的先验判断覆盖最新信息。

5.3 翻车案例二:除权除息导致的“假暴跌”

第二个坑发生在数据层。某股票实施了大额分红,除息日当天价格向下跳空,技术面 Agent 把所有均线指标重新计算了一遍,发现价格跌破了关键均线支撑,于是发出止损信号。但这是一次纯粹由分红除息造成的价格调整,公司的实际经营没有任何变化,股票仓位被白白止损了。

排查链路是我最熟悉的一段:先怀疑行情数据错了,但检查接口原始数据没问题;再怀疑是复权没做好,一查才发现我数据管道的复权处理只覆盖了历史数据回测场景,实时增量更新的数据没有同步做前复权处理,导致技术面 Agent 拿到的序列是“未复权当前价 + 复权历史价”的拼接数据。修复倒不复杂,把所有实时行情统一改为前复权口径,并把除权除息日的调整因子单独记录,在研报里标注“价格跳空是否由分红导致”。

这个坑的坑点在于“复权处理做了但没做全”,分成历史回测和实时更新两套逻辑,是最容易埋雷的工程实现方式。后来我把复权逻辑统一收口到一个函数里,所有数据进入 Agent 之前都要过一遍这个函数,再没出现过同类问题。

5.4 翻车案例三:大模型输出 JSON 格式损坏,交易 Agent 罢工

模拟盘跑到第二周,某一天交易 Agent 突然一整天空转,调度系统显示任务全部失败。排查了半天才发现是底层模型输出了一段不合法 JSON,字段之间多了一个逗号,交易 Agent 的解析器直接抛异常,并且重试机制在同一错误上反复重试,把当天剩余的重试额度全部耗尽了。

这个问题的根因在解析容错不够,而不是模型真的有多不稳定。修复方案做了三层:第一层加了格式化解析器,对常见 JSON 错误(多余逗号、缺引号、末尾逗号)做自动修复;第二层解析失败之后先做字段级校验,缺失字段用默认值补充并标记为低置信度,而不是整个任务失败重试;第三层把重试策略改成指数退避,避免同一个错误反复烧钱。改完之后,后续运行里没有再出现过一整天空转。

6. 后续还能怎么迭代:方向和一些思路

6.1 引入行业知识图谱,增强跨股票关联分析

现在的研报 Agent 是逐股分析的,它不会主动把上下游产业链的联动考虑在内。比如上游原材料价格大涨,下游制造业的成本压力就会上升,单看标的的财务报表不一定能提前看出来。后续我想加一个轻量行业知识图谱,把公司所属产业链上下游关系和主要原材料映射关系存成图结构,在基本面 Agent 做分析时作为附加上下文注入,让研报分析带上一点行业联动视角。

6.2 用强化学习优化仓位管理

现在的仓位管理是固定比例加风控阈值,比较粗糙。比如系统判断某只股票是高置信度机会,当前仓位上限是 15%,实际上可能愿意冒更多风险;低置信度机会即使不触发风控,也应该更保守。这个权衡逻辑很难用规则手写,后续打算用模拟环境的强化学习来训练一个仓位管理器,让它根据账户回撤和信号强度动态调整仓位,风控硬规则继续作为上限保护。

6.3 多策略 Agent 并行与投票机制

目前整套系统只有一个策略主线:基本面加技术面共振。单策略在特定市场环境下必然失效,后面计划加两个独立策略线,比如事件驱动策略和均值回归策略,让三条策略线各自独立跑分析,最后通过投票机制决定整体仓位态度。这样能降低对单一策略的依赖,系统在不同市场风格下的适应力会更强。

6.4 成本优化:把重复性任务切换到本地小模型

整套系统跑一个完整研报周期大约消耗十几万 token,如果一天跑几次,API 成本确实肉疼。实际跑下来发现,不同 Agent 对模型能力的要求差异巨大:数据清洗、格式规整、字段抽取这类任务用本地开源小模型完全够用;只有研报分析和交易决策这类需要强推理能力的环节才值得用闭源大模型 API。后续优化方向是做一个模型路由层,根据任务类型自动选择模型服务,把运行成本降下来。

我自己跑下来最大的感受是:AI Agent 在金融场景里的瓶颈从来不是“模型不够聪明”,而是工程细节不够扎实。数据一脏,再聪明的 Agent 也分析不出正确结果;风控一软,一个格式错误就能让系统穿仓。想复现这套模拟系统的朋友,我的建议是把数据管道和风控硬规则当作第一优先级,先把底层打扎实再优化 prompt,会比逆向迭代省力得多。后面我还会继续迭代这套模拟系统,有新坑再第一时间来同步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询