MQL5 交易策略自动化(第十部分):开发趋势盘整动量策略·进阶篇
📈

MQL5 交易策略自动化(第十部分):开发趋势盘整动量策略·进阶篇

(2/3)· 用11/25均线交叉搭趋势骨架,再让RSI(27)与CCI(36/55)替你筛掉假突破,EA自动进场

含代码示例偏理论 第 2/3 篇
很多人把均线金叉死叉直接当信号跑,结果在盘整里被来回扫损。给交叉加一层动量确认,不是锦上添花,而是把噪音挡在门外的核心步骤。

◍ 指标句柄初始化与资源释放的硬约束

在 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 句柄也补进释放逻辑,保持对称。

MQL5 / C++
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误当信号源的出错点。

MQL5 / C++
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 数据验证。

MQL5 / C++
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 的错误描述,方便排查订单被拒原因(如保证金不足或报价过期)。

MQL5 / C++
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 根样本窗口的命中率。

MQL5 / C++
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.
把多指标共振检查交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到均线交叉与RSI/CCI状态是否同步,你只管判断大周期方向。

常见问题

36周期CCI捕捉中短波动能,55周期CCI看更长一段的动量背景,两者同向才确认趋势质量,单周期容易受局部波动误导。
需在代码中用iLow或自定义波段检测函数取得前低价格,再传给obj_Trade.PositionModify或下单时的stop参数,偏理论部分会展开结构。
中位价(H+L)/2比收盘价更抗单根影线干扰,在贵金属跳空时常能减少假交叉,属于过滤层面的细节优化。
可以,把均线周期与RSI/CCI阈值填进小布的品种页条件单,未接MT5也能在共振出现时推送,省去盯盘精力。
不等价,300点在外汇是3美元迷你波动、在黄金是30美元级别,回测必须按品种点值分别评估风险收益比。