ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

多Agent研报+模拟交易+风控拦截:全流程踩坑记录

多Agent研报+模拟交易+风控拦截:全流程踩坑记录

做这个项目的初衷很简单:我想知道——如果把研报、选股、交易、风控这几件事全交给AI Agent,它们到底能不能自己跑完一个完整的投资闭环?市面上聊Agent的文章很多,但大多停在一个Chatbot或者一个能查天气的工具调用演示。真正把多个Agent串起来、落地到一个有数据、有规则、有资金概念的模拟盘系统里,能讲清楚的实操经验其实很少。所以我自己搭了一个“多Agent研报 + 模拟交易 + 风控拦截”的全流程系统,本文把这套架构、实现思路、调参过程、踩坑记录一次性写透。适合想自己动手折腾AI Agent应用、或者对量化交易自动化感兴趣的人参考。

1. 项目整体设计与思路拆解

1.1 为什么非要拆成多个Agent,而不是一个超级Agent

一开始我也犯过“一个超大Prompt解决所有问题”的错误:把数据读取、技术分析、基本面判断、交易执行、风控检查全部塞进一个LLM上下文里,让它一口气给我“该买还是该卖”。结果很惨,输出看似连贯,但经不起推敲——它会在同一段话里先说“建议加仓”,又分析半天后说“当前风险偏高建议观望”,最后连自己到底想做什么都自相矛盾。

问题的根源在于:单一大模型在长链路决策里的角色混乱。就像让一个人同时当分析师、交易员、风控员,他在压力下大概率会偏袒某个立场,很难持续保持客观。拆成多Agent之后,每个Agent都只负责一种角色,Prompt固定、输出格式固定、评判口径固定。分析师只输出观点和理由,交易员只负责把观点翻译成具体的订单参数,风控员则拥有“一票否决权”,其它Agent说了不算。

多Agent带来的另一个实际好处是可调试性。系统出了问题,我可以直接定位到具体环节,比如“回撤太大”是风控参数太松还是策略Agent下单太频繁。如果只用一个超级Agent,排查起来基本是黑盒,根本不知道是哪段逻辑出了问题。

1.2 整体角色分工和消息流转

我的系统一共分五层,分别是数据层、研报层、决策层、执行层、监控层,对应四个Agent角色加上一组工具服务。

数据层负责向全系统提供行情快照和历史K线。这部分为了可复现性,我没有接实时行情,而是用本地CSV加上一个模拟数据生成器,生成器会基于基期价格和随机波动生成一段连续行情。对于“模拟交易”这个目标来说,这不影响整个系统的验证,反而让实验可以随时回放,不用等待真实的开盘时间。

研报层是第一个Agent,叫“研究分析师”。它的输入是某只股票的历史走势、最近一段时间的事件摘要(同样由模拟器产生),输出是一份结构化的研报,里面包含趋势判断、关键支撑位/压力位、波动率特征、风险提示。输出格式我是强制约束成JSON的,不允许AI自由发挥,否则下游Agent无法解析。

决策层里面有两个角色:策略官和交易员。策略官拿到研报后,结合当前整体仓位和现金比例,决定“此刻该不该操作”。如果策略官给出“可以操作”的结论,交易员才会进一步生成订单参数:买入/卖出方向、数量、挂单价、委托类型。

执行层不调用真实券商API,而是用一个自建的模拟撮合器。撮合器根据当前行情、模拟市场深度、滑点模型来判定订单是否成交,以及按什么价位成交。这里有一个关键点:模拟撮合一定要加滑点,不然系统会过于“聪明”,和实际差距太大。

监控层是风控Agent,时刻盯着整个组合的持仓、现金、浮动盈亏。它不负责出主意,只负责拦截。如果风控Agent认为某个订单会把单票仓位推得太高,或者当天的累计亏损已经超过阈值,它会直接拒绝这笔订单并写一条拦截日志。

消息流转的完整路径是这样的:数据服务 → 研报Agent → 策略官Agent → 交易员Agent → 风控Agent → 模拟撮合器 → 更新持仓明细和数据源缓存。每个环节的结果都会写入审计日志,方便事后回看。

1.3 Agent框架选型的几个坑

LLM这块,我一开始直接用了一个多轮对话框架,所有Agent共用同一个模型,只是靠不同的System Prompt区分角色。这样用的好处是快,坏处是容易出现“角色越狱”——交易员Agent偶尔会模仿分析师的语气长篇大论分析市场,而不是直接给订单。后来我在调参时发现,把每个Agent的温度调低、再配合工具调用的方式,能让角色保持得更稳。

框架层面我试过成熟的多Agent编排框架,但最后换成了自己写的一个轻量编排器。原因很现实:成熟框架抽象程度太高,调试时你很难看清楚一条消息到底从哪个Agent传到了哪个Agent。而我需要的只是顺序调用加条件分支,这种流程用几十行Python代码就可以写得很直观,每次调试都能看到完整的上下文传递过程。

我也没用复杂的向量数据库。Agent之间的消息是结构化的JSON,直接存在内存字典里,需要追溯历史时,把JSON快照写到文件里就行。对于这种中小型模拟系统,引入超出必要范围的存储组件反而增加复杂度。

2. 多 Agent 研报模块:从数据到观点

2.1 数据层设计:行情数据怎么生成才可信

模拟交易最容易被忽略的就是数据本身。如果你生成的数据毫无规律,Agent再聪明也不可能分析出有价值的东西。我采用的是“随机游走 + 趋势项 + 波动率状态切换”的生成方式。

先给每个模拟标的指定一个基准价和一个日收益率均值,例如年化10%折算为日收益约0.04%。然后每天的涨跌幅,由三个部分叠加:正态随机扰动、动量项(之前N日收益的平均值)、周期性波动(用正弦函数叠加一个中低频信号)。这样生成的K线看起来不会是一条纯白噪声,而是有涨有跌、偶尔出现连续趋势段,更接近真实市场的统计特征。

模拟事件数据也比较关键。研报Agent不能只看行情,还要结合公司新闻和事件摘要。我在模拟数据里附带了一个事件池,里面预置了不同类型的事件,比如业绩超预期、管理层变动、行业政策利好、技术突破、核心用户流失等。每个事件都有对应的“情绪分值”,事件发生后会影响后续一段时间的行情走势。这样研报Agent的结论不是凭空生成的,它有可解释的输入依据。

数据刷新频率我设成每分钟一次,每次生成一个新K线,整个模拟盘在几分钟内就能走出“几天”的效果。这也方便快速验证整个系统的迭代流程。

2.2 研报Agent的Prompt设计和输出约束

研究分析师这个Agent的Prompt,是整个系统中我打磨得最久的一块。核心原则就一条:让LLM输出可解析、可比较、可组合的结构化研报,而不是一篇让人读着爽但没法下钻的长文。

我要求的输出格式是一个JSON对象,包括几个关键字段:趋势倾向(看多/中性/看空,这个是字符串枚举)、观点理由(最多三点,每点不超过30字)、支撑位/压力位(根据近期价格区间给出的具体数值)、波动率水平(低/中/高)、风险提示(最多两条)。所有数值型字段都要求保留两位小数,枚举字段只能从我给的可选值里选。

这里有一个挺重要的小技巧:不要直接对LLM说“请返回JSON”,而是给出一个具体的JSON Schema示例,外加一个“反面示例”。我的Prompt里直接放了一段“你绝不能输出这样的内容”的坏示范,里面是一个典型的AI生成废话版本。比如某句无关痛痒的自我介绍、空泛的风险套话。这种对比约束比单纯讲规则效果好得多。

研报Agent的输入也做了结构化处理。不会把整个历史K线的每一行数据都塞进上下文,而是先用Python代码提取出关键统计量,比如20日均线、60日均线、近期高低点、区间振幅、成交模拟量等,再把这些统计量作为上下文传给LLM。这样不仅能省Token,更重要的是过滤了噪声。LLM最怕的就是一堆无序数字,你给它一堆原始K线,它的分析基本是胡扯。

2.3 多份研报如何合并成一个投资备忘录

研报Agent是对单只标的输出观点,但是大盘环境也很重要,所以我还给每轮模拟加了一份“市场综述”。市场综述由同一个研报Agent基于几个核心指数的模拟走势生成,它只说明当前市场风格和市场风险偏好,比如“当前模拟市场整体处于震荡格局,成交活跃度中等”。

投资备忘录,就是“市场综述 + 标的研报列表”的一个汇总结构。这个汇总过程我不让LLM做合并,而是用确定性脚本去处理。因为在模拟标的数量较少时,用代码合并反而快且稳定。

合并之后,策略官Agent拿到的输入是一个结构清晰的JSON对象,里面有市场状态、每个候选标的的关键指标、当前投资组合快照。这样策略官就只需要做一件事:判断“当前市场状态下,组合要不要调仓”。这个决策的输入输出都被收窄了,可控性一下子提高了不少。

3. 交易决策与执行层:模拟盘里的自主交易

3.1 决策链路:从研报到订单需要几步

策略官Agent的逻辑并不复杂,但它的Prompt里我加了一个约束条件——必须基于组合当前的真实持仓来思考,不能只看到机会而忽略仓位状态。我会把组合快照里的数据直接拼进Prompt,包括现金比例、各标持仓数量、持仓市值占比、最大持仓标的的浮动盈亏。没有这些上下文,策略官很容易犯“永远满仓”的毛病。

策略官输出的是一个“动作指令”,包含三块内容:目标操作类型(增仓/减仓/清仓/观望)、目标标的具体代码、操作优先级。注意,策略官不输出数量,因为数量测算属于交易员的职责。这是为了避免LLM在决策里做出超出合理范围的计算。

交易员Agent拿到动作指令后,会读取当前行情快照和对应标的的20日均线,计算出建议下单量。下单量的规则是:如果是增仓,目标仓位比例减去当前仓位比例,再乘以总资产,最后除以最新价格,取整到100股倍数。如果是减仓,则按持仓市值的一定比例分批卖出。

这里有一个细节:交易员计算出来的订单,必须包含限价价格。限价价格的设计是在最新成交价的基础上加一个滑点缓冲,买入时向上浮动0.5%,卖出时向下浮动0.5%。因为模拟交易里如果没有限价,直接按最新价成交等于给了系统“免费插队权”,会过分乐观地估计成交情况。

3.2 模拟撮合器如何工作

我实现的撮合器是一个简化模型。它维护着一个订单簿,订单按照价格优先、时间优先的原则排队,同时给每个订单加入一个“成交概率”。成交概率用一个简单的逻辑回归函数来估算:如果限价单价格和当前市价之间的差足够大,比如买入限价高于市价0.3%以上,就立刻成交;如果挂在市价附近,需要一定的等待轮次;如果挂的距离太远,就可能在几轮之后被撤单。

滑点模型也在这个模块里。成交价格 = 限价价格 × 实际市场冲击系数。买入时实际成交价比限价略高,卖出时略低。模拟市场深度是动态变化的,每次有订单成交后,深度随机微调,用来制造真实感。

有一轮我为了测试撮合器的逻辑,故意让交易员下了一张数量比当前市场深度总量还大的订单。撮合器会把这张订单拆成三次成交,每一次成交都会使成交价格偏移得更远。这个“大单冲击”的效果,让风控Agent有机会看到极端情况下策略的反馈,这是我设计时特意保留的。

3.3 交易规则约束的模拟处理

实际交易有一堆规则要处理。我在模拟器里也加入了几条常见约束,比如T+1限制、价格涨跌幅限制、单笔最小交易量。这些规则本质上是在限制Agent的“自由度”。如果模拟盘里没有这些限制,系统会在短期内做出很多现实中根本不可能发生的操作,比如当天买入当天卖出套利,这种情况多了,实验结论就完全失真了。

T+1约束的实现方式比较直接:持仓表里每条持仓记录增加一个可卖日期字段,当天买入的标的不可卖出,等到下一轮模拟周期才解锁。涨跌幅限制则是给当轮生成的K线加入一个上下限,最新价不能偏离前收盘价超过10%,如果Agent发出的订单价格超过了这个范围,撮合器会在撮合前自动调整到边界价。

还有一个容易被忽略的约束——最小交易量。我设成100股,也就是一手。交易员计算出来的建议数量如果不是100的倍数,必须向下取整到最接近的百位。这个逻辑必须在交易员生成订单之前用代码做掉,不能让LLM自己去做四舍五入,否则它的计算结果时准时不准。

4. 风控模块:让我敢把交易权交给 Agent 的底气

4.1 风控规则四层设计

不带风控的自主交易,大概率会把本金亏光。我在系统里设计了四层风控,每一层管的东西都不一样,层层嵌套、互相独立。

第一层是单笔订单风控。任何订单如果超过了当前总资产的8%,直接判无效。这条规则防止Agent在某一个标的上下“重注”式交易。第二层是持仓集中度风控,单只标的的持仓市值占比不能超过组合总资产的25%。第三层是组合每日亏损限额,当日浮动亏损超过初始资产的3%时,当日不再接收任何买入指令,但卖出指令仍可执行。第四层是回撤熔断,组合最大回撤超过15%以上时,停止一切新建仓操作,只保留减仓和清仓动作。

每一层风控都配置了独立的Agent或者脚本逻辑。前三层我用的都是确定性脚本,因为它们的规则非常明确,不需要LLM判断。只有回撤熔断代码里,加了一个风控Agent来辅助判断:当回撤逼近阈值时,风控Agent会被要求写一段“市场状态摘要”,帮助系统判断是否应该解除熔断。这个摘要需要附带从市场综述里提取的当前波动率数据作为证据,防止Agent拍脑袋。

4.2 风控Agent的否决权机制

风控Agent和策略官Agent之间是一种“建议—否决”的关系。策略官Agent给出的每条操作指令,无论是买还是卖,都要先经过风控模块校验,校验通过后才生成订单。如果校验不通过,绝不允许直接发到撮合器。

风控Agent的Prompt角色设定是“保守派”。它的系统提示里明确写了:当决策存在不确定性时,默认选择拒绝交易;宁可错过机会,不能承担不必要的回撤。它收到前置校验结果后,会输出一个字段:pass(通过)、reject(拒绝)、reduce(降低数量)。

有个我看完还挺意外的现象:风控Agent并不需要每次都调用推理,有些明显超过阈值大的订单,比如单票仓位直接怼到40%,风控Agent完全可以瞬间判定拒绝。所以我在实现里加了一个前置快筛层,超限严重的订单根本不会进LLM。只有当指标比较接近阈值,比如28%这种“暧昧区间”,才需要风控Agent来给意见。这样既节省了Token,又保证了安全边界。

4.3 一次风控拦截的真实案例

模拟运行到某个周期时,策略官Agent连续三轮看好某只新能源标的,交易员准备第三次加仓。前面几轮加仓之后,该标的的市值占比已经达到21%。下一轮订单如果成交,占比会直接突破25%的阈值。

前置快筛层发现这笔提醒订单的预计占比是24.7%,没有超阈值,但很接近,所以就把这笔订单交给了风控Agent。风控Agent读到了我拼进去的持仓明细和当日市场波动数据,在输出里写了这样一段判断逻辑:“该标的持仓正在形成集中度过高风险,虽然单笔订单未超限,但叠加已有持仓后风险敞口过大,建议不执行加仓,控制单一标的风险暴露。”最终这笔订单被成功拦截了。

拦截发生之后,审计日志里清清楚楚地记录着哪条消息被哪层风控拦截了。拿着这样的日志做复盘,比任何口头解释都有说服力。这也是我坚持在系统里做审计日志的原因。

5. 实际运行效果与踩坑记录

5.1 跑了一个月模拟盘发现的问题

整个系统连续跑了一个月之后,我发现了一些有意思的现象,如果不长期跑根本暴露不出来。

第一个问题是研报Agent有时会“过拟合”。当模拟数据里趋势特别明显的时候,研报Agent连续几天看多某只标的,当趋势进入尾声甚至已经反转时,它还在重复之前的看多理由。这是LLM的通病,它倾向于延续上下文里最近出现的结论,缺乏自我否定的能力。后来我在研报Agent的输入里加了“最近三日行情对比”,让它必须面对当前K线,同时给它加了一条指令:“如果发现市场观点与模拟行情走势背离,必须重新审视前期假设。”

第二个问题是交易员Agent偶尔会给出“结构性荒诞”的订单。比如在某次回撤比较大时,它给出的建议居然是“清仓后再全仓买入另一只股票”。单看两只标的,每一条风控规则都没有触发,但整体组合风险极度升高。这种组合层面的风险,不是单笔风控能覆盖的,所以我后来又加了“换仓总额限制”:每次调仓涉及的总金额变化比例不能超过15%。

第三个问题是模拟撮合器在没有足够市场深度时,成交价格会被大单冲击得很离谱。有一轮系统里只有两个标的,深度都比较薄,交易员的一笔订单直接把成交价推高了4%。虽然这笔订单最终在风控层被拦住了,但它提醒我,模拟深度应该设为可配置参数,而不是固定值。

5.2 调参经验与Prompt迭代记录

调参这件事没有捷径,我总结下来就是“分模块单测、逐步放开”。

最开始我是全链路一起跑,出了问题四个Agent互相甩锅,根本分不清是策略Agent判断错误,还是风控Agent拦截错了单,还是撮合器滑点设置得太离谱。后来我改成模块级单测,先单独测研报Agent的JSON输出格式,再单独测撮合器的大单拆分逻辑,最后才把多Agent链路串起来。

温度的设置,我是这样调的:研报Agent和策略官Agent用0.3,交易员Agent用0.2,风控Agent用0.1。前两者稍微保留一点多样性,让策略不至于完全固化;交易员和执行层面的逻辑要尽量稳定,不要出现随机性的下单差异;风控Agent一定要极其保守,温度最低,基本上只按规则出结果。

Prompt迭代里最值得分享的是“负面条款”的作用。每个Agent的System Prompt里都有类似的负面约束,比如交易员不能输出长篇分析、风控Agent不能使用“可能、也许”这种模糊措辞、策略官Agent必须优先参考当前持仓状态而不是市场情绪。这些负面约束写出来之后,Agent的越界行为明显变少。

5.3 后续可以怎么扩展

这个系统到了目前这个状态,其实已经是一个可以持续实验的框架了。后面我计划做两件事。

第一是把模拟数据换成生成长周期历史数据,用回测模式跑过去几年,让系统在更多类型行情里接受检验。现在的模拟生成器只能生成随机的趋势片段,但真实市场的周期长度、波动聚集性要复杂得多,回测能更接近实战。

第二是想给策略官Agent增加“多策略并行”的能力。现在整个系统只有一套策略逻辑,全市场只用一个判断框架。更合理的架构应该是每个策略配一个独立的策略官Agent,比如一个趋势策略、一个低估值策略、一个超跌反转策略。它们各自跑各自的研报和信号,最终由一个资金分配Agent来综合调度仓位。这样才能回答“不同策略在不同市场阶段哪套有效”这种更有趣的问题。

如果大家想复现这个项目,我建议不要一开始就追求大而全的架构,最小可行版本可以是:一个数据生成器 + 一个研报Agent + 一个风控脚本 + 一个模拟撮合器。先把单标的的交易闭环跑起来,再逐步加标的、加角色。多Agent系统的复杂度是叠加出来的,跳跃式搭建只会让调试成本直线上升。

返回列表