您应当知道的 MQL5 向导技术(第 44 部分):平均真实范围(ATR)技术指标·综合运用
📘

您应当知道的 MQL5 向导技术(第 44 部分):平均真实范围(ATR)技术指标·综合运用

第 3/3 篇

「尾随停止的三种基准形态」

ATR 在 EA 里最实际的用处不是生成信号,而是挂尾随停止和调仓。把它封进自定义类,仍能被 MQL5 向导直接汇编进智能系统,省去重复接线。 这里刻意把实现压到最薄:尾随价位就是「基准 ± ATR 倍数」。基准给了三种取法,分别编为索引 0、1、2——原生价格、移动平均价、SAR 指标位。 形态 0 直接用市价减(多)或加(空)一个 ATR 缓冲;形态 1 把基准换成 MA 同周期值,再叠 ATR 偏移;形态 2 更挑条件,仅当 SAR 落在 K 线外侧(多单 SAR 低于最低价、空单 SAR 高于最高价)才以其为基准挂停止。 三种形态可单独优化,也可一次性丢进向导按权重混跑。外汇与贵金属波动跳空频繁,ATR 尾随仅降低被毛刺扫损的概率,不消除方向性风险,开 MT5 用下方代码替换自己的 CTrailingATR 片段即可验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for Pattern class="num">0.                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTrailingATR::IsPattern_0(class="type">class="kw">double &Price, class="type">ENUM_POSITION_TYPE T)
{  if(T == POSITION_TYPE_BUY)
   { Price -= (m_stop_level * ATR(StartIndex()));
   }
   else if(T == POSITION_TYPE_SELL)
   { Price += (m_stop_level * ATR(StartIndex()));
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for Pattern class="num">1.                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTrailingATR::IsPattern_1(class="type">class="kw">double &Price, class="type">ENUM_POSITION_TYPE T)
{  if(T == POSITION_TYPE_BUY)
   { Price = (MA(StartIndex()) - (m_stop_level * ATR(StartIndex())));
   }
   else if(T == POSITION_TYPE_SELL)
   { Price = (MA(StartIndex()) + (m_stop_level * ATR(StartIndex())));
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for Pattern class="num">2.                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTrailingATR::IsPattern_2(class="type">class="kw">double &Price, class="type">ENUM_POSITION_TYPE T)
{  if(T == POSITION_TYPE_BUY && SAR(StartIndex()) < Low(StartIndex()))
   { Price = (SAR(StartIndex()) - (m_stop_level * ATR(StartIndex())));
   }
   else if(T == POSITION_TYPE_SELL && SAR(StartIndex()) > High(StartIndex()))
   { Price = (SAR(StartIndex()) + (m_stop_level * ATR(StartIndex())));
   }
}

用 ATR 止损距算开仓手数的坑与护栏

把止损间距直接当作持仓规模的计算基准,逻辑上说得通:一笔单子的最大账面亏损上限就是触发止损那刻的跳空前价位差。只要有了止损位和「最多亏掉可用保证金百分之多少」这个 m_percent,就能反推出理论手数。 但理论只是理论。止损价不等于成交价,2015 年 1 月瑞郎黑天鹅里,很多单子跳空穿透止损,实际亏损远超预设百分比,外汇和贵金属的高风险在这类事件里会被放大数倍。 另一个隐蔽问题是:当 ATR 算出的止损距极小时,公式会给出过大的手数。所以实战里要把手数标准化,上限锁死在「可用保证金默认风险百分比对应手数」的两倍以内——m_percent 既是手数上限的锚,也是理论亏损占可用保证金的比例定义。 下面这段 MT5 代码演示了多模型并行时如何取手数均值并夹住上限,开多和开空各一套: [CODE] <span class="comment">//+------------------------------------------------------------------+</span>

<span class="comment">//Getting lot size for open long position.</span>

<span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">double</span> CMoneyATR::CheckOpenLong(<span class="keyword">double</span> price, <span class="keyword">double</span> sl) { <span class="keyword">if</span>(m_symbol == <span class="macro">NULL</span>) <span class="keyword">return</span>(<span class="number">0.0</span>); <span class="comment">//--- select lot size</span> <span class="keyword">double</span> lot; <span class="keyword">if</span>(price == <span class="number">0.0</span>) lot = m_account.MaxLotCheck(m_symbol.Name(), <span class="macro">ORDER_TYPE_BUY</span>, m_symbol.Ask(), m_percent); <span class="keyword">else</span> lot = m_account.MaxLotCheck(m_symbol.Name(), <span class="macro">ORDER_TYPE_BUY</span>, price, m_percent); <span class="comment">//---</span> <span class="keyword">double</span> result = <span class="number">0.0</span>, results = <span class="number">0.0</span>; price =m_symbol.Bid(); <span class="comment">//--- if the model 0 is used and "ATR-Based Stop Loss"</span> <span class="keyword">if</span>(((m_patterns_usage &amp; <span class="number">0x01</span>) != <span class="number">0</span>)) { IsPattern_0(price, <span class="macro">POSITION_TYPE_BUY</span>); result += m_pattern_0 * price; results += m_pattern_0; } <span class="comment">//--- if the model 1 is used and "ATR-MA-Channel Stop Loss"</span> <span class="keyword">if</span>(((m_patterns_usage &amp; <span class="number">0x02</span>) != <span class="number">0</span>)) { IsPattern_1(price, <span class="macro">POSITION_TYPE_BUY</span>); result += m_pattern_1 * price; results += m_pattern_1; } <span class="comment">//--- if the model 2 is used and "ATR-SAR-Channel Stop Loss"</span> <span class="keyword">if</span>(((m_patterns_usage &amp; <span class="number">0x04</span>) != <span class="number">0</span>)) { IsPattern_2(price, <span class="macro">POSITION_TYPE_BUY</span>); result += m_pattern_2 * price; results += m_pattern_2; } <span class="comment">//---</span> <span class="keyword">if</span>(results &gt; <span class="number">0</span>) { result /= results; <span class="keyword">double</span> _risk = (<span class="functions">fabs</span>(m_symbol.Bid()-result)/m_symbol.<span class="functions">Point</span>())*(m_symbol.TickSize()/m_symbol.<span class="functions">Point</span>())*m_symbol.TickValue(); _risk /= m_account.FreeMargin(); _risk *= <span class="number">100.0</span>; <span class="keyword">double</span> _risk_lots = m_percent/_risk;<span class="comment">// where m_percent is also max risk</span> lot = <span class="functions">fmin</span>(<span class="number">2.0</span>*lot, <span class="functions">fmax</span>(_risk_lots, m_symbol.LotsMin())); } <span class="comment">//--- return trading volume</span> <span class="keyword">return</span>(Optimize(lot)); } <span class="comment">//+------------------------------------------------------------------+</span>

<span class="comment">//Getting lot size for open short position.</span>

<span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">double</span> CMoneyATR::CheckOpenShort(<span class="keyword">double</span> price, <span class="keyword">double</span> sl) { <span class="keyword">if</span>(m_symbol == <span class="macro">NULL</span>) <span class="keyword">return</span>(<span class="number">0.0</span>); <span class="comment">//--- select lot size</span> <span class="keyword">double</span> lot; <span class="comment">//---</span> <span class="keyword">if</span>(price == <span class="number">0.0</span>) lot = m_account.MaxLotCheck(m_symbol.Name(), <span class="macro">ORDER_TYPE_SELL</span>, m_symbol.Bid(), m_percent); <span class="keyword">else</span> lot = m_account.MaxLotCheck(m_symbol.Name(), <span class="macro">ORDER_TYPE_SELL</span>, price, m_percent); <span class="comment">//---</span> 逐行拆解:

  • if(m_symbol == NULL) return(0.0); 符号指针空则直接退出,避免后续崩溃。
  • MaxLotCheck(..., m_percent) 用账户接口按风险百分比算初步手数上限。
  • 三个 if((m_patterns_usage & 0x01/0x02/0x04) != 0) 分别启用三种 ATR 衍生止损模型,把各模型权重乘价累加进 resultresults
  • result /= results 得到多模型止损价的加权均值。
  • _risk 计算每点价值占空闲保证金的百分比,m_percent/_risk 得出按真实风险倒推的手数。
  • fmin(2.0*lot, fmax(_risk_lots, LotsMin())) 就是上文说的「两倍护栏」:不超过默认手数两倍,且不低于平台最小手数。
  • 开空函数结构对称,只是买卖方向和 Ask/Bid 引用互换。

回测里同时挂三种模型并给不同权重,比单模型在震荡市里手数波动更平滑,但极端行情仍可能触雷。把这段代码丢进 MT5 策略测试器,改 m_percent 从 1 到 5 跑一遍,能直接看到两倍上限如何压住小止损时的手数膨胀。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Getting lot size for open class="type">long position.                        |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">class="kw">double</span> CMoneyATR::CheckOpenLong(<span class="keyword">class="type">class="kw">double</span> price, <span class="keyword">class="type">class="kw">double</span> sl)
{  <span class="keyword">if</span>(m_symbol == <span class="macro">NULL</span>)
      <span class="keyword">class="kw">return</span>(<span class="number">class="num">0.0</span>);
<span class="comment">class=class="str">"cmt">//--- select lot size</span>
   <span class="keyword">class="type">class="kw">double</span> lot;
   <span class="keyword">if</span>(price == <span class="number">class="num">0.0</span>)
      lot = m_account.MaxLotCheck(m_symbol.Name(), <span class="macro">ORDER_TYPE_BUY</span>, m_symbol.Ask(), m_percent);
   <span class="keyword">else</span>
      lot = m_account.MaxLotCheck(m_symbol.Name(), <span class="macro">ORDER_TYPE_BUY</span>, price, m_percent);
<span class="comment">class=class="str">"cmt">//---</span>
   <span class="keyword">class="type">class="kw">double</span> result  = <span class="number">class="num">0.0</span>, results = <span class="number">class="num">0.0</span>;
   price =m_symbol.Bid();
<span class="comment">class=class="str">"cmt">//--- if the model class="num">0 is used and "ATR-Based Stop Loss"</span>
   <span class="keyword">if</span>(((m_patterns_usage &amp; <span class="number">0x01</span>) != <span class="number">class="num">0</span>))
   {  IsPattern_0(price, <span class="macro">POSITION_TYPE_BUY</span>);
      result += m_pattern_0 * price;
      results += m_pattern_0;
   }
<span class="comment">class=class="str">"cmt">//--- if the model class="num">1 is used and "ATR-MA-Channel Stop Loss"</span>
   <span class="keyword">if</span>(((m_patterns_usage &amp; <span class="number">0x02</span>) != <span class="number">class="num">0</span>))
   {  IsPattern_1(price, <span class="macro">POSITION_TYPE_BUY</span>);
      result += m_pattern_1 * price;
      results += m_pattern_1;
   }
<span class="comment">class=class="str">"cmt">//--- if the model class="num">2 is used and "ATR-SAR-Channel Stop Loss"</span>
   <span class="keyword">if</span>(((m_patterns_usage &amp; <span class="number">0x04</span>) != <span class="number">class="num">0</span>))
   {  IsPattern_2(price, <span class="macro">POSITION_TYPE_BUY</span>);
      result += m_pattern_2 * price;
      results += m_pattern_2;
   }
<span class="comment">class=class="str">"cmt">//---</span>
   <span class="keyword">if</span>(results &gt; <span class="number">class="num">0</span>)
   {  result /= results;
      <span class="keyword">class="type">class="kw">double</span> _risk = (<span class="functions">fabs</span>(m_symbol.Bid()-result)/m_symbol.<span class="functions">Point</span>())*(m_symbol.TickSize()/m_symbol.<span class="functions">Point</span>())*m_symbol.TickValue();
      _risk /= m_account.FreeMargin();
      _risk *= <span class="number">class="num">100.0</span>;
      <span class="keyword">class="type">class="kw">double</span> _risk_lots = m_percent/_risk;<span class="comment">class=class="str">"cmt">// where m_percent is also max risk</span>
      lot = <span class="functions">fmin</span>(<span class="number">class="num">2.0</span>*lot, <span class="functions">fmax</span>(_risk_lots, m_symbol.LotsMin()));
   }
<span class="comment">class=class="str">"cmt">//--- class="kw">return trading volume</span>
   <span class="keyword">class="kw">return</span>(Optimize(lot));
}
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Getting lot size for open class="type">short position.                       |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">class="kw">double</span> CMoneyATR::CheckOpenShort(<span class="keyword">class="type">class="kw">double</span> price, <span class="keyword">class="type">class="kw">double</span> sl)
{  <span class="keyword">if</span>(m_symbol == <span class="macro">NULL</span>)
      <span class="keyword">class="kw">return</span>(<span class="number">class="num">0.0</span>);
<span class="comment">class=class="str">"cmt">//--- select lot size</span>
   <span class="keyword">class="type">class="kw">double</span> lot;
<span class="comment">class=class="str">"cmt">//---</span>
   <span class="keyword">if</span>(price == <span class="number">class="num">0.0</span>)
      lot = m_account.MaxLotCheck(m_symbol.Name(), <span class="macro">ORDER_TYPE_SELL</span>, m_symbol.Bid(), m_percent);
   <span class="keyword">else</span>
      lot = m_account.MaxLotCheck(m_symbol.Name(), <span class="macro">ORDER_TYPE_SELL</span>, price, m_percent);
<span class="comment">class=class="str">"cmt">//---</span>

◍ 卖单模型聚合与按风险反推手数

这段逻辑跑在空单信号判定之后,先把三种止损模型(基于 ATR、ATR-MA 通道、ATR-SAR 通道)的权重按位掩码 m_patterns_usage 分别唤起。位 0x01、0x02、0x04 对应模型 0/1/2,命中才执行 IsPattern_x 并把加权价格累加到 result、把权重累加到 results。 当 results 大于 0 时,用 result/results 得到模型融合后的参考止损价,再和当前 Ask 算价差点数,乘上 TickValue 与合约比换算成单点金额风险。接着除以账户空闲保证金并乘 100,得到该笔占用的自由保证金百分比 _risk。 用预设的单笔最大风险百分比 m_percent 除以 _risk,得出理论手数 _risk_lots;最终手数被钳在 LotsMin() 与 2.0*lot 之间,最后丢进 Optimize() 做滑点/流动性微调后返回。外汇与贵金属杠杆高,这套按风险反推的手数在极端跳空时可能低估真实亏损,上 MT5 用策略测试器跑不同 m_percent(如 1.0~2.0)能看到回撤曲线明显分化。

MQL5 / C++
  class="type">class="kw">double result  = class="num">0.0, results = class="num">0.0;
  price =m_symbol.Ask();
class=class="str">"cmt">//--- if the model class="num">0 is used and "ATR-Based Stop Loss"
  if(((m_patterns_usage & 0x01) != class="num">0))
  { IsPattern_0(price, POSITION_TYPE_SELL);
      result += m_pattern_0 * price;
      results += m_pattern_0;
  }
class=class="str">"cmt">//--- if the model class="num">1 is used and "ATR-MA-Channel Stop Loss"
  if(((m_patterns_usage & 0x02) != class="num">0))
  { IsPattern_1(price, POSITION_TYPE_SELL);
      result += m_pattern_1 * price;
      results += m_pattern_1;
  }
class=class="str">"cmt">//--- if the model class="num">2 is used and "ATR-SAR-Channel Stop Loss"
  if(((m_patterns_usage & 0x04) != class="num">0))
  { IsPattern_2(price, POSITION_TYPE_SELL);
      result += m_pattern_2 * price;
      results += m_pattern_2;
  }
class=class="str">"cmt">//---
  if(results > class="num">0)
  { result /= results;
      class="type">class="kw">double _risk = (fabs(result-m_symbol.Ask())/m_symbol.Point())*(m_symbol.TickSize()/m_symbol.Point())*m_symbol.TickValue();
      _risk /= m_account.FreeMargin();
      _risk *= class="num">100.0;
      class="type">class="kw">double _risk_lots = m_percent/_risk;class=class="str">"cmt">// where m_percent is also max risk
      lot = fmin(class="num">2.0*lot, fmax(_risk_lots, m_symbol.LotsMin()));
  }
class=class="str">"cmt">//--- class="kw">return trading volume
  class="kw">return(Optimize(lot));
}

「画得少,看得清」

前几节把 ADX 的收尾和 ATR 的全貌都拆开了,本文给出的 ATR 运用思路大多只停在构想层,没有落码也没有回测。随文挂了六个文件:MoneyWZ_44.mqh(13.81 KB)、TrailingWZ_44.mqh(11.24 KB)、SignalWZ_44.mqh(15.63 KB)、wz_44_signal.mq5(7.47 KB)、wz_44_trail.mq5(8.35 KB)、wz_trail_mm_44.mq5(9.07 KB),直接丢进 MT5 的 MQL5/Include 与 Scripts 就能编译跑通。 真要验证哪些 ATR 变体在贵金属或外汇上站得住,得自己在更多品种和更长历史里压一遍;这类品种杠杆高、跳空频繁,参数过拟合的曲线看着美,实盘可能直接吃掉本金。 把指标想明白比把面板堆满更重要,三个 mqh 加三个 mq5 就是让你少画无用线、多盯真信号。

常见问题

采用尾随停止的三种基准形态里的中值或慢速形态作缓冲,并给 ATR 乘子设上限,避免单根 K 线把止损顶飞。
常见坑是用了点值近似却忽略合约规格与杠杆,护栏做法是先取品种真实合约大小再反推,并预留滑点缓冲。
可以,小布能读取当前品种 ATR 与合约参数,按你设的风险百分比直接给出止损距和手数建议,省去手动核算。
先定每笔最大亏损额,除以 ATR 止损距换算的总风险点数和点值,得出手数,聚合订单时取各模型手数最小值。
只留一个基准 ATR 通道加尾随止损线,其余参数后台算,做到画得少、看得清,不被辅助线干扰价格行为。