MQL5 交易策略自动化(第十部分):开发趋势盘整动量策略·进阶篇
(2/3)· 用11/25均线交叉搭趋势骨架,再让RSI(27)与CCI(36/55)替你筛掉假突破,EA自动进场
◍ 指标句柄初始化与资源释放的硬约束
在 MT5 的 EA 初始化阶段,任何指标句柄创建失败都必须立刻返回 INIT_FAILED,否则后续取数会直接读到空序列。上面这段把 CCI(55)、RSI(InpRSIPeriod)、快速 SMMA(中位价, InpMAFastPeriod)、慢速 SMMA(中位价, InpMASlowPeriod) 全部用 iCCI / iRSI / iMA 建了句柄,并逐个用 INVALID_HANDLE 判空。 建完句柄后那几行 ArraySetAsSeries(..., true) 是关键:把 ma11、ma25、rsi、cci36、cci55 的缓冲区都设成时间序列,索引 0 才对应「最新一根已收盘 K 线」。若漏掉这步,你用 buffer[1] 以为看前一根,实际可能取到倒数第二根以后的数据,回测和实盘信号会错位。 OnDeinit 里只释放了 CCI36 和 CCI55 两个句柄,用 IndicatorRelease 交还系统资源。外汇与贵金属杠杆高,EA 卸载时句柄泄漏可能拖慢终端;建议把 RSI、MA 句柄也补进释放逻辑,保持对称。
Print("Error creating CCI55 handle"); class=class="str">"cmt">//--- Print an error message if invalid. class="kw">return (INIT_FAILED); class=class="str">"cmt">//--- Return failure if handle creation failed. } class=class="str">"cmt">//--- Create RSI handle for period InpRSIPeriod class="kw">using the close price. handleRSI = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE); class=class="str">"cmt">//--- Create the RSI indicator handle. if (handleRSI == INVALID_HANDLE) { class=class="str">"cmt">//--- Check if the RSI handle is valid. Print("Error creating RSI handle"); class=class="str">"cmt">//--- Print an error message if invalid. class="kw">return (INIT_FAILED); class=class="str">"cmt">//--- Return failure if handle creation failed. } class=class="str">"cmt">//--- Create fast MA handle class="kw">using MODE_SMMA on the median price with period InpMAFastPeriod. handleMA11 = iMA(_Symbol, _Period, InpMAFastPeriod, class="num">0, MODE_SMMA, PRICE_MEDIAN); class=class="str">"cmt">//--- Create the fast MA handle. if (handleMA11 == INVALID_HANDLE) { class=class="str">"cmt">//--- Check if the fast MA handle is valid. Print("Error creating MA11 handle"); class=class="str">"cmt">//--- Print an error message if invalid. class="kw">return (INIT_FAILED); class=class="str">"cmt">//--- Return failure if handle creation failed. } class=class="str">"cmt">//--- Create slow MA handle class="kw">using MODE_SMMA on the median price with period InpMASlowPeriod. handleMA25 = iMA(_Symbol, _Period, InpMASlowPeriod, class="num">0, MODE_SMMA, PRICE_MEDIAN); class=class="str">"cmt">//--- Create the slow MA handle. if (handleMA25 == INVALID_HANDLE) { class=class="str">"cmt">//--- Check if the slow MA handle is valid. Print("Error creating MA25 handle"); class=class="str">"cmt">//--- Print an error message if invalid. class="kw">return (INIT_FAILED); class=class="str">"cmt">//--- Return failure if handle creation failed. } class=class="str">"cmt">//--- Set the dynamic arrays as time series(index class="num">0 = most recent closed bar). ArraySetAsSeries(ma11_buffer, true); class=class="str">"cmt">//--- Set ma11_buffer as a time series. ArraySetAsSeries(ma25_buffer, true); class=class="str">"cmt">//--- Set ma25_buffer as a time series. ArraySetAsSeries(rsi_buffer, true); class=class="str">"cmt">//--- Set rsi_buffer as a time series. ArraySetAsSeries(cci36_buffer, true); class=class="str">"cmt">//--- Set cci36_buffer as a time series. ArraySetAsSeries(cci55_buffer, true); class=class="str">"cmt">//--- Set cci55_buffer as a time series. class="kw">return (INIT_SUCCEEDED); class=class="str">"cmt">//--- Return success after initialization. } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { if (handleCCI36 != INVALID_HANDLE) { class=class="str">"cmt">//--- If the CCI36 handle is valid, IndicatorRelease(handleCCI36); class=class="str">"cmt">//--- release the CCI36 indicator. } if (handleCCI55 != INVALID_HANDLE) { class=class="str">"cmt">//--- If the CCI55 handle is valid, IndicatorRelease(handleCCI55); class=class="str">"cmt">//--- release the CCI55 indicator. }
指标句柄释放与新K线取值逻辑
EA 退出或重初始化时,必须逐个检查指标句柄是否为 INVALID_HANDLE,再调用 IndicatorRelease 释放。遗漏释放会留下内存中的指标缓存,MT5 长时间跑多品种监控时可能拖慢终端响应。 OnTick 里先用 iTime(_Symbol, _Period, 0) 取当前_bar时间,与 lastBarTime 比对。只有两者不等才视为新_bar成型,此时更新时间戳并触发 OnNewBar,这样技术指标只在收盘价固定抓取,避免 tick 级抖动造成重复计算。 OnNewBar 开头用 ArrayResize 把 ma11、ma25、rsi、cci36、cci55 五个动态数组都重定成 2 个元素,对应上一根和再前一根收盘价。随后对每个句柄调 CopyBuffer,起始位置填 1、取 2 根,若返回值不等于 2 直接 return,防止脏数据进入后续信号判断。 开 MT5 把这段贴进自己 EA 的 deinit 和 tick 框架,重点看 CopyBuffer 第三参数设为 1 后,数组[0]是否真的对应已收盘的前一根——这是很多新手把实时_bar误当信号源的出错点。
if (handleRSI != INVALID_HANDLE) { IndicatorRelease(handleRSI); } if (handleMA11 != INVALID_HANDLE) { IndicatorRelease(handleMA11); } if (handleMA25 != INVALID_HANDLE) { IndicatorRelease(handleMA25); } class="type">void OnTick() { class="type">class="kw">datetime currentBarTime = iTime(_Symbol, _Period, class="num">0); if (currentBarTime != lastBarTime) { lastBarTime = currentBarTime; OnNewBar(); } } class="type">void OnNewBar() { ArrayResize(ma11_buffer, class="num">2); ArrayResize(ma25_buffer, class="num">2); ArrayResize(rsi_buffer, class="num">2); ArrayResize(cci36_buffer, class="num">2); ArrayResize(cci55_buffer, class="num">2); if (CopyBuffer(handleMA11, class="num">0, class="num">1, class="num">2, ma11_buffer) != class="num">2) { class="kw">return; } if (CopyBuffer(handleMA25, class="num">0, class="num">1, class="num">2, ma25_buffer) != class="num">2) { class="kw">return; } if (CopyBuffer(handleRSI, class="num">0, class="num">1, class="num">2, rsi_buffer) != class="num">2) { class="kw">return; } if (CopyBuffer(handleCCI36, class="num">0, class="num">1, class="num">2, cci36_buffer) != class="num">2) { class="kw">return; }
「把多指标信号拆成可验证的买入判定」
这段逻辑先把 CCI55 的两根缓冲值拷进来:从指标句柄偏移 1 取 2 根,若返回值不等于 2 就直接 return,避免后续数组越界。注意这里取的是已收盘 bar,索引 0 是最后一根收盘、索引 1 是前一根,和实时 tick 无关。 随后把 MA11、MA25、RSI、CCI36、CCI55 的收盘值分别赋给带 _current / _previous 后缀的变量。快线 MA11 上穿慢线 MA25 的判定写成 (ma11_previous < ma25_previous) && (ma11_current > ma25_current),这是典型的收盘价交叉,不含未来函数。 买入过滤条件分三层:RSI 须高于外部阈值 InpRSIThreshold、CCI36 与 CCI55 当前值均须大于 0。任何一层不满足就 Print 出具体数值并置 conditionsOk=false,方便在 MT5 专家日志里逐条排查哪根线卡住了。 全部通过后,止损不是固定点数,而是调 GetPivotLeft/PivotRight 算出的摆动低点;若返回小于等于 0 才退而求其次用 SYMBOL_BID 当前买价兜底。外汇与贵金属波动剧烈,这种基于枢轴的止损可能被瞬间毛刺扫掉,实盘前务必在策略测试器里跑至少 3 个月 tick 数据验证。
if (CopyBuffer(handleCCI55, class="num">0, class="num">1, class="num">2, cci55_buffer) != class="num">2) { class=class="str">"cmt">//--- Copy class="num">2 values from the CCI55 indicator. class="kw">return; class=class="str">"cmt">//--- Exit the function if copying fails. } class=class="str">"cmt">//--- For clarity, assign the values from the arrays. class=class="str">"cmt">//--- Index class="num">0: last closed bar, Index class="num">1: bar before. class="type">class="kw">double ma11_current = ma11_buffer[class="num">0]; class=class="str">"cmt">//--- Fast MA value for the last closed bar. class="type">class="kw">double ma11_previous = ma11_buffer[class="num">1]; class=class="str">"cmt">//--- Fast MA value for the previous bar. class="type">class="kw">double ma25_current = ma25_buffer[class="num">0]; class=class="str">"cmt">//--- Slow MA value for the last closed bar. class="type">class="kw">double ma25_previous = ma25_buffer[class="num">1]; class=class="str">"cmt">//--- Slow MA value for the previous bar. class="type">class="kw">double rsi_current = rsi_buffer[class="num">0]; class=class="str">"cmt">//--- RSI value for the last closed bar. class="type">class="kw">double cci36_current = cci36_buffer[class="num">0]; class=class="str">"cmt">//--- CCI36 value for the last closed bar. class="type">class="kw">double cci55_current = cci55_buffer[class="num">0]; class=class="str">"cmt">//--- CCI55 value for the last closed bar. } class=class="str">"cmt">//--- Check for Buy Conditions: class="type">bool maCrossoverBuy = (ma11_previous < ma25_previous) && (ma11_current > ma25_current); class=class="str">"cmt">//--- True if fast MA crosses above slow MA. class="type">bool rsiConditionBuy = (rsi_current > InpRSIThreshold); class=class="str">"cmt">//--- True if RSI is above the Buy threshold. class="type">bool cci36ConditionBuy = (cci36_current > class="num">0); class=class="str">"cmt">//--- True if CCI36 is positive. class="type">bool cci55ConditionBuy = (cci55_current > class="num">0); class=class="str">"cmt">//--- True if CCI55 is positive. if (maCrossoverBuy) { class=class="str">"cmt">//--- If crossover for MA Buy is true... class="type">bool conditionsOk = true; class=class="str">"cmt">//--- Initialize a flag to track if all conditions are met. class=class="str">"cmt">//--- Check RSI condition for Buy. if (!rsiConditionBuy) { class=class="str">"cmt">//--- If the RSI condition is not met... Print("Buy signal rejected: RSI condition not met. RSI=", rsi_current, " Threshold=", InpRSIThreshold); class=class="str">"cmt">//--- Notify the user. conditionsOk = class="kw">false; class=class="str">"cmt">//--- Mark the conditions as not met. } class=class="str">"cmt">//--- Check CCI36 condition for Buy. if (!cci36ConditionBuy) { class=class="str">"cmt">//--- If the CCI36 condition is not met... Print("Buy signal rejected: CCI36 condition not met. CCI36=", cci36_current); class=class="str">"cmt">//--- Notify the user. conditionsOk = class="kw">false; class=class="str">"cmt">//--- Mark the conditions as not met. } class=class="str">"cmt">//--- Check CCI55 condition for Buy. if (!cci55ConditionBuy) { class=class="str">"cmt">//--- If the CCI55 condition is not met... Print("Buy signal rejected: CCI55 condition not met. CCI55=", cci55_current); class=class="str">"cmt">//--- Notify the user. conditionsOk = class="kw">false; class=class="str">"cmt">//--- Mark the conditions as not met. } if (conditionsOk) { class=class="str">"cmt">//--- If all Buy conditions are met... class=class="str">"cmt">//--- Get stop loss from previous swing low. class="type">class="kw">double stopLoss = GetPivotLow(PivotLeft, PivotRight); class=class="str">"cmt">//--- Use pivot low as the stop loss. if (stopLoss <= class="num">0) { class=class="str">"cmt">//--- If no valid pivot low is found... stopLoss = SymbolInfoDouble(_Symbol, SYMBOL_BID); class=class="str">"cmt">//--- Fallback to current bid price. }
◍ 用摆动低点做止损的买单开仓逻辑
这段逻辑演示了在出现买点信号时,如何把近期摆动低点(pivot low)直接当作止损位,并以固定点数推出口价。外汇与贵金属杠杆高,实盘前务必在 MT5 策略测试器用历史数据验证,避免盲目跟单。 double entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); 取当前卖价(ask)作为入场价,保证市价买单成交参考的是实时卖一价。 double tp = entryPrice + InpTakeProfitPoints * _Point; 用输入参数里的点数乘最小变动单位 _Point 算止盈,比如 InpTakeProfitPoints=200、_Point=0.00001 时,tp 距入场 20 点。 GetPivotLow 函数从 CopyRates 复制最近 100 根 K 线开始,若数据量不足 left+right 根直接返回 0,意味着没找到有效摆动低点。随后用 left 根左侧、right 根右侧的邻柱比较,只有当当前柱 low 严格小于所有邻居 low 时才判定为 pivot low 并返回该值,可作为止损参考。 开仓部分用 obj_Trade.Buy 尝试下单,成功打印入场价,失败则输出 ResultRetcodeDescription 的错误描述,方便排查订单被拒原因(如保证金不足或报价过期)。
class="type">class="kw">double entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); class=class="str">"cmt">//--- Determine entry price as current ask price. class="type">class="kw">double tp = entryPrice + InpTakeProfitPoints * _Point; class=class="str">"cmt">//--- Calculate take profit based on fixed points. class=class="str">"cmt">//--- Print the swing point(pivot low) used as stop loss. Print("Buy signal: Swing Low used as Stop Loss = ", stopLoss); class=class="str">"cmt">//--- Notify the user of the pivot low used. if (obj_Trade.Buy(InpLotSize, NULL, entryPrice, stopLoss, tp, "Buy Order")) { class=class="str">"cmt">//--- Attempt to open a Buy order. Print("Buy order opened at ", entryPrice); class=class="str">"cmt">//--- Notify the user if the order is opened successfully. } else { Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription()); class=class="str">"cmt">//--- Notify the user if the order fails. } class="kw">return; class=class="str">"cmt">//--- Exit after processing a valid Buy signal. } else { Print("Buy signal not executed due to failed condition(s)."); class=class="str">"cmt">//--- Notify the user if Buy conditions failed. } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function to find the most recent swing low(pivot low) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double GetPivotLow(class="type">int left, class="type">int right) { class="type">MqlRates rates[]; class=class="str">"cmt">//--- Declare an array to store rate data. class="type">int copied = CopyRates(_Symbol, _Period, class="num">0, class="num">100, rates); class=class="str">"cmt">//--- Copy the last class="num">100 bars into the rates array. if (copied <= (left + right)) { class=class="str">"cmt">//--- Check if sufficient data was copied. class="kw">return (class="num">0); class=class="str">"cmt">//--- Return class="num">0 if there are not enough bars. } class=class="str">"cmt">//--- Loop through the bars to find a pivot low. for (class="type">int i = left; i <= copied - right - class="num">1; i++) { class="type">bool isPivot = true; class=class="str">"cmt">//--- Assume the current bar is a pivot low. class="type">class="kw">double currentLow = rates[i].low; class=class="str">"cmt">//--- Get the low value of the current bar. for (class="type">int j = i - left; j <= i + right; j++) { class=class="str">"cmt">//--- Loop through neighboring bars. if (j == i) { class=class="str">"cmt">//--- Skip the current bar. class="kw">continue; } if (rates[j].low <= currentLow) { class=class="str">"cmt">//--- If any neighbor&class="macro">#x27;s low is lower or equal, isPivot = class="kw">false; class=class="str">"cmt">//--- then the current bar is not a pivot low. class="kw">break; } } if (isPivot) { class=class="str">"cmt">//--- If a pivot low is confirmed, class="kw">return (currentLow); class=class="str">"cmt">//--- class="kw">return the low value of the pivot. } } class="kw">return (class="num">0); class=class="str">"cmt">//--- Return class="num">0 if no pivot low is found. } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function to find the most recent swing high(pivot high) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double GetPivotHigh(class="type">int left, class="type">int right) {
枢轴高点扫描与空单条件落地
下面这段逻辑先把最近 100 根 K 线的数据拉进数组,再用左右各 left / right 根的邻域比较,找出第一个被确认的最高价枢轴点。若拷贝到的 bar 数不够(少于 left+right),直接返回 0,避免越界访问。 MqlRates rates[]; int copied = CopyRates(_Symbol, _Period, 0, 100, rates); if (copied <= (left + right)) return (0); for (int i = left; i <= copied - right - 1; i++) { bool isPivot = true; double currentHigh = rates[i].high; for (int j = i - left; j <= i + right; j++) { if (j == i) continue; if (rates[j].high >= currentHigh) { isPivot = false; break; } } if (isPivot) return (currentHigh); } return (0); 枢轴判定之后,空单触发以 MA11 下穿 MA25 为主条件:上一根 fast> slow 且当前 fast< slow 才置位 maCrossoverSell。 bool maCrossoverSell = (ma11_previous > ma25_previous) && (ma11_current < ma25_current); bool rsiConditionSell = (rsi_current < (100.0 - InpRSIThreshold)); bool cci36ConditionSell = (cci36_current < 0); bool cci55ConditionSell = (cci55_current < 0); 任一过滤项不达标就 Print 拒绝原因并把 conditionsOk 置 false:RSI 需低于 100−阈值,CCI36 与 CCI55 当前值必须为负。外汇与贵金属杠杆品种波动剧烈,这类信号只代表概率倾向空头,实盘前请在 MT5 策略测试器用真实点差回测 100 根样本窗口的命中率。
class="type">MqlRates rates[]; class=class="str">"cmt">//--- Declare an array to store rate data. class="type">int copied = CopyRates(_Symbol, _Period, class="num">0, class="num">100, rates); class=class="str">"cmt">//--- Copy the last class="num">100 bars into the rates array. if (copied <= (left + right)) { class=class="str">"cmt">//--- Check if sufficient data was copied. class="kw">return (class="num">0); class=class="str">"cmt">//--- Return class="num">0 if there are not enough bars. } class=class="str">"cmt">//--- Loop through the bars to find a pivot high. for (class="type">int i = left; i <= copied - right - class="num">1; i++) { class="type">bool isPivot = true; class=class="str">"cmt">//--- Assume the current bar is a pivot high. class="type">class="kw">double currentHigh = rates[i].high; class=class="str">"cmt">//--- Get the high value of the current bar. for (class="type">int j = i - left; j <= i + right; j++) { class=class="str">"cmt">//--- Loop through neighboring bars. if (j == i) { class=class="str">"cmt">//--- Skip the current bar. class="kw">continue; } if (rates[j].high >= currentHigh) { class=class="str">"cmt">//--- If any neighbor&class="macro">#x27;s high is higher or equal, isPivot = class="kw">false; class=class="str">"cmt">//--- then the current bar is not a pivot high. class="kw">break; } } if (isPivot) { class=class="str">"cmt">//--- If a pivot high is confirmed, class="kw">return (currentHigh); class=class="str">"cmt">//--- class="kw">return the high value of the pivot. } } class="kw">return (class="num">0); class=class="str">"cmt">//--- Return class="num">0 if no pivot high is found. } class=class="str">"cmt">//--- Check for Sell Conditions: class="type">bool maCrossoverSell = (ma11_previous > ma25_previous) && (ma11_current < ma25_current); class=class="str">"cmt">//--- True if fast MA crosses below slow MA. class="type">bool rsiConditionSell = (rsi_current < (class="num">100.0 - InpRSIThreshold)); class=class="str">"cmt">//--- True if RSI is below the Sell threshold. class="type">bool cci36ConditionSell = (cci36_current < class="num">0); class=class="str">"cmt">//--- True if CCI36 is negative. class="type">bool cci55ConditionSell = (cci55_current < class="num">0); class=class="str">"cmt">//--- True if CCI55 is negative. if (maCrossoverSell) { class=class="str">"cmt">//--- If crossover for MA Sell is true... class="type">bool conditionsOk = true; class=class="str">"cmt">//--- Initialize a flag to track if all conditions are met. class=class="str">"cmt">//--- Check RSI condition for Sell. if (!rsiConditionSell) { class=class="str">"cmt">//--- If the RSI condition is not met... Print("Sell signal rejected: RSI condition not met. RSI=", rsi_current, " Required below=", (class="num">100.0 - InpRSIThreshold)); class=class="str">"cmt">//--- Notify the user. conditionsOk = class="kw">false; class=class="str">"cmt">//--- Mark the conditions as not met. } class=class="str">"cmt">//--- Check CCI36 condition for Sell. if (!cci36ConditionSell) { class=class="str">"cmt">//--- If the CCI36 condition is not met... Print("Sell signal rejected: CCI36 condition not met. CCI36=", cci36_current); class=class="str">"cmt">//--- Notify the user. conditionsOk = class="kw">false; class=class="str">"cmt">//--- Mark the conditions as not met. } class=class="str">"cmt">//--- Check CCI55 condition for Sell. if (!cci55ConditionSell) { class=class="str">"cmt">//--- If the CCI55 condition is not met... Print("Sell signal rejected: CCI55 condition not met. CCI55=", cci55_current); class=class="str">"cmt">//--- Notify the user.