引言:从"问答框"到"能动手的执行者"
2026年9月24日,MetaQuotes发布MetaTrader 5 Build 6230。官方更新说明的标题很克制——"扩展的AI功能",但内容值得多读两遍:AI助手现在可以检索EA的输入参数并为策略测试准备好运行配置;可以在图表上启动EA与脚本、删除EA和指标、查询指标的当前状态;可以访问内置经济日历,按国家、事件、指标ID取数。
把这三条连起来看,它描述的是一条动作链:取参数→配测试→读结果→补宏观背景。这和过去"贴代码进对话框、等回答、再复制回来"的用法并不是一回事。前者是聊天,后者是执行。
本文不谈AI会不会取代开发者这类空话题,只谈工程:这套机制下工作流该怎么改,哪些环节可以交出去,哪些交出去会出事。文中代码基于MQL5标准库,可直接编译,但请先在模拟账户验证。
一、先看清版本脉络:6230不是起点,是第三个台阶
很多人把6230当成普通的功能更新,照着旧习惯用,两天就放弃了。要理解它的价值,得把最近半年几个版本连起来看。
| 版本 | 时间 | 与开发相关的关键变化 |
|---|---|---|
| 6060 | 2026-07-23 | 终端与MetaEditor内置MCP支持与智能体能力;MetaEditor助手可生成程序、分析代码、定位错误、重构、MQL4转MQL5;支持接入外部模型供应商 |
| 6090 | 2026-07-29 | 补充图表指标相关工具:添加指标、列举指标及其参数 |
| 6140 | 2026-08 | 扩充模型供应商列表;新增停止策略测试器的工具 |
| 6180 | 2026-09-03 | 智能体可读取策略测试报告、按条件启动优化、读取测试器设置与测试器日志、读取终端与EA日志 |
| 6230 | 2026-09-24 | 读取EA输入参数并准备测试运行;在图表上启动EA/脚本、移除EA与指标、查询指标状态;访问经济日历;MQL5侧新增向量与矩阵排序、.NET CoreCLR 8+、矩阵Print与复数矩阵运算方法 |
这个顺序很关键:6060解决"能不能动",6180解决"能不能看测试结果",6230解决"能不能自己把测试跑起来、并把宏观背景一起考虑进去"。三件事拼起来才是闭环。
二、原生"AI接口"的真实形态:是工具层,不是MQL5新函数
这里有一个很常见的误解,值得先纠正:6230并没有给MQL5语言增加"调用大模型"的内置函数。你在MQL5里找不到AskAI()这种东西。新增的是MCP工具(tool)——由智能体那一侧调用的能力接口,而MQL5程序在这套机制里是"被操作的对象",不是"调用方"。
既然参数会被工具读取、函数会被工具定位、日志会被工具解析,那么"可被机器读懂"本身就是一项设计要求。具体到代码层面:把input变量写规范,用group分组,用尾部注释写清单位和取值范围。
// 参数分组 + 单位标注:智能体读取参数表时能直接理解语义
input group "=== 信号参数 ==="
input int InpFastEMA = 20; // 快线周期(根),建议范围10-40
input int InpSlowEMA = 50; // 慢线周期(根),建议范围40-120
input group "=== 风控参数 ==="
input double InpAtrSL = 2.0; // 止损ATR倍数(倍),建议1.5-3.0
input double InpAtrTP = 4.0; // 止盈ATR倍数(倍),建议3.0-6.0
input double InpRiskPct = 1.0; // 单笔风险占比(%),建议0.5-2.0
input group "=== 运行参数 ==="
input ulong InpMagic = 20261110; // 魔术号,同账户内需保持不重复
input bool InpAllowLong = true; // 是否允许做多
这段代码没有"AI"成分,却决定了后面所有自动化环节能否跑通。参数无注释、无分组、命名随意,智能体读到的是一张无语义的表格,只能靠猜——猜错的代价是几十分钟的无效优化。
三、架构设计:三条桥接路径,先别急着造轮子
"MQL5和外部大模型怎么接"这个问题,答案已经不像2025年那样只有一种了。按投入从低到高,现在有三条路。
路径A:内置AI助手。装最新版终端、登录MQL5.community账户即可,平台自动配好默认供应商与密钥;也可在终端设置里换成自己的Key,官方列出的可选供应商包括OpenAI、Anthropic、Gemini、DeepSeek、Ollama等。适合单人开发、快速验证想法。
路径B:外部MCP客户端。终端与MetaEditor本身就是MCP服务提供方,支持MCP的外部系统可直接连上来,官方点名的包括OpenAI Codex、Claude Code。它的价值在于:你能用熟悉的工程化环境(命令行、Git、脚本)驱动平台,而不是被限制在聊天窗口里。
路径C:自建桥接。需要私有提示词、批量处理几十个文件、或接进CI流水线时,才考虑自建中间层。MQL5侧用WebRequest直接发HTTP;或反过来,用Python的MetaTrader5包从外部拉数据、调模型、回写结果。
文件读写 · 编译 · 图表 · 测试器 · 经济日历
路径C的MQL5侧写法大致是这样,注意两个细节:域名必须先加进"工具→选项→EA交易"的WebRequest白名单,否则会返回-1;另外WebRequest在策略测试器里不可用,只能用于实盘图表或脚本。
// 自建桥接:MQL5 侧直接调用外部模型HTTP接口
bool AskLLM(const string prompt, string &answer)
{
string url = "https://api.example-llm.com/v1/chat/completions";
string body = "{\"model\":\"your-model\",\"temperature\":0.2,\"messages\":["
"{\"role\":\"system\",\"content\":\"你是MQL5代码审查员\"},"
"{\"role\":\"user\",\"content\":\"" + EscapeJSON(prompt) + "\"}]}";
char post[], result[];
string reqHeaders = "Content-Type: application/json\r\n"
"Authorization: Bearer " + InpApiKey + "\r\n";
string respHeaders = "";
StringToCharArray(body, post, 0, StringLen(body), CP_UTF8);
ResetLastError();
int code = WebRequest("POST", url, reqHeaders, 15000, post, result, respHeaders);
if(code == -1)
{
PrintFormat("WebRequest 调用失败,错误码=%d(先检查URL白名单)", GetLastError());
return false;
}
answer = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
return (code == 200);
}
四、实战一:需求转代码——把"感觉"写成契约
同一个需求,两个人写的提示词能差出一个数量级的返工量。原因通常不是模型不够强,而是需求写得像愿望:"帮我写一个均线策略,带止损,风险控制好一点"。这句话里没有一条可验证的约束。
下面这个模板是我实际在用的结构,五个部分缺一不可:角色与边界、语言硬约束、接口契约、错误处理要求、自检清单。其中"语言硬约束"这一节是专门用来压制MQL4语法穿越的。
【角色】你是MQL5高级开发工程师,熟悉标准库与CTrade类。
【语言硬约束】
- 只允许MQL5语法。禁止出现OrderSend旧签名、OrderClose、MarketInfo、iATR四参数写法。
- 下单必须用CTrade或MqlTradeRequest,禁止手写结构体填充。
- 指标句柄只在OnInit创建、OnDeinit用IndicatorRelease释放。
- 所有CopyBuffer调用必须校验返回值,禁止直接下标访问未确认的数组。
【接口契约】
- 输入参数:快慢EMA周期、ATR周期、止损止盈ATR倍数、单笔风险百分比、魔术号。
- 手数由风险反算:lots = 风险金额 / (止损距离/最小报价单位*每手每跳价值)。
- 手数需按平台手数步长向下取整,低于平台手数下限则放弃开仓。
- 止损距离必须大于等于平台SYMBOL_TRADE_STOPS_LEVEL。
- 只在换K线时计算信号,禁止在tick级重复拉取指标。
【错误处理】
- OnInit任一指标句柄无效时返回INIT_FAILED。
- 下单失败必须打印retcode与注释,不得静默失败。
【输出格式】
- 单个.mq5文件,含#property与分区注释。
- 代码后可附一段不超过10行的要点说明。
【自检清单】(生成后逐条回答)
1. 是否存在MQL4专有函数?
2. 是否有未释放的指标句柄?
3. 是否所有数组访问前都确认了数据量?
4. 手数是否可能为0或为负?
5. 是否可能在已有持仓时重复开仓?
用这个模板生成的骨架基本可直接编译。下面是核心部分(删去了注释块与属性声明):
#include <Trade\Trade.mqh>
CTrade trade;
int hFast = INVALID_HANDLE, hSlow = INVALID_HANDLE, hAtr = INVALID_HANDLE;
int OnInit()
{
hFast = iMA(_Symbol, PERIOD_CURRENT, InpFastEMA, 0, MODE_EMA, PRICE_CLOSE);
hSlow = iMA(_Symbol, PERIOD_CURRENT, InpSlowEMA, 0, MODE_EMA, PRICE_CLOSE);
hAtr = iATR(_Symbol, PERIOD_CURRENT, InpAtrPeriod);
if(hFast == INVALID_HANDLE || hSlow == INVALID_HANDLE || hAtr == INVALID_HANDLE)
return INIT_FAILED;
trade.SetExpertMagicNumber(InpMagic);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(hFast != INVALID_HANDLE) IndicatorRelease(hFast);
if(hSlow != INVALID_HANDLE) IndicatorRelease(hSlow);
if(hAtr != INVALID_HANDLE) IndicatorRelease(hAtr);
}
bool ReadBuf(const int handle, const int shift, double &out)
{
double buf[];
if(CopyBuffer(handle, 0, shift, 1, buf) != 1) return false; // 返回值必须校验
out = buf[0];
return true;
}
double CalcLots(const double slDistance)
{
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tickSize <= 0.0 || tickValue <= 0.0 || slDistance <= 0.0) return 0.0;
double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * InpRiskPct / 100.0;
double lossPerLot = slDistance / tickSize * tickValue;
if(lossPerLot <= 0.0) return 0.0;
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minL = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxL = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lots = MathFloor(riskMoney / lossPerLot / step) * step; // 向下取整,宁少不多
if(lots < minL) return 0.0; // 低于平台手数下限就不开
if(lots > maxL) lots = maxL;
return NormalizeDouble(lots, 2);
}
bool HasPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionGetTicket(i) == 0) continue;
if(PositionGetInteger(POSITION_MAGIC) == (long)InpMagic &&
PositionGetString(POSITION_SYMBOL) == _Symbol)
return true;
}
return false;
}
void OnTick()
{
static datetime lastBar = 0;
datetime barTime = (datetime)SeriesInfoInteger(_Symbol, PERIOD_CURRENT, SERIES_LASTBAR_DATE);
if(barTime == lastBar) return; // 换bar才计算,避免tick级重复运算
lastBar = barTime;
if(HasPosition()) return;
double fast, slow, atr;
if(!ReadBuf(hFast, 1, fast) || !ReadBuf(hSlow, 1, slow) || !ReadBuf(hAtr, 1, atr)) return;
if(atr <= 0.0) return;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double stopLevel = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
if(InpAllowLong && fast > slow)
{
double sl = MathMin(ask - atr * InpAtrSL, ask - stopLevel); // 取更远的一端,满足平台距离
double tp = ask + atr * InpAtrTP;
double lots = CalcLots(ask - sl);
if(lots > 0.0) trade.Buy(lots, _Symbol, ask, sl, tp, "AtrTrend");
}
else if(!InpAllowLong && fast < slow)
{
double sl = MathMax(bid + atr * InpAtrSL, bid + stopLevel);
double tp = bid - atr * InpAtrTP;
double lots = CalcLots(sl - bid);
if(lots > 0.0) trade.Sell(lots, _Symbol, bid, sl, tp, "AtrTrend");
}
}
五、实战二:代码审查与重构——AI擅长"模式化错误"
把三五百行的老EA丢给模型做审查,效果比"从零写"稳定。原因很实在:审查有参照物,模型只需在已知的正确模式里做匹配,发挥空间小,跑偏概率也就小。而MQL5的坑高度模式化,翻来覆去就那么几类。
| 典型问题 | 错误写法 | 正确做法 |
|---|---|---|
| 指标句柄泄漏 | 在OnTick里调用iMA/iATR | OnInit创建一次,OnDeinit释放 |
| 把句柄当数值 | double ma = iMA(...) | 先取句柄,再CopyBuffer读值 |
| 数组越界 | CopyBuffer后直接buf[1] | 校验返回值等于期望数量 |
| MQL4语法穿越 | OrderClose / MarketInfo / iATR四参数 | CTrade / SymbolInfoDouble / iATR三参数 |
| 重复开仓 | 只看信号不看持仓 | 按魔术号+品种查重后再下单 |
| 除零与负手数 | 止损距离为0时直接算手数 | 先判距离与每手亏损是否为正 |
审查用的提示词不必长,但要把"只报可验证问题"钉死,否则模型会给你一堆"建议增加注释"式的正确废话,把真问题淹掉。
你是MQL5代码审查员。输入是一段MQL5源码,请只报告可验证的缺陷。
报告规则:
1. 每条问题必须给出:行号或函数名 → 问题类型 → 触发条件 → 修正代码。
2. 只报以下类别:句柄生命周期、数组与缓冲区越界、除零、
手数与保证金计算、重复开仓、MQL4语法残留、类型截断、逻辑死锁。
3. 不评论代码风格、命名、注释。没有缺陷就回答"未发现上述类别的缺陷"。
4. 不确定的条目单独列在"待人工确认",不要混进缺陷列表。
六、实战三:让智能体自己把测试跑起来
6180之后智能体能读测试报告、按条件启动优化;6230之后它能读EA输入参数、自己准备测试运行。两件事合起来,"配参数→跑优化→看结果→再调范围"这一轮循环不必再由你手动点。
落地绕不开.set文件。测试器参数集里每个可优化参数占一行,格式是"参数名=当前值||起始||步进||终止||是否参与优化"。这一步写对,智能体生成的配置就能直接被吃进去。
[TesterInputs]
InpFastEMA=20||10||2||40||Y
InpSlowEMA=50||40||5||120||Y
InpAtrSL=2.0||1.5||0.1||3.0||Y
InpAtrTP=4.0||3.0||0.2||6.0||Y
InpRiskPct=1.0||0.5||0.1||2.0||Y
InpMagic=20261110||0||0||0||N
InpAllowLong=true||false||0||true||N
让智能体生成这张表,提示词的关键是"给出理由"而非"给个范围":根据本EA的信号构造(双EMA交叉+ATR止损),为每个参数提出优化区间与步进并说明依据;步进须使组合数控制在可接受规模内;不参与优化的参数标注N。要求它说明依据,你才能判断区间是推理出来的还是随手编的。
七、避坑指南:AI幻觉在MQL5里的四种形态
AI能提效,不等于可以放手不管。MQL5的训练语料里混着大量MQL4内容,模型对两者边界并不总是清楚。实际踩下来,问题集中在四种形态。
| 形态 | 表现 | 拦截手段 |
|---|---|---|
| 语法穿越 | 混入OrderClose、MarketInfo、Bars变量、Point常量等MQL4写法 | 编译即报错;提示词里显式列黑名单 |
| 函数捏造 | 生成看似合理但根本不存在的内置函数或参数名 | 逐个对照官方文档核对签名 |
| 语义错位 | 语法全对、能编译,但把句柄当价格用、把序号当时间戳用 | 只能靠回测与日志核对,编译拦不住 |
| 边界失守 | 不校验返回值、不判空、不防除零,行情异常时崩溃 | 代码审查清单 + 异常行情段回测 |
对应的防御是四步:生成 → 编译 → 静态核对 → 回测与日志核对。后两步是重点,编译只能拦住前两种形态,后两种必须靠运行验证。编译可以脚本化,MetaEditor的命令行参数就够用:
# Windows 下用 MetaEditor 命令行编译并输出日志
"C:\Program Files\MetaTrader 5\metaeditor64.exe" /compile:"C:\...\MQL5\Experts\MyEA.mq5" /log:"C:\...\build.log"
# Linux/Mac 侧可用 Python 做批量编译 + 日志关键字扫描
for f in Experts/*.mq5; do
metaeditor64.exe /compile:"$f" /log:"build.log"
grep -i "error" build.log && echo "FAIL: $f"
done
八、效能评估:省下来的到底是哪段时间
网上的效率数字往往夸张。我在自己的项目里做过一次单次记录:同一个需求(双EMA+ATR风控的标准EA),分别用纯手写和人机协同各做一遍,记录各阶段耗时。样本只有一次,不能当统计结论,但比例关系有参考价值。
| 阶段 | 纯手写 | 人机协同 | 差异来源 |
|---|---|---|---|
| 需求梳理与参数设计 | 约40分钟 | 约25分钟 | 用提示词把约束先写清,减少后期返工 |
| 框架代码编写 | 约90分钟 | 约15分钟 | 骨架生成快,收益主要在这一段 |
| 风控与容错补全 | 约60分钟 | 约45分钟 | 仍需人工确认,AI只能提示不能担保 |
| 编译排错 | 约45分钟 | 约20分钟 | 报错解释与批量修正提速明显 |
| 测试配置与首轮回测 | 约30分钟 | 约10分钟 | 参数集与优化区间可由智能体代写 |
| 合计 | 约265分钟 | 约115分钟 | 压缩幅度集中在"打字"环节 |
更值得看的是"没省下的部分":风控与容错只从60分钟降到45分钟,降幅远小于框架代码。这符合预期——风险边界怎么定是判断题,不是检索题,模型没有你的账户规模、品种池与回撤容忍度这些前置信息。
九、收尾:把工作流拆成"可交出去的"和"必须自己守的"
回头看Build 6230,它做的事很朴素:把平台的操作面暴露给智能体。这对开发流程的意义不在"AI更聪明了",而在"验证环节可以自动化了"。过去AI能帮你写,写完还得你自己编译、跑测试、看日志;现在这一段开始能连起来。
但连起来之后,人的位置反而更清楚了。我的划分是这样的:可以交出去的——样板代码、参数集、编译排错、日志初筛、报告整理、跨文件检索;必须自己守的——开仓逻辑、止损止盈规则、资金管理、上线节奏、以及"这个策略在当前市场还成不成立"的判断。前者是体力,后者是责任。
如果你准备动手,建议的顺序是:先把现有EA的参数规范化(分组、注释、范围);再用内置助手做一次全量代码审查,挑出能编译但语义错位的地方;然后挑一个已跑通的小策略,生成参数集、跑优化、做样本外验证;流程跑顺了再接外部MCP客户端做批量处理。倒过来做,容易在工具链上耗掉热情。
工具会一直变,半年后可能又有新版本把这些能力重写一遍。但有一件事不会变:能不能把需求写成可验证的约束,能不能看出生成结果哪里不对——这两项能力,决定了你是工具的驾驶员,还是乘客。
扫码关注,获取更多EA开发干货
微信公众号
视频号
