MQL5 向导: 根据计算价位下单, 止损和止盈。标准库扩展·综合运用
📘

MQL5 向导: 根据计算价位下单, 止损和止盈。标准库扩展·综合运用

第 3/3 篇

「DeltaZZ 价格模块的类骨架与默认参数」

下面这段 CPriceDeltaZZ 类的声明和构造函数,把基于 ZigZag 偏离的价格模块骨架定死了。它管几件事:挂单层数 m_levels、盈亏比 m_tp_ratio、订单存活根数 m_exp_bars,以及从 ZZ 极值反推买卖止损位的 getBuySL / getSellSL。 构造函数里给的默认值值得直接抄进 MT5 试:m_app_price=1、m_rev_mode=0、m_pips=300、m_percent=0.5、m_levels=2。也就是说初始状态按 300 点基准、0.5% 比例、两层网格去算价格偏移,反转模式关闭。 calcExpiration 用 TimeCurrent() 加 m_exp_bars 乘 PeriodSeconds(m_period) 算订单过期时间,这根数直接决定挂单在盘面留多久。外汇和贵金属波动随机性高,m_exp_bars 设太大可能吃不到出场就废单。 逐行看声明段:m_levels 是 int 型层数;m_tp_ratio 注释写明是 tp:sl 比值;m_exp_bars 是订单以 K 线根计的生命周期;InitDeltaZZ 负责把指标挂进 CIndicators 指针;公开区一堆 set 方法让你运行时改参,ValidationSettings 做参数合法性校验,Prices 是输出最终价格的主函数。

MQL5 / C++
class="type">int                m_levels;
  class="type">class="kw">double             m_tp_ratio;            class=class="str">"cmt">//tp:sl ratio
  class="type">int                m_exp_bars;           class=class="str">"cmt">//lifetime of the orders in bars
  class=class="str">"cmt">//--- method of indicator initialization
  class="type">bool               InitDeltaZZ(CIndicators *indicators);
  class=class="str">"cmt">//--- helper methods
  class="type">class="kw">datetime           calcExpiration() { class="kw">return(TimeCurrent()+m_exp_bars*PeriodSeconds(m_period)); }
  class="type">class="kw">double             getBuySL();           class=class="str">"cmt">//function for searching latest minimum of ZZ for buy SL
  class="type">class="kw">double             getSellSL();          class=class="str">"cmt">//function for searching latest maximum of ZZ for sell SL
class="kw">public:
                     CPriceDeltaZZ();
                    ~CPriceDeltaZZ();
  class=class="str">"cmt">//--- methods of changing module settings
  class="type">void               setAppPrice(class="type">int ap)          { m_app_price=ap;  }
  class="type">void               setRevMode(class="type">int rm)           { m_rev_mode=rm;   }
  class="type">void               setPips(class="type">int pips)            { m_pips=pips;     }
  class="type">void               setPercent(class="type">class="kw">double perc)      { m_percent=perc;  }
  class="type">void               setLevels(class="type">int rnum)          { m_levels=rnum;   }
  class="type">void               setTpRatio(class="type">class="kw">double tpr)       { m_tp_ratio=tpr;  }
  class="type">void               setExpBars(class="type">int bars)         { m_exp_bars=bars; }
  class=class="str">"cmt">//--- method of checking correctness of settings
  class="kw">virtual class="type">bool       ValidationSettings(class="type">void);
  class=class="str">"cmt">//--- method of creating indicators
  class="kw">virtual class="type">bool       InitIndicators(CIndicators *indicators);
  class=class="str">"cmt">//--- main method of price module updating the output data
  class="kw">virtual class="type">class="kw">double     Prices();
  };
CPriceDeltaZZ::CPriceDeltaZZ() : m_app_price(class="num">1),
                     m_rev_mode(class="num">0),
                     m_pips(class="num">300),
                     m_percent(class="num">0.5),
                     m_levels(class="num">2),

◍ 信号类里的参数闸门与指标挂载

自定义信号类 CPriceDeltaZZ 在跑策略前,先由 ValidationSettings() 卡一道参数边界。这里硬性规定:射线最小跨度 m_pips 必须 ≥10,否则直接 printf 报错并返回 false;m_app_price 与 m_rev_mode 只能取 0 或 1,m_percent、m_levels、m_tp_ratio 都必须大于 0,m_exp_bars 允许 0 但不可为负。 这些数字不是随便写的——m_pips<10 意味着价格射线连 1 个点差台阶都构不成,后续 ZigZag 拐点识别会失效。你在 MT5 里改 EA 输入参数时,若日志抛出 "Number of pips in a ray must be at least 10",就是这里拦的。 InitIndicators() 负责把指标挂进集合。它先调父类 CExpertSignal::InitIndicators() 保底,再走 InitDeltaZZ() 把自定义 m_deltazz 指针加进 indicators 容器;若 Add() 失败会打印 "error adding object" 并返回 false。 InitDeltaZZ() 里用 MqlParam parameters[6] 开 6 个槽位准备传参,首参 parameters[0].type=TYPE_STRING 表明自定义指标名以字符串形式递进去。外汇与贵金属波动剧烈,这类校验缺失可能导致异常下单,实盘前务必在策略测试器跑一遍初始化日志。

MQL5 / C++
m_tp_ratio(class="num">1),
 m_exp_bars(class="num">10)
  {
  }
class="type">bool CPriceDeltaZZ::ValidationSettings(class="type">void)
  {
class=class="str">"cmt">//--- checking for settings of additional filters
   if(!CExpertSignal::ValidationSettings())
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- initial data check
   if(m_app_price<class="num">0 || m_app_price>class="num">1)
     {
       printf(__FUNCTION__+": Applied price must be class="num">0 or class="num">1");
       class="kw">return(class="kw">false);
     }
   if(m_rev_mode<class="num">0 || m_rev_mode>class="num">1)
     {
       printf(__FUNCTION__+": Reversal mode must be class="num">0 or class="num">1");
       class="kw">return(class="kw">false);
     }
   if(m_pips<class="num">10)
     {
       printf(__FUNCTION__+": Number of pips in a ray must be at least class="num">10");
       class="kw">return(class="kw">false);
     }
   if(m_percent<=class="num">0)
     {
       printf(__FUNCTION__+": Percent must be greater than class="num">0");
       class="kw">return(class="kw">false);
     }
   if(m_levels<class="num">1)
     {
       printf(__FUNCTION__+": Ray Number must be at least class="num">1");
       class="kw">return(class="kw">false);
     }
   if(m_tp_ratio<=class="num">0)
     {
       printf(__FUNCTION__+": TP Ratio must be greater than zero");
       class="kw">return(class="kw">false);
     }
   if(m_exp_bars<class="num">0)
     {
       printf(__FUNCTION__+": Expiration must be zero or positive value");
       class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//--- parameter check passed
   class="kw">return(true);
  }
class="type">bool CPriceDeltaZZ::InitIndicators(CIndicators *indicators)
  {
class=class="str">"cmt">//--- initialization of indicator filters
   if(!CExpertSignal::InitIndicators(indicators))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- creating and initializing of custom indicator
   if(!InitDeltaZZ(indicators))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- success
   class="kw">return(true);
  }
class="type">bool CPriceDeltaZZ::InitDeltaZZ(CIndicators *indicators)
  {
class=class="str">"cmt">//--- adds to collection
   if(!indicators.Add(GetPointer(m_deltazz)))
     {
       printf(__FUNCTION__+": error adding object");
       class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//--- specifies indicator parameters
   class="type">MqlParam parameters[class="num">6];
class=class="str">"cmt">//---
   parameters[class="num">0].type=TYPE_STRING;

把指标缓冲映射成挂单参数

这段实现把自定义指标 deltazigzag.ex5 的 6 个输入参数塞进 parameters 数组,再交给 CPriceDeltaZZ 类去初始化。参数 0 是字符串类型的指标文件名,1~3 与 5 都是 TYPE_INT(价格类型、反转模式、点值、缓冲层数),4 是 TYPE_DOUBLE 的百分比阈值;Create 调用若失败直接返回 false 并打印函数名报错,NumBuffers(5) 则强制指标使用 5 个缓冲。 Prices() 是真正的取值入口:从缓冲 3 读 openbuy、缓冲 4 读 opensell,二者大于 0 才认为有信号。多头挂单价用 NormalizeDouble 按品种小数位对齐,止损取自 getBuySL(),止盈按 m_tp_ratio 乘(开仓价-止损价)推算;空头对称处理,缓冲 1 提供卖单止损。 getBuySL / getSellSL 用 while 从缓冲 0 / 1 向前扫描,遇到非 0 值即返回,等于跳过历史空白柱找最近的有效止损位。外汇与贵金属波动剧烈,这类自动挂单参数仅反映指标最近一根柱的逻辑,实盘前务必在 MT5 策略测试器用真实点差回测验证。

MQL5 / C++
  parameters[class="num">0].string_value="deltazigzag.ex5";
  parameters[class="num">1].type=TYPE_INT;
  parameters[class="num">1].integer_value=m_app_price;
  parameters[class="num">2].type=TYPE_INT;
  parameters[class="num">2].integer_value=m_rev_mode;
  parameters[class="num">3].type=TYPE_INT;
  parameters[class="num">3].integer_value=m_pips;
  parameters[class="num">4].type=TYPE_DOUBLE;
  parameters[class="num">4].double_value=m_percent;
  parameters[class="num">5].type=TYPE_INT;
  parameters[class="num">5].integer_value=m_levels;
class=class="str">"cmt">//--- object initialization
  if(!m_deltazz.Create(m_symbol.Name(),m_period,IND_CUSTOM,class="num">6,parameters))
    {
      printf(__FUNCTION__+": error initializing object");
      class="kw">return(class="kw">false);
    }
class=class="str">"cmt">//--- number of the indicator buffers
  if(!m_deltazz.NumBuffers(class="num">5)) class="kw">return(class="kw">false);
class=class="str">"cmt">//--- ок
  class="kw">return(true);
  }
class="type">class="kw">double CPriceDeltaZZ::Prices(class="type">void)
  {
   class="type">class="kw">double openbuy =m_deltazz.GetData(class="num">3,class="num">0);class=class="str">"cmt">//receive the last value from buffer class="num">3
   class="type">class="kw">double opensell=m_deltazz.GetData(class="num">4,class="num">0);class=class="str">"cmt">//receive the last value from buffer class="num">4
class=class="str">"cmt">//--- clear parameter values
   m_order_open_long=EMPTY_VALUE;
   m_order_stop_long=EMPTY_VALUE;
   m_order_take_long=EMPTY_VALUE;
   m_order_expiration_long=class="num">0;
   m_order_open_short=EMPTY_VALUE;
   m_order_stop_short=EMPTY_VALUE;
   m_order_take_short=EMPTY_VALUE;
   m_order_expiration_short=class="num">0;
   class="type">int digits=m_symbol.Digits();
class=class="str">"cmt">//--- check for the prices to buy
   if(openbuy>class="num">0)class=class="str">"cmt">//if buffer class="num">3 is not empty
     {
      m_order_open_long=NormalizeDouble(openbuy,digits);
      m_order_stop_long=NormalizeDouble(getBuySL(),digits);
      m_order_take_long=NormalizeDouble(m_order_open_long + m_tp_ratio*(m_order_open_long - m_order_stop_long),digits);
      m_order_expiration_long=calcExpiration();
     }
class=class="str">"cmt">//--- check for the prices to sell
   if(opensell>class="num">0)class=class="str">"cmt">//if buffer class="num">4 is not empty
     {
      m_order_open_short=NormalizeDouble(opensell,digits);
      m_order_stop_short=NormalizeDouble(getSellSL(),digits);
      m_order_take_short=NormalizeDouble(m_order_open_short - m_tp_ratio*(m_order_stop_short - m_order_open_short),digits);
      m_order_expiration_short=calcExpiration();
     }
   class="kw">return(class="num">0);
  }
class="type">class="kw">double CPriceDeltaZZ::getBuySL(class="type">void)
  {
   class="type">int i=class="num">0;
   class="type">class="kw">double sl=class="num">0.0;
   while(sl==class="num">0.0)
     {
      sl=m_deltazz.GetData(class="num">0,i);
      i++;
     }
   class="kw">return(sl);
  }
class="type">class="kw">double CPriceDeltaZZ::getSellSL(class="type">void)
  {
   class="type">int i=class="num">0;
   class="type">class="kw">double sl=class="num">0.0;
   while(sl==class="num">0.0)
     {
      sl=m_deltazz.GetData(class="num">1,i);
      i++;
     }
   class="kw">return(sl);
  }

「内柱线模块的双边挂单逻辑」

内柱线指当前根 K 线的最高价和最低价都包在前一根 K 线的高低区间之内,属于纯价格行为形态,不依赖任何指标。它往往暗示原方向动能衰减,价格倾向出现反转或至少一波突破。 当识别到内柱线,模块会依据其极值直接算出向上突破买、向下突破卖两个入场价:突破前高加偏移挂多,跌破前低减偏移挂空。这两个开仓价同时就是反向订单的止损位,止盈则用止损距离乘以设置里的系数(默认 2)得出。 图例中上行趋势下虽未显示卖单成交,但模块仍会生成双向级别;部分止盈位因距离过远超出了可视窗口。该模块算法极简,不需要指标初始化,代码量很少。 Prices() 方法在没有可用信号时返回 EMPTY_VALUE,调用方据此跳过本轮。外汇与贵金属杠杆高,内柱线假突破概率不低,挂单前建议在 MT5 用 10 根 bars 过期参数实测命中率。

MQL5 / C++
class="macro">#include <Expert\ExpertSignalAdvanced.mqh>
class=class="str">"cmt">// wizard description start
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Description of the class                                          |
class=class="str">"cmt">//| Title=Inside Bar Price Module                                    |
class=class="str">"cmt">//| Type=SignalAdvanced                                              |
class=class="str">"cmt">//| Name=Inside Bar Price Module                                     |
class=class="str">"cmt">//| ShortName=IB_PM                                                  |
class=class="str">"cmt">//| Class=CPriceInsideBar                                            |
class=class="str">"cmt">//| Page=not used                                                    |
class=class="str">"cmt">//| Parameter=setTpRatio,class="type">class="kw">double,class="num">2,TP:SL ratio                        |
class=class="str">"cmt">//| Parameter=setExpBars,class="type">int,class="num">10,Expiration after bars number         |
class=class="str">"cmt">//| Parameter=setOrderOffset,class="type">int,class="num">5,Offset for open and stop loss     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// wizard description end
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class CPriceInsideBar                                            |
class=class="str">"cmt">//| Purpose: Class of the generator of price levels for orders based |
class=class="str">"cmt">//|          on the "inside bar" pattern.                            |
class=class="str">"cmt">//| Is derived from the CExpertSignalAdvanced class.                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CPriceInsideBar : class="kw">public CExpertSignalAdvanced
  {
class="kw">protected:
   class="type">class="kw">double            m_tp_ratio;       class=class="str">"cmt">//tp:sl ratio
   class="type">int               m_exp_bars;       class=class="str">"cmt">//lifetime of the orders in bars
   class="type">class="kw">double            m_order_offset;   class=class="str">"cmt">//shift of the opening and Stop Loss levels
   class="type">class="kw">datetime          calcExpiration()  { class="kw">return(TimeCurrent()+m_exp_bars*PeriodSeconds(m_period)); }
class="kw">public:
                     CPriceInsideBar();

◍ 母子形态信号的参数闸门与取值

Inside Bar 信号类把几个可调项收进成员函数:止盈比、挂单失效根数、入场偏移点数。setTpRatio 直接写 m_tp_ratio;setExpBars 控 m_exp_bars;setOrderOffset 把 pips 乘上 Point() 转成价格偏移,避免手算小数位出错。 ValidationSettings 先调基类校验,再卡两道硬门槛:m_tp_ratio 必须 >0,否则终端打印「TP Ratio must be greater than zero」并返回 false;m_exp_bars 不能 <0,负过期根数同样驳回。这两处不通过,后续 Prices 根本不会被调用。 Prices 函数从 1 号偏移位取近 2 根 K 的高低:CopyHigh/CopyLow 各取 2 个值,失败返 EMPTY_VALUE。判定母子条的核心是 h[0]>=h[1] && l[0]<=l[1]——即第 1 根完全被第 2 根包容。成立时多头挂单价落在 h[0]+m_order_offset。外汇与贵金属波动跳空频繁,这类包容判定在重大数据前可能连续假触发,实盘前建议在 MT5 用 EURUSD 的 M15 跑一遍打印验证。

MQL5 / C++
   ~CPriceInsideBar();
   class="type">void      setTpRatio(class="type">class="kw">double ratio){ m_tp_ratio=ratio; }
   class="type">void      setExpBars(class="type">int bars)     { m_exp_bars=bars;}
   class="type">void      setOrderOffset(class="type">int pips){ m_order_offset=m_symbol.Point()*pips;}
   class="type">bool      ValidationSettings();
   class="type">class="kw">double    Prices();
};
class=class="str">"cmt">//+------------------------------------------------------------------+
CPriceInsideBar::CPriceInsideBar()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
CPriceInsideBar::~CPriceInsideBar()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CPriceInsideBar::ValidationSettings(class="type">void)
  {
class=class="str">"cmt">//--- verification of the filter parameters
   if(!CExpertSignal::ValidationSettings())
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- initial data check
  if(m_tp_ratio<=class="num">0)
     {
       printf(__FUNCTION__+": TP Ratio must be greater than zero");
       class="kw">return(class="kw">false);
     }
   if(m_exp_bars<class="num">0)
     {
       printf(__FUNCTION__+": Expiration must be zero or positive value");
       class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//--- check passed
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CPriceInsideBar::Prices(class="type">void)
{
   class="type">class="kw">double h[class="num">2],l[class="num">2];
   if(CopyHigh(m_symbol.Name(),m_period,class="num">1,class="num">2,h)!=class="num">2 || CopyLow(m_symbol.Name(),m_period,class="num">1,class="num">2,l)!=class="num">2)
      class="kw">return(EMPTY_VALUE);
class=class="str">"cmt">//--- check for inside bar
   if(h[class="num">0] >= h[class="num">1] && l[class="num">0] <= l[class="num">1])
   {
      m_order_open_long=h[class="num">0]+m_order_offset;

多空挂单的止损止盈对称推导

这段逻辑把突破挂单的价位链一次性算完:多单止损取左侧参考价减去偏移量,多单止盈则按开仓价与止损的距离乘以盈亏比 m_tp_ratio 向外推。 空单的处理是镜像的——开仓价直接等于多单止损位,空单止损等于多单开仓价,止盈再按同样比例向内收。这样多空两边的风险敞口在数值上保持对称。 过期时间两个方向共用同一个 calcExpiration() 返回值,避免长短线挂单生命周期不一致。外汇与贵金属杠杆高,这类对称挂单仍可能因跳空触发滑点,实际成交价与计算价可能有偏差,开 MT5 用策略测试器跑一遍更稳妥。

MQL5 / C++
   m_order_stop_long=l[class="num">0]-m_order_offset;
   m_order_take_long=m_order_open_long+(m_order_open_long-m_order_stop_long)*m_tp_ratio;
   m_order_expiration_long=calcExpiration();
   
   m_order_open_short=m_order_stop_long;
   m_order_stop_short=m_order_open_long;
   m_order_take_short=m_order_open_short-(m_order_stop_short-m_order_open_short)*m_tp_ratio;
   m_order_expiration_short=m_order_expiration_long;
   class="kw">return(class="num">0);
   }
   class="kw">return(EMPTY_VALUE);
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「外柱线模块怎么挂双向突破单」

外柱线(Outside Bar)在价格行为里常被称为吞噬柱,特征是当前柱的最高价和最低价完全包住前一根柱的波动区间。它一旦出现,往往意味着接下来一段针对性波动的概率上升,适合在极值外缘布置突破单。 实际落单时,模块会在外柱线的高点上方挂买、低点下方挂卖,两组挂单的开仓价同时互为对方止损。止盈则用止损距离乘以设置里的系数得出,图例中红色水平线是开仓与止损、绿色是止盈。 趋势偏空时可能只留卖单,比如图例 5 里就只生成了向下突破的挂单,逻辑和前一种模块一致,只是 Prices() 方法换了判断条件。 下面这段是核心取价逻辑:先取最近两根 K 线高低价,若当前柱 high≤前柱 high 且 low≥前柱 low 即判定为外柱线,随后算出多空双向的开仓、止损、止盈与有效期。CopyHigh/CopyLow 取不到两根就返回 EMPTY_VALUE,避免误触发。

MQL5 / C++
class="type">class="kw">double CPriceOutsideBar::Prices(class="type">void)
{
   class="type">class="kw">double h[class="num">2],l[class="num">2];
   if(CopyHigh(m_symbol.Name(),m_period,class="num">1,class="num">2,h)!=class="num">2 || CopyLow(m_symbol.Name(),m_period,class="num">1,class="num">2,l)!=class="num">2)
      class="kw">return(EMPTY_VALUE);
class=class="str">"cmt">//--- check of outside bar
   if(h[class="num">0] <= h[class="num">1] && l[class="num">0] >= l[class="num">1])
   {
      m_order_open_long=h[class="num">1]+m_order_offset;
      m_order_stop_long=l[class="num">1]-m_order_offset;
      m_order_take_long=m_order_open_long+(m_order_open_long-m_order_stop_long)*m_tp_ratio;
      m_order_expiration_long=calcExpiration();
      
      m_order_open_short=m_order_stop_long;
      m_order_stop_short=m_order_open_long;
      m_order_take_short=m_order_open_short-(m_order_stop_short-m_order_open_short)*m_tp_ratio;
      m_order_expiration_short=m_order_expiration_long;
      class="kw">return(class="num">0);
   }
   class="kw">return(EMPTY_VALUE);
}

◍ 把三个价格模块拆开单独跑一遍

这一节只干一件事:把前面写好的三个价格模块分别挂到 EA 里,验证代码本身能不能独立跑通、有没有逻辑偏差,而不是看谁赚得多。 测试环境是 EURUSD 模拟盘,MetaQuotes-Demo 服务器,周期 2013.01.01–2014.06.01,终端版本 5.00 编译号 965(2014.06.27 版)。动量振荡器模块固定在 H12 做过滤,价格模块跑在 H6;资金管理的固定手数和尾随止损都没开,纯粹测性能。 三个模块依次测:增量之字折线、内柱线、外柱线,各出一张余额图。看内柱线那张图时要清醒——这阶段的目标是查代码毛病,不是找能盈利的策略,曲线平或亏都正常。 每一阶段都把价格区间里的每笔操作扒开核对,没发现策略错误或偏移。文末附了全部程序,你拉进 MT5 自己按同样周期复跑就行,外汇和贵金属模拟测试本身就有高波动风险,别直接上实盘。

向导生成后改代码的几个硬坑

用 MQL5 向导拼 EA 时,模块添加顺序直接决定编译结果是否可控。信号模块(CExpertSignal 的继承者)必须先于价格模块(CExpertSignalAdvanced 的继承者)加入,顺序乱了行为不可预料;本例里先挂动量振荡器做唯一过滤器,再随机塞进三个价格模块(DeltaZZ、内柱线、外柱线)。 向导跑完得到的代码不能直接用。开头要把标准头文件换成高级版:#include <Expert\Expert.mqh> 改为 <Expert\ExpertAdvanced.mqh>,信号对象也要从 CExpertSignal 换成 CExpertSignalAdvanced,否则价格模块权重和索引逻辑不会生效。 模块在代码里按包含次序自动命名,filter0、filter1、filter2 依次排。本例只有一个信号模块,所以第一个价格模块编号是 filter2;要在 m_filters 数组里插入 signal.AddFilter(filter1) 前,先写一句 signal.CalcPriceModuleIndex() 让主信号识别过滤器指向。搜「filter」加数字能快速定位,别凭肉眼找。 删掉开盘缩进、止损止盈、过期等 input 参数串可以瘦身代码,但会引发编译报错,得再删一组连带声明才能过。样例 EA 名叫 TEST_EA_AO_DZZ_IB_OB,注释行可一并清掉。外汇与贵金属杠杆高,实盘前请在 MT5 策略测试器用历史数据验证模块权重。

MQL5 / C++
class="macro">#include <Expert\Expert.mqh>
class="macro">#include <Expert\ExpertAdvanced.mqh>
CExpert ExtExpert;
CExpertAdvanced ExtExpert;
CExpertSignal *signal=new CExpertSignal;
CExpertSignalAdvanced *signal=new CExpertSignalAdvanced;
signal.AddFilter(filter1);
signal.CalcPriceModuleIndex();
input class="type">class="kw">double Signal_PriceLevel        =class="num">0.0;                 class=class="str">"cmt">// Price level to execute a deal
input class="type">class="kw">double Signal_StopLevel         =class="num">50.0;                class=class="str">"cmt">// Stop Loss level(in points)
input class="type">class="kw">double Signal_TakeLevel         =class="num">50.0;                class=class="str">"cmt">// Take Profit level(in points)
input class="type">int   Signal_Expiration         =class="num">4;                   class=class="str">"cmt">// Expiration of pending orders(in bars)
  signal.PriceLevel(Signal_PriceLevel);
  signal.StopLevel(Signal_StopLevel);
  signal.TakeLevel(Signal_TakeLevel);
  signal.Expiration(Signal_Expiration);

「把这条线请下神坛」

向导生成的 EA 现在能跨价位挂单、带止损止盈,并靠一组价格模块挑最合适的参数组进场;设置里指定的组优先,方向清楚但点位模糊时,它会干等信号出现。目前标准兼容模块只给了三个——pricedeltazz、priceinsidebar、priceoutsidebar,体积分别在 22.93 KB、9.45 KB、9.47 KB,后续数量大概率会扩。 没写过节的人也能基于现成模型改出更进阶的 EA;继承机制是加功能最干净的路子。上面那段 Direction() 判断就是模块选择性生效的开关:返回 EMPTY_VALUE 直接 false,不强行发单。 外汇和贵金属杠杆高、滑点跳空频繁,这种选择性 EA 也只是把“乱枪打鸟”变成“挑位置开枪”,胜率倾向提升但绝不包赢。真要验证,把 mql5_library_extension.zip 里的 mqh 丢进 MT5 的 Include,跑一晚复盘看看拒单率再说。

MQL5 / C++
  class="type">class="kw">double direction=Direction();
class=class="str">"cmt">//-- 禁止信号
  if(direction==EMPTY_VALUE)
    class="kw">return(class="kw">false);

常见问题

把类骨架里的触发阈值调大、过滤窗口拉长,可减少假突破带来的重复挂单;具体数值建议先在历史品种上回看再定。
检查参数闸门是否放行了该指标周期,以及缓冲索引是否与信号类读取位置一致;不一致时映射会静默失败。
可以。小布能读取你当前品种页的挂单逻辑,提示多空止损止盈是否对称、有无单边裸奔风险,省去手动核对。
按原文逻辑,刺穿后未成交挂单应撤除或转市价;具体看你在双边逻辑里写的撤单条件分支。
优先看形态确认所需的柱数闸门与最小实体比例;这两项偏严会直接砍掉多数候选信号。