使用 SMA 和 EMA 自动优化止盈和指标参数的示例·进阶篇
(2/3)· 当指标周期不再写死,EA 如何在 OnTick 里动态重算止盈与手数
「用分类信号卡住均线过滤的进场口」
EA 把模型输出的 ExtPredictedClass 当成方向开关:等于 0 倾向多,等于 2 倾向空,其余值不动作。光有分类还不够,代码里再用收盘价同时跌破/突破 SMA 与 EMA 做二层确认,避免模型单帧噪声直接触发下单。 CopyRates 只取最新 1 根 K 线(参数 0,0,1),ArraySetAsSeries 置为时间序列后 rates[0] 即当前柱。若 copied<=0 直接 Print 错误并 return,这一步能帮你当场抓到品种不可交易或历史数据未加载的问题。 信号成立且 TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) 为真才进入发单分支。注意 sl、tp 初始化为 0,代表本段逻辑暂不挂止损止盈——实盘接黄金或欧美这类高波动品种前,你得自己补风控参数,否则裸奔进场概率上很被动。 让小布替你跑这套:把 ExtPredictedClass 打印到日志,观察模型判 0/2 时 close 与双均线的位置关系,能快速验证过滤器是不是太松。
class="type">void CheckForOpen(class="type">class="kw">double sma, class="type">class="kw">double ema)class=class="str">"cmt">//, class="type">class="kw">double willr) { class="type">MqlRates rates[]; ArraySetAsSeries(rates,true); class="type">int copied = CopyRates(_Symbol,class="num">0,class="num">0,class="num">1,rates); if(copied <= class="num">0) { Print("Error copying rates: ", GetLastError()); class="kw">return; } class="type">class="kw">double Close[class="num">1]; Close[class="num">0]=rates[class="num">0].close; class="type">class="kw">double close = Close[class="num">0]; ENUM_ORDER_TYPE signal = WRONG_VALUE; Print("ExtPredictedClass ",ExtPredictedClass); class=class="str">"cmt">//--- check signals if(ExtPredictedClass == class="num">2)class=class="str">"cmt">//PRICE_DOWN) { Print("ExtPredictedClass Sell ",ExtPredictedClass); Print("close ",close, " sma ",sma, " ema ", ema); class=class="str">"cmt">// Venta if((close < sma && close < ema)) { signal = ORDER_TYPE_SELL; Print("Order Sell detected"); } } else if(ExtPredictedClass == class="num">0)class=class="str">"cmt">//PRICE_UP) { Print("ExtPredictedClass Buy ",ExtPredictedClass); Print("close ",close, " sma ",sma, " ema ", ema); class=class="str">"cmt">// Compra if((close > sma && close > ema)) { signal = ORDER_TYPE_BUY; Print("Order Buy detected"); } } class=class="str">"cmt">//--- open position if possible according to signal if(signal != WRONG_VALUE && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Print("Proceding open order"); class="type">class="kw">double price, sl=class="num">0, tp=class="num">0; class="type">class="kw">double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); class="type">class="kw">double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); class="type">MqlTradeRequest request = {}; class="type">MqlTradeResult result = {}; request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.deviation = Slippage; request.magic = MAGIC_SE;
卖单建仓里的 FOK 与 ATR 止损分支
这段逻辑只处理 signal 为 ORDER_TYPE_SELL 时的下单装配。先把成交方式钉死成 ORDER_FILLING_FOK,意味着挂出的卖单要么按指定价全量成交,要么直接废单,在流动性骤断的外汇/贵金属行情里能避免半成仓被动留缝,但也可能错过回补窗口。 手数走两条路:固定模式直接取 inp_lot_fix,动态模式交给 get_lot(price) 按账户风险算。CheckVolumeValue 不通过就 return,这是防止最小交易单位违规的最前闸门。 ATR 开关翻开时,止损放在 bid + StopLossATR*ATRValue,止盈放在 ask - TakeProfitATR*ATRValue,都用 NormalizeDouble 对齐 _Digits;同时 CheckMoneyForTrade 校验保证金,不够就打印西语提示并退出。若 ATR 关掉但代码里 !InpUseStops && ATR 仍为真,sl/tp 被置 0,否则走优化后的点值止损:sl=bid+InpStopLoss1*_Point,tp=ask-InpTakeProfit1*_Point。 下面这段是原文里卖单分支的 MQL5 片段,逐行拆完你能直接抄进 EA 的 OrderSend 前装配段。外汇与贵金属杠杆高,FOK 拒单率会随点差跳涨而上升,实盘前务必在 MT5 策略测试器用 Tick 级数据跑一遍。
request.type_filling = ORDER_FILLING_FOK; class=class="str">"cmt">//request.comment = "AKWr"; class="type">class="kw">double lotaje; if(signal == ORDER_TYPE_SELL) { price = bid; Print("Price: ",price); if(inp_lot_type == LOT_TYPE_FIX) lotaje=inp_lot_fix ; else lotaje=get_lot(price); if(!CheckVolumeValue(lotaje)) class="kw">return; if(!InpUseStops && ATR) { sl = NormalizeDouble(bid + StopLossATR * ATRValue, _Digits); tp = NormalizeDouble(ask - TakeProfitATR * ATRValue, _Digits); if(!CheckMoneyForTrade(_Symbol, lotaje,ORDER_TYPE_SELL)) { Print("No hay suficiente margen para abrir la posición"); class="kw">return; } request.type = ORDER_TYPE_SELL; request.price = SymbolInfoDouble(_Symbol, SYMBOL_BID); request.volume = lotaje; request.sl = sl; request.tp = tp; request.comment = "SEW Opened sell order"; } if(!InpUseStops && ATR) { sl = class="num">0; tp = class="num">0; } else { InpTakeProfit1 =optimizedTakeProfit; InpStopLoss1= optimizedStopLoss; sl = NormalizeDouble(bid + InpStopLoss1*_Point, _Digits); tp = NormalizeDouble(ask - InpTakeProfit1*_Point, _Digits); } } else { price = ask; Print("Price: ",price); if(inp_lot_type == LOT_TYPE_FIX) lotaje=inp_lot_fix ; else lotaje=get_lot(price); if(!CheckVolumeValue(lotaje)) class="kw">return; if(!InpUseStops) { sl = NormalizeDouble(ask - StopLossATR * ATRValue, _Digits);
◍ 止损止盈与下单前的保证金闸门
这段逻辑先把 ATR 倍数换算成目标价:tp 用 NormalizeDouble 把 bid 加上 TakeProfitATR*ATRValue 对齐到 _Digits 小数位,避免跨品种点数精度错配。若 InpUseStops 关闭但 ATR 生效,sl 和 tp 直接置 0,走无硬止损的通道;否则用优化后的 StopLoss、TakeProfit 乘 _Point 换算,买单 sl 挂在 ask 下方、tp 挂在 bid 上方。 每次发单前都跑 CheckMoneyForTrade 校验保证金,西班牙语报错“No hay suficiente margen”只是日志提示,真实拦截靠 return 中止。外汇与贵金属杠杆高,保证金不足会直接废单,不是滑点能解释的。 下单结构体 request 填完 type/price/volume/sl/tp/comment 后走 OrderSend,失败就 Print 出 GetLastError 码。开 MT5 把这段代码塞进 EA 测试前,先确认 InpUseStops 和 ATR 开关组合,不然 sl/tp 会被静默清零。
tp = NormalizeDouble(bid + TakeProfitATR * ATRValue, _Digits); if(!CheckMoneyForTrade(_Symbol, lotaje,ORDER_TYPE_BUY)) { Print("No hay suficiente margen para abrir la posición"); class="kw">return; } request.type = ORDER_TYPE_BUY; request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); request.volume = lotaje; request.sl = sl; request.tp = tp; request.comment = "SEW Opened buy order"; } if(!InpUseStops && ATR) { sl = class="num">0; tp = class="num">0; } else { InpTakeProfit1 =optimizedTakeProfit; InpStopLoss1= optimizedStopLoss; sl = NormalizeDouble(ask - InpStopLoss1*_Point, _Digits); tp = NormalizeDouble(bid + InpTakeProfit1*_Point, _Digits); } } Print("No InpUseStops used"); class=class="str">"cmt">//ExtTrade.PositionOpen(_Symbol, signal, lotaje, price, sl, tp); if(!CheckMoneyForTrade(_Symbol, lotaje, (ENUM_ORDER_TYPE)signal)) { Print("No hay suficiente margen para abrir la posición"); class="kw">return; } Print("Volume ", lotaje); request.type = signal; request.price = price;class=class="str">"cmt">//SymbolInfoDouble(_Symbol, SYMBOL_ASK); request.volume = lotaje; request.sl = sl; request.tp = tp; request.comment = "SEW"; if(!OrderSend(request, result)) { Print("Error opening the order: ", GetLastError()); class="kw">return; } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check for close position conditions | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CheckForClose(class="type">class="kw">double sma, class="type">class="kw">double ema)class=class="str">"cmt">//, class="type">class="kw">double willr) { if(InpUseStops) class="kw">return; class="type">bool bsignal = false; class=class="str">"cmt">//--- position already selected before
「用 ONNX 预测价反手平仓的实盘逻辑」
持仓反手信号来自模型对后一帧价格的分类。若持多单且预测类别为 2(价格下行),或持空单且预测类别为 0(价格上行),bsignal 置真,随后在终端允许交易的前提下平掉当前符号仓位并触发开仓检查。 PredictPrice 先把近 SAMPLE_SIZE 根收盘价做 min-max 归一化,再丢进 ONNX 推理。推理失败时 Print 报错并把 ExtPredictedClass 设为 -1,避免用脏数据交易。输出还原为真实价后,用 last_close 与 predicted 的差值判定 PRICE_UP / DOWN / SAME,阈值写死在 0.00001——外汇与贵金属波动小,这个精度在 5 位数报价下可能偏紧。 OptimizeParameters 用双层循环扫 TP、SL:tp 从 65 到 500、sl 从 65 到 500,步长均 5 点。也就是说总共约 88×88 ≈ 7744 次 TestStrategy 调用,EA 回测优化时这块最吃 CPU,建议先在策略测试器里缩小样本再跑。 小布盯盘接这套逻辑时,可把 ONNX 模型换成自己的轻量网络,但归一化上下界 ExtMin/ExtMax 必须和训练时一致,否则 Predicted Class 会系统性偏移。外汇贵金属杠杆高,信号仅作概率参考,实盘前务必用 MT5 历史数据复跑一遍优化区间。
class="type">long type = PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- check signals if(type == POSITION_TYPE_BUY && ExtPredictedClass == class="num">2)class=class="str">"cmt">//PRICE_DOWN) bsignal = true; if(type == POSITION_TYPE_SELL && ExtPredictedClass == class="num">0)class=class="str">"cmt">//PRICE_UP) bsignal = true; class=class="str">"cmt">//--- close position if possible if(bsignal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { ExtTrade.PositionClose(_Symbol); CheckForOpen(sma, ema);class=class="str">"cmt">//, willr); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Predict next price | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void PredictPrice(class="type">void) { class="kw">static vectorf output_data(class="num">1); class="kw">static vectorf x_norm(SAMPLE_SIZE); if(ExtMin >= ExtMax) { Print("ExtMin >= ExtMax"); ExtPredictedClass = -class="num">1; class="kw">return; } if(!x_norm.CopyRates(_Symbol, _Period, COPY_RATES_CLOSE, class="num">1, SAMPLE_SIZE)) { Print("CopyRates ", x_norm.Size()); ExtPredictedClass = -class="num">1; class="kw">return; } class="type">float last_close = x_norm[SAMPLE_SIZE - class="num">1]; x_norm -= ExtMin; x_norm /= (ExtMax - ExtMin); if(!OnnxRun(ExtHandle, ONNX_NO_CONVERSION, x_norm, output_data)) { Print("OnnxRun"); ExtPredictedClass = -class="num">1; class="kw">return; } class="type">float predicted = output_data[class="num">0] * (ExtMax - ExtMin) + ExtMin; class="type">float delta = last_close - predicted; if(fabs(delta) <= class="num">0.00001) ExtPredictedClass = PRICE_SAME; else if(delta < class="num">0) ExtPredictedClass = PRICE_UP; else ExtPredictedClass = PRICE_DOWN; class=class="str">"cmt">// Debugging output Print("Predicted price: ", predicted, " Delta: ", delta, " Predicted Class: ", ExtPredictedClass); } class="type">void OptimizeParameters() { class="type">class="kw">double bestTakeProfit = InpTakeProfit1; class="type">class="kw">double bestStopLoss = InpStopLoss1; class="type">class="kw">double bestPerformance = -DBL_MAX; for(class="type">int tp = class="num">65; tp <= class="num">500; tp += class="num">5) class=class="str">"cmt">// rango de TakeProfit { for(class="type">int sl = class="num">65; sl <= class="num">500; sl += class="num">5) class=class="str">"cmt">// rango de StopLoss { class="type">class="kw">double performance = TestStrategy(tp, sl);
把止损止盈和均线周期交给回测去挑
上面这段逻辑干的事很直接:在双层循环里挨个试 TakeProfit、StopLoss 组合,只要当前 performance 跑赢历史最优,就把对应的 tp、sl 记到 best 变量里。外层循环扫 tp,内层扫 sl,最后把胜出的数值写进 optimizedTakeProfit 与 optimizedStopLoss,并用 Print 打到日志,方便你进 MT5 终端的 Experts 标签核对。 指标周期也是同一套思路。OptimizeIndicators 先用 TimeCurrent 减去 LookbackPeriod 乘 H1 的秒数圈定回测窗口,再对 SMA 与 EMA 周期做嵌套遍历,调用 TestIndicatorPerformance 打分,保留 bestPerformance 最高的那对周期。外汇与贵金属波动受时段影响大,这种遍历式寻优只能给出历史样本里概率占优的参数,实盘仍可能失效,属高风险操作。 IsMarketClosed 用 TimeToStruct 拆出星期与小时,周末直接返回 true;若小时落在 after 到 before 的空白时段也判为休市,具体边界要按你盯的品种时区改。CheckVolumeValue 则拿 SymbolInfoDouble 取 SYMBOL_VOLUME_MIN,手数低于下限直接 false,避免报单被拒。 开 MT5 把这几段塞进 EA,先把 MinSMAPeriod、MaxEMAPeriod 和 after、before 按自己品种填好,跑一轮优化日志,看看打印出来的最优 SL/TP 和均线周期是否和肉眼看的支撑阻力对得上。
if(performance > bestPerformance) { bestPerformance = performance; bestTakeProfit = tp; bestStopLoss = sl; class=class="str">"cmt">//Print("Best Take Profit",bestTakeProfit); class=class="str">"cmt">//Print("Best Stop Loss",bestStopLoss); } } } optimizedTakeProfit = bestTakeProfit; optimizedStopLoss = bestStopLoss; Print("Optimized TakeProfit: ", optimizedTakeProfit); Print("Optimized StopLoss: ", optimizedStopLoss); } class="type">void OptimizeIndicators() { class="type">class="kw">datetime startTime = TimeCurrent() - LookbackPeriod * PeriodSeconds(PERIOD_H1); class="type">class="kw">datetime endTime = TimeCurrent(); class="type">int bestSMAPeriod = SMA_Period; class="type">int bestEMAPeriod = EMA_Period; class="type">class="kw">double bestPerformance = -DBL_MAX; for(class="type">int smaPeriod = MinSMAPeriod; smaPeriod <= MaxSMAPeriod; smaPeriod++) { for(class="type">int emaPeriod = MinEMAPeriod; emaPeriod <= MaxEMAPeriod; emaPeriod++) { class="type">class="kw">double performance = TestIndicatorPerformance(smaPeriod, emaPeriod, startTime, endTime); if(performance > bestPerformance) { bestPerformance = performance; bestSMAPeriod = smaPeriod; bestEMAPeriod = emaPeriod; } } } SMA_Period = bestSMAPeriod; EMA_Period = bestEMAPeriod; Print("Optimized SMA Period: ", SMA_Period); Print("Optimized EMA Period: ", EMA_Period); } class="type">bool IsMarketClosed() { class="type">class="kw">datetime currentTime = TimeCurrent(); class="type">MqlDateTime tm; TimeToStruct(currentTime, tm); class="type">int dayOfWeek = tm.day_of_week; class="type">int hour = tm.hour; class=class="str">"cmt">// Verifica si es fin de semana if(dayOfWeek <= Sunday || dayOfWeek >= Saturday) { class="kw">return true; } class=class="str">"cmt">// Verifica si está fuera del horario habitual de mercado(ejemplo: class="num">21:class="num">00 a class="num">21:class="num">59 UTC) if(hour >= after || hour < before) class=class="str">"cmt">// Ajusta estos valores según el horario del mercado { class="kw">return true; } class="kw">return false; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check the correctness of the order volume | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CheckVolumeValue(class="type">class="kw">double volume)class=class="str">"cmt">//,class="type">class="kw">string &description) { class=class="str">"cmt">//--- minimal allowed volume for trade operations class="type">class="kw">double min_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); if(volume<min_volume) { class=class="str">"cmt">//description=StringFormat("Volume is less than the minimal allowed SYMBOL_VOLUME_MIN=%.2f",min_volume); class="kw">return(false); } class=class="str">"cmt">//--- maximal allowed volume of trade operations