逆转形态:测试头肩形态·综合运用
把亏损品种上的信号翻个面
回测里 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 跑自己品种前先小资金验证。
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">bool</span> ReverseTrade = <span class="macro">true</span>; <span class="comment">class=class="str">"cmt">//反向交易</span> <span class="keyword">class="type">bool</span> CheckPattern(CPattern *pattern) { <span class="keyword">class="type">int</span> signal=<span class="number">class="num">0</span>; <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>; <span class="keyword">if</span>(!pattern.CheckSignal(signal,sl,tp1,tp2)) <span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>; <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">class="type">class="kw">double</span> price=<span class="number">class="num">0</span>; <span class="keyword">class="type">class="kw">double</span> to_close=<span class="number">class="num">100</span>; <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> <span class="keyword">class="kw">switch</span>(signal) { <span class="keyword">case</span> <span class="number">class="num">1</span>: CLimitTakeProfit::Clear(); <span class="keyword">if</span>(!ReverseTrade) { price=<span class="functions">SymbolInfoDouble</span>(<span class="predefines">_Symbol</span>,<span class="macro">SYMBOL_ASK</span>); <span class="keyword">if</span>((tp1-price)><span class="functions">SymbolInfoInteger</span>(<span class="predefines">_Symbol</span>,<span class="macro">SYMBOL_TRADE_STOPS_LEVEL</span>)*<span class="predefines">_Point</span>) <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)>=<span class="predefines">_Point</span> ? <span class="number">class="num">50</span> : <span class="number">class="num">100</span>))) to_close-=(<span class="functions">fabs</span>(tp1-tp2)>=<span class="predefines">_Point</span> ? <span class="number">class="num">50</span> : <span class="number">class="num">100</span>); <span class="keyword">if</span>(to_close><span class="number">class="num">0</span> && (tp2-price)><span class="functions">SymbolInfoInteger</span>(<span class="predefines">_Symbol</span>,<span class="macro">SYMBOL_TRADE_STOPS_LEVEL</span>)*<span class="predefines">_Point</span>) <span class="keyword">if</span>(!CLimitTakeProfit::AddTakeProfit((<span class="keyword">class="type">uint</span>)((tp2-price)/<span class="predefines">_Point</span>),to_close)) <span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>; <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>)) <span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>; } <span class="keyword">else</span> { price=<span class="functions">SymbolInfoDouble</span>(<span class="predefines">_Symbol</span>,<span class="macro">SYMBOL_BID</span>); temp=tp1; tp1=sl-i_SL*<span class="predefines">_Point</span>; sl=temp; tp1=(price-tp1)/<span class="predefines">_Point</span>; 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 与分量阈值跑一遍验证。
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,验证分支是否按预期走。
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 公共头,改策略逻辑时先读它,能少踩一半编译坑。