多智能体AI选股器实战:Agent分工、数据防穿越与回测验证

发布时间:2026/10/22 4:09:57
多智能体AI选股器实战:Agent分工、数据防穿越与回测验证
做量化的人大概都有过这种幻想晚上十点半系统推来一条消息AI 已经翻完当天公告、给新闻打了情绪分、把财务异常标红最后还排了一份可买名单。这个月初我把这个幻想落成了一个能跑起来的模块名字就叫 Stock Agent AI 模型的选股器。它并不是写一个“输入股票代码就预测涨跌”的黑箱而是把行情、公告、新闻和财务数据拆成不同任务交给多个 Agent 角色并行处理最后合流成一份带理由的候选名单。这篇文章是这次实现过程的完整复盘如果你正打算用大模型做金融数据任务或者想给自己搭一个自动筛选股票池的工具可以顺着我的思路走一遍省掉不少试错。1. 整体设计Stock Agent 选股器为什么拆成多角色1.1 核心痛点规则选股和人工研报的局限以前我写过一套纯规则选股脚本本质就是“市盈率小于多少、ROE 大于多少、成交量不低于多少”这类硬条件叠在一起。好处是逻辑透明坏处也很明显信号维度一多规则之间互相打架而且每天要人工盯数据更新稍不注意就把濒临退市的股票选出来了。更麻烦的是很多信息是文本形态的比如公司公告里那句“本次计提减值准备对利润影响较大”规则脚本根本没法理解。后来我把思路换成了大模型。刚开始是单角色方案把一个股票的全部行情、财务和新闻摘要塞进一次 Prompt让模型直接回答“买不买”。试了几轮以后发现很不稳定上下文太长会漏看指标模型还会把一年前的旧新闻当成当期新闻甚至在理由里编造不存在的财务数据。真正让我下决心做多智能体的是一次实盘模拟中模型给某家 ST 公司打了高分理由是“破产重整可能带来机会”而规则端早就把它标记为不可交易了。所以这个选股器的设计目标不是“让 AI 拍脑袋给答案”而是把选股拆成“收集信息、过滤风险、理解语义、综合决策”四步每步都有人盯着错了也知道错在哪。多智能体不是炫技是为了让复杂任务可以被检查、被定位、被降级。1.2 多智能体流水线的分工逻辑我的第一版流水线里设定了四个角色类似一个投研小组研究员负责看财务和公告风控专员负责否决高风险标的舆情分析员负责读新闻决策组长负责汇总打分。每个角色都只处理自己那一块内容输出固定的结构化字段再由调度代码拼起来。实际跑下来这个分工最大的好处是当决策结果很离谱时可以顺着一层一层倒查。比如某只股票评分很高那我看研究员输出的摘要有没有夸大营收看舆情分析员是不是把一条负面新闻误判成了正面看风控有没有漏掉停牌状态。单角色 Prompt 做不到这一点你让一个模型同时扮演研究员、风控、决策者它的注意力很容易被最近那段文字带跑。角色多了自然会担心“多一个环节多一次出错”。所以我在设计上做了一条硬规矩凡是确定性的过滤规则绝对不交给模型。ST、停牌、上市时间不足、日均成交额过低、涨跌幅异常这些直接用代码跑Agent 只有解读信息的权限没有修改硬规则的权限。模型的作用是理解模糊信息而不是做精确计算。1.3 技术选型大模型 API 加本地数据管道这个项目没有自己训练模型也没有找专门的金融大模型私有化部署而是直接调用通用大模型 API。做这件事之前我想得很清楚选股任务里 80% 的工作量在数据清洗和流程编排模型只是在几个关键节点做文本理解和综合判断自己训练一个模型既不经济也没必要。数据层我用 Python 写了一套定时任务每天收盘后从公开数据接口拉行情、财务指标、公告和新闻标题。数据存进本地轻量数据库格式尽量做成一张宽表每一行代表“某只股票在某一天的快照”并且每条记录都带上数据日期和公告日期。这两个日期字段非常重要后面回测那节我会专门讲为什么。Agent 调度层没有用很重的框架就是普通的 Python 脚本加异步调用。我试过为这个项目单独引入一套 Agent 编排框架学习成本高不说调试时反而多了一层不确定性。对于这种数据流清晰、角色固定的场景自己用字典和列表组织结果反而更顺手。2. 数据准备选股器的“食材”和“冰箱”2.1 行情与基本面数据先把结构化数据洗好数据质量决定选股器的上限。我一开始犯过懒直接拿原始行情数据喂 Agent结果模型被大量无关字段干扰输出经常漂移。后来我设计了一个标准的日频因子表开盘价、收盘价、最高价、最低价、成交量、成交额、换手率、振幅、MA5、MA20、RSI、量比、20 日波动率。不要急着堆几十个因子。Agent 不是传统量化因子库它最擅长的是比较和解释而不是在高维数值里找关系。你给它的字段越多它越容易忽略真正重要的东西。我自己最后只保留了大约 15 个核心字段每只股票的 Input 被压缩成一张两三百字的卡片效果反而比传完整行情要好。基本面数据我单独建了一张表字段包括总市值、PE_TTM、PB、ROE、毛利率、营收同比增速、净利润同比增速、资产负债率。这里有一个特别容易踩的坑财报有滞后性一季报可能四月底才披露如果直接按最新值计算回测时会把未来信息提前用进去。所以基本面表里必须同时记录公告日期和实际数据日期取数时只能用公告日期小于等于回测日期的数据。2.2 公告新闻和舆情喂给 Agent 前的清洗规则文本数据比行情数据难处理得多。公告的 PDF 直接抓下来丢给模型既不现实也会撑爆上下文。我只保留三样东西公告标题、摘要正文、关联股票代码。摘要正文控制在 200 字以内如果超了就用规则截断到关键句而不是让模型去读全文。新闻和舆情数据同样要做预清洗。我的源是几路公开的财经资讯接口每天会抓到几百条标题和摘要。先把包含退市、停牌、监管立案、业绩预亏等关键词的新闻打上负向标签再把个股名称映射成标准股票代码最后按股票维度聚合成一份“当日舆情快照”。这里不要想着让模型做主题分类那是它的弱项规则词表和模型判断结合才靠谱。舆情数据里最烦的是重复。同一个新闻被不同源转载时间戳还不一样如果不去重Agent 会以为舆情热度很高实际上只是传播了一次。我的去重逻辑是先按标题的 SHA 值或者编辑距离聚类保留最早发布的一条再按转载量统计热度。这个步骤虽然不起眼但对后面情绪分数的稳定性影响非常大。2.3 日频更新调度增量同步与去重幂等数据管道我直接用系统定时任务跑没有上复杂的任务调度平台。每天收盘后拉一次行情凌晨再拉一次公告和新闻。流程不复杂但必须保证“幂等”同一个任务重复跑两遍不应该产生两份脏数据。具体做法是每张表都设置唯一键比如行情表用股票代码加交易日做唯一键公告表用公告 ID 做唯一键舆情表用来源加标题哈希加发布时间做唯一键。插入数据时先查重存在就跳过。这样即使某天接口超时导致脚本重跑也不会把重复数据带进快照。每次跑完数据管道我会生成一张叫“每日数据快照”的表把当天所有股票的因子值、财务摘要、舆情摘要放进去。Agent 上线时只读这张快照不直接碰底层表。这个设计让我回测和实盘共用同一套数据读取逻辑最大程度避免“实盘和回测数据不一致”的尴尬。3. Agent 模型实现把“研究—风控—决策”变成代码3.1 研究 Agent只负责出摘要不负责下结论研究 Agent 的输入是一张压缩后的财务卡片以及当天与该公司相关的公告标题。它的任务不是输出“买入”而是输出结构化的摘要包括一句话基本面评价、三个明显的财务风险点、一组参考依据编号。Prompt 里我明确写了如果数据不足必须输出“数据不足”不允许补全。为了让模型输出可控我在 Prompt 里给了严格的 JSON 模板并且强调不要输出模板之外的任何内容。初期模型偶尔还会带出 Markdown 代码块后来我在解析层做了兼容。研究 Agent 输出中的一个重要字段是 source_refs它记录了当前结论用了哪些数据行。比如“净利润增速较上季度回落”这一条必须对应到财报数据里的某一行。有了它后续的人工核查才能进行。这里有个容易被忽略的点研究 Agent 一次只处理一只股票还是批量处理多只股票。如果一次只处理一只调用次数多但上下文干净如果一次处理十只速度快但模型容易串号。我的经验是控制在五只以内超过五只模型出现张冠李戴的概率显著上升。3.2 风控 Agent硬规则一票否决风控 Agent 在所有 Agent 里最特殊它的职责不是分析而是执行一票否决。实际开发中我把它拆成了两层第一层是纯代码硬规则第二层才是模型判断。代码硬规则包括股票状态为 ST上市时间不足 60 个交易日最近 20 日日均成交金额低于某个阈值当天处于停牌状态最近一个交易日涨跌幅异常。这些规则直接用脚本过滤结果不经过任何 Agent。模型判断只处理代码过滤不了的模糊风险比如“大股东质押比例过高”“诉讼信息密集出现”这类语义信号。为什么要保留风控 Agent因为硬规则覆盖不了全部风险。有些风险藏在冗长的公告里比如公司突然变更审计机构、频繁修正前期业绩这些文字信号用代码写规则很难覆盖完整交给模型做判断更高效。但它没有最终决定权只是输出一个 fail 或 warning任何 fail 都会直接让该股票退出候选池。3.3 决策 Agent加权投票和候选池生成当研究 Agent、舆情 Agent、行情因子都输出评分后我不直接让模型做总量决策而是先在代码里算加权分。权重可以调我第一版用的是基本面评分 40%、舆情评分 30%、动量评分 30%。这个权重不是拍脑袋定的是通过小范围历史试跑对比出来的后面回测那节会讲。加权分算完把全市场所有股票排序取出前 20 只进入“决策候选池”。这时候才轮到决策 Agent 出场。它拿到的不是全市场数据而是这 20 只股票的压缩摘要、加权分数、被风控标记的 warning。它需要做的事是输出最终的观察名单和回避名单并给每只股票写一句理由。有人会问既然代码已经算完分了为什么还要决策 Agent 再走一遍因为我希望最终名单有“可读的解释”。模型可以把“低估值舆情回暖资金流入”这种多维信号翻译成人话方便复盘。同时它还可以捕捉到一种特殊情况某只股票各项评分都不错但多个 risk 提示同时出现这时决策 Agent 会把它的排名下调。3.4 降低 API 成本与耗时的几个技巧很多朋友搭这类系统时最焦虑的就是 API 费用。我实测下来如果让 Agent 跑全市场五千只股票每天光调用费就是一笔不小的数字而且完成时间会拖到几小时。优化方法只有一个核心思路先用便宜、确定性的规则把股票池缩到很小再把大模型火力集中在值得深挖的标的身上。我的流程是第一步用硬规则过滤把全市场缩小到大约三百只第二步用因子打分再看一眼财务摘要把三百只压到五十只第三步才让研究 Agent 和舆情 Agent 并行处理这五十只。这样每天总调用量能控制在两百次以内节点超时和费用压力都小很多。另一个技巧是缓存。同一只股票在同一天的数据不变就没有必要重复调用。我在 Agent 调度层做了一个简单缓存Key 是股票代码加日期加数据版本。如果当天同一只股票已经跑出结果直接从缓存取除非数据源有了新增公告。这个优化能让系统在调试期反复重跑时不会重复烧钱。4. 回测与验证证明 AI 选股器不是过拟合4.1 最小可行回测循环逐日跑一遍流程选股器不是写出来就能上实盘的必须先在历史数据上模拟。我的回测框架非常简单核心循环是这样的拿到某一天的数据快照用硬规则过滤股票池再调用 Agent 流水线给候选股票打分最后选出当天持仓。for trading_date in backtest_dates: snapshot load_snapshot(trading_date) pool rule_filter(snapshot) scores agent_pipeline(pool, snapshot) position select_top(scores, top_n10) next_ret load_next_day_return(position) portfolio_value * (1 next_ret)这里最关键的一点是执行偏移。当天收盘后产生信号绝不假设当天收盘价已经成交必须用下一个交易日的数据来结算收益。如果你的代码用当天收盘价买入、当天收盘价结算回测收益会虚高因为市场不可能给你零延迟成交。由于 Agent 调用比较贵我不可能在三年的每个交易日都跑全量 Agent。我的处理是抽样回测每周末跑一次 Agent 选股持有到下个周末。这个频率和实际操作频率不完全一样但足以验证 Agent 是否持续有效。真到要上线时再改为每日跑一次实盘流程。4.2 回测必看的四个指标回测不能只看总收益率收益率可以在运气好时骗人。我会同时记录年化收益、最大回撤、胜率和平均换手率。年化收益和最大回撤放在一起看光有高收益但回撤超过 30%这样的策略很难拿得住。最大回撤计算我用的是累计净值的回撤幅度用当前净值除以历史最高净值再取 1 减去这个比值的最小值。胜率统计的是持仓期间中“盈利交易日”的占比注意这和“选股命中率”不是一回事。平均换手率则反映了策略稳定性——如果一个选股器每天换仓一半以上它的收益很多会被手续费和滑点吃掉。还有一件必须做的事和宽基指数基准对比。我不关心绝对收益有多高只关心跑赢基准多少个点。如果 Agent 选出的股票池长期跑输宽基那说明它不是在创造 alpha只是在制造焦虑。用基准对标后我的第一版策略差点被推翻因为这个教训印象很深。4.3 消融实验拆掉舆情 Agent 会怎样回测的价值不只是看“最终版赚不赚钱”还要看“每一个模块到底有没有贡献”。我自己做了三组对比纯规则选股、不带舆情的 Agent 选股、完整版 Agent 选股。结果很有意思加上舆情 Agent 后年化收益和最大回撤并没有明显改善有些时间段甚至更差。这并不说明舆情数据没用而是说明我采集的新闻源噪声太大。模型把一部分小道消息当成了强信号导致换手率上升、策略稳定性下降。后来我调整了舆情打分逻辑只保留权威公告和重要媒体报道过滤掉普通转载才看到情绪分对收益的正贡献。消融实验给所有想做 Agent 选股的人提了个醒不要默认“模块越多越好”。每多一个信息来源就多一份噪声。做这类系统一定要像做科学实验一样敢于拆掉模块看结果而不是把所有东西糊在一起然后期待 Agent 自己开窍。5. 实测中的坑与排查记录5.1 LLM 幻觉编造财报和新闻怎么办模型幻觉是这类系统绕不开的坎。我的实测里出现过 Agent 在理由中写“净利润同比增长 128%”但原始数据里根本没有这一条还出现过它把去年同期新闻当成当期公告导致评分虚高。解决办法不是让模型“保证真实”而是从工程上限制它能接触到的信息范围。首先Prompt 里明确规定只允许引用输入数据中出现的信息如果输入里没有就输出“数据不足”。其次要求每个结论都带 source_refs指向具体的输入行编号。最后在代码层写了一个校验函数如果决策理由里的关键数值在摘要里找不到对应来源就自动把该条理由的可信度降级。即使这样幻觉还不能被完全消灭。最有效的兜底是人工抽查每周随机抽三只入选股票对照原始公告和新闻看 Agent 的解释是否成立。这听起来很原始但它能帮你尽早发现数据源格式变化引发的连锁问题。我的经验是一旦模型开始频繁出现幻觉多半不是模型变笨了而是某一类输入数据的格式变了。5.2 数据穿越未来的消息提前进到了回测里数据穿越是我踩过最狠的坑症状是回测曲线特别漂亮实盘却完全对不上。事后排查才发现有一部分信号用了“未来的市值”和“未来的财务数据”。比如在历史某一天回测时代码读的是当前最新的总市值而这个市值包含了之后几个月股价涨跌的结果等于提前知道了答案。要根治这个问题必须让所有数据都带时间属性。行情表带交易日财报表带公告日期新闻表带发布时间。每次生成快照时只用公告日期小于等于回测日期的数据。我在代码里加了一条断言如果某条数据的日期大于当前回测日期直接报错中止宁可让程序停下来也不允许脏数据进入信号计算。这个坑也影响了我对数据管道的态度每天跑任务时不能只想着让数据“完整”还要保留这个数据“是何时被外界知道”的信息。没有这条时间线任何回测都可能是自欺欺人。5.3 流程超时与解析失败降级大模型 API 调用不是百分百可靠的偶尔会超时偶尔返回空内容偶尔输出的 JSON 不合法。早期我的代码遇到解析失败就重试结果两三次重试后开始死循环整个流水线卡死。后来我定了两条规矩每个 Agent 最多重试两次超时后直接跳过该股票而不是一直等待。对于解析失败我会把原始输出和错误信息重新拼成一个“纠错 Prompt”再给模型一次修正机会。修正后还不行就用规则评分作为兜底并给这只股票打上 low_confidence 标记。这样做的代价是会漏掉少量好股票但换来了整条流水线的稳定性。在日志里我也会记录每次重试的原因比如“输出中包含 markdown”或“字段缺失”。连续多天出现同一类解析问题时说明 Prompt 里给出的示例已经过时需要更新模板。模型输出的偏好会随着版本变化所以 Agent 模板不能一劳永逸。5.4 上线小资金试跑的个人建议回测通过以后我没有直接把策略接到实盘而是先跑了一个模拟盘和一个最小资金盘。模拟盘用来验证流水线每天能不能准时产出结果最小资金盘用来观察实盘交易中滑点、手续费、涨跌停成交不了这些问题。很多回测里能成交的信号实盘会因为开盘直接封涨停而买不进去。建议你也先把这个选股器当成“研究助手”而不是“自动交易系统”。让它每天输出一份候选清单你再人工快速翻阅一遍重点看那些 Agent 给出高分但你自己有疑问的股票。连续跑一个月记录人工确认和 Agent 判断的分歧再针对分歧调整 Prompt 和数据源。我个人实际操作中还有一个习惯每天把 Agent 输出的解释存下来过一周再回看。这样做不是为了预测下一周行情而是为了追踪 Agent 的逻辑是否稳定。如果同一只股票被反复选中理由却从“低估值”变成“情绪回暖”说明它可能只是在追热点而不是在抓基本面变化。这种细节比单纯盯着收益率更能暴露问题。