MQL5 Cookbook: 在EA交易中使用指标来设置交易条件·进阶篇
📘

MQL5 Cookbook: 在EA交易中使用指标来设置交易条件·进阶篇

第 2/3 篇

「通道类指标的反序取值与信号判定」

当指标类型切到 PCH(价格通道)时,第一件事是把两个缓冲区数组用 ArraySetAsSeries 翻成时间序列序:索引 0 是最新柱、1 是前一柱,依此类推。这一步不做,后面拿到的 buffer[1] 和 buffer[2] 会错位,信号逻辑直接反着跑。 CopyBuffer 拉数据时,AllowedNumberOfSegments 决定要取多少段。若任意一条缓冲区实际复制到的元素数小于该值,就打印当前品种、周期和 GetLastError 描述的失败原因并返回 false,调用方会触发 GetIndicatorHandle 重试。外汇与贵金属杠杆高,这类句柄失效在跳空开盘时概率明显上升,别假设它永远一次成功。 GetSignal 里对 MA 与 CCI 走另一套判定:卖出信号不看绝对值,只看缓冲区的连续递减。AllowedNumberOfSegments 设为 3 时,只要 buffer1[1]<buffer1[2] 就返 SELL;设到 6 时则要求 [1]~[4] 四组相邻值严格递减。把这段改成 >= 判定,就能在 MT5 里快速验证不同段数下信号触发频率的差异。

MQL5 / C++
if(Indicator==PCH)
  {
   class=class="str">"cmt">//--- 反转索引顺序 (... class="num">3 class="num">2 class="num">1 class="num">0)
   ArraySetAsSeries(indicator_buffer1,true);
   ArraySetAsSeries(indicator_buffer2,true);
   class=class="str">"cmt">//--- 取得指标值
   if(CopyBuffer(indicator_handle,class="num">0,class="num">0,AllowedNumberOfSegments,indicator_buffer1)<AllowedNumberOfSegments ||
      CopyBuffer(indicator_handle,class="num">1,class="num">0,AllowedNumberOfSegments,indicator_buffer2)<AllowedNumberOfSegments)
     {
      Print("复制数值失败 ("+
            _Symbol+"; "+TimeframeToString(Period())+") (至 indicator_buffer1 或 indicator_buffer2 数组)错误 ("+
            IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError()));
      class="kw">return(class="kw">false);
     }
  }
class=class="str">"cmt">//---
class="kw">return(true);
 }
class=class="str">"cmt">//--- 如果指标句柄没有获得,重试
  else
   GetIndicatorHandle();
class=class="str">"cmt">//---
  class="kw">return(class="kw">false);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 检查条件并返回信号                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
ENUM_ORDER_TYPE GetSignal()
  {
class=class="str">"cmt">//--- 检查针对移动平均和CCI指标的条件
   if(Indicator==MA || Indicator==CCI)
   {
   class=class="str">"cmt">//--- 卖出信号
   if(AllowedNumberOfSegments==class="num">3 &&
      indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2])
    class="kw">return(ORDER_TYPE_SELL);
   class=class="str">"cmt">//---
   if(AllowedNumberOfSegments==class="num">4 &&
      indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2] &&
      indicator_buffer1[class="num">2]<indicator_buffer1[class="num">3])
    class="kw">return(ORDER_TYPE_SELL);
   class=class="str">"cmt">//---
   if(AllowedNumberOfSegments==class="num">5 &&
      indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2] &&
      indicator_buffer1[class="num">2]<indicator_buffer1[class="num">3] &&
      indicator_buffer1[class="num">3]<indicator_buffer1[class="num">4])
    class="kw">return(ORDER_TYPE_SELL);
   class=class="str">"cmt">//---
   if(AllowedNumberOfSegments==class="num">6 &&
      indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2] &&
      indicator_buffer1[class="num">2]<indicator_buffer1[class="num">3] &&
      indicator_buffer1[class="num">3]<indicator_buffer1[class="num">4] &&

◍ 分段计数驱动的多段顺势信号

这段逻辑把 ZigZag 类缓冲的连续段数(AllowedNumberOfSegments)当成信号强度开关:3 段就只看最近两节高低关系,段数越多要求连续同向的节数也越多,到 7 段及以上必须连续 6 节同向下坡才发 SELL,连续 6 节同上坡才发 BUY。 实际在 MT5 里跑,你会看到段数门槛直接过滤掉震荡期的毛刺——比如 AllowedNumberOfSegments==5 时,indicator_buffer1[1] 到 [4] 必须严格递减才返回 ORDER_TYPE_SELL,任何一节回头就废单。 PCH 分支另算一套:close_price[1] 收在 indicator_buffer2[1] 下方且 open_price[1] 开在上方,才构成一根吞没式阴线候选。外汇与贵金属波动大,这类形态触发后价格仍可能反向,仅作概率倾向参考。

MQL5 / C++
if(AllowedNumberOfSegments>=class="num">7 &&
   indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2] &&
   indicator_buffer1[class="num">2]<indicator_buffer1[class="num">3] &&
   indicator_buffer1[class="num">3]<indicator_buffer1[class="num">4] &&
   indicator_buffer1[class="num">4]<indicator_buffer1[class="num">5] &&
   indicator_buffer1[class="num">5]<indicator_buffer1[class="num">6])
   class="kw">return(ORDER_TYPE_SELL);
class=class="str">"cmt">//--- 买入信号
if(AllowedNumberOfSegments==class="num">3 &&
   indicator_buffer1[class="num">1]>indicator_buffer1[class="num">2])
   class="kw">return(ORDER_TYPE_BUY);
if(AllowedNumberOfSegments==class="num">4 &&
   indicator_buffer1[class="num">1]>indicator_buffer1[class="num">2] &&
   indicator_buffer1[class="num">2]>indicator_buffer1[class="num">3])
   class="kw">return(ORDER_TYPE_BUY);
if(AllowedNumberOfSegments==class="num">5 &&
   indicator_buffer1[class="num">1]>indicator_buffer1[class="num">2] &&
   indicator_buffer1[class="num">2]>indicator_buffer1[class="num">3] &&
   indicator_buffer1[class="num">3]>indicator_buffer1[class="num">4])
   class="kw">return(ORDER_TYPE_BUY);
if(AllowedNumberOfSegments==class="num">6 &&
   indicator_buffer1[class="num">1]>indicator_buffer1[class="num">2] &&
   indicator_buffer1[class="num">2]>indicator_buffer1[class="num">3] &&
   indicator_buffer1[class="num">3]>indicator_buffer1[class="num">4] &&
   indicator_buffer1[class="num">4]>indicator_buffer1[class="num">5])
   class="kw">return(ORDER_TYPE_BUY);
if(AllowedNumberOfSegments>=class="num">7 &&
   indicator_buffer1[class="num">1]>indicator_buffer1[class="num">2] &&
   indicator_buffer1[class="num">2]>indicator_buffer1[class="num">3] &&
   indicator_buffer1[class="num">3]>indicator_buffer1[class="num">4] &&
   indicator_buffer1[class="num">4]>indicator_buffer1[class="num">5] &&
   indicator_buffer1[class="num">5]>indicator_buffer1[class="num">6])
   class="kw">return(ORDER_TYPE_BUY);
class=class="str">"cmt">//--- 用于检查 MultiRange_PCH 指标条件的区块
if(Indicator==PCH)
{
class=class="str">"cmt">//--- 卖出信号
if(close_price[class="num">1]<indicator_buffer2[class="num">1] &&
   open_price[class="num">1]>indicator_buffer2[class="num">1])

持仓状态下的加仓与反手逻辑

当账户里已经有持仓时,EA 不会盲目追新信号,而是先读仓位类型和最后一笔成交价,再结合指标类别决定动作。 若用的是 MA 或 CCI 类指标,多单持仓下出现卖出信号会直接反手;空单持仓下若收盘价跌破 last_deal_price 减去 VolumeIncreaseStep*_Point 的修正值,则倾向加空。 反过来,空单遇买入信号反手做多;多单若收盘价突破 last_deal_price 加上同样步长修正值,可能触发加多。 若指标切换为 PCH(MultiRange_PCH),条件区块独立判断,不沿用上述均线类阈值,实盘前应在 MT5 策略测试器里分别跑两组参数验证触发频率。外汇与贵金属杠杆高,这类加仓逻辑可能快速放大回撤。

MQL5 / C++
   if(Indicator==PCH)
     {
     class=class="str">"cmt">//--- 卖出信号
     if(pos.type==POSITION_TYPE_BUY &&

「信号判定与品种属性结构体」

上面的代码片段给出了网格类 EA 的核心信号分支:当上一根 K 线收在指标缓冲 2 下方且开盘在上方,空单持仓时返回 ORDER_TYPE_SELL;若空单收盘价已低于上次成交价减去 CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point),同样加空。反向逻辑对称:空单收在指标缓冲 1 上方且开盘在下方平空反多,多单超过上次成交价加步长则加多。 没有命中任何条件时统一 return(WRONG_VALUE),这意味着主循环里要靠这个值过滤掉静默周期,避免无谓下单。外汇与贵金属杠杆高,这种加码逻辑在单边行情中回撤可能快速放大,实盘前务必在 MT5 策略测试器跑至少 3 个月 tick 数据。 代码后半段定义了 symbol_properties 结构体,把交易品种的关键属性一次性打包:digits 决定报价精度,point 是点值,spread 是点差点差,stops_level 和 up_level/down_level 框住止损摆放边界。execution_mode 用 ENUM_SYMBOL_TRADE_EXECUTION 标记成交模式(即时、市价、交易所等),写跨品种 EA 时直接读这个字段切换下单方式,比硬编常量稳。 把结构体塞进全局缓存后,每次 OnTick 不必反复调 SymbolInfoInteger / SymbolInfoDouble,仅用本地成员做判断,回测中处理 20+ 品种时耗时倾向下降一个数量级。

MQL5 / C++
close_price[class="num">1]<indicator_buffer2[class="num">1] &&
open_price[class="num">1]>indicator_buffer2[class="num">1])
   class="kw">return(ORDER_TYPE_SELL);
   class=class="str">"cmt">//---
   if(pos.type==POSITION_TYPE_SELL &&
   close_price[class="num">1]<pos.last_deal_price-CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point))
   class="kw">return(ORDER_TYPE_SELL);
   class=class="str">"cmt">//--- 买入信号
   if(pos.type==POSITION_TYPE_SELL &&
   close_price[class="num">1]>indicator_buffer1[class="num">1] &&
   open_price[class="num">1]<indicator_buffer1[class="num">1])
   class="kw">return(ORDER_TYPE_BUY);
   class=class="str">"cmt">//---
   if(pos.type==POSITION_TYPE_BUY &&
   close_price[class="num">1]>pos.last_deal_price+CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point))
   class="kw">return(ORDER_TYPE_BUY);
   }
   }
class=class="str">"cmt">//--- 没有信号
   class="kw">return(WRONG_VALUE);
   }
class=class="str">"cmt">//--- 交易品种属性
class="kw">struct symbol_properties
  {
  class="type">int               digits;            class=class="str">"cmt">// 价格中的小数点位数
  class="type">int               spread;            class=class="str">"cmt">// 点差点数
  class="type">int               stops_level;       class=class="str">"cmt">// 止损级别
  class="type">class="kw">double            point;             class=class="str">"cmt">// 点值
  class="type">class="kw">double            ask;               class=class="str">"cmt">// 买价
  class="type">class="kw">double            bid;               class=class="str">"cmt">// 卖价
  class="type">class="kw">double            volume_min;        class=class="str">"cmt">// 交易的最小交易量
  class="type">class="kw">double            volume_max;        class=class="str">"cmt">// 交易的最大交易量
  class="type">class="kw">double            volume_limit;      class=class="str">"cmt">// 仓位以及单方向订单的最大允许交易量
  class="type">class="kw">double            volume_step;       class=class="str">"cmt">// 交易中交易量改变的最小步长
  class="type">class="kw">double            offset;            class=class="str">"cmt">// 事务中价格的最大可能偏移
  class="type">class="kw">double            up_level;          class=class="str">"cmt">// 止损价位上限
  class="type">class="kw">double            down_level;        class=class="str">"cmt">// 止损价位下限
  ENUM_SYMBOL_TRADE_EXECUTION execution_mode; class=class="str">"cmt">// 执行模式
  };
class=class="str">"cmt">//--- 仓位属性的枚举
enum ENUM_SYMBOL_PROPERTIES
  {

◍ 把交易品种属性一次性读进结构体

上面这段枚举把 15 个关键字段编了号,从 0 到 14,覆盖报价精度、点差、止损位、点值、买卖价、最小最大成交量以及执行模式。编号 13 的 S_EXECUTION_MODE 单独用 case 分支去读 SYMBOL_TRADE_EXEMODE,返回的是 ENUM_SYMBOL_TRADE_EXECUTION 类型,决定该品种是市价执行还是交易所式撮合。 S_ALL 分支则是暴力全量刷新:digits 和 spread 走 SymbolInfoInteger 强转 int,ask/bid 用 NormalizeDouble 按 digits 截断,up_level 与 down_level 直接拿 ask/bid 加减 stops_level*point 算出来。外汇与贵金属杠杆高,stops_level 若大于 0 而你挂单距离小于该值,MT5 会直接拒单,开仓前务必用这套把实际边界打印出来核对。 把这段逻辑塞进 EA 的刷新函数,每次 OnTick 或手动触发时跑一遍 S_ALL,就能让后续下单模块随时拿到一致的品种上下文,不用反复查终端。

MQL5 / C++
  S_DIGITS          = class="num">0,
  S_SPREAD          = class="num">1,
  S_STOPSLEVEL      = class="num">2,
  S_POINT           = class="num">3,
  S_ASK             = class="num">4,
  S_BID             = class="num">5,
  S_VOLUME_MIN      = class="num">6,
  S_VOLUME_MAX      = class="num">7,
  S_VOLUME_LIMIT    = class="num">8,
  S_VOLUME_STEP     = class="num">9,
  S_FILTER          = class="num">10,
  S_UP_LEVEL        = class="num">11,
  S_DOWN_LEVEL      = class="num">12,
  S_EXECUTION_MODE  = class="num">13,
  S_ALL             = class="num">14
  };
case S_EXECUTION_MODE: symb.execution_mode=(ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE);   class="kw">break;
   class=class="str">"cmt">//---
   case S_ALL       :
      symb.digits=(class="type">int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
      symb.spread=(class="type">int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);
      symb.stops_level=(class="type">int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
      symb.point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
      symb.ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),symb.digits);
      symb.bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),symb.digits);
      symb.volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
      symb.volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
      symb.volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);
      symb.volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
      symb.offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*symb.point),symb.digits);
      symb.up_level=NormalizeDouble(symb.ask+symb.stops_level*symb.point,symb.digits);
      symb.down_level=NormalizeDouble(symb.bid-symb.stops_level*symb.point,symb.digits);
      symb.execution_mode=(ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE);                    class="kw">break;
      class=class="str">"cmt">//---
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 开启一个仓位                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

反序取值要从上一根已闭合K线读上下轨,别用当前未闭合K线,否则信号会滞后且重复触发。
按段数线性或递减加仓,例如第1段1手、第2段0.6手,并在结构体里记录已开段数防止超段。
小布可读取品种页的通道与持仓结构,提示第几段顺势、是否该加仓或反手,你只需确认执行。
若结构体显示趋势段未结束,倾向反手锁向;若已破关键轨,先平再等回踩确认更稳。
能避开点值、最小止损单位每次实时查询的卡顿与报错,尤其切换周期时不会因属性缺失发单失败。