MQL5 简介(第 10 部分):MQL5 中使用内置指标的初学者指南·进阶篇
📊

MQL5 简介(第 10 部分):MQL5 中使用内置指标的初学者指南·进阶篇

(2/3)· 从指标句柄到图表标记,新手也能写出内存干净、风险可控的 RSI 自动交易程序

偏理论进阶 第 2/3 篇
很多初学者把指标直接画在图表就以为「接进策略了」,其实 EA 里连句柄都没建,回测根本读不到值。先搞清句柄和缓冲区的分工,后面的自动化才不会再踩空指针的坑。

EA 初始化与百根 K 线数据接管

这段 MT5 代码把 RSI 和最近 100 根蜡烛的 OHLC 全部拉进数组,方便后续做高低点扫描。注意 CopyBuffer 的起始位是 1 而不是 0,意味着实时那根未完成 K 线被刻意排除,回测里能少踩很多假信号坑。 初始化阶段先用 ArraySetAsSeries 把 rsi_buffer、open、close、high、low、time 全部倒序,这样 [0] 永远是最新一根。RSI 句柄用 iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 建,周期参数写死 14 是经典设置,但你可以改第二个数字切到 M15 或 H1 验证不同品种反应。 OnTick 里每次报价都重拷 100 根:CopyOpen/CopyClose/CopyHigh/CopyLow 的第三参都是 1、第四参 100,和 RSI 的拷贝窗口完全对齐。外汇和贵金属波动大,这种高频重拷会吃 CPU,实盘前建议在策略测试器里跑一遍看是否掉帧。 max_high、min_low 及对应时间变量留作后面比对新高/new低用,目前只是清零占位。MagicNumber=1111 是订单指纹,多 EA 同品种跑时改这个数就能隔离仓位。

MQL5 / C++
input class="type">int MagicNumber = class="num">1111;
class="type">int      rsi_handle;
class="type">class="kw">double    rsi_buffer[];
class="type">class="kw">double open[];
class="type">class="kw">double close[];
class="type">class="kw">double high[];
class="type">class="kw">double low[];
class="type">class="kw">datetime time[];
class="type">class="kw">double max_high = class="num">0;      class=class="str">"cmt">// Maximum high for the candlesticks
class="type">class="kw">datetime min_time1 = class="num">0;   class=class="str">"cmt">// Time of the maximum high candlestick
class="type">class="kw">double min_low = class="num">0;       class=class="str">"cmt">// Minimum low for the candlesticks
class="type">class="kw">datetime min_time2 = class="num">0;   class=class="str">"cmt">// Time of the minimum low candlestick
class="type">class="kw">datetime time_low = class="num">0;    class=class="str">"cmt">// Time of the RSI low
class="type">class="kw">datetime times_high = class="num">0;  class=class="str">"cmt">// Time of the RSI high
class="type">int OnInit()
  {
   ArraySetAsSeries(rsi_buffer, true);
   rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE);
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(time, true);
   trade.SetExpertMagicNumber(MagicNumber);
   class="kw">return (INIT_SUCCEEDED);
  }
class="type">void OnTick()
  {
   CopyBuffer(rsi_handle, class="num">0, class="num">1, class="num">100, rsi_buffer);
   CopyOpen(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, open);
   CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, close);
   CopyHigh(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, high);
   CopyLow(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, low);

◍ 用前12根K线锚定反转结构

这段逻辑只扫最近 12 根蜡烛,寻找两类价格形态与 RSI 极值的时间锚点。外汇与贵金属波动快,12 根窗口太宽会吞掉局部结构,太窄又容易漏掉隔根反转,实盘里可以拿 EURUSD 的 M15 先跑一遍看命中率。 第一个循环找看涨反转:当前根收低于开、前一根收高于开,即阴包阳后的反向孕线,取两根最高价中的较大值作为 max_high,并记录较早时间 min_time1。第二个循环镜像处理看跌:当前收高于开、前一根收低于开,取两根最低价较小值 min_low 与较早时间 min_time2。 RSI 部分分两头:当 rsi_buffer[i+1] 小于 30 且比前一根更低,判为超卖低点,记 time_low;当 rsi_buffer[i+1] 大于 70 且比前一根更高,判为超买高点,记 times_high。阈值 30/70 是经典设置,但黄金常在亚盘假破,建议接小布盯盘回看近一月触 70 后的回撤概率再决定是否收紧。 所有循环都在首次命中后 break,意味着只取最近一次信号。若你想统计 12 根内共出现几次背离,得把 break 去掉并改用数组收集——这是验证策略样本量的第一步。

MQL5 / C++
  CopyTime(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, time);
class=class="str">"cmt">// Loop to find the maximum high from a bullish candlestick pattern
  for(class="type">int i = class="num">0; i < class="num">12; i++)
    {
    class=class="str">"cmt">// Check for a bullish pattern: current close < open and previous close > open
    if(close[i] < open[i] && close[i+class="num">1] > open[i+class="num">1])
      {
      class=class="str">"cmt">// Calculate the maximum high between the two candlesticks
      max_high = MathMax(high[i], high[i+class="num">1]);
      class=class="str">"cmt">// Record the time of the corresponding candlestick
      min_time1 = MathMin(time[i], time[i+class="num">1]);
      class="kw">break;
      }
    }
class=class="str">"cmt">// Loop to find the minimum low from a bearish candlestick pattern
  for(class="type">int i = class="num">0; i < class="num">12; i++)
    {
    class=class="str">"cmt">// Check for a bearish pattern: current close > open and previous close < open
    if(close[i] > open[i] && close[i+class="num">1] < open[i+class="num">1])
      {
      class=class="str">"cmt">// Calculate the minimum low between the two candlesticks
      min_low = MathMin(low[i], low[i+class="num">1]);
      class=class="str">"cmt">// Record the time of the corresponding candlestick
      min_time2 = MathMin(time[i], time[i+class="num">1]);
      class="kw">break;
      }
    }
class=class="str">"cmt">// Loop to find the RSI low point
  for(class="type">int i = class="num">0; i < class="num">12; i++)
    {
    class=class="str">"cmt">// Check if the RSI is oversold and forms a low point
    if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1])
      {
      class=class="str">"cmt">// Record the time of the RSI low
      time_low = time[i+class="num">1];
      class="kw">break;
      }
    }
class=class="str">"cmt">// Loop to find the RSI high point
  for(class="type">int i = class="num">0; i < class="num">12; i++)
    {
    class=class="str">"cmt">// Check if the RSI is overbought and forms a high point
    if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1])
      {
      class=class="str">"cmt">// Record the time of the RSI high
      times_high = time[i+class="num">1];
      class="kw">break;
      }
    }
  }
class=class="str">"cmt">// Loop to find RSI and candlestick lows
for(class="type">int i = class="num">0; i < class="num">12; i++)
  {
class=class="str">"cmt">// Check if the RSI is oversold and forms a low point
  if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1])
    {
    class=class="str">"cmt">// Record the time of the RSI low
    time_low = time[i+class="num">1];

「RSI背离扫描的变量与初始化落点」

这段逻辑干的事很直接:在最近 12 根 K 线里,先扫 RSI 低于阈值且形成局部低点的组合,取两根 K 线的最低低价写进 min_low 并记下时间;再反向扫 RSI 大于 70 且抬头的组合,取两根 K 线的最高高价写进 max_high。循环里一旦命中就 break,说明只认第一个信号,不追溯更早的背离。 初始化部分把 rsi_buffer 和 OHLC 数组全设成 ArraySetAsSeries(true),索引 0 就是最新一根柱,回看历史直接加下标即可。RSI 句柄用 iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 建,周期跟当前图表走,长度 14 是默认参数,实战里可以改成 7 或 21 看敏感度变化。 MagicNumber 设成 1111 只是订单标识,多 EA 同跑时靠它隔离持仓。OnTick 里 CopyBuffer 从偏移 1 拷 100 根 RSI、CopyOpen/CopyClose 同理,意味着第 0 根(成型中阳线)被故意跳过,只处理已收盘数据,能降低重绘导致的信号抖动。外汇与贵金属杠杆高,这类扫描仅作概率参考,实盘前请在 MT5 策略测试器用历史数据验证命中率。

MQL5 / C++
min_low = (class="type">class="kw">double)MathMin(low[i], low[i+class="num">1]);
class="kw">break;
}
}
class=class="str">"cmt">// Loop to find RSI and candlestick highs
for(class="type">int i = class="num">0; i < class="num">12; i++)
{
class=class="str">"cmt">// Check if the RSI is overbought and forms a high point
  if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1])
  {
    times_high = time[i+class="num">1];
    max_high = (class="type">class="kw">double)MathMax(high[i], high[i+class="num">1]);
    class="kw">break;
  }
}
input class="type">int MagicNumber = class="num">1111;
class="type">int      rsi_handle;
class="type">class="kw">double    rsi_buffer[];
class="type">class="kw">double open[];
class="type">class="kw">double close[];
class="type">class="kw">double high[];
class="type">class="kw">double low[];
class="type">class="kw">datetime time[];
class="type">class="kw">double max_high = class="num">0;
class="type">class="kw">datetime min_time1 = class="num">0;
class="type">class="kw">double min_low = class="num">0;
class="type">class="kw">datetime min_time2 = class="num">0;
class="type">class="kw">datetime time_low = class="num">0;
class="type">class="kw">datetime times_high = class="num">0;
class="type">int OnInit()
{
  ArraySetAsSeries(rsi_buffer, true);
  rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE);
  ArraySetAsSeries(open, true);
  ArraySetAsSeries(close, true);
  ArraySetAsSeries(high, true);
  ArraySetAsSeries(low, true);
  ArraySetAsSeries(time, true);
  trade.SetExpertMagicNumber(MagicNumber);
  class="kw">return (INIT_SUCCEEDED);
}
class="type">void OnTick()
{
  CopyBuffer(rsi_handle, class="num">0, class="num">1, class="num">100, rsi_buffer);
  CopyOpen(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, open);
  CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, close);

用静态变量锁住最近的高低点拐点

这段逻辑先把当前品种、当前周期最近 100 根 K 线的高、低、时间分别拉进数组,供后面扫描使用。注意 CopyHigh/CopyLow/CopyTime 的起始位置是 1 而非 0,意味着跳过了正在形成的那根实时 K 线,避免未闭合数据干扰判定。 四个 static 变量(max_high_static、min_time1_static、min_low_static、min_time2_static)负责跨 tick 保留上一次找到的拐点。外汇与贵金属波动剧烈,实时刷新容易把瞬时噪声当拐点,用 static 缓存能减少重复计算与抖动。 两段 12 根内的 for 循环分别找「阴线接阳线」的最高价与最早时间、「阳线接阴线」的最低价与最早时间,作为潜在顶底。另两段循环在 RSI 缓冲里抓跌破 30 后回升、突破 70 后回落的时刻,记下 time_low / times_high。 Bars() 统计从静态时间点到现在的 Bar 数。若 total_bar_high 为 0 或大于 12,且 min_time1 与 times_high 重合,才把本次算出的 max_high、min_time1 写回静态变量;否则当 min_time1 不等 times_high 且 Bar 数超 13,就把静态高点与时间清零。开 MT5 把这段贴进 EA,把 12、13、30、70 几个数改小改大,能直接看到拐点捕获频率的变化。

MQL5 / C++
  CopyHigh(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, high);
  CopyLow(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, low);
  CopyTime(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, time);
  class="kw">static class="type">class="kw">double max_high_static = max_high;
  class="kw">static class="type">class="kw">datetime min_time1_static = min_time1;
  class="kw">static class="type">class="kw">double min_low_static = min_low;
  class="kw">static class="type">class="kw">datetime min_time2_static = min_time2;
  class="type">int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent());
  for(class="type">int i = class="num">0; i < class="num">12; i++)
    {
      if(close[i] < open[i] && close[i+class="num">1] > open[i+class="num">1])
        {
         max_high = (class="type">class="kw">double)MathMax(high[i],high[i+class="num">1]);
         min_time1 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]);
         class="kw">break;
        }
    }
  class="type">int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent());
  for(class="type">int i = class="num">0; i < class="num">12; i++)
    {
      if(close[i] > open[i] && close[i+class="num">1] < open[i+class="num">1])
        {
         min_low = (class="type">class="kw">double)MathMin(low[i],low[i+class="num">1]);
         min_time2 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]);
         class="kw">break;
        }
    }
  for(class="type">int i = class="num">0; i < class="num">12; i++)
    {
      if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1])
        {
         time_low = time[i+class="num">1];
         class="kw">break;
        }
    }
  for(class="type">int i = class="num">0; i < class="num">12; i++)
    {
      if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1])
        {
         times_high = time[i+class="num">1];
         class="kw">break;
        }
    }
  if((total_bar_high == class="num">0 || total_bar_high > class="num">12) && (min_time1 == times_high))
    {
      max_high_static = max_high;
      min_time1_static = min_time1;
    }
  else
    if(min_time1 != times_high && total_bar_high > class="num">13)
      {
       max_high_static = class="num">0;
       min_time1_static = class="num">0;
      }

◍ 用静态变量锁住高低点参照

这段代码把前一段算出的极值用 static 变量固化下来,避免每根 tick 重算导致参照线跳变。注意 total_bar_low 的判定阈值:等于 0 或大于 12 且时间吻合时才更新最低价静态值,否则当偏离超过 13 根就清空,相当于承认原波段失效。 初始化里 rsi_handle 用 iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 建句柄,14 周期是我们后面筛背离的基准。所有价格数组都 ArraySetAsSeries(true),让索引 0 永远是当前未完成 K 线,回看历史用正数偏移即可。 OnTick 每次拉 100 根数据从偏移 1 开始,刻意跳过第 0 根实时 Bar,这样 RSI 与 OHLC 的序列对齐不会错位。max_high_static 和 min_time1_static 用 static 承接,保证跨 tick 的极值记忆不丢,这是画线判断突破的前提。外汇与贵金属波动剧烈,static 缓存若不定期随市场结构重置,可能捕捉到早已失效的旧极值,实盘前应在 MT5 策略测试器用不同品种验证重置逻辑。

MQL5 / C++
if((total_bar_low == class="num">0 || total_bar_low > class="num">12) && (min_time2 == time_low))
  {
    min_low_static = min_low;
    min_time2_static = min_time2;
  }
  else
    if(min_time2 != time_low && total_bar_low > class="num">13)
      {
       min_low_static = class="num">0;
       min_time2_static = class="num">0;
      }
}
class="macro">#include <Trade/Trade.mqh>
CTrade trade;
class=class="str">"cmt">// Magic number
input class="type">int MagicNumber = class="num">1111;
class="type">int rsi_handle;
class="type">class="kw">double rsi_buffer[];
class="type">class="kw">double open[];
class="type">class="kw">double close[];
class="type">class="kw">double high[];
class="type">class="kw">double low[];
class="type">class="kw">datetime time[];
class="type">class="kw">double max_high = class="num">0;
class="type">class="kw">datetime min_time1 = class="num">0;
class="type">class="kw">double min_low = class="num">0;
class="type">class="kw">datetime min_time2 = class="num">0;
class="type">class="kw">datetime time_low = class="num">0;
class="type">class="kw">datetime times_high = class="num">0;
class="type">class="kw">string high_obj_name = "High_Line";
class="type">class="kw">string low_obj_name = "Low_Line";
class="type">long chart_id;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">// Configure RSI buffer as a series for easier indexing
   ArraySetAsSeries(rsi_buffer, true);
class=class="str">"cmt">// Initialize RSI handle for the current symbol, timeframe, and parameters
   rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE);
class=class="str">"cmt">// Configure candlestick arrays as series
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(time, true);
class=class="str">"cmt">// Set the magic number
   trade.SetExpertMagicNumber(MagicNumber);
   class="kw">return (INIT_SUCCEEDED); class=class="str">"cmt">// Indicate successful initialization
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">// Copy RSI values from the indicator into the buffer
   CopyBuffer(rsi_handle, class="num">0, class="num">1, class="num">100, rsi_buffer);
class=class="str">"cmt">// Copy candlestick data
   CopyOpen(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, open);
   CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, close);
   CopyHigh(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, high);
   CopyLow(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, low);
   CopyTime(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, time);
   class="kw">static class="type">class="kw">double max_high_static = max_high;
   class="kw">static class="type">class="kw">datetime min_time1_static = min_time1;

「用静态变量锁住高低点扫描状态」

这段逻辑干的事,是在当前图表上把最近一段的潜在顶底和 RSI 极值时间锚定下来,靠 static 变量跨 tick 保留上一次结果。外汇和贵金属波动快,这种状态保持能避免每帧重算把信号抖掉,但高频误触风险也高,参数要自己压。 先抓图表 ID 并算两个 Bars 计数:从记录的 min_time1_static / min_time2_static 到当前时间,在 PERIOD_CURRENT 上一共有多少根 K。若返回 0 或大于 12,说明锚点已失效或太老,后续才允许更新。 接着四个循环各扫 12 根:前两个找「阴线接阳线」取两K最高价、和「阳线接阴线」取两K最低价;后两个看 RSI 缓冲,rsi<30 且回升、rsi>70 且走高,分别记下时间。注意循环上限写死 12,改大可能吞掉更早形态。 最后用 total_bar 计数和 RSI 时间做交叉验证:只有 Bars 异常或超 12 且时间对得上,才把扫描值写回 static;若时间不对且 Bars>13,直接把 static 清零。这样老信号不会一直挂屏,但清零后下一帧若条件不满足就空跑,建议开 MT5 用 EURUSD 15M 跑一遍看 static 归零频率。

MQL5 / C++
  class="kw">static class="type">class="kw">double min_low_static = min_low;
  class="kw">static class="type">class="kw">datetime min_time2_static = min_time2;
class=class="str">"cmt">//CHART ID
  chart_id = ChartID();
  class="type">int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent());
  for(class="type">int i = class="num">0; i < class="num">12; i++)
    {
      if(close[i] < open[i] && close[i+class="num">1] > open[i+class="num">1])
        {
         max_high = (class="type">class="kw">double)MathMax(high[i],high[i+class="num">1]);
         min_time1 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]);
         class="kw">break;
        }
    }
  class="type">int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent());
  for(class="type">int i = class="num">0; i < class="num">12; i++)
    {
      if(close[i] > open[i] && close[i+class="num">1] < open[i+class="num">1])
        {
         min_low = (class="type">class="kw">double)MathMin(low[i],low[i+class="num">1]);
         min_time2 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]);
         class="kw">break;
        }
    }
  for(class="type">int i = class="num">0; i < class="num">12; i++)
    {
      if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1])
        {
         time_low = time[i+class="num">1];
         class="kw">break;
        }
    }
  for(class="type">int i = class="num">0; i < class="num">12; i++)
    {
      if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1])
        {
         times_high = time[i+class="num">1];
         class="kw">break;
        }
    }
  if((total_bar_high == class="num">0 || total_bar_high > class="num">12) && (min_time1 == times_high))
    {
      max_high_static = max_high;
      min_time1_static = min_time1;
    }
  else
    if(min_time1 != times_high && total_bar_high > class="num">13)
      {
       max_high_static = class="num">0;
       min_time1_static = class="num">0;
      }
  if((total_bar_low == class="num">0 || total_bar_low > class="num">12) && (min_time2 == time_low))
    {
      min_low_static = min_low;
      min_time2_static = min_time2;
    }
  else
    if(min_time2 != time_low && total_bar_low > class="num">13)
      {
       min_low_static = class="num">0;
       min_time2_static = class="num">0;
      }
把指标诊断交给小布盯盘
这些 RSI 高点低点识别和每笔百分比风险的计算逻辑,小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时标注,你只管判断要不要跟信号。

常见问题

句柄是指标在程序里的唯一引用,靠它初始化并设置周期、价格类型;缓冲区才是真正存放计算后数值的地方,EA 通过句柄去读缓冲区拿数据。
可以,小布盯盘对应品种页会用对象标注重要 RSI 高低点,省去你自己写创建对象代码的重复劳动,你专注决策即可。
不同周期烛形实体差异大,固定手数在极端波动里风险失衡;用账户百分比配合当前烛形计算手数,能缓解不一致带来的爆仓概率。
通过检索指标缓冲区的局部极大极小对应烛台,结合价格扫掉前高前低但 RSI 未确认,即可在代码里标记潜在清扫,详见本文第 5 节。