头肩形态基础:从理论结构到EA测试的认知铺垫(基础篇)
(1/3)·多数交易者只认图形不读形成逻辑,回测时才发现信号全是假突破噪声
用 MT5 回测头肩逆转形态
头肩形态被很多交易者当成趋势逆转的先行信号,但在实盘里它经常假突破。想验证它在你常用品种上的有效性,直接开 MT5 跑历史数据比看教科书靠谱。 Dmitriy Gizlyk 在 2018 年 12 月 31 日发布的头肩测试示例,至今在社区有 6980 次查看、11 条讨论,说明这个形态仍有实操参考价值。 打开 MT5 的策略测试器,加载对应形态识别脚本,把时间轴拉到 2018 年以后的 EURUSD 或 XAUUSD 日线,观察左肩、头、右肩的量价配合。外汇与贵金属杠杆高、波动剧烈,形态失效时止损可能被瞬间打掉,验证过程请严格控制仓位。
◍ 头肩形态的理论根基与交易落点
头肩图形被归为趋势逆转形态,其理论假定是:在一段明确上涨后,价格三次试探高位,中间一次(头)高于两侧(肩),颈线连接两次回调低点;跌破颈线即视为多头动能衰竭。下跌段的镜像结构同理,只是方向反转。 原文把讨论拆成理论观点、交易策略(含两个具体案例)、EA 编写、双形态合并、回测与亏损品种信号分配几块,说明这一形态不是靠目测,而是能进 MT5 用历史数据反复验证的对象。 高风险提示:外汇与贵金属杠杆品种中,头肩失败率不低,颈线假突破在震荡市尤为频繁,任何信号都只是概率倾向,不可视作确定性反转。 下一节会先铺开形态形成的理论观点,建议你在 MT5 打开 EURUSD 日线,把最近一年明显头肩个案标出来,数一下右肩成交量相对左肩的萎缩比例,作为自己的基准样本。
「头肩结构背后的博弈逻辑」
头肩与倒头肩是价格行为里辨识度最高的反转图形。正头肩出现在看涨趋势末端,释放卖出倾向;倒头肩为其镜像,暗示看涨走势可能开启。形态由三个连续顶部构成,中间顶部高于两侧,俗称头部,两侧为肩部,连接顶底的是颈线——颈线向左下倾斜时,信号被认为更强。 这种形态多数发生在支撑或阻力位被假突破之后。趋势跟踪者把左肩上方的小幅修正视为加仓机会,价格回到原趋势并越过左肩价位;当关键位被突破,逆势资金进场阻止了疲软趋势,引发新调整,头部随之形成。 随后恢复趋势的尝试显露疲态,走出较小的右肩。此时市场参与者察觉逆转,顺势盘大规模离场、逆势盘加码,催生强劲反向走势。外汇与贵金属市场杠杆高,此类形态失效概率不低,仅可作为概率判断依据,须结合实时验证。
头肩形的两种进场打法
头肩形态的交易逻辑和双顶/双底同源,核心都绕不开颈线。最直白的打法是等所分析周期收盘价实破颈线后直接开单,止损放在头部极值或略缩进位置,同时必须把品种点差算进成本里。 这种突破即追的缺点很明显:若价格跑得猛,一根柱线能直接甩离颈线,原本该吃的利润可能回吐成亏损,特定形态下亏损风险被放大。一个变通思路是等价格越过颈线、顺着新趋势再走一段固定距离后才动手,用空间换过滤。 另一路是在突破后等价格回踩颈线时进场。止损收在最近一个极值,距离更紧,同样风险下能堆更大仓位,潜在盈亏比更好看。但回踩不是必然的——和双顶双底一样,突破后不回头的形态一大把,这时候就该跳过、观望,别硬等。 无论用哪一路,建议的最小止盈目标设为:从头部到颈线的那段垂直距离,等量移到颈线另一侧。外汇与贵金属杠杆高、滑点跳空频繁,上述距离仅作参考,实盘前请在 MT5 用历史数据回测验证。
◍ 把头肩形态塞进已有的 EA 框架
双顶双底和头肩在识别逻辑上高度相似,所以没必要从零写 EA。直接复用前一篇的 CPattern 基类,新写一个 CHS_Pattern 子类,把形态判定和入场逻辑重写掉就行,父类里现成的方法都能直接调。 类初始化先调父类 Create,再把头部极值点和右肩起点两个结构清掉备用。形态搜索要求至少 6 个极值,否则直接返回 false;从指定日期定位极值索引往回扫,每次取连续 6 个极值比对头肩结构,命中就退出,扫完没命中也退出。 入场点方法先确认前面真找着形态了,再看形态出现后走了多少根 K 线——刚形成就放弃。之后下载报价,在循环里找带回抽的颈线突破,突破时算潜在止盈;若回抽前价格已摸到潜在止盈,形态作废。有效入场点必须在当前时间帧最后两根柱线之内,不然不认。
- 年跑过 10 个月回测,盈利交易占比 57% 以上,盈利因子 2.17,但 10 个月只成交 14 笔,样本薄、外汇贵金属本身高风险,参数别盲目照搬。
下面这段是 CHS_Pattern 类及两个核心方法的骨架,逐行对应上面说的继承与重写思路:
class CHS_Pattern : class="kw">public CPattern { class="kw">protected: s_Extremum s_HeadExtremum; class=class="str">"cmt">//头部极值点 s_Extremum s_StartRShoulder; class=class="str">"cmt">//右侧肩部起始点 class="kw">public: CHS_Pattern(); ~CHS_Pattern(); class=class="str">"cmt">//--- 初始化类 class="kw">virtual class="type">bool Create(CTrends *trends, class="type">class="kw">double min_correction, class="type">class="kw">double max_correction); class=class="str">"cmt">//--- 形态和入场点搜索方法 class="kw">virtual class="type">bool Search(class="type">class="kw">datetime start_time); class="kw">virtual class="type">bool CheckSignal(class="type">int &signal, class="type">class="kw">double &sl, class="type">class="kw">double &tp1, class="type">class="kw">double &tp2); class=class="str">"cmt">//--- s_Extremum HeadExtremum(class="type">void) class="kw">const { class="kw">return s_HeadExtremum; } s_Extremum StartRShoulder(class="type">void) class="kw">const { class="kw">return s_StartRShoulder; } }; class="type">bool CHS_Pattern::Create(CTrends *trends,class="type">class="kw">double min_correction,class="type">class="kw">double max_correction) { if(!CPattern::Create(trends,min_correction,max_correction)) class="kw">return class="kw">false; class=class="str">"cmt">//--- s_HeadExtremum.Clear(); s_StartRShoulder.Clear(); class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CHS_Pattern::Search(class="type">class="kw">datetime start_time) { if(CheckPointer(C_Trends)==POINTER_INVALID || C_Trends.Total()<class="num">6) class="kw">return class="kw">false; class="type">int start=C_Trends.ExtremumByTime(start_time); if(start<class="num">0) class="kw">return class="kw">false; b_found=class="kw">false; for(class="type">int i=start;i>=class="num">0;i--) { if((i+class="num">5)>=C_Trends.Total()) class="kw">continue; if(!C_Trends.Extremum(s_StartTrend,i+class="num">5) || !C_Trends.Extremum(s_StartCorrection,i+class="num">4) || !C_Trends.Extremum(s_EndCorrection,i+class="num">3)|| !C_Trends.Extremum(s_HeadExtremum,i+class="num">2) || !C_Trends.Extremum(s_StartRShoulder,i+class="num">1) || !C_Trends.Extremum(s_EndTrend,i)) class="kw">continue; class=class="str">"cmt">//--- class="type">class="kw">double trend=MathAbs(s_StartCorrection.Price-s_StartTrend.Price); class="type">class="kw">double correction=MathAbs(s_StartCorrection.Price-s_EndCorrection.Price);