如何在MQL5的EA中实现自优化(基础篇)
📘

如何在MQL5的EA中实现自优化(基础篇)

第 1/3 篇

让EA自己跟着市场改参数

把EA当成靠移动均线看趋势的交易员,它本来能跑,但如果能随市场状态自己调策略,就成了会学习的黑箱。自优化的核心,是交易跑完一轮后,EA根据最新市况重写下一轮的参数,而不是等人手改。 主要好处很直接:MT5上的EA能7×24小时盯着秒级机会,省掉持续人工监督;市场一变,参数自动跟,策略不中断。原文给出的背景数据是,这类思路的示例文章在2025年5月5日发布,浏览量1605,说明关注的人不少但远没烂大街。 坑也得认:最典型的是过度拟合近期数据,换个行情就拉胯;再就是代码复杂了之后CPU吃不消,参数切换时稳定性容易抖。外汇和贵金属杠杆高、跳空频繁,这种自动调参一旦过拟合,实盘回撤可能远超回测。 落地从最朴素的均线交叉开始——趋势市里它从没真正失效过。后面我们要把它改成能自优化的版本,而不是永远用死参数。

「自优化EA的库引入与参数骨架」

要让EA具备自优化能力,先得把底层工具链搭起来。Trade库提供下单、平仓等交易函数,ArrayObj库则用来管理对象动态数组——优化跑出来的多组结果就塞进这种数组里,方便后续按性能排序筛选。 输入参数全部用 input 修饰,交易者能在MT5的EA属性面板直接改,不用碰代码。默认快线周期10、慢线20、SMA方法、收盘价计算、0.1手、止损50点、止盈100点;另有自动优化开关、每5000 ticks触发一次优化、至少攒1000个数据点才允许跑优化。 全局句柄方面,CTrade实例负责实盘交易动作,fastMA_Handle和slowMA_Handle管两条均线的指标句柄,optimizationResults指针指向存优化结果的数组。tickCount用来数tick,决定是否到了重优化的节点。 优化范围写死为常量:快线周期5到50、步长1;慢线周期10到100、步长1。这个范围只是起点,做黄金和欧美时因波动特性不同,倾向根据品种单独调窄或放宽。外汇和贵金属杠杆高、滑点跳空频繁,回测最优不代表实盘能复现,参数需先在策略测试器用历史数据验证。

MQL5 / C++
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <Arrays\ArrayObj.mqh>
input class="type">int MA_Fast_Period = class="num">10;      class=class="str">"cmt">// Fast Moving Average Period
input class="type">int MA_Slow_Period = class="num">20;      class=class="str">"cmt">// Slow Moving Average Period
input ENUM_MA_METHOD MA_Method = MODE_SMA; class=class="str">"cmt">// Moving Average Method
input ENUM_APPLIED_PRICE Applied_Price = PRICE_CLOSE; class=class="str">"cmt">// Applied Price
input class="type">class="kw">double LotSize = class="num">0.1;         class=class="str">"cmt">// Lot Size
input class="type">int StopLoss = class="num">50;            class=class="str">"cmt">// Stop Loss in points
input class="type">int TakeProfit = class="num">100;         class=class="str">"cmt">// Take Profit in points
class=class="str">"cmt">// Optimization parameters
input class="type">bool AutoOptimize = false;    class=class="str">"cmt">// Enable Auto Optimization
input class="type">int OptimizationPeriod = class="num">5000; class=class="str">"cmt">// Number of ticks between optimizations
input class="type">int MinDataPoints = class="num">1000;     class=class="str">"cmt">// Minimum number of data points for optimization
CTrade trade;
class="type">int fastMA_Handle, slowMA_Handle;
class="type">class="kw">double fastMA[], slowMA[];
class="type">int tickCount = class="num">0;
CArrayObj* optimizationResults;
class=class="str">"cmt">// Optimization ranges
const class="type">int MA_Fast_Min = class="num">5, MA_Fast_Max = class="num">50, MA_Fast_Step = class="num">1;
const class="type">int MA_Slow_Min = class="num">10, MA_Slow_Max = class="num">100, MA_Slow_Step = class="num">1;

◍ EA三个生命周期钩子怎么落地

MT5 的 EA 跑起来靠三个系统回调撑住全程:OnInit 在加载图表瞬间触发,OnDeinit 在移除 EA 或终端关闭时清场,OnTick 则在每个报价跳变时被调用。把这三处写稳,后面双均线交叉的逻辑才有承载。 OnInit 里先建快慢两条 MA 的句柄,顺手 new 一个 CArrayObj 存优化结果。若 iMA 返回 INVALID_HANDLE,直接 return INIT_FAILED,EA 起不来——这能避免图表上挂个死脚本还以为在跑。 OnDeinit 必须 IndicatorRelease 掉两个句柄,并把 optimizationResults 判空后 delete 置 NULL。漏了这步,来回加载 EA 几次就可能看到内存句柄泄漏的报警。 OnTick 开头用 Bars(_Symbol,PERIOD_CURRENT) < MA_Slow_Period 卡掉历史不足的情况;CopyBuffer 只取 0 和 1 两根(最近两根)做交叉判断。若 AutoOptimize 开且 tickCount 累到 OptimizationPeriod,就跳 Optimize() 重置计数。交叉条件写死用 [1] 对 [0]:快线从下穿上为买、从上穿下为卖,止损止盈以 _Point 倍数展开。外汇与贵金属杠杆高,这类均线交叉信号在历史数据不足或震荡市中失效概率偏大,实盘前请在策略测试器跑足样本。

MQL5 / C++
class="type">int OnInit()
{
   class=class="str">"cmt">// Initialize MA handles
   fastMA_Handle = iMA(_Symbol, PERIOD_CURRENT, MA_Fast_Period, class="num">0, MA_Method, Applied_Price);
   slowMA_Handle = iMA(_Symbol, PERIOD_CURRENT, MA_Slow_Period, class="num">0, MA_Method, Applied_Price);
   
   if(fastMA_Handle == INVALID_HANDLE || slowMA_Handle == INVALID_HANDLE)
   {
      Print("Failed to create MA indicators");
      class="kw">return INIT_FAILED;
   }
   
   class=class="str">"cmt">// Initialize optimization results array
   optimizationResults = new CArrayObj();
   
   class="kw">return INIT_SUCCEEDED;
}
class="type">void OnDeinit(const class="type">int reason)
{
   class=class="str">"cmt">// Release MA handles
   IndicatorRelease(fastMA_Handle);
   IndicatorRelease(slowMA_Handle);
   
   class=class="str">"cmt">// Clean up optimization results
   if(optimizationResults != NULL)
   {
      class="kw">delete optimizationResults;
      optimizationResults = NULL;
   }
}
class="type">void OnTick()
{
   class=class="str">"cmt">// Check if we have enough bars to calculate MAs
   if(Bars(_Symbol, PERIOD_CURRENT) < MA_Slow_Period) class="kw">return;
   
   class=class="str">"cmt">// Copy MA values
   if(CopyBuffer(fastMA_Handle, class="num">0, class="num">0, class="num">2, fastMA) != class="num">2) class="kw">return;
   if(CopyBuffer(slowMA_Handle, class="num">0, class="num">0, class="num">2, slowMA) != class="num">2) class="kw">return;
   
   class=class="str">"cmt">// Auto Optimization
   if(AutoOptimize && ++tickCount >= OptimizationPeriod)
   {
      Optimize();
      tickCount = class="num">0;
   }
   
   class=class="str">"cmt">// Trading logic
   if(fastMA[class="num">1] <= slowMA[class="num">1] && fastMA[class="num">0] > slowMA[class="num">0])
   {
      class=class="str">"cmt">// Open buy position
      class="type">class="kw">double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      trade.Buy(LotSize, _Symbol, ask, ask - StopLoss * _Point, ask + TakeProfit * _Point);
   }
   else if(fastMA[class="num">1] >= slowMA[class="num">1] && fastMA[class="num">0] < slowMA[class="num">0])
   {
      class=class="str">"cmt">// Open sell position
      class="type">class="kw">double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      trade.Sell(LotSize, _Symbol, bid, bid + StopLoss * _Point, bid - TakeProfit * _Point);

MQL5 中 CSampleClass 的析构与释放

在 MQL5 面向对象写法里,用 new 申请的类实例不会随函数退出自动回收,必须显式调用 delete,否则会造成终端内存占用缓慢堆积。 上面这段收尾代码先对指针做空值判断,非空才执行 delete 并置 nullptr,是防止重复释放导致 MT5 报 'invalid pointer' 错误的标准写法。 实盘脚本里若 CSampleClass 对象在 OnDeinit 未被清理,多次加载 EA 后内存曲线可能每次多出几 KB 到几十 KB,开 MT5 的 'Experts' 日志配合任务管理器即可观察到该现象。

MQL5 / C++
   }
   }

「暴力遍历双均线参数的自优化实现」

EA 自优化的核心落在 Optimize() 上,思路很直接:把快、慢均线周期在给定区间内按步长全部跑一遍,属于典型的暴力搜索。慢周期小于等于快周期的组合会被直接跳过,因为双均线逻辑里慢线必须压在快线之上才有交叉意义。 每组有效参数都交给 TestParameters() 算出一个收益值,封装进 OptimizationResult 对象后塞进 optimizationResults 数组。全部测完再线性扫一遍数组,挑 profit 最大的那组作为最优解。 找到最优解后,代码会把 MA_Fast_Period / MA_Slow_Period 改写成新值,并释放旧指标句柄、用 iMA() 按当前品种和周期重建快慢线句柄。外汇与贵金属市场高杠杆、滑点随机,这种重优化结果在历史样本上表现好,实盘可能明显衰减,上 MT5 跑前先把 MA_Fast_Min/Max/Step 等宏定义核对清楚。 下面这段是 Optimize() 的主体,注意嵌套 for 的步长控制和最后的句柄重绑:

MQL5 / C++
class="type">void Optimize()
{
   Print("Starting optimization...");
   
   optimizationResults.Clear();
   
   class=class="str">"cmt">// Loop through all combinations of MA periods
   for(class="type">int fastPeriod = MA_Fast_Min; fastPeriod <= MA_Fast_Max; fastPeriod += MA_Fast_Step)
   {
      for(class="type">int slowPeriod = MA_Slow_Min; slowPeriod <= MA_Slow_Max; slowPeriod += MA_Slow_Step)
      {
         if(slowPeriod <= fastPeriod) class="kw">continue; class=class="str">"cmt">// Slow period should be greater than fast period
         
         class="type">class="kw">double profit = TestParameters(fastPeriod, slowPeriod);
         
         OptimizationResult* result = new OptimizationResult;
         result.fastPeriod = fastPeriod;
         result.slowPeriod = slowPeriod;
         result.profit = profit;
         
         optimizationResults.Add(result);
      }
   }
   
   class=class="str">"cmt">// Find the best result
   OptimizationResult* bestResult = NULL;
   for(class="type">int i = class="num">0; i < optimizationResults.Total(); i++)
   {
      OptimizationResult* currentResult = optimizationResults.At(i);
      if(bestResult == NULL || currentResult.profit > bestResult.profit)
      {
         bestResult = currentResult;
      }
   }
   
   if(bestResult != NULL)
   {
class=class="str">"cmt">// Update the EA parameters
      MA_Fast_Period = bestResult.fastPeriod;
      MA_Slow_Period = bestResult.slowPeriod;
      
      class=class="str">"cmt">// Update indicator handles
      IndicatorRelease(fastMA_Handle);
      IndicatorRelease(slowMA_Handle);
      fastMA_Handle = iMA(_Symbol, PERIOD_CURRENT, MA_Fast_Period, class="num">0, MA_Method, Applied_Price);
      slowMA_Handle = iMA(_Symbol, PERIOD_CURRENT, MA_Slow_Period, class="num">0, MA_Method, Applied_Price);
      
      Print("Optimization complete. New parameters: Fast MA = ", MA_Fast_Period, ", Slow MA = ", MA_Slow_Period);
   }
   else
   {
      Print("Optimization failed to find better parameters.");
   }
}

◍ 遍历结果挑出最优解

框架跑完参数遍历后,结果都塞进了 optimizationResults 容器里,下一步就是从中捞出表现最好的那一组。 上面这段逻辑先声明一个 bestResult 指针并置空,随后用 for 循环从索引 0 扫到 optimizationResults.Total()-1,逐次取出当前项 currentResult。 判定规则很直白:若 bestResult 还是空,或者当前项的 profit 大于已记录最优项的 profit,就把 bestResult 指向 currentResult。扫完一轮,bestResult 就是遍历范围内 profit 最高的结果。 在 MT5 里接这段时,注意 profit 字段必须已在 OptimizationResult 结构里正确赋值,否则比较会基于脏数据;外汇与贵金属回测含极高杠杆风险,该 profit 仅反映历史样本表现,实盘可能显著偏离。

MQL5 / C++
OptimizationResult* bestResult = NULL;
for(class="type">int i = class="num">0; i < optimizationResults.Total(); i++)
{
   OptimizationResult* currentResult = optimizationResults.At(i);
   if(bestResult == NULL || currentResult.profit > bestResult.profit)
   {
      bestResult = currentResult;
   }
}

常见问题

可以,在EA里引入自优化逻辑,按固定周期用历史数据重算参数,但外汇高风险,改完也可能继续亏,需先回测验证。
主要在初始化、每笔tick或定时、退出三个生命周期钩子里落地,骨架先留参数数组,再在定时钩子触发优化。
小布可对接你的品种页,把EA输出的参数组合和回测曲线做AIGC诊断,提示过拟合与样本外衰减风险,你再决定用不用。
用嵌套循环限定均线周期范围逐步算收益,跑完按 Sharpe 或净利排序取前几组,别全市场全周期暴力扫。
析构函数里释放申请的缓冲与句柄即可,MT终端会回收,但没写释放逻辑长期挂EA可能拖慢终端。