这段时间我把一套基于 AI Agent 的股票分析模拟交易系统完整地跑通了,从多 Agent 分工出研报,到带风控的自主交易全流程自动化。这个项目解决的核心问题很明确:让大模型不只会聊天,而是能像一个团队那样协作,完成行情解读、研报产出、交易决策和风险控制这一整条链路。不管你是被 Agent 框架吸引的开发者,还是想给量化策略里塞进 LLM 能力的投资者,这篇拆解应该都能给你一些能直接用的思路。
先说结论:这条路能走通,但坑也远比想象中多。Agent 不是简单堆几个 Prompt 就完事,任务拆解、数据约束、风控兜底缺一不可。以下是我从设计到落地的完整实录。
1. 整体设计思路:为什么一定要用多 Agent,而不是一个大 Prompt
1.1 单一 Prompt 的瓶颈在哪里
很多人的第一反应是:直接把行情数据、公司财报、新闻资讯全部丢给一个 LLM,让它输出买卖建议就行。我最初也这么试过,效果非常不稳定。模型在超长上下文里经常“迷失”,既抓不住财报里的关键数字,又容易忽略近期技术形态的变化,最后给出的结论像是一锅乱炖,缺乏可解释性。
更致命的问题在于幻觉。让单一模型同时完成数据提取、逻辑推理、决策输出三件事,它会在你不注意的地方悄悄捏造数据,比如把“营收增速”说成“净利润增速”,甚至凭空生成一个不存在的市盈率。你说它编得离谱吗?也不算,但金融场景里任何一个不准确的数字,都可能导致错误的决策。
1.2 多 Agent 的拆解逻辑
多 Agent 架构的核心思路是:把复杂任务拆成互相独立、职责单一的子任务,每个 Agent 只干一件事,干完把结果交给下一个环节。这和现实里的投资团队一模一样,研究员、交易员、风控员各司其职,谁也不越界。
我设计的角色清单包括:
- 数据采集 Agent:负责抓取行情、财报和新闻原文,做数据清洗,不进行任何判断。
- 基本面分析 Agent:只读财报数据,计算财务指标,输出估值与成长性评价。
- 技术面分析 Agent:只看 K 线和量能数据,计算均线、MACD、RSI,输出趋势和买卖点信号。
- 舆情情绪 Agent:抓取新闻与社媒讨论,做情感分析,但必须附原始文本出处。
- 研报汇总 Agent:把前三者的结论汇总成结构化研报,并标注各模块来源。
- 策略决策 Agent:基于研报内容和当前仓位状态,生成具体的交易指令。
- 风控 Agent:对交易指令进行独立审核,拥有“否决权”,可拦截超出风控边界的指令。
这种拆法的好处是:每一个 Agent 的输入输出都是结构化、可验证的,中间任何一环出错都能定位到具体模块。而且你会发现,每个子任务的 Prompt 可以写得很长很详细,不用担心上下文爆炸,因为各自只需要关注自己的那一小段数据。
1.3 为什么强调“模拟交易”
我之所以强调模拟交易而不是直接对接实盘,原因有两个。第一,Agent 的行为具有不确定性,必须在仿真环境里跑足够多轮次,才能找到触发幻觉和异常决策的边界条件。第二,金融场景的合规要求很高,Agent 给出的任何信号都只能作为参考,模拟撮合能在不承担真实资金风险的前提下,检验整套系统的逻辑闭环是否成立。
模拟交易还有一个额外价值:你可以完整记录每一笔 Agent 决策的前置输入和触发条件。这些记录是后续调 Prompt、调参数最宝贵的数据资产。我每跑完一个模拟周期,都会把决策日志导出来复盘,很多问题就是这么暴露出来的。
2. 系统架构与核心模块拆解
2.1 整体分层设计
整个系统的架构我分成五层:数据层、Agent 层、决策层、风控层、执行层。每一层都是独立的 Python 模块,层与层之间通过消息队列通信,这样做的好处是任何一层升级或者替换,都不会影响其他层。
数据层做的是最基础的“食材”准备。行情数据我用的是日线级别,包含开高低收、成交量、成交额;财报数据则来自爬取的公开公告,直接入库存成结构化表单;新闻资讯通过 RSS 和财经站点抓取,保留了标题、正文、发布时间三个字段。这里有个容易忽略的点:数据必须统一时区,并且做复权处理。否则你会发现历史回测结果完美,实盘模拟却一塌糊涂,多半就是前复权和后复权没搞对。
Agent 层我用的是 LangChain 的 Agent 框架来管理工具调用和上下文记忆。每个 Agent 都定义了对应的工具函数,比如行情工具、财报工具、新闻检索工具,LLM 只能通过工具获取数据,不能凭空生成。这一步是抑制幻觉的关键——你必须在 Prompt 里写死“如果工具没有返回数据,就如实说没有,不得编造”。
决策层是策略决策 Agent 的工作区,它拿到研报汇总结果后,结合当前的模拟账户持仓、可用资金、历史交易记录,输出一个结构化的交易指令。指令格式类似{"action": "buy", "symbol": "SAMPLE", "quantity": 100, "reason": "..."},强制结构化是为了让后续的风控层和执行层能程序化处理。
2.2 风控层为何独立存在
风控层是我非常看重的一层。它完全独立于决策层,有自己的指标计算逻辑,不依赖任何 LLM 输出。也就是说,不管 Agent 的决策看起来多合理,只要触发了风控规则,指令就必须被拦截。
这样做是因为我踩过一次真实的坑:策略决策 Agent 在牛市行情里连续加仓,单只股票仓位一路飘到 80%。从背后来看,Agent 的逻辑是“趋势成立就加仓”,但它没有意识到账户整体风险已经接近失控。如果没有硬性的风控拦截,模拟账户差点被一次极端波动打回原点。从那以后,风控层就彻底从决策层里剥离开来,而且拥有最高优先级的否决权。
执行层是模拟交易引擎。它接收风控层放行后的指令,按照市场行情、手续费、滑点模型进行撮合。这里你可以理解为是一个简化版的交换撮合器,先按最新收盘价模拟成交,再扣除万分之几的手续费,并设置一个随流动性变化的滑点偏移。
2.3 模块间通信与调度
我用一个统一的调度器(Scheduler)来管理整个流程。每天的运行流程是:
- 交易日收盘后,调度器触发数据采集 Agent。
- 数据清洗入库完成后,并行启动基本面、技术面、舆情三个分析 Agent。
- 三个分析 Agent 都提交结果后,研报汇总 Agent 开始聚合。
- 研报保存到本地并作为策略决策 Agent 的输入。
- 策略决策 Agent 输出交易指令。
- 风控 Agent 对指令进行规则检查。
- 通过则进入模拟交易引擎执行,拒绝则丢弃并记录原因。
调度器本身不涉及任何推理,它只负责状态机的编排和超时重试。这个设计让整个系统的可观测性很好,任何一步卡住都能通过日志追踪到。
3. 多 Agent 研报系统的设计与实现
3.1 数据采集 Agent:把原始数据整理成标准范式
数据采集 Agent 是整个系统里最“无聊”但最重要的角色。它不产生任何观点,只负责把分散的数据收集起来并清洗成统一格式。
实际的代码实现里,我用的是可配置的数据源适配器。行情数据、财报数据、新闻数据各自对应一个适配器函数,所有适配器返回统一的DataFrame结构,列名固定为date, open, high, low, close, volume, amount。代码非常简单,核心逻辑就是爬取、解析、清洗三步:
# 数据采集 Agent 核心逻辑示例 import pandas as pd def fetch_quote(symbol: str, start_date: str, end_date: str) -> pd.DataFrame: raw = quote_source.get_daily(symbol, start_date, end_date) df = pd.DataFrame(raw) df.columns = ["date", "open", "high", "low", "close", "volume", "amount"] df["date"] = pd.to_datetime(df["date"]) df = df.sort_values("date").reset_index(drop=True) # 统一时区并做复权处理 df = df.assign(close_adj=adjust_factor(df["close"])) return df def fetch_financials(symbol: str) -> dict: raw = fin_source.get_report(symbol) return { "revenue": raw["revenue"], "net_profit": raw["net_profit"], "roe": raw["roe"], "gross_margin": raw["gross_margin"], "debt_ratio": raw["debt_ratio"], }这里有一个必须强调的细节:数据源时区不统一是个大坑。有的接口返回的是 UTC 时间,有的是北京时间。如果直接把两个数据源的时间戳拼接在一起,你的“当日”数据实际上可能错位了 8 个小时。我的解决方案是在采集阶段就统一转换成东八区日期,并且对重复日期的数据做去重,保留最后一个取值。
3.2 基本面分析 Agent:专注于财务指标的解读
基本面分析 Agent 只接收财报结构化数据,它的任务是对企业做体检。我会把最近几期的营收、净利润、毛利率、ROE、负债率等字段组成一张对比表,交给 LLM 做趋势分析。
为了限制幻觉,它在 Prompt 里被要求只能引用数据中出现的字段名和数值,不能使用“大概”“可能”这类模糊表述来修饰具体数字。如果数据缺失,必须明确写“该指标本期未披露”。
实际输出结果是一段结构化文本,包括:成长性评分(0-100)、盈利质量评分(0-100)、估值水平描述、风险点列举。这些打分会让研报汇总 Agent 直接引用,不需要重新解释原始财务数据。我试过让基本面 Agent 直接写研报长文,效果很拖沓,经常把旧数据拿出来反复说。后来改成先输出指标打分,再到汇总 Agent 那里统一成文字,流程清楚多了。
3.3 技术面分析 Agent:指标计算避免一眼假
技术面 Agent 的任务比较简单清晰。它接收行情序列,计算均线、MACD、RSI、布林带等常用技术指标,然后输出当前趋势状态和潜在信号。
因为技术指标的计算完全可以用 Python 完成,不需要 LLM 参与,所以我的做法是让工具函数直接算好数值交给 LLM 去解读。代码逻辑一点都不复杂,就是标准的指标公式,比如 RSI 的计算:
def compute_rsi(close: pd.Series, period: int = 14) -> pd.Series: delta = close.diff() gain = delta.clip(lower=0) loss = -delta.clip(upper=0) avg_gain = gain.rolling(window=period, min_periods=period).mean() avg_loss = loss.rolling(window=period, min_periods=period).mean() rs = avg_gain / avg_loss rsi = 100 - (100 / (1 + rs)) return rsiLLM 在这个环节的作用是综合多指标输出结论,比如 “MA5 上穿 MA20,MACD 金叉,RSI 处于 60 附近,整体偏多”。这个角色更像一个看图说话的分析师,而不是计算器。我让技术面 Agent 输出的结果是信号列表,每个信号包含指标名称、数值、阈值、判断结果,方便后续回溯验证。
3.4 舆情情绪 Agent:新闻情感分析要带出处
舆情情绪 Agent 负责抓取近期新闻和社媒讨论,做情感极性判断。这个 Agent 是最容易出幻觉的环节。模型看到一条标题为“某公司发布新品”的新闻,可能会脑补出“市场反应热烈,股吧讨论积极”的结论,而实际数据里完全没有这些信息。
为了解决这个问题,我在 Prompt 里强制要求:每条情感判断都必须附带原文标题和来源链接,并且只能从新闻文本中提取证据。情感分数采用 -1 到 1 的连续值,0.3 以上视为积极,-0.3 以下视为消极。给出中性结论时,必须说明“已有信息不足”而不是强行站队。
3.5 研报汇总 Agent:所有结论的聚合与冲突处理
研报汇总 Agent 拿到前面三个 Agent 的输出后,需要生成一篇结构化的研报。它做的事情本质上是一个“内容合并”任务,但难点在于如何处理多个 Agent 观点冲突的情况。技术面说看空,基本面却说持续增长,这时候汇总 Agent 不能简单把两个观点并列,它必须给出一个调和后的综合判断。
我的做法是让研报汇总 Agent 输出三段结构:多空因素清单、综合评分、关键风险提示。多空因素清单里每条都要注明来源(基本面 / 技术面 / 情绪面),综合评分是 0-100 的加权分,权重可以在配置里调整。这样策略决策 Agent 拿到的输入是可计算的,不是一篇长文。
研报的最终形式不是给人看的自然语言长篇,而是这样一组 JSON 数据:
{ "score_overall": 72, "score_fundamental": 68, "score_technical": 75, "score_sentiment": 55, "bull_points": ["连续三季度营收增长", "MA5上穿MA20"], "bear_points": ["负债率有所上升", "近期舆情中性"], "risk_notice": "行业估值处于历史偏高区间" }这种结构化的输出让策略决策 Agent 处理起来非常顺利,它直接读取 JSON 字段就能做后续判断。
4. 带风控的自主交易实现
4.1 策略决策 Agent 的交易指令生成
策略决策 Agent 是连接研报和交易执行的桥梁。它的输入不仅仅是研报数据,还包括模拟账户的当前状态:持仓数量、可用资金、成本价、今日盈亏、历史交易记录。只有结合这些信息,它才能决定是开仓、加仓、减仓还是平仓。
我给它设定的决策规则是:当综合评分大于 70 且当前没有持仓时,考虑开仓;当评分在 55-70 之间,保持观望或继续持仓;当评分低于 45,触发减仓或平仓思考。这些阈值都是经验值,需要在回测里调整,但作为初始模板已经能跑通整个流程。
决策 Agent 输出的必须是一个结构化指令,格式必须包含操作类型、模拟股票代码、数量、建议价格、触发原因。输出不合法时,调度器会要求它重新生成,但最多重试两次,超过次数就丢弃当天的交易机会。
# 模拟交易指令的数据结构 TRADE_COMMAND = { "action": "buy", # buy / sell / hold "symbol": "SAMPLE01", # 模拟股票代码 "quantity": 100, # 股数 "limit_price": 12.50, # 建议限价 "reason": "综合评分72,基本面与趋势面共振,开仓试仓", }4.2 仓位管理与资金分配
仓位管理是自主交易里最容易做的部分,也是决定账户最终收益的核心。我选用了固定比例仓位法配合风险预算的思路,而不是复杂的凯利公式,因为凯利公式的输入参数(胜率、赔率)在 Agent 环境中太不稳定,误差被放大后会直接导致仓位过大或过小。
固定比例仓位法的逻辑是这样的:单次开仓的金额 = 账户总权益 × 单次风险比例。我默认设的是 5%,比如账户里有 10 万模拟资金,单笔开仓金额就是 5000 元。同时我设置单票最大持仓不超过总权益的 20%,这是硬限制,任何 Agent 都不能突破。
这套逻辑还有一个好处:当账户从 10 万涨到 12 万,单笔开仓金额会跟着从 5000 涨到 6000,天然实现了盈利后增加仓位。反过来,如果账户从 10 万跌到 8 万,单笔开仓金额也会缩水到 4000,降低了继续亏损的力度。这就是所谓的“让利润奔跑,把亏损截住”的机械化实现。
4.3 风控 Agent 的关键规则设计
风控 Agent 我总结了一套金字塔形的规则体系,从最基础的单个指令校验,到组合层面的风险评估,一共有三层。
最下层是单指令校验。买入指令的模拟股票必须存在于可交易名单内,价格必须在合理区间内,数量必须是正数且不超过可用资金可购买的最大上限。这一层拦截的是 Agent 输出的明显异常指令,比如 LLM 把股票代码写错、数量写成负数这类离谱情况。
中间层是仓位限制。买入后单票仓位不得超过 20%,总仓位在正常情况下不得超过 80%。同时卖出指令的持仓数量必须大于等于实际持仓,避免出现“卖空”这种模拟交易里不允许的操作。
最高层是组合层面的动态风控。这里我会监控账户的当前回撤幅度,如果从近期最高权益回撤超过 10%,风控层会强制调降最大仓位上限至 50%,强制暂停新开仓,只允许减仓或平仓。回撤超过 18% 时,系统会直接清仓止损。这组参数我用一个独立的配置文件管理,不写在 Agent 的 Prompt 里,确保规则修订时不需要动代码。
提示:风控规则必须程序化硬编码,交给 LLM 判断风控是最危险的事情。模型会基于“当时看到的数据”作出判断,但风控需要在毫秒级、在完全确定性的规则下运行。
4.4 模拟交易引擎的参数细节
模拟交易引擎是这个系统的执行末端,它把风控层放行后的指令转化成模拟账户的持仓变化。我在这里配置了几个真实感很强的参数:手续费按成交金额的 0.03% 计算,最低 5 元;滑点按买卖价差的 0.1% 模拟,也就是买入时实际成交价格比信号价略高,卖出时略低。
模拟撮合我用的是下一个交易日的开盘价作为基准。这样做的好处是避免未来函数 —— 你基于当天收盘数据产生的信号,最快只能在第二天开盘执行,这更接近真实交易中“收盘后分析、次日操作”的节奏。很多新手跑回测时喜欢用当天价格成交,回测结果漂亮得吓人,但这种成绩在真实市场里根本不可能复制。
# 模拟交易日循环伪代码 def run_trading_day(date, account, engine): research_report = scheduler.run_research_agents(date) trade_cmd = decision_agent.generate_cmd(research_report, account) if risk_agent.approve(trade_cmd, account): engine.execute( cmd=trade_cmd, fill_price=next_open_price(trade_cmd["symbol"], date), commission_rate=0.0003, slippage_rate=0.001, )执行完成后,账户权益、持仓记录、资金流水都会更新并落库。每个交易日的运行结果会生成一张对账单,包括当日盈亏、持仓市值、可用资金、总权益等指标。这些数据是回测分析和 Agent 效果评估的基础。
5. 常见问题与排查技巧实录
5.1 Agent 幻觉数据,如何拦截
幻觉是 AI Agent 在金融场景里最棘手的问题。我遇到过几次基本面分析 Agent 在描述营收时,把上一个季度的数据当成本季度来写。因为 LLM 在推理时会把注意力分散到大段的上下文里,容易混淆数值的归属时间。
我的解决方案有三重。第一重,严格限制输入,工具返回的数据以 JSON 字典传入,Prompt 里明确标注“所有财务数据必须来自上一轮消息中的 JSON 字段”。第二重,输出约束,要求分析结果里每个数值都必须以 JSON 格式引用原始字段的key。第三重,程序校验,研报汇总 Agent 在聚合前,会有一个数据校验函数对关键字段做值域检查,比如净利润变化率超过正负 500% 时自动标记为异常。
这三重拦截并不能 100% 消灭幻觉,但把幻觉率从最初的约 8% 压到了 0.5% 以内。对于防御体系和试运行而言,这个误差是可以接受的。
5.2 上下文长度溢出与无效重试
多 Agent 在长时间运行后,对话历史会越积越厚。如果不做上下文裁剪,策略决策 Agent 会消耗大量 Token,响应变慢,甚至因为超出模型上下文限制而报错。我遇到过一次研报汇总 Agent 因为把过去 30 天的全部新闻都塞进上下文,直接导致 API 报 429 限流错误。
解决方案是在调度器里为每个 Agent 设置最大上下文轮数,比如策略决策 Agent 只保留最近 3 轮对话的摘要,研报汇总 Agent 只读取当天的输入数据。历史的完整数据不会丢失,都落在数据库里,需要时可以直接查询。这样既保证了推理质量,又控制了 Token 消耗。
5.3 回测过拟合与策略幸存者偏差
模拟交易系统跑一段时间后,我遇到了一个经典问题:参数过拟合。把阈值调到恰好和历史数据表现最好,看起来收益曲线很完美,但一旦换一段新的行情数据,效果就立刻拉胯。这是所有量化策略都会掉进去的坑,Agent 驱动的策略也不例外。
我的应对方法是把数据集切成三份:训练段用于调参,验证段用于检查稳定性,测试段用于最终评估。每次调参后都必须跑三份数据,如果训练段表现极好而验证段表现一般,直接判定为过拟合。另外我还加入了一个随机扰动测试,给模拟价格加上一定比例的噪声,观察策略是否还能保持逻辑一致性。如果加了一点噪声后系统就频繁触发风控或频繁买卖,说明策略过度依赖历史数据的偶然噪声,而不是真正的趋势。
5.4 多 Agent 之间的死锁与循环调用
多 Agent 协作时容易出现一种尴尬的僵局:一个 Agent 的输出需要另一个 Agent 的结果,而另一个 Agent 又在等待这个 Agent 的数据。我一开始没有设置调用超时,结果调度器在某一天因为两个 Agent 互相等待,卡了整整一个模拟交易日。
解决办法是调度器强制每个 Agent 调用设置超时时间,超时则返回一个“调用超时”的占位结果,并把这个 Agent 的本次输出标记为不可用。后续分析或决策阶段会明确把该模块的影响权重降为零,避免把一个坏结果传下去。同时,我用日志记录了每次调用的顺序,确保调用链是 DAG(有向无环图),一旦发现 B 调用了 A、A 又等待 B 的循环,就立即中断并检查配置。
5.5 信号延迟与执行偏差的量化
模拟交易里,信号生成时间和模拟成交时间之间必然有延迟。我的策略是 T 天收盘后分析,T+1 天开盘价成交。这个设计在回测里看起来损失了一点点利润,但实际仓位的波动性更真实,整体净值曲线的稳定性反而更高。
我还做了一张统计表,每次模拟成交后都记录信号发出时的参考价与模拟成交价之间的偏差。当偏差超过 0.5% 时,系统会自动标记这条记录,并在阶段报告里对比偏差的来源,比如是滑点模型的值设置得太激进,还是行情数据在当天出现了异常跳空。这个量化过程帮助我把滑点参数从初始的 0.3% 校准到了更适合本地数据特征的 0.1%,效果非常明显。
6. 实测效果与个人心得
6.1 模拟盘运行数据
整个系统跑了我大概三个月的模拟交易日数据,主要用我自建的样本股票池,覆盖了几只科技类、消费类和周期类的中等波动样本。运行下来最直观的感受是:稳定。多数交易日的决策都是“观望”或“小幅调仓”,真正触发主动买卖的次数并不多。这说明 Agent 并没有想象中那么频繁交易,恰恰相反,给它足够的上下文后,它会变得相当克制。
从关键指标看,模拟账户最大回撤大约在 7%,年化收益折算下来谈不上暴利,但曲线非常平滑。和同期的被动买入持有基准相比,收益略高,而最大回撤显著更低。我觉得这主要归功于风控层的硬性拦截——有几笔带有明显追高性质的买入指令都被风控直接拦下,事后看这些拦截确实是正确的,单票仓位上限和回撤熔断机制在极端行情里发挥了核心作用。
6.2 对 Agent 能力边界的新认识
这个项目让我重新理解了“Agent 能干什么,不能干什么”。Agent 可以很好地完成信息提取、打分、观点汇总这类“分析师”角色,但在需要快速反应、动态判断的场景下,它做得并不好,甚至可以说很笨。比如突发利空导致盘前低开,模型在第二天开盘时才知道消息,却因为策略逻辑滞后而错过了最佳减仓时机。
这让我意识到,Agent 最适合的是做低频、慢节奏的投研辅助,而高频交易或快速事件驱动策略,还是传统程序化方案更靠谱。两者结合才是最优解:用 Agent 做研究和辅助决策,用传统规则做风控和快速执行。
6.3 改进方向和进一步探索
目前这套系统仍然有很明显的短板。首先,研报汇总 Agent 的权重分配是静态的,不同市场环境应该有不同权重,比如震荡市里技术面可能更重要,而趋势市里基本面更可靠。我正计划引入一个“环境判断 Agent”来做动态配权。其次,系统目前只能处理日线级数据,后续想加入分钟级数据来捕捉日内关键节点,这需要更强的并发处理能力。最后,多 Agent 框架下所有日志和决策留痕已经做得比较完善,下一步想基于这些日志构建一个自动化的 Agent 行为分析面板,对所有指令和研报做归档与统计,持续追踪模型输出质量的变化趋势。
最后分享一个我特别推荐的做法:给模拟交易系统建立“决策日记”。每个交易日后,自动保存当天的研报、指令、风控记录、成交回报和账户快照。这不仅是复盘的基础,更是你判断 Agent 是否“学坏”的最直接证据。我靠着这份决策日记,定位了至少五次 Agent 在特定表述下的输出偏差,而这种偏差在短期收益曲线上是完全看不出来的。