使用 SMA 和 EMA 自动优化止盈和指标参数的示例(基础篇)
用均线交叉自动调止盈与参数
在 MT5 里,SMA 与 EMA 的快慢线交叉不仅能给方向信号,还可以作为自动优化止盈位和指标周期的输入源。思路是:当快线穿越慢线时,重新计算止盈距离,并按近期波动重设均线周期。 原文给出的示例在 2025 年 3 月 27 日发布,累计查看 1348 次,说明这类把均线用作自适应参数的做法在实盘社群里有稳定需求。外汇与贵金属杠杆高,回测通过不代表 live 账户同效,参数自适应可能放大滑点风险。 落地时建议先开 MT5 策略测试器,用 EURUSD 的 H1 跑一遍,观察快线周期从 10 调到 20 时止盈触发率的变化,再决定是否写进 EA。
「EA 怎么把机器学习和均线揉在一起」
这套 EA 把 ONNX 机器学习模型和传统指标绑在一起做决策,核心目标工具是 #AAPL 股票,但参数层留了口子,换到其他品种也能跑。它用 SMA 定趋势方向,用周期动态优化的 EMA 补近期价格敏感度,两者叠加生成信号。 ATR 虽然在代码里没有显式写计算函数,但止损、止盈和动态手数都按波动性来推。也就是说,波动一大,仓位和风控距离会跟着变,不是写死的数字。 机器学习部分走 ONNX 模型:喂一串最近价格进去,吐出下一个价格走向的预测,给策略加一层预判。SMA、EMA、ATR 逻辑和 ONNX 输出在运行时按近期市况反复调参,再配上追踪止损,让系统在不同状态里都可能抓到交易机会。 外汇和贵金属自带高杠杆高风险,这套思路直接搬过去前,得先在 MT5 策略测试器里用历史数据验证参数适应性。
◍ EA 骨架与 ONNX 初始化怎么落地的
这套 EA 把传统均线和机器学习塞进同一个交易循环:SMA(20)、EMA(50) 做趋势参照,ATR 倍数管止损止盈,外加一个 ONNX 模型在 OnTick 里跑价格预测。初始化阶段最关键的一步是把静态编译进资源的模型文件读进内存并绑定输入输出维度,失败就直接 INIT_FAILED,不会带病启动。
代码里 #resource 把 model.EURUSD.D1.1_1_2024.onnx 以 uchar 数组形式打进 exe,OnInit 中 OnnxCreateFromBuffer 从缓冲区建句柄;输入形状写死为 {1, SAMPLE_SIZE, 1},输出 {1, 1},意味着每次喂一个样本、吐一个预测值。想换品种或周期,得连资源文件和 SAMPLE_SIZE 一起改,否则形状对不上会崩。
回测统计显示该策略夏普比率到过 6.21,但回撤幅度偏大,外汇和贵金属这类高杠杆品种上跑它,必须先把仓位和资金检查逻辑验清楚。建议直接把下面片段丢进 MT5 的 EA 模板,看 OnInit 能否在你本地终端正常建出 ExtHandle——建不出多半是 onnx 资源路径或编译器版本问题。
优化参数组里 OptimizationDays=1、LookbackPeriod=7 控制重优化的节奏,Min/MaxSMAPeriod 和 EMA 周期给的是 5~50 的搜索区间,实盘前最好先关掉自动优化用手动值跑两周,确认预测信号和均线过滤不打架再放开。
class="macro">#include <Trade\Trade.mqh> class="macro">#resource "/Files/model.EURUSD.D1.1_1_2024.onnx" as class="type">uchar ExtModel[]; input group "----- Indicators Parameters -----" class="type">int SMA_Period = class="num">20; class="type">int EMA_Period = class="num">50; input class="type">class="kw">double StopLossATR = class="num">1.5; input class="type">class="kw">double TakeProfitATR = class="num">3.0; input class="type">int OptimizationDays = class="num">1; class=class="str">"cmt">// Hours between optimizations input class="type">int LookbackPeriod = class="num">7; class=class="str">"cmt">// Hours loockback periods input class="type">int MinSMAPeriod = class="num">5; class=class="str">"cmt">// Period min para SMA input class="type">int MaxSMAPeriod = class="num">50; class=class="str">"cmt">// Periodo max para SMA input class="type">int MinEMAPeriod = class="num">5; class=class="str">"cmt">// Periodo min para EMA input class="type">int MaxEMAPeriod = class="num">50; class=class="str">"cmt">// Periodo max para EMA class="macro">#define MAGIC_SE class="num">12321 class="type">class="kw">datetime lastOptimizationTime = class="num">0; class="type">class="kw">double optimizedTakeProfit = class="num">0.0;class=class="str">"cmt">//InpTakeProfit; class="type">class="kw">double optimizedStopLoss = class="num">0.0;class=class="str">"cmt">//InpStopLoss; class="type">class="kw">double InpTakeProfit1 ; class="type">class="kw">double InpStopLoss1; class="type">int OnInit() { class=class="str">"cmt">//--- create a model from class="kw">static buffer ExtHandle = OnnxCreateFromBuffer(ExtModel, ONNX_DEFAULT); if(ExtHandle == INVALID_HANDLE) { Print("OnnxCreateFromBuffer error ", GetLastError()); class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- set input and output shapes const class="type">long input_shape[] = {class="num">1, SAMPLE_SIZE, class="num">1}; if(!OnnxSetInputShape(ExtHandle, ONNX_DEFAULT, input_shape)) { Print("OnnxSetInputShape error ", GetLastError()); class="kw">return(INIT_FAILED); } const class="type">long output_shape[] = {class="num">1, class="num">1}; if(!OnnxSetOutputShape(ExtHandle, class="num">0, output_shape)) { Print("OnnxSetOutputShape error ", GetLastError()); class="kw">return(INIT_FAILED); }
指标句柄与周期重优化的落地写法
EA 初始化时先拿 SMA 与 EMA 的句柄,iMA 调用里周期参数用 SMA_Period / EMA_Period 变量而非写死,方便后续动态改周期。若返回 INVALID_HANDLE 就打印 GetLastError() 并回 INIT_FAILED,这套判断能帮你当场抓出符号或周期不匹配的坑。 trade.SetDeviationInPoints(Slippage) 和 SetExpertMagicNumber(MAGIC_SE) 放在句柄之后,保证下单对象与魔数在优化前就绑定好。OptimizeIndicators() 与 OptimizeParameters() 在 init 末尾各跑一次,相当于首根 K 线前先把参数铺平。 OnDeinit 里除了释放 ONNX 外部模型句柄,必须用 IndicatorRelease 关掉 SMAHandle 与 EMAHandle,否则 MT5 会残留指标实例,多次加载 EA 可能拖慢终端。 OnTick 的前几行是重优化的节拍器:用 TimeCurrent() 减去上次时间,跨过 OptimizationDays * PeriodSeconds(PERIOD_H1) 就重算指标周期并重建句柄;另一个分支按 HoursAnalyze 小时的 H1 秒数重算交易参数。外汇与贵金属杠杆高,这类定时重优化若踩在重大数据发布前后,可能放大滑点与错价风险,建议先在策略测试器按历史跳空段回测。
SMAHandle = iMA(_Symbol, _Period, SMA_Period, class="num">0, MODE_SMA, PRICE_CLOSE); class=class="str">"cmt">// Ensure correct period if(SMAHandle == INVALID_HANDLE) { Print("Error initializing SMA indicator: ", GetLastError()); class="kw">return INIT_FAILED; } EMAHandle = iMA(_Symbol, _Period, EMA_Period, class="num">0, MODE_EMA, PRICE_CLOSE); class=class="str">"cmt">// Ensure correct index if(EMAHandle == INVALID_HANDLE) { Print("Error initializing EMA indicator: ", GetLastError()); class="kw">return INIT_FAILED; } trade.SetDeviationInPoints(Slippage); trade.SetExpertMagicNumber(MAGIC_SE); OptimizeIndicators(); OptimizeParameters(); class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { if(ExtHandle != INVALID_HANDLE) { OnnxRelease(ExtHandle); ExtHandle = INVALID_HANDLE; } IndicatorRelease(SMAHandle); IndicatorRelease(EMAHandle); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { if(IsMarketClosed()) class=class="str">"cmt">// Verificar si el mercado está cerrado { class="kw">return; class=class="str">"cmt">// Si el mercado está cerrado, no hacer nada } class="kw">static class="type">class="kw">datetime lastOptimizationTime2 = class="num">0; if(TimeCurrent() - lastOptimizationTime2 >= OptimizationDays * PeriodSeconds(PERIOD_H1)) { OptimizeIndicators(); lastOptimizationTime2 = TimeCurrent(); class=class="str">"cmt">// Actualizar los indicadores con los nuevos períodos IndicatorRelease(SMAHandle); IndicatorRelease(EMAHandle); SMAHandle = iMA(_Symbol, _Period, SMA_Period, class="num">0, MODE_SMA, PRICE_CLOSE); EMAHandle = iMA(_Symbol, _Period, EMA_Period, class="num">0, MODE_EMA, PRICE_CLOSE); } class=class="str">"cmt">//--- Optimización cada class="num">2 días if(TimeCurrent() - lastOptimizationTime >= PeriodSeconds(PERIOD_H1) * HoursAnalyze) { OptimizeParameters(); lastOptimizationTime = TimeCurrent(); } class=class="str">"cmt">//--- if(NewBarTS()==true)class=class="str">"cmt">//gather statistics and launch trailing stop
「平仓判据与新交易日切换的实现细节」
这段逻辑把前一根 K 线的高低点换算成点数,分别喂给多空两侧的止损止盈计算函数。open 取的是 TFTS 周期下第 1 根(已收盘)的开盘价,up 和 dn 各自记录上方/下方波动点数,buy_sl 与 sell_sl 由 CalcSL 反向取位,buy_tp 与 sell_tp 由 CalcTP 顺向取位。 trailing stop 分三档:Simple 走固定步长跟进,MoralExp 按道德期望动态调整,None 则直接 return 不做任何跟进。外汇与贵金属杠杆高,SL 点位计算偏差可能被放大,实盘前建议在策略测试器里把三档都跑一遍。 价格触及中性位或买卖边界时,循环遍历所有持仓并用 PositionClose 一刀平掉。判断条件是 bid==SLNeutral 或 bid<=SLBuy 或(SLSell>0 且 bid>=SLSell),也就是说卖方边界生效后才参与平卖。 新交易日用 TimeCurrent 对齐到 D1 起点后加一天来触发 GetMinMax 重算区间极值;新 BAR 则按当前周期秒数取整后推一根。ExtMin / ExtMax 随每根收盘价刷新,SMA 与 EMA 各取最新一个缓冲区值,最后交给 PredictPrice 出方向、CheckForOpen / CheckForClose 决定是否动手。
{
class="type">class="kw">double open=iOpen(_Symbol,TFTS,class="num">1);
CalcLvl(up,(class="type">int)MathRound((iHigh(_Symbol,TFTS,class="num">1)-open)/_Point));
CalcLvl(dn,(class="type">int)MathRound((open-iLow(_Symbol,TFTS,class="num">1))/_Point));
buy_sl=CalcSL(dn);
buy_tp=CalcTP(up);
sell_sl=CalcSL(up);
sell_tp=CalcTP(dn);
if(TypeTS==Simple)class=class="str">"cmt">//simple trailing stop
SimpleTS();
if(TypeTS==MoralExp)class=class="str">"cmt">//Moral expectation
METS();
if(TypeTS==None)class=class="str">"cmt">//None TS
class="kw">return;
}
class="type">class="kw">double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
if(bid==SLNeutral || bid<=SLBuy || (SLSell>class="num">0 && bid>=SLSell))
{
for(class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--)
{
class="type">ulong ticket=PositionGetTicket(i);
if(PositionSelectByTicket(ticket)==true)
trade.PositionClose(ticket);
}
}
class=class="str">"cmt">//---
class=class="str">"cmt">//--- check new day
if(TimeCurrent() >= ExtNextDay)
{
GetMinMax();
ExtNextDay = TimeCurrent();
ExtNextDay -= ExtNextDay % PeriodSeconds(PERIOD_D1);
ExtNextDay += PeriodSeconds(PERIOD_D1);
}
class=class="str">"cmt">//--- check new bar
if(TimeCurrent() < ExtNextBar)
class="kw">return;
ExtNextBar = TimeCurrent();
ExtNextBar -= ExtNextBar % PeriodSeconds();
ExtNextBar += PeriodSeconds();
class=class="str">"cmt">//--- check min and max
class="type">float close = (class="type">float)iClose(_Symbol, _Period, class="num">0);
if(ExtMin > close)
ExtMin = close;
if(ExtMax < close)
ExtMax = close;
class="type">class="kw">double sma[], ema[];class=class="str">"cmt">//, willr[];
CopyBuffer(SMAHandle, class="num">0, class="num">0, class="num">1, sma);
CopyBuffer(EMAHandle, class="num">0, class="num">0, class="num">1, ema);
class=class="str">"cmt">//CopyBuffer(WillRHandle, class="num">0, class="num">0, class="num">1, willr);
class=class="str">"cmt">//--- predict next price
PredictPrice();
class=class="str">"cmt">//--- check trading according to prediction and indicators
if(ExtPredictedClass >= class="num">0)
{
if(PositionSelect(_Symbol))
CheckForClose(sma[class="num">0], ema[class="num">0]);class=class="str">"cmt">//, willr[class="num">0]);
else
CheckForOpen(sma[class="num">0], ema[class="num">0]);class=class="str">"cmt">//, willr[class="num">0]);
}
}