引言:写代码这件事,正在被重新分工
过去几年,MQL5开发者提升效率的路径基本只有两条:一是练手速,把常用的订单封装、指标调用、日志模块背下来,形成肌肉记忆;二是攒代码库,把写过的模块复制粘贴、改吧改吧再用一遍。这两条路的天花板都很明显——你的产出上限,等于你自己单位时间的敲键盘速度乘以你的记忆容量。
2026年这条路径出现了分叉。7月23日MetaQuotes在Build 6060中给MetaTrader 5引入了原生MCP(Model Context Protocol)支持,AI助手同时进入交易终端和MetaEditor;9月初的Build 6180把助手推进了策略测试器;9月24日的Build 6230则进一步放开了一组新工具,让智能体能够自己读取EA的输入参数、配置测试、在图表上启动或移除程序,甚至直接查询内置经济日历的数据。
这意味着一个变化:AI在EA开发中的角色,从"帮你写一段代码",变成了"能替你走完一段流程"。写代码只是流程里的一环,配环境、改参数、跑测试、看日志、分析报告,这些过去相当消耗耐心的碎活,现在都可以被串成一条自动化的链。
这篇文章不打算讨论"AI会不会取代EA开发者"这种没有答案的问题,只想把三件具体的事说清楚:Build 6230到底放开了什么、AI在MQL5上哪些活能干哪些活不能干、以及一套可以直接照着做的AI辅助开发SOP。全程给代码、给Prompt、给踩过的坑。
一、Build 6230改了什么:AI从"会聊天"变成"会动手"
要看懂6230的价值,得先把6060、6180、6230这三个版本连起来看。它们不是三次孤立的功能更新,而是一条清晰的演进线:先让AI能"看见"平台,再让AI能"操作"平台,进而让AI能"自己串起一串操作"。
1.1 三个版本的能力叠加
| 版本 | 官方更新要点 | 对开发流的实际影响 |
|---|---|---|
| Build 6060 2026.07.23 |
原生支持MCP与智能体型AI;内置AI助手进入终端与MetaEditor;可外接OpenAI Codex、Claude Code等支持MCP的外部系统;免费MQL5 Lite模型,也可自带OpenAI、Anthropic、Gemini、DeepSeek、Ollama等API Key | 从"网页里问AI,再复制粘贴回编辑器",变成"编辑器内直接对话";外部Agent获得了访问终端的标准接口 |
| Build 6180 2026年9月初 |
助手可读取策略测试器的回测报告并分析、按指定标准发起优化、读取测试器当前设置、查看测试器日志、给图表添加指定参数的指标、读取终端与EA日志 | "测试—分析—调参"这一段首次可以交给AI处理,尤其是对比多轮优化结果这种纯体力活 |
| Build 6230 2026.09.24 |
① 准备EA进行测试:检索EA输入参数并为策略测试运行做准备;② 图表上的程序管理:获取EA与脚本参数、在图表启动、移除EA与指标、检查指标当前状态;③ 经济日历:查询国家与事件列表、读取经济指标值、按ID查找记录 | 智能体可以自己把"环境准备—挂载程序—配参数—跑测试"连成一条链,不需要人一步步点 |
1.2 为什么"工具调用"比"代码生成"更关键
很多人对AI编程的想象停留在"描述需求,吐出一段代码"。但真实的EA开发里,写代码的时间占比并不高。以一个中等复杂度的单品种趋势EA为例,2500行左右的代码量,纯敲键盘可能只占全部工时的四成,剩下的六成耗在:改参数、跑回测、等结果、看报告、发现某一段逻辑不对、回去改、再跑一遍。这个循环每转一圈,半天就没了。
6230开放的这三组工具,恰好打在这个循环上。检索EA输入参数意味着AI不用再问"你的止损参数叫什么名字",它自己能看到input列表;在图表上启动程序并检查指标状态意味着它能验证"我加的指标到底加载成功没有";经济日历意味着它可以把"非农数据公布前后两小时不新开仓"这类规则真正落到代码里,而不是靠开发者自己查日历手填时间。
1.3 语言层同步更新的两个小福利
6230在MQL5语言层也补了几块短板,虽然不如AI功能抢眼,但对写代码的人很实用。一是向量与矩阵增加了Sort()排序方法,支持升序、降序,矩阵还可以沿指定轴排序;二是增加了对.NET CoreCLR 8及以上版本的支持,为对应平台编译的库可以直接导入MQL5程序使用;三是复数矩阵的乘积与解法方法补齐了Power、Kron、Inner、Outer、CorrCoef、Cov、Correlate、Convolve以及Solve、LstSq、Inv、PInv。
排序这一项立刻能用。以前处理优化结果、给多组参数按某个指标排序,要么自己写冒泡,要么调ArraySort再自己搬数据。现在一行就够:
// Build 6230起:vector / matrix 支持 Sort()
// 场景:把N组参数的回撤指标收进向量,按从大到小排,挑出前若干组做二次验证
vector dd;
dd.Resize(rows);
for(int i = 0; i < rows; i++)
dd[i] = MathAbs(result[i].maxDrawdown); // 只取绝对值,排序用
dd.Sort(SORT_DESCENDING); // 6230新增,无需手写排序
PrintFormat("回撤上界 %.2f%%,中位回撤 %.2f%%", dd[0], dd[rows / 2]);
二、AI在MQL5上的能力边界:哪些活能交,哪些活不能交
先给一组官方数据做背景:2026年8月11日MetaQuotes公布,MCP支持与内置AI助手正式上线仅三周,用户通过免费的MQL5 Lite模型处理的token量就突破了1万亿。这个量级说明AI已经实实在在进入了交易者的日常工作流,而不是概念演示。
但用得多人多,不等于用得好的人多。我们把2026年10月内部对照测试中,AI生成代码被打回的原因做了归类,结论比想象中更集中:语法错误几乎没有,逻辑错误占了绝大多数。AI写MQL5很少写错分号,它写错的是边界条件、状态时序和资源生命周期。
2.1 一张能力边界表
| 任务类型 | AI可靠度 | 典型失败方式 |
|---|---|---|
| 样板代码:类封装、参数校验、日志输出 | 高 | 偶尔过度设计,加一堆你用不上的接口 |
| 指标句柄与缓冲区管理 | 中高 | 起始索引算错、历史数据不足时越界读、忘记IndicatorRelease |
| 订单发送与错误码处理 | 中 | 重试不带幂等、把不可重试的返回码也拿去重试、忽略STOPS_LEVEL |
| 事件驱动与状态机(OnTradeTransaction) | 低 | 把成交当成"下单成功"、状态更新顺序颠倒、重复处理同一笔成交 |
| 内存与资源生命周期 | 低 | new与delete不配平、OnDeinit销毁顺序错、句柄泄漏 |
| 经纪商差异适配 | 低 | 不知道你的品种带后缀、filling mode不同、报价位数不同 |
| 性能热点优化 | 低 | 给出"看起来很快"的写法,实际在OnTick里反复调用CopyBuffer |
这张表值得注意的不是"低"那几行,而是"中"那一行。订单发送与错误码处理看起来是AI的强项——毕竟CTrade类的用法在互联网上有海量样本,模型见过太多了。但恰恰是这块,AI容易给你一段"能编译、能跑通、但在极端行情下会重复开仓"的代码。原因很简单:幂等、重试上限、止损距离校验这些防御性设计,在训练语料的"标准示例"里通常是缺失的,因为示例代码只关心"怎么发单成功",不关心"发单失败之后怎么办"。
三、一套可落地的AI辅助EA开发SOP
下面这套流程是我们在实际项目里跑了一年多、迭代了若干轮之后稳定下来的版本。它不是"用AI写代码"的教程,而是一套把AI嵌进工程流程的规范。核心思想只有四个字:分而治之。
3.1 四个阶段与准入条件
| 阶段 | 你要做的事 | 阶段产出 | 进入下一阶段的条件 |
|---|---|---|---|
| P0 需求规格化 | 把交易想法拆成模块清单,每个模块写清输入、输出、异常路径 | 一份spec.md,8到15个模块条目 | 每个模块能用一句话说清"它负责什么、不负责什么" |
| P1 分块生成 | 一次只让AI生成一个模块,单文件控制在300行内 | 可独立编译的.mqh头文件 | 编译零错误零警告 |
| P2 交叉审查 | 人工过一遍审查清单,再用另一个模型复审同一份代码 | 审查记录与修改diff | 七道关(见第七章)全部有明确结论 |
| P3 测试器验证 | 单品种单周期初筛 → 真实Tick精测 → 参数敏感性 → 多品种 | 四份回测记录与结论 | 参数邻域内结果不出现断崖式变化 |
3.2 为什么必须分块:上下文污染
一次性把两千行需求丢给AI,让它"生成一个完整EA",是新手常犯的错。表面看省事,实际代价极高:模型在长上下文里会逐渐"忘记"早期的约束,前面写的是CTrade,后面某一段突然换成裸OrderSend;前面定的命名规范,到第八个函数就变形了;你提出的"止损必须由服务端托管",在代码后半段悄悄变成了客户端平仓。
分块生成把这个问题从根上切断了。每生成一个模块,上下文都是干净的、需求都是局部的、验收标准都是明确的。代价是你需要提前做P0的拆解工作,而这一步恰恰是AI帮不上太多忙的地方——它不知道你的策略为什么要这样设计。
RiskManager.mqh、OrderRouter.mqh、SignalEngine.mqh这样的独立头文件后,某一模块出问题只需要替换那一个文件,其余部分不动。反之,如果两千行全在一个.mq5里,AI改一次就可能引入三处新bug,而你没有精力逐行diff。
3.3 目录约定:让AI"看得懂"你的工程
如果你用Codex、Claude Code这类能读整个目录的Agent,工程结构本身就是Prompt的一部分。目录混乱,AI的补全就会乱猜;结构清晰,它读两三个文件就能理解你的约定。
MyEA/ ├─ MyEA.mq5 // 只做装配:OnInit/OnTick/OnDeinit ├─ Include/ │ ├─ SignalEngine.mqh // 信号生成,不碰订单 │ ├─ RiskManager.mqh // 手数计算、敞口校验,不碰信号 │ ├─ OrderRouter.mqh // 统一下单出口,含幂等与重试 │ ├─ PositionTracker.mqh // 持仓状态机,只认deal │ └─ Logger.mqh // 统一日志格式 ├─ spec.md // 需求规格:模块职责与接口约定 └─ review.md // 审查记录:每个模块过了哪几道关
spec.md是给AI看的,让它知道每个模块的边界;review.md是给自己看的,记录"这个模块的人工复核点在哪、为什么这么改"。半年后你要改这段代码时,review.md会替你省下重新读代码的时间。另外,把"统一下单出口"写进目录约定,是从架构层面防重复下单——只要所有下单都走OrderRouter,你只需要在一个文件里做幂等,而不是全工程搜OrderSend。
四、三个Prompt模板:把"感觉"变成"规格"
下面三个模板是实际项目里反复用过的版本。它们的共同结构是:角色与目标 + 环境约束 + 接口契约 + 失败处理要求 + 输出格式 + 禁止项。少任何一项,AI都会用它的"默认习惯"填补空白,而默认习惯通常不是你要的。
4.1 模板一:复杂指标编写
你是一名MQL5开发者,请为我编写一个自定义指标。 【目标】 双均线动量归一化指标:快慢均线差值除以滚动窗口内的绝对值上界,输出落在[-1,1]区间。 【环境约束】 - 语言:MQL5,目标版本 MetaTrader 5 build 6200 及以上 - 运行位置:指标运行在独立窗口(indicator_separate_window) - 输入参数:InpFast=12、InpSlow=48、InpNorm=100,三者均需 input 声明并加中文注释 【接口契约】 - 缓冲区0:归一化动量,DRAW_HISTOGRAM - 缓冲区1:原始差值,INDICATOR_CALCULATIONS(不绘制) - 必须在OnInit中用SetIndexBuffer绑定,并设置PLOT_DRAW_BEGIN 【必须处理的边界】 - rates_total不足时直接返回0,不允许读未初始化区间 - prev_calculated>0时只从prev_calculated-1开始增量计算 - 起始索引必须同时满足快线、慢线、归一化三个回看窗口,请明确写出推导过程 - maxAbs为0时输出0,不允许除零 【输出格式】 先给完整代码,再用列表说明三处你认为容易出错的地方及原因。 【禁止】 - 不要使用未在输入参数中声明的魔术数字 - 不要省略注释 - 不要写OnTimer或任何定时器逻辑
这个模板的设计要点有三个。其一,把推导过程要出来。指标类代码容易出错的环节就是起始索引,要求AI写出推导,你就能在它算错的时候一眼看出来,而不是等到实盘跑出奇怪的断点。其二,明确禁止项。不写"不要魔术数字",AI大概率会在循环里塞个100;不写"不要定时器",它有时会自作聪明地加个刷新机制。其三,要求它自评风险点,这相当于让模型先做一次自审,命中率比你自己找要高。
下面是我们实际拿到、并经过人工修正后的代码。注意start那一行——AI首版写的是start = InpFast,当InpNorm大于InpFast时,归一化窗口会读到索引0以下,这是我们人工改掉的一处。
// AI生成 + 人工复核。复核点:起始索引、除零保护、增量计算
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots 1
#property indicator_type1 DRAW_HISTOGRAM
#property indicator_color1 clrDodgerBlue
#property indicator_level1 0.0
input int InpFast = 12; // 快线周期
input int InpSlow = 48; // 慢线周期
input int InpNorm = 100; // 归一化窗口
double g_mom[]; // 归一化动量
double g_raw[]; // 原始差值(计算缓冲)
int OnInit()
{
SetIndexBuffer(0, g_mom, INDICATOR_DATA);
SetIndexBuffer(1, g_raw, INDICATOR_CALCULATIONS);
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, InpSlow + InpNorm);
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
return(INIT_SUCCEEDED);
}
int OnCalculate(const int rates_total, const int prev_calculated,
const datetime &time[], const double &open[],
const double &high[], const double &low[],
const double &close[], const long &tick_volume[],
const long &volume[], const int &spread[])
{
if(rates_total < InpSlow + InpNorm + 1) return(0);
// 人工修正:起始索引必须覆盖全部回看需求,不能只取InpFast
int need = (InpNorm > InpSlow) ? InpNorm : InpSlow;
int start = (prev_calculated > 0) ? prev_calculated - 1 : need + 1;
for(int i = start; i < rates_total; i++)
{
double fast = 0.0, slow = 0.0;
for(int k = 0; k < InpFast; k++) fast += close[i - k];
for(int k = 0; k < InpSlow; k++) slow += close[i - k];
fast /= InpFast;
slow /= InpSlow;
g_raw[i] = fast - slow;
double maxAbs = 0.0;
for(int k = 0; k < InpNorm; k++)
{
double v = MathAbs(g_raw[i - k]);
if(v > maxAbs) maxAbs = v;
}
g_mom[i] = (maxAbs > 0.0) ? g_raw[i] / maxAbs : 0.0;
}
return(rates_total);
}
iCustom或iATR拿到句柄,务必在OnDeinit里IndicatorRelease。这不是洁癖:在长时间运行、频繁切换周期的场景下,句柄泄漏会让终端的指标计算线程持续堆积,表现出来就是"EA越跑越慢,重启终端就恢复正常"。AI生成的代码几乎从不主动写这一行,因为它不在"示例代码"的常规套路里。
4.2 模板二:带风控逻辑的订单管理
你是一名熟悉实盘工程细节的MQL5开发者,请编写一个安全的开仓函数。
【目标】
函数签名:bool PlaceOrderSafe(CTrade &trade, const string symbol,
const ENUM_ORDER_TYPE type, const double lots,
double slPoints, ulong &ticket);
【必须满足的硬约束】
1. 止损距离必须同时满足经纪商SYMBOL_TRADE_STOPS_LEVEL与当前点差,
不满足时向上调整到合法值,并在日志中记录调整前后的数值
2. 幂等:同一根K线、同一方向、同一Magic只允许成功开仓一次,
重试不得产生重复持仓
3. 重试上限3次,且只对可重试的返回码重试(重新报价、价格变动),
其他返回码立即退出并打印ResultRetcode与描述
4. 价格每次重试必须重新获取,不允许复用旧价格
5. 止损价必须用NormalizeDouble按SYMBOL_DIGITS规范化
【禁止】
- 禁止使用固定点数作为止损(必须来自入参slPoints)
- 禁止在函数内使用Sleep超过500毫秒
- 禁止吞掉错误(catch后不打印)
- 禁止假设symbol没有后缀
【输出格式】
完整函数 + 一段说明:你的幂等方案在什么情况下会失效。
"说明你的幂等方案在什么情况下会失效"这一句是刻意加的。绝大多数AI给出的幂等方案是GlobalVariable,而GlobalVariable是终端本地存储——换一台机器、换一个终端实例、或者用户手动清理全局变量,幂等键就没了。让它自己说出这个局限,你才能判断这段代码能不能用在你的场景里。
// AI生成 + 人工复核。复核点:幂等键的持久性、重试返回码白名单
bool PlaceOrderSafe(CTrade &trade, const string symbol, const ENUM_ORDER_TYPE type,
const double lots, double slPoints, ulong &ticket)
{
ticket = 0;
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
long stops = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
if(point <= 0.0 || ask <= 0.0 || bid <= 0.0) return false;
// 1) 止损距离下限:经纪商限制与当前点差取大者,再留2点余量
double spreadPts = (ask - bid) / point;
double minDist = ((double)stops > spreadPts ? (double)stops : spreadPts) + 2.0;
if(slPoints < minDist)
{
PrintFormat("止损距离被抬升 symbol=%s 原值=%.1f 调整为=%.1f", symbol, slPoints, minDist);
slPoints = minDist;
}
// 2) 幂等键:同K线+同方向+同Magic 只允许一次
string key = StringFormat("ord_%s_%s_%I64d_%I64d", symbol, EnumToString(type),
(long)iTime(symbol, PERIOD_CURRENT, 0), (long)trade.MagicNumber());
if(GlobalVariableCheck(key))
{
PrintFormat("幂等拦截,本K线已下过单 key=%s", key);
return false;
}
// 3) 有限次重试,只重试可重试的返回码
for(int attempt = 0; attempt < 3; attempt++)
{
double price = (type == ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK)
: SymbolInfoDouble(symbol, SYMBOL_BID);
if(price <= 0.0) break;
double sl = (type == ORDER_TYPE_BUY) ? price - slPoints * point
: price + slPoints * point;
sl = NormalizeDouble(sl, digits);
if(trade.PositionOpen(symbol, type, lots, price, sl, 0.0))
{
GlobalVariableSet(key, (double)TimeCurrent());
ticket = trade.ResultOrder();
return true;
}
uint ret = trade.ResultRetcode();
if(ret != TRADE_RETCODE_REQUOTE && ret != TRADE_RETCODE_PRICE_CHANGED)
{
PrintFormat("下单失败且不可重试 symbol=%s ret=%u desc=%s",
symbol, ret, trade.ResultRetcodeDescription());
break;
}
Sleep(200); // 仅对报价类问题短暂退避
}
return false;
}
人工复核这一版时,我们加了两处改动:一是把minDist的比较改成三元表达式而不是MathMax,因为AI混用了long和double导致编译警告;二是在注释里明确写下"GlobalVariable在多终端场景失效,如需跨终端幂等必须用外部状态存储"。第二处不是代码改动,但比代码改动更重要——它把AI代码的适用边界标了出来。
4.3 模板三:历史数据读取与跨周期处理
你是一名MQL5开发者,请编写历史数据读取函数,重点是"绝不引入未来信息"。 【目标】 1. bool ReadClosedBars(symbol, tf, count, double &out[]): 只读取已收盘K线,索引0对应刚收盘的那根K线 2. double GetHigherTfClose(symbol, higherTf, int currentShift): 取当前周期某根K线所对应的高周期已收盘收盘价,要求时间对齐 【必须满足】 - 使用CopyClose/CopyTime,禁止用iClose/iTime逐根循环取数(性能) - Copy返回值必须校验是否等于请求数量,不等则打印GetLastError并返回false - 高周期取数必须用iTime对齐,禁止直接按shift平移索引 - 高周期当前未收盘的那根K线一律不许使用 【输出格式】 两个函数 + 一段说明:为什么"按shift平移"是未来函数。
// AI生成 + 人工复核。复核点:索引0语义、时间对齐、返回长度校验
bool ReadClosedBars(const string symbol, const ENUM_TIMEFRAMES tf,
const int count, double &out[])
{
// 起点取1:索引0是正在形成的当前K线,绝不能用
int copied = CopyClose(symbol, tf, 1, count, out);
if(copied != count)
{
PrintFormat("CopyClose未取满 symbol=%s tf=%s need=%d got=%d err=%d",
symbol, EnumToString(tf), count, copied, GetLastError());
return false;
}
ArraySetAsSeries(out, true); // 统一:索引0 = 刚收盘的那根K线
return true;
}
double GetHigherTfClose(const string symbol, const ENUM_TIMEFRAMES higherTf,
const int currentShift)
{
// 关键:先用时间对齐,再判断该高周期K线是否已收盘
datetime t = iTime(symbol, PERIOD_CURRENT, currentShift);
if(t == 0) return 0.0;
int shift = iBarShift(symbol, higherTf, t, false);
if(shift < 0) return 0.0;
// shift==0 表示高周期当前K线还在形成中,向前挪一根
if(shift == 0) shift = 1;
double c[];
if(CopyClose(symbol, higherTf, shift, 1, c) != 1) return 0.0;
return c[0];
}
if(shift == 0) shift = 1;这一行就是分水岭——去掉它,你的EA在回测时就能"提前知道"高周期还没走完的那根K线收盘落在哪里。AI在写跨周期逻辑时,有相当比例会漏掉这一行,因为它更关注"取到值",而不是"取到的值在当时是否可得"。
五、把智能体接进测试闭环:从"生成代码"到"跑完一整轮"
写代码只是前半程。真正吃掉工时的,是后半程那个"跑—看—改—再跑"的循环。Build 6180和6230让这个循环首次具备了被自动化的条件。
5.1 一条可执行的五步链
把6230的三组工具、6180的测试器访问能力、以及MCP的外部接入能力串起来,可以得到这样一条链:
第1步 读取EA输入参数 -> 智能体调"准备EA进行测试",拿到全部input列表 第2步 按指定标准配优化 -> 设定优化目标、参数范围、步长,发起测试 第3步 取回报告与日志 -> 读取策略测试器报告、终端日志、EA日志 第4步 归因分析 -> 找出参数邻域内的断崖点,标注疑似过拟合区 第5步 生成下一轮配置 -> 收窄范围、重设步长,回到第2步
5.2 报告分析Prompt:让AI找"脆弱参数"
下面是一份MT5策略测试器的优化结果表,列含义: 参数A、参数B、参数C、总交易数、回撤(%)、净值峰值、平均每笔。 【任务】 1. 找出"脆弱参数":某个参数在相邻步长上仅变化一档, 而关键指标出现明显跳变的组合,逐条列出并给出跳变幅度 2. 标出结果不连续的参数区间(疑似过拟合区) 3. 指出样本量不足的组合(总交易数偏低时结论不可靠) 4. 给出建议:哪些参数区间值得进入下一轮精测,哪些应当放弃 【约束】 - 只基于表内数据判断,不要引入任何外部假设 - 不要给出任何关于实盘表现的预测 - 如果数据不足以支撑某个结论,直接说"数据不足",不要编造 【输出格式】 先给一段不超过200字的总体判断,再给分条结论表格。
六、效率与成本:40小时到12小时是怎么算出来的
2026年10月,我们用一个中等复杂度的单品种趋势EA做了内部对照:约2500行MQL5代码,含自定义指标、动态止损、分批出场、日志与状态恢复。同一名开发者,先用纯手工方式从零写一版,间隔一周后用本文这套SOP重写一版,记录各阶段工时。
| 阶段 | 纯手工(小时) | AI辅助(小时) | 差异说明 |
|---|---|---|---|
| 需求拆解 | 3.0 | 4.0 | 反而变慢:要写出可交给AI的规格,比自己在脑子里过一遍更费时 |
| 指标与数据层 | 9.0 | 2.5 | 样板密集,收益占比高的一块 |
| 订单与风控 | 12.0 | 4.5 | AI出初稿,人工改幂等与边界,改动量约三成 |
| 日志与异常处理 | 5.0 | 1.0 | 格式化输出几乎可全托管 |
| 回测与调参 | 11.0 | 0.5 | 智能体自动取报告与初步归因,人工只看结论 |
| 合计 | 40.0 | 12.5 | 端到端耗时约为原来的三成 |
除了工时,还有两个值得记录的变化。一是编译警告数量:手工版首次编译有11条警告,AI辅助版有6条,但AI版的警告更集中(类型转换、未使用变量),人工版更分散。二是首轮回测通过率:手工版首次跑通策略测试用了约2小时排查逻辑,AI辅助版用了40分钟,但AI版多花了一轮时间修"幂等缺失"导致的重复开仓——这个缺陷在回测里不会暴露,是代码审查时抓到的。
七、避坑清单:AI生成的代码上线前必须过的七道关
以下七条是我们内部审查清单的完整版。它的用法不是"逐条读一遍",而是每一道关都要留下明确结论——过了、没过、改了什么。没有结论的审查等于没审查。
1. 未来函数:检查所有跨周期取数、所有用到索引0的地方、所有"预判"型逻辑。判定方法:把数据源换成只有前N根K线的历史切片,看结果是否变化;变化即为未来函数。
2. 魔术数字:全工程搜索裸露的数字常量。点值、止损距离、手数、时间窗口,必须来自input或具名常量。AI很爱在循环里塞100、0.01这类数字,改一个参数就全崩。
3. 资源生命周期:每个new必须有对应的delete,每个指标句柄必须有IndicatorRelease,且释放顺序与创建顺序相反。AI写OnDeinit时经常漏掉一半。
4. 幂等与重试:所有可能被重试的下单路径都要有幂等键;重试必须有次数上限;只对白名单内的返回码重试。这道关过不了的代码,宁可不上。
5. 状态以成交为准:不要用"下单返回成功"当作持仓依据,要以OnTradeTransaction里的成交事件为准。AI在这块的默认写法是把返回值当结论,实盘中一旦遇到部分成交或延迟成交,状态立刻错乱。
6. 风控硬约束不可绕过:单笔风险上限、同时持仓数上限、单日亏损上限、账户净值保护阈值,这四项必须在代码里有硬性拦截,且拦截点集中在一处。AI有时会把校验写在开仓函数内部,而某条分支绕过了这个函数——这是架构问题,靠改代码改不干净,只能靠"单一入口"设计。
7. 合规与可追溯:日志要能还原每一次决策(时间、信号、参数、返回码);不要硬编码任何账号、密码、API Key;如果代码里用了第三方库或他人开源模块,必须确认授权并保留出处。这一条既是工程要求,也是法律要求。
八、总结:角色升级,而不是岗位消失
把这篇的内容收一下,其实是三句话。其一,Build 6230的价值在于把AI从"生成器"变成了"执行器"——它能读参数、配测试、管图表、查日历,这让"写代码—跑测试—看报告"这条链首次具备了端到端自动化的可能。其二,能力边界是清晰的:样板代码、日志、格式化输出可以大量交给AI;订单幂等、状态机、资源生命周期、跨周期取数必须人写人审。其三,提效的真正来源不是模型,是流程:规格化、分块、审查、验证,这四步做扎实了,模型换哪一家都能干活;这四步不做,换什么模型都是垃圾进垃圾出。
我们在项目里体会较深的一条是:AI把"写"的成本压下来了,于是"想"的价值被抬上去了。过去你可以靠手速和记忆量吃饭,现在这部分正在快速贬值;但"这个策略为什么这样设计""风控红线划在哪里""哪一组参数是真的稳,哪一组是回测骗人"——这些问题,AI一个都替不了你。它只会更快地给你一堆候选答案,然后由你来判断哪个能上。
扫码关注,获取更多EA开发干货
微信公众号
视频号
