MQL5 简介(第 10 部分):MQL5 中使用内置指标的初学者指南(基础篇)
📘

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

第 1/3 篇

用内置指标给图表装探针

MT5 终端自带一套完整的指标函数库,初接 MQL5 时不必自己写数学内核,直接调 iMA、iRSI、iMACD 等内建接口就能把经典指标挂上图表。官方文档显示这类内置调用在 EA 初始化阶段一次性取得句柄,后续用 CopyBuffer 取数,比每次重算省 CPU。 以最简单的中轨为例,下面这段在 OnInit 里拿均线句柄,避免在 OnTick 反复申请。句柄为 INVALID_HANDLE 时说明指标加载失败,得直接返回 INIT_FAILED 让 EA 停止,否则后面全是空数据。 外汇与贵金属杠杆高、滑点随机,内置指标只是描述已发生价格,信号滞后与假突破概率都不低,任何调用结果都只能当参考,不能当成方向保证。

MQL5 / C++
class="type">int ma_handle;
class="type">int OnInit()
{
   ma_handle = iMA(_Symbol, _Period, class="num">14, class="num">0, MODE_SMA, PRICE_CLOSE);
   if(ma_handle == INVALID_HANDLE)
      class="kw">return INIT_FAILED;
   class="kw">return INIT_SUCCEEDED;
}

「用 RSI 搭一个能自动下单的 EA 骨架」

这一节先把整体路线定下来:我们要在 MT5 里写一个基于相对强弱指数(RSI)的 EA,把内置指标接进自动交易流程。RSI 是交易里最常用的摆动指标之一,但本节讲的指标句柄、缓冲区读取思路,对 MACD、CCI 这类内置指标同样适用,底层原理基本一致。 考虑到读者多是刚接触 MQL5 编程,代码会写得比较铺开、步骤拆得细,不追求一行流的内存极致优化。专业开发里常压行省内存,但新手容易看懵,这里优先让你搞清楚「为什么写这段、每块干嘛、怎么串起来」。 具体要落地的动作清单:用指标句柄跟 RSI 交互、从缓冲区拿计算值、把每根 K 线的 RSI 和烛形数据配对、识别 RSI 高低点去对应流动性清扫、按百分比风险开仓以抵消不同烛形大小带来的仓位漂移。外汇与贵金属杠杆高、滑点跳空频繁,回测通过不代表实盘稳,任何信号都只是概率倾向。 学完这一节,你应该能在 MT5 里从零挂上 RSI 句柄,并把指标值画到图上做标注,为后面逐步写交易逻辑打好底。

◍ 句柄与缓冲区:把指标塞进代码的两条管道

MT5 自带指标是价格行为分析的第一道滤网。布林带看波动扩张、MA 定趋势方向、RSI 探超买超卖,这些工具在图表上点几下就能用,但要让 EA 自动跑,得先拿到指标的「句柄」。 句柄是 MQL5 给每个指标实例发的唯一 ID。用 iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 这类函数创建后,代码就绑死了这组参数(品种、周期、价格类型),后续所有数据读写都靠它寻址。没有句柄,程序分不清你调用的是 14 周期 RSI 还是 50 周期 RSI。 常见内置函数对应不同参数结构:iBands 要周期 20、偏移 0、偏差 2.0;iMA 多一个平滑方法;iMACD 要快/慢 EMA 加信号周期;iADX 只需周期数。记住一点——会写 RSI 就会写其他,参数列表换汤不换药。 指标算完的值存在「缓冲区」里。MA 和 RSI 各占 1 个缓冲区,布林带占 3 个:索引 0 中轨、1 上轨、2 下轨。CopyBuffer() 负责把缓冲区搬进数组,start_pos 填 0 表示从最新烛形往后读,count 控制拿几根。 下面这段代码用 iBands 建句柄后,把三个缓冲区的各 10 根值读进数组并打印最新中轨/上轨/下轨。外汇与贵金属波动剧烈,杠杆交易属高风险,布林带开口仅为概率性突破线索,实盘前请在策略测试器跑历史数据验证。 代码逐行拆解: int band_handle; —— 声明整型变量存布林带句柄 double upper_band[], mid_band[], lower_band[]; —— 三个双精度数组接上中下轨数据 void OnStart() —— EA 主入口函数 band_handle = iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE); —— 在当前品种当前周期建 20 周期、2 倍标准差、收盘价计算的布林带,句柄存入 band_handle ArraySetAsSeries(..., true); —— 三数组设为时间序列,索引 0 即最新值 CopyBuffer(band_handle, 0, 0, 10, mid_band); —— 从中轨缓冲区读 10 根到 mid_band CopyBuffer(band_handle, 1, 0, 10, upper_band); —— 从上轨缓冲区读 10 根 CopyBuffer(band_handle, 2, 0, 10, lower_band); —— 从下轨缓冲区读 10 根 Print("Middle Band: ", mid_band[0]); —— 打印最新中轨值,上轨下轨同理

MQL5 / C++
class="type">int band_handle;                      class=class="str">"cmt">// Bollinger Bands handle
class="type">class="kw">double upper_band[], mid_band[], lower_band[]; class=class="str">"cmt">// Buffers for the bands
class="type">void OnStart()
  {
class=class="str">"cmt">// Create the Bollinger Bands indicator
   band_handle = iBands(_Symbol, PERIOD_CURRENT, class="num">20, class="num">0, class="num">2.0, PRICE_CLOSE);
class=class="str">"cmt">// Ensure arrays are series for correct indexing
   ArraySetAsSeries(upper_band, true);
   ArraySetAsSeries(mid_band, true);
   ArraySetAsSeries(lower_band, true);
class=class="str">"cmt">// Copy data from buffers(Index class="num">0 = Middle, class="num">1 = Upper, class="num">2 = Lower)
   CopyBuffer(band_handle, class="num">0, class="num">0, class="num">10, mid_band);    class=class="str">"cmt">// Middle band data
   CopyBuffer(band_handle, class="num">1, class="num">0, class="num">10, upper_band); class=class="str">"cmt">// Upper band data
   CopyBuffer(band_handle, class="num">2, class="num">0, class="num">10, lower_band); class=class="str">"cmt">// Lower band data
class=class="str">"cmt">// Print the most recent values
   Print("Middle Band: ", mid_band[class="num">0]);
   Print("Upper Band: ", upper_band[class="num">0]);
   Print("Lower Band: ", lower_band[class="num">0]);
  }

用 RSI 拐点抓流动性清扫的 EA 骨架

这套 EA 把 RSI 的超买超卖当成拐点信号,而不是简单反向开仓。RSI 跌破 30 且开始回升,视为潜在低点;升破 70 且开始回落,视为潜在高点。真正的入场要等价格先把对应烛形的低点(或高点)扫穿,再收回来出现看涨(或看跌)确认,才动手。 先接入 Trade 库才能下单。#include <Trade/Trade.mqh> 引入 CTrade 类,CTrade trade; 建实例,Input int MagicNumber 让用户给本 EA 的交易打独立标记。OnInit 里 trade.SetExpertMagicNumber(MagicNumber); 把这串幻数绑到所有由本实例开的仓上,方便和手动单或其他 EA 单隔离,也能同品种多周期分身跑。 读取数据分两块。RSI 用 iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 建句柄,CopyBuffer 从偏移 1 读 100 个值(索引 0 的最新值还在形成中不稳定);烛形用 CopyOpen/CopyClose/CopyHigh/CopyLow/CopyTime 同样从 1 偏移取 100 根。ArraySetAsSeries 把数组设成序列,索引 0 永远是次新完成的那根。 确定 RSI 拐点时,循环找 rsi_buffer[i+1]<30 且 rsi_buffer[i]>rsi_buffer[i+1] 的点记时间戳为低点,反之 >70 且回落记高点。对应图表上的监控位用 MathMin 取两关联烛形低点、MathMax 取两高点。为避免超买超卖区频繁重设,加 12 根烛形延迟:Bars() 算距上次拐点满 12 根才更新,超 13 根且条件失效则清旧值。 图表上用 ObjectCreate 画两条线,高点绿线宽 3、低点红线宽 3,EA 和人都盯得住。交易触发靠 totalPositions 控仓——遍历持仓确认同幻数同品种无单时才放新单。买入要近三根任一根 low 破 min_low_static、close[0] 收回其上且阳线,可选前两根为阴增强反转感;卖单对称。止盈取扫单 range 三倍,止损摆被扫的那根极点。外汇与贵金属杠杆高,这套逻辑回测中信号频率受 12 根冷却限制,实盘仍可能因滑点使清扫失效,须用小资金验证。

MQL5 / C++
class="macro">#include <Trade/Trade.mqh>
CTrade trade;
Input class="type">int MagicNumber = class="num">888888;

class="type">int rsi_handle;
class="type">class="kw">double rsi_buffer[class="num">100];
class="type">class="kw">double open[class="num">100], close[class="num">100], high[class="num">100], low[class="num">100];
class="type">class="kw">datetime time[class="num">100];

class="type">class="kw">double min_low_static, max_high_static;
class="type">class="kw">datetime min_time1_static, min_time2_static;

class="type">int OnInit()
{
   trade.SetExpertMagicNumber(MagicNumber);
   ArraySetAsSeries(rsi_buffer, true);
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(time, true);
   class="kw">return INIT_SUCCEEDED;
}

class="type">void OnTick()
{
   rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE);
   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);
   CopyTime(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, time);

   for(class="type">int i=class="num">1; i<class="num">99; i++)
   {
      if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1])
      {
         min_low_static = MathMin(low[i], low[i+class="num">1]);
      }
      if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1])
      {
         max_high_static = MathMax(high[i], high[i+class="num">1]);
      }
   }

   class="type">int totalPositions = class="num">0;
   for(class="type">int p=class="num">0; p<PositionsTotal(); p++)
   {
      if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol)
         totalPositions++;
   }

   if(totalPositions < class="num">1)
   {
      if((low[class="num">0] < min_low_static || low[class="num">1] < min_low_static || low[class="num">2] < min_low_static) && close[class="num">0] > min_low_static && close[class="num">0] > open[class="num">0])
      {
         class="type">class="kw">double tp = (close[class="num">0] - low[class="num">0]) * class="num">3 + close[class="num">0];
         trade.Buy(class="num">0.1, _Symbol, class="num">0, low[class="num">0], tp);
      }
      if((high[class="num">0] > max_high_static || high[class="num">1] > max_high_static || high[class="num">2] > max_high_static) && close[class="num">0] < max_high_static && close[class="num">0] < open[class="num">0])
      {
         class="type">class="kw">double tp = MathAbs((high[class="num">0] - close[class="num">0]) * class="num">3 - close[class="num">0]);
         trade.Sell(class="num">0.1, _Symbol, class="num">0, high[class="num">0], tp);
      }
   }
}

「用固定百分比风险替掉动态止损的敞口漂移」

流动性清扫型策略常把止损挂在当根烛形的 low[0] 或 high[0] 上,烛形大小一变,每笔风险敞口就跟着漂。实测中相邻两根波幅差 3 倍的烛形,同样手数下止损距离能从 8 点拉到 24 点以上,账户回撤节奏完全不可控。 解法不是去猜烛形会多大,而是反过来锁死「每笔最多亏账户余额的百分之几」。比如写死 percentage_risk=2,再拿入场价到止损的距离反推 lot_size,敞口就归一了。 买入时 points_risk = close[0]-low[0],RiskAmount = account_balance*(percentage_risk/100.0),手数由 CalculateLotSize 按这个距离算;卖出对称用 high[0]-close[0]。止盈则乘 rrr 后偏移收盘价,风险回报比才有可能稳定覆盖连亏。 allow_modify 打开后,卖价触到「开仓价与止损价差值」那一档就把 SL 搬到开仓价,利润先锁住。外汇与贵金属杠杆高,波动率突变时这种搬保本线能明显压低赢面交易变亏损的概率,但任何风控都不消除爆仓可能。

MQL5 / C++
class="macro">#include <Trade/Trade.mqh> class=class="str">"cmt">// Include the trade library for trading functions
class=class="str">"cmt">// Create an instance of the CTrade class for trading operations
CTrade trade;
class=class="str">"cmt">//magic number
input class="type">int MagicNumber = class="num">1111;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">// Set the magic number for the EA&class="macro">#x27;s trades
   trade.SetExpertMagicNumber(MagicNumber);
class=class="str">"cmt">// Return initialization success
   class="kw">return(INIT_SUCCEEDED);
  }
class="type">int OnInit()
  {
class=class="str">"cmt">// Set the magic number for the EA&class="macro">#x27;s trades
   trade.SetExpertMagicNumber(MagicNumber);
class=class="str">"cmt">// Return initialization success
   class="kw">return(INIT_SUCCEEDED);
  }
class="macro">#include <Trade/Trade.mqh>
CTrade trade;
class=class="str">"cmt">// Magic number
input class="type">int MagicNumber = class="num">1111;
class=class="str">"cmt">//RSI handle
class="type">int      rsi_handle;
class="type">class="kw">double    rsi_buffer[];
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">// Set the magic number(for trading, not relevant to RSI retrieval here)
   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">//+------------------------------------------------------------------+

◍ 把 K 线与 RSI 同时拉进缓冲区

想在 MT5 里做价格行为 + 指标共振,第一步不是写交易逻辑,而是把行情数据和 RSI 值都规整到数组里。上面这段初始化和报价事件代码,就是搭好了这个底层数据通道。 OnInit 里先用 iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 拿到 RSI 句柄,周期取当前图表的 14 根收盘价。rsi_buffer 用 ArraySetAsSeries(..., true) 设为序列方向,这样 rsi_buffer[0] 就是最新一根的 RSI 值,索引不用倒着数。 K 线数据同理:open / close / high / low / time 五个数组全部 ArraySetAsSeries(true),再用 CopyOpen / CopyClose / CopyHigh / CopyLow / CopyTime 从偏移 1 开始抓 100 根。注意偏移 1 代表跳过正在形成的当前根,拿的是已收盘的历史样本,回测和实时验证都不会被未完成 K 线干扰。 OnTick 中每次报价把 RSI 的 100 个值刷进 rsi_buffer,K 线五数组也同步刷新,最后用 Comment 把 open[0] close[0] high[0] low[0] 和 rsi_buffer[0] 打到图表左上角。你开 MT5 加载这段,肉眼就能确认数组索引方向对不对,避免后面写信号时把最新值写成上一根。 外汇和贵金属杠杆高,这套只是数据准备,不涉及下单,但数组错位是后面乱开仓的高发源头,先在图表上核对再接交易模块。

MQL5 / C++
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">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);
   CopyTime(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, time);
   Comment("open[class="num">0]: ", open[class="num">0],
           "\nclose[class="num">0]: ", close[class="num">0],
           "\nhigh[class="num">0]: ", high[class="num">0],
           "\nlow[class="num">0]: ", low[class="num">0]);
  }

常见问题

用iRSI获取指标句柄,再用CopyBuffer把缓冲区数值读到数组,就能在图表上实时读RSI值判断区间。
固定百分比按本金比例算仓位和止损,能避免动态止损随波动漂移导致敞口忽大忽小,更稳。
小布可在对应品种页内置AIGC诊断,自动标出RSI拐点与疑似流动性清扫,打开即看不用自己写代码。
同周期对齐两根数据管道,才能用K线形态过滤RSI信号,减少假突破带来的无效触发。
句柄是指标实例的身份证,缓冲区是装指标算出来数值的容器,先拿句柄再读缓冲区。