让大模型自己写量化策略,最后能赚钱吗?
A) 起因
前几年做股民情绪分析时,我就搭过一套股票数据服务。当时主要是抓评论、算情绪,再把情绪曲线和上证指数叠在一起看。项目做完后,相关性确实能看出来,但离“根据它做交易”还很远。
后来大模型越来越好用,网上也出现了很多 AI 炒股项目。最常见的做法,是把一段行情和新闻丢给模型,让它给出买入或卖出建议。拿来演示很有意思,真要研究策略就有点麻烦:今天问一次和明天问一次,答案未必相同;模型说“趋势转强”,程序也不知道这个“强”究竟是多少。
我想做的不是再加一个荐股聊天框,而是让模型给出一套能够真正执行的规则。规则先跑历史数据,跑完以后再换时间、换标的继续测。最后赚不赚钱先放在一边,至少每次结果都能重现,也知道问题出在哪里。
于是有了 Stock Broker。项目最初只是一个策略编辑器,后面陆续补上了行情数据、回测、AI 迭代和策略评估,最后不可避免地长成了一套完整系统。
B) 先解决数据问题
这类项目最容易让人兴奋的是 AI,真正开始写以后,最先遇到的却是数据。
系统需要知道有哪些市场和标的,要保存股票、ETF、指数的历史日线,还要处理交易日历、送股和拆股。如果这些数据散落在接口返回里,规则预览算一套、Agent 算一套、回测实验室再算一套,很快就会出现同一条策略三个结果。
所以我先做了数据中心,将沧海数据同步到 PostgreSQL。同步过的数据统一从服务层读取,股票的拆股调整也在这里处理。这样 K 线展示、策略回测和 Agent 使用的是同一份数据,至少不会因为取数口径不同互相打架。
同步任务放到了 Celery 中。前端发起任务后只拿到一个任务 ID,Worker 在后台拉取和写入数据,进度再通过 WebSocket 回到页面。Redis 在中间负责消息和任务状态。这样做比接口一直转圈麻烦不少,但同步几年日线或者批量评估策略时,页面不会跟着一起卡死。
C) 把交易规则变成数据
最早的策略条件是一段字符串,大概长这样:
close > ma60 and rsi14 < 70
很短,也很好懂,但不适合继续扩展。加上嵌套条件、函数、历史引用和仓位以后,光靠字符串已经很难编辑,更别说让大模型稳定输出。
现在的策略使用 JSON 描述。买入、卖出、条件组、变量、函数和仓位都有固定结构,界面再把这份结构渲染成规则编辑器。
上图中的两条买入规则分别处理不同场景。每条规则内部可以嵌套 AND/OR 条件组,也可以设置不同仓位。规则按顺序判断,先命中的先执行。如果同一天同时出现买入和卖出信号,还可以选择优先卖出、优先买入、卖出后重新买入,或者干脆跳过。
表达式目前支持均线、RSI、MACD、KDJ、波动率、区间位置、Z-Score、历史百分位等指标,也能引用过去的数据。保存前会检查字段、函数参数、括号、量纲和仓位。未来数据引用会被直接拒绝,否则模型很容易无意间写出一条“准确预测昨天”的完美策略。
这部分最费时间的并不是递归渲染,而是让前端编辑、后端校验和回测执行对同一份规则保持相同理解。好在规则一旦结构化,导入导出、拖动排序和 AI 生成也就顺理成章了。
D) 回测中踩过的坑
回测的第一版很快就能跑起来:遇到买点买入,遇到卖点卖出,最后比较本金和剩余资产。做完以后结果也很好看,好看到让人怀疑。
检查后发现,最容易出问题的是成交时间。如果使用当天收盘价计算指标,又让交易在当天收盘价成交,相当于先知道答案再操作。现在的处理方式是收盘后判断信号,订单放到下一个交易日开盘执行。历史数据还会额外预留一段指标预热区间,避免回测刚开始时均线和波动率没有足够样本。
每次回测都会记录现金、持仓、成交价格、仓位和规则触发原因,并生成策略与买入持有两条净值曲线。结果中除了收益,还有最大回撤、Sharpe、波动率、胜率和交易次数。
加入买入持有基准后,一些原本看起来不错的策略立刻变得普通:策略确实赚钱了,但同一时期什么都不做反而赚得更多。这个对比不复杂,却能过滤掉不少自我感动。
目前的回测仍然是研究用途,还没有把手续费、滑点、流动性和停牌等情况全部模拟进去。数字可以拿来比较策略,不能直接当作未来收益。
E) AI Agent 如何改策略
规则确定以后,大模型的工作就简单了许多。它不需要直接猜第二天涨跌,而是根据过去几轮结果修改规则。
每轮开始时,Agent 会读到近期表现、历史最佳结果以及不同时间区间的测试情况,然后决定是沿用最佳策略、调整最近一轮、探索新结构,还是做一次幅度更大的变化。模型返回分析、修改计划和新的策略 JSON,Worker 校验后执行回测,再把结果存入这一轮记录。
记忆中保留最近 10 轮和历史最佳 3 轮。最开始只放收益率,模型很容易围着某个高收益结果反复改几个数字;后来又加入回撤、Sharpe、交易次数和时间稳定性,它才开始知道一条曲线好看不等于策略可靠。
模型生成的内容不会直接覆盖已有策略。JSON 格式错误、使用未知指标、条件量纲不一致或者引用未来数据,都会在校验阶段失败。通过校验也只是获得回测资格,表现差一样会被记录下来。
Agent 任务通常需要运行很多轮,所以同样放在 Celery Worker 中。页面右下角有一个任务入口,可以查看当前轮次和最佳表现,也可以中途停止。后来又加了定时计划,用来按日、周或月重复执行实验。
F) 回测实验室
做到这里,还有一个问题没有解决:模型非常擅长针对一段历史数据找规律。指标和函数越多,它越容易拼出一条只在某几年有效的规则。
回测实验室就是用来给这些“优秀策略”找麻烦的。
策略保存后,系统会在相近标的和多个时间区间上重新运行。时间区间包括单年、连续三年和连续五年;标的则从有完整行情的数据中选择。每个样本都和同期买入持有比较,并单独计算收益、回撤、Sharpe、胜率和交易次数。
上图中,同一条策略在不同年份的表现差异很大。2022 年通过,不代表 2023 年也适用;某个区间年化收益很高,也可能同时伴随更大的回撤。把结果摊开以后,过拟合通常比在单条净值曲线上明显得多。
点开区间可以继续查看策略与基准的净值、具体指标和每次成交记录。交易记录会保留触发它的规则,方便反查到底是哪个条件带来了收益或者亏损。
实验室最后会从跨标的表现、跨时间表现、风险控制和交易健康度几个方面给出汇总。交易次数也被单独考虑:一年只交易一次的策略可能只是碰巧押中,交易过于频繁则可能一直被噪声触发。
评估完成后,可以让模型根据失败区间和建议生成一个调整版本。这里没有直接修改原策略,而是打开新建页面并预填结果,是否保存仍然由人决定。
G) 项目结构
前端使用 Next.js、React、TypeScript、Ant Design 和 ECharts,主要负责规则编辑、K 线、净值曲线、Agent 过程和评估结果的展示。
Flask API 负责认证、策略和任务接口,SQLAlchemy 管理 PostgreSQL 中的行情、策略和实验记录。数据同步、Agent 迭代与批量回测交给 Celery Worker,Celery Beat 负责周期任务,Redis 则提供任务队列和状态通信。浏览器通过普通 HTTP 接口处理业务,通过 WebSocket 接收任务更新。
部署时使用 Docker Compose 启动 PostgreSQL、Redis、API、Worker、Beat 和前端,再由 Nginx 统一处理页面、接口和 WebSocket。开发环境也沿用相同的服务划分,只把前后端进程放到本机运行,调试起来方便一些。
项目发展到现在,已经包含数据中心、策略搭建、AI Agent、计划任务、回测实验室和交易决策台。交易决策台不会重新发明一套规则,它直接读取已有策略和最新行情,展示当前命中了哪些条件。这样研究阶段和查看当前信号时,用的仍然是同一个 DSL 和执行逻辑。
H) 最后
做这个项目之前,我以为主要工作会是调 Prompt。实际写下来,时间更多花在数据口径、规则协议、异步任务和回测细节上。大模型只占其中一部分,而且是最不稳定的那部分。
它确实能快速提出不同的策略组合,也能根据失败结果继续修改,比手工一点点试参数省事。但只要给的尝试次数足够多,它迟早能在历史数据里找到一条漂亮曲线。因此系统不能只奖励最高收益,还要不断换区间、换标的,并把回撤和交易次数一起拿出来看。
至于最关心的问题——能赚钱吗?历史上能,未来不知道。否则这个项目就应该叫自动提款机,而不是策略研究平台了。
接下来还会继续补充更接近真实交易的成本模型,并尝试把新闻、财报和市场情绪处理成可以回测的因子。比起让模型直接讲一个听起来合理的故事,我还是更愿意先让历史数据为它找点麻烦。
本文仅记录项目设计与实现,不构成投资建议。历史回测结果不代表未来表现。