逆转形态:测试头肩形态·综合运用
📘

逆转形态:测试头肩形态·综合运用

第 3/3 篇

把亏损品种上的信号翻个面

回测里 GBPUSD 和 USDJPY 两个品种吃瘪,说明原形态在它们身上的反转强度不够,净值一路被止损磨掉。既然原逻辑里持仓大多死在止损、很少先碰止盈,那把止损和止盈 1 对调,相当于顺着原信号的相反方向进场,是顺理成章的修正思路。 翻看亏损成交的图表,假信号区间往往叠着通道——标准图形分析里通道是趋势延续形态,意味着价格大概率沿前一个走势再走一段。参考形态搜索类里的 CheckSignal,前一波幅度本由止盈 2 收尾,所以只需把止盈 2 摆到距现价等距、但反方向上的位置。 实现上不用动形态搜索类,只在 EA 加一个 ReverseTrade 开关。CheckPattern 里添个 temp 局部变量暂存价位,switch 内判标志:false 走老逻辑;true 就把 sl 与 tp1 互换,按点重算 tp 再丢给 CLimitTakeProfit,迭代通过后开反向单。 实测两轮都扭亏为盈。GBPUSD 同周期下 127 笔成交 95 笔盈利,胜率 75.59%,盈利因子约 2.35;USDJPY 108 笔里 66 笔盈利,胜率 61.11%,盈利因子 2.45。外汇与贵金属属高风险品种,这套反转在一个品种吃香,换一对货币未必复刻,上 MT5 跑自己品种前先小资金验证。

MQL5 / C++
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;ReverseTrade&nbsp;&nbsp; =&nbsp;&nbsp;<span class="macro">true</span>; <span class="comment">class=class="str">"cmt">//反向交易</span>
<span class="keyword">class="type">bool</span> CheckPattern(CPattern *pattern)
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">int</span> signal=<span class="number">class="num">0</span>;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> sl=-<span class="number">class="num">1</span>, tp1=-<span class="number">class="num">1</span>, tp2=-<span class="number">class="num">1</span>;
&nbsp;&nbsp; <span class="keyword">if</span>(!pattern.CheckSignal(signal,sl,tp1,tp2))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>;
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> price=<span class="number">class="num">0</span>;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> to_close=<span class="number">class="num">100</span>;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> temp=<span class="number">class="num">0</span>;
<span class="comment">class=class="str">"cmt">//---</span>
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">class="kw">switch</span>(signal)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> <span class="number">class="num">1</span>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CLimitTakeProfit::Clear();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(!ReverseTrade)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; price=<span class="functions">SymbolInfoDouble</span>(<span class="predefines">_Symbol</span>,<span class="macro">SYMBOL_ASK</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>((tp1-price)&gt;<span class="functions">SymbolInfoInteger</span>(<span class="predefines">_Symbol</span>,<span class="macro">SYMBOL_TRADE_STOPS_LEVEL</span>)*<span class="predefines">_Point</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(CLimitTakeProfit::AddTakeProfit((<span class="keyword">class="type">uint</span>)((tp1-price)/<span class="predefines">_Point</span>),(<span class="functions">fabs</span>(tp1-tp2)&gt;=<span class="predefines">_Point</span> ? <span class="number">class="num">50</span> : <span class="number">class="num">100</span>)))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; to_close-=(<span class="functions">fabs</span>(tp1-tp2)&gt;=<span class="predefines">_Point</span> ? <span class="number">class="num">50</span> : <span class="number">class="num">100</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(to_close&gt;<span class="number">class="num">0</span> &amp;&amp; (tp2-price)&gt;<span class="functions">SymbolInfoInteger</span>(<span class="predefines">_Symbol</span>,<span class="macro">SYMBOL_TRADE_STOPS_LEVEL</span>)*<span class="predefines">_Point</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(!CLimitTakeProfit::AddTakeProfit((<span class="keyword">class="type">uint</span>)((tp2-price)/<span class="predefines">_Point</span>),to_close))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(Trade.Buy(d_Lot,<span class="predefines">_Symbol</span>,price,sl-i_SL*<span class="predefines">_Point</span>,<span class="number">class="num">0</span>,<span class="macro">NULL</span>))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; price=<span class="functions">SymbolInfoDouble</span>(<span class="predefines">_Symbol</span>,<span class="macro">SYMBOL_BID</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; temp=tp1;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tp1=sl-i_SL*<span class="predefines">_Point</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sl=temp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tp1=(price-tp1)/<span class="predefines">_Point</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tp2=(tp2-price)/<span class="predefines">_Point</span>;

◍ 挂限价止盈的层级与反手分支

下面这段逻辑处理两种信号分支:普通做空与反手做空,核心是把仓位拆成两段限价单止盈(CLimitTakeProfit),并受经纪商最小止损位 SYMBOL_TRADE_STOPS_LEVEL 约束。 先看 case 0 附近的普通分支:当 tp1 大于最小停损层级时才挂第一段止盈;若 tp2 与 tp1 间距 ≥1 则点 50 手分量,否则点 100。剩余 to_close 若仍大于 0 且 tp2 合规,则补齐挂单,否则函数直接 return false 中断。 case -1 里先 Clear 清场。若 ReverseTrade 为假,用 BID 价做空,挂单距离用 (price-tp1)/_Point 换算成点数;tp1、tp2 差值的绝对值 ≥_Point 时同样分 50/100 两份。若开启反手,则切到 ASK 价并把原 sl 与 tp1 互换角色,重新算 tp1、tp2 的点数表达。 外汇与贵金属杠杆高,SYMBOL_TRADE_STOPS_LEVEL 在重大数据前可能瞬扩,回测里 50/100 的分量比例在点差跳变时可能触发 return false 而漏单,建议开 MT5 用策略测试器改 i_SL 与分量阈值跑一遍验证。

MQL5 / C++
if(tp1>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL))
   if(CLimitTakeProfit::AddTakeProfit((class="type">uint)(tp1),((tp2-tp1)>=class="num">1? class="num">50 : class="num">100)))
      to_close-=((tp2-tp1)>=class="num">1 ? class="num">50 : class="num">100);
   if(to_close>class="num">0 && tp2>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL))
      if(!CLimitTakeProfit::AddTakeProfit((class="type">uint)(tp2),to_close))
         class="kw">return class="kw">false;
   if(Trade.Sell(d_Lot,_Symbol,price,sl,class="num">0,NULL))
      class="kw">return class="kw">false;
   }
   class="kw">break;
case -class="num">1:
   CLimitTakeProfit::Clear();
   if(!ReverseTrade)
     {
      price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
      if((price-tp1)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point)
         if(CLimitTakeProfit::AddTakeProfit((class="type">uint)((price-tp1)/_Point),(fabs(tp1-tp2)>=_Point ? class="num">50 : class="num">100)))
            to_close-=(fabs(tp1-tp2)>=_Point ? class="num">50 : class="num">100);
      if(to_close>class="num">0 && (price-tp2)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point)
         if(!CLimitTakeProfit::AddTakeProfit((class="type">uint)((price-tp2)/_Point),to_close))
            class="kw">return class="kw">false;
      if(Trade.Sell(d_Lot,_Symbol,price,sl+i_SL*_Point,class="num">0,NULL))
         class="kw">return class="kw">false;
     }
   else
     {
      price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      temp=tp1;
      tp1=sl+i_SL*_Point;
      sl=temp;
      tp1=(tp1-price)/_Point;
      tp2=(price-tp2)/_Point;
      if(tp1>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL))

「分批止盈的距离判定与下单收口」

上面这段逻辑处理的是限价止盈的堆叠与最终买入触发。核心在于:当第一段止盈位 tp1 与第二段 tp2 的间距大于等于 1 时,给第一层挂 50 单位量;否则挂 100 单位量,剩余待平量 to_close 同步扣减。 若扣减后 to_close 仍大于 0,且 tp2 大于当前品种的最小止损止盈距离(SYMBOL_TRADE_STOPS_LEVEL),则把剩余量挂到 tp2;挂单失败直接返回 false,避免半残仓位。 最后用 Trade.Buy 以 d_Lot 手数、指定 price 与 sl 市价买入,止盈传 0 表示由前面限价单接管。外汇与贵金属杠杆高,最小停损距离在不同品种间差异明显,实盘前务必用 SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL) 打印确认。 把这段代码粘进 MT5 的 EA 回测,改 tp2-tp1 的阈值从 1 调到 2,能看到第一层挂单量从 50 变为条件不命中而走 100,验证分支是否按预期走。

MQL5 / C++
if(CLimitTakeProfit::AddTakeProfit((class="type">uint)(tp1),((tp2-tp1)>=class="num">1 ? class="num">50 : class="num">100)))
   to_close-=((tp2-tp1)>=class="num">1 ? class="num">50 : class="num">100);
 if(to_close>class="num">0 && tp2>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL))
   if(!CLimitTakeProfit::AddTakeProfit((class="type">uint)(tp2),to_close))
     class="kw">return class="kw">false;
 if(Trade.Buy(d_Lot,_Symbol,price,sl,class="num">0,NULL))
   class="kw">return class="kw">false;
}
 class="kw">break;
 }
class=class="str">"cmt">//---
 class="kw">return true;
}

把演示 EA 当起点而非终点

这套基于标准图形形态的策略只是在多品种上跑出了正收益,但不同品种的特性差异很大,同一套参数在 A 品种盈利、在 B 品种可能直接亏穿。逆转交易在某些行情里能改善回撤,但不能当成通用解药,外汇和贵金属的高杠杆随时会把小概率事件放大成爆仓。 文章里的 EA 只是策略演示壳,原始代码没动过,但实盘前必须加盈亏平衡移动和尾随止损这类保护。已知历史回测为负的品种,别拿它去硬试。 一个低成本的验证思路:用限价单替代止盈,不碰 EA 源码就能测双顶/双底逆转形态的真实触发率。你在 MT5 里加载 EA 后手动挂反向限价单,统计一周触发的胜率,比直接改代码更快看清策略边界。

◍ 随包附带的工程文件

这套头肩与双顶双底反转策略的实盘骨架,作者打包在 1352.35 KB 的 MQL5.zip 里,直接丢进 MT5 的 MQL5 目录就能编译。里面不是单文件耍花枪,而是分了层:ZigZag.mqh 负责之字折线抽取,Trends.mqh 做趋势段搜索,Pattern.mqh 和 HS_Pattern 分别管双顶双底与头肩的几何识别,LimitTakeProfit.mqh 则用限价单替掉常规止盈挂单。 EA 主体有两套,Head-Shoulders.mq5 只跑头肩,TwoPatterns.mq5 把两种形态合并触发。想验证历史表现,开 MT5 策略测试器选 GPUUSD/M30,有用户在 2020.11 跑过同条件延伸样本,结果明显弱于原文区间,说明形态衰竭和品种漂移都不能忽视。 外汇与贵金属杠杆高、滑点跳空频繁,这类形态 EA 在实盘前必须用 Tick 级数据重测,参数别照抄。下载包里还带了 Header.mqh 作为 EA 公共头,改策略逻辑时先读它,能少踩一半编译坑。

常见问题

先确认原信号是否因假突破失效,再把同向头肩翻成反向观察;用历史回看验证反转概率再下手,别凭感觉翻面。
按颈线到头部距离的0.5、1.0、1.5倍分三档挂限价;最后一层可留反手分支防趋势延续。
小布可在品种页直接标出头肩结构并给出层级止盈参考,你只需确认是否跟单,重复劳动交给它。
间隔取近期平均波动的1~2倍,临近收盘或重大数据前收口到一批全平,降低滑点风险。
只能当起点:先改参数适配品种波动率,接真实点差与滑点测试,确认胜率后再考虑实盘。