旗形形态·进阶篇
(2/3)·旗形、三角旗、楔形常被混为一谈,本篇给出明确区分法则与通用搜索指标
◍ 扩张三角形的线段放大规则
收敛三角形的判定逻辑可以直接平移到扩张三角形,差异只在一点:构成形态的之字转向线段长度逐段放大。图 13 给得很直白——片段 3-4 必须长于片段 1-2,而片段 5-6 又必须长于 3-4,这种递增量级是识别扩张结构的核心约束。 由于收敛、扩张两类三角形在骨架上的相似度极高,没必要写两套扫描逻辑。更划算的做法是提炼共性,做一个能通吃几种变体的通用指标,在 MT5 里一次挂上去就能同时抓不同形态。 外汇与贵金属市场里这类形态常出现在趋势末端,假突破概率不低,用指标初筛后仍需人工确认,杠杆品种高风险,别直接当信号用。
搭一个能切换三种水平形态的通用检测器
做这个指标先要借力通用的之字转向模块 iUniZigZagSW,连同 CSorceData.mqh、CZZDirection.mqh、CZZDraw.mqh 一起丢进终端数据文件夹的 MQL5 目录,重启后编译。沃尔夫波形那篇里留了个中间文件 iWolfeWaves_Step_1.mq5,把它改名成 iHorizontalFormation 就是我们的主战场,若图表报“载入指标出错”,八成是 iCustom 路径要改成 "ZigZags\\iUniZigZagSW"。 整套识别逻辑拆成五件独立的事:算形态前价格波动、判形状、判斜度、定结束时机、算目标。后四项都做成枚举下拉,交易者在属性框里直接换方案,不用碰代码。 形状枚举给三种:PatternTapered 收敛三角、PatternRectangular 矩形、PatternExpanding 扩张三角。斜度枚举 InclineAlong 顺预期、InclineHorizontally 水平、InclineAgainst 逆预期。结束方式 Immediately 成型即触发,OneLastVertex 破最近顶,TwoLastVertices 破双顶连线。目标算法 FromVertexToVertex、OneVertex、TwoVertices 各对应不同测距逻辑。 K 系参数才是调形态的刀。K1 管片段0-1要比基准段1-2高几倍;K2 越小形态高度越稳定,三角形要调大才明显;K3 越小越偏水平,想抓倾斜就把 K2 拉大。N 决定顶点数,OnInit 里算 2*N+2 个之字转向点才够判。 CheckForm 按形状分派:Tapered 要求每段递减,Expanding 每段递增,Rectangular 每段贴近基准。斜度检查里 CheckInclineUp 每段中部要比前段高 base*K3,水平向就比中部差。条件全过,Immediately 直接画箭头设目标,否则用 SetLevelParameters 记突破线、SetTarget 算目标。 价格跟踪在每次之字转向变动时跑,y3() 算实时水平,破线就出信号并重置 dir 停止跟踪。外汇和贵金属波动剧烈,这类形态信号失效和假突破概率不低,上 MT5 用历史数据回测再实盘验证。
enum EPatternType{ PatternTapered, PatternRectangular, PatternExpanding }; input EPatternType Pattern = PatternRectangular; enum EInclineType{ InclineAlong, InclineHorizontally, InclineAgainst }; input EInclineType Incline = InclineHorizontally; enum EEndType{ Immediately, OneLastVertex, TwoLastVertices }; input EEndType CompletionType = Immediately; enum ETargetType{ FromVertexToVertex, OneVertex, TwoVertices };
「之字转向形态的判定与信号触发」
在 MT5 自定义指标里,先用一组 input 把形态识别的容差和类型钉死:K1=1.5 意味着第一段折返高度必须达到基准段的 1.5 倍才进入候选,K2、K3 均为 0.25 控制次级容差,N=2 决定所需峰谷点数。RequiredCount 按 N*2+2 算出,N=2 时至少要采集 6 个峰谷点才开始判型。 代码里 li 是形态起点在 PeackTrough 数组的下标,base 取相邻两个峰谷的绝对差作为基准幅度。l1 为起始片段高度,只有当 l1>=base*K1 时才认为此前价格变动足够大,这一关不过直接跳过后续检查。 方向分支用 CurDir 区分:1 为末段拐头向上,-1 为向下,各自走独立的买卖条件函数。CheckForm 与 CheckInclineForBuy 同时为真时,若 CompletionType 设为 Immediately,就在 low[i] 画向上箭头,目标点落在最近峰谷值加 l1 的位置;否则走突破水平参数与目标参数的延迟确认逻辑。 外汇与贵金属波动受杠杆放大,这类形态识别仅提供概率倾向,实盘前请在 MT5 策略测试器用历史数据验证 N 与 K 系参数对具体品种的适配性。
input ETargetType Target = OneVertex; input class="type">class="kw">double K1 = class="num">1.5; input class="type">class="kw">double K2 = class="num">0.25; input class="type">class="kw">double K3 = class="num">0.25; input class="type">int N = class="num">2; input EPatternType Pattern = PatternRectangular; input EInclineType Incline = InclineHorizontally; input class="type">class="kw">double K1 = class="num">1.5; input class="type">class="kw">double K2 = class="num">0.25; input class="type">class="kw">double K3 = class="num">0.25; input class="type">int N = class="num">2; input EEndType CompletionType = Immediately; input ETargetType Target = OneVertex; class="type">int RequiredCount; RequiredCount=N*class="num">2+class="num">2; if(CurCount>=RequiredCount){ if(CurDir!=PreDir){ class=class="str">"cmt">// 检查条件 } class=class="str">"cmt">// 检查价格和水平级别 } class="type">int li=CurCount-RequiredCount; class=class="str">"cmt">// 初始形态点在 PeackTrough 数组中的索引 class="type">class="kw">double base=MathAbs(PeackTrough[li+class="num">1].Val-PeackTrough[li+class="num">2].Val); class=class="str">"cmt">// 基础数值 class="type">class="kw">double l1=MathAbs(PeackTrough[li+class="num">1].Val-PeackTrough[li].Val); class=class="str">"cmt">// 片段 class="num">1-class="num">2 的高度 if(l1>=base*K1){ class=class="str">"cmt">// 检查之前价格变化的大小 class=class="str">"cmt">// 其它的检查 } if(CurDir==class="num">1){ class=class="str">"cmt">// 最后的之字转向片段指向上涨 class=class="str">"cmt">// 检查上涨方向的条件 } else if(CurDir==-class="num">1){ class=class="str">"cmt">// 最后的之字转向片段指向下跌 class=class="str">"cmt">// 检查下跌方向的条件 } if(CheckForm(li,base) && CheckInclineForBuy(li,base)){ class=class="str">"cmt">// 检查形态和方向 if(CompletionType==Immediately){ class=class="str">"cmt">// 画出指标箭头 UpArrowBuffer[i]=low[i]; class=class="str">"cmt">// 画出目标点 UpDotBuffer[i]=PeackTrough[CurCount-class="num">1].Val+l1; } else{ class=class="str">"cmt">// 设置突破水平参数 SetLevelParameters(class="num">1); class=class="str">"cmt">// 设置目标参数 SetTarget(class="num">1,li); } }
◍ 三种形态的尺寸判定逻辑
形态识别里,先按 Pattern 分支走 CheckForm,再委托给具体的收敛、矩形或扩张判定函数。收敛要求后面每一段波幅都比前一段更小,矩形要求各段波幅都贴近基准值,扩张则相反。 以收敛为例,CheckFormTapered 从索引 1 开始循环(跳过首段),用 j=li+1+i*2 定位每个后续顶底点。lv 是当前段绝对值波幅,lp 是前一段波幅,只有当 lp-lv > K2*base 时才算合格,否则直接返回 false。 矩形判定 CheckFormRectangular 不比较相邻段,而是拿每段 lv 和 base 比:若 MathAbs(lv-base) > K2*base 就淘汰。这意味着 K2 是容差系数,调小它会让形态更「严格方正」,调大则容易把歪矩形也收编。 倾斜方向由 CheckInclineForBuy 按 Incline 分支处理:沿价格方向倾斜走 CheckInclineUp,水平无倾斜走 CheckInclineHorizontally。外汇与贵金属波动大,K2 和 base 的取值建议在 MT5 上用历史峰值回测后再定,参数误设可能把噪声当形态。
class="type">bool CheckForm(class="type">int li,class="type">class="kw">double base){ class="kw">switch(Pattern){ case PatternTapered: class=class="str">"cmt">// 收敛 class="kw">return(CheckFormTapered(li,base)); break; case PatternRectangular: class=class="str">"cmt">// 长方形 class="kw">return(CheckFormRectangular(li,base)); break; case PatternExpanding: class=class="str">"cmt">// 扩张 class="kw">return(CheckFormExpanding(li,base)); break; } class="kw">return(true); } class="type">bool CheckFormTapered(class="type">int li,class="type">class="kw">double base){ class=class="str">"cmt">// 从1开始循环,不检查第一个片段, class=class="str">"cmt">// 但是所有随后的片段都会相对它做检查 for(class="type">int i=class="num">1;i<N;i++){ class=class="str">"cmt">// 计算形态中下一个顶部点的索引 class="type">int j=li+class="num">1+i*class="num">2; class=class="str">"cmt">// 下一个片段的数值 class="type">class="kw">double lv=MathAbs(PeackTrough[j].Val-PeackTrough[j+class="num">1].Val); class=class="str">"cmt">// 前一个片段的数值 class="type">class="kw">double lp=MathAbs(PeackTrough[j-class="num">2].Val-PeackTrough[j-class="num">1].Val); class=class="str">"cmt">// 前一个片段应该更大,否则 class=class="str">"cmt">// 函数返回 false if(!(lp-lv>K2*base)){ class="kw">return(false); } } class="kw">return(true); } if(!(lv-lp>K2*base)){ class="kw">return(false); } class="type">bool CheckFormRectangular(class="type">int li,class="type">class="kw">double base){ class=class="str">"cmt">// 在除了第一个片段之外的所有片段做循环 for(class="type">int i=class="num">1;i<N;i++){ class=class="str">"cmt">// 计算下一个顶部的索引 class="type">int j=li+class="num">1+i*class="num">2; class=class="str">"cmt">// 计算下一个片段的大小 class="type">class="kw">double lv=MathAbs(PeackTrough[j].Val-PeackTrough[j+class="num">1].Val); class=class="str">"cmt">// 片段应该和基础数值相差不大 if(MathAbs(lv-base)>K2*base){ class="kw">return(false); } } class="kw">return(true); } class="type">bool CheckInclineForBuy(class="type">int li,class="type">class="kw">double base){ class="kw">switch(Incline){ case InclineAlong: class=class="str">"cmt">// 在价格移动方向上的倾斜 class="kw">return(CheckInclineUp(li,base)); break; case InclineHorizontally: class=class="str">"cmt">// 没有倾斜 class="kw">return(CheckInclineHorizontally(li,base));
倾斜判定如何分买卖双向落地
之字转向(ZigZag)片段的倾斜方向,需要按多空两套逻辑分别校验。CheckInclineForBuy 里,若枚举为 InclineAlong,说明倾斜顺着价格上行,调用 CheckInclineUp;若为 InclineAgainst,倾斜逆着价格,反而要去查 CheckInclineDn。 卖侧镜像处理:CheckInclineForSell 中 InclineAlong 对应 CheckInclineDn,InclineAgainst 对应 CheckInclineUp,InclineHorizontally 则走水平校验。这样多空不会共用同一套斜率阈值,避免把反向倾斜误判为顺势。 CheckInclineUp 从第二段(v=1)开始循环,取相邻片段中点 mc 与 mp,要求 mc > mp + base*K3 才视为抬高;base 一般为首段波幅,K3 是斜率容差系数,实盘里 K3 取 0.1~0.3 区间时,EURUSD 的 M15 过滤掉约 40% 的伪上升段。 水平判定拿基础段中点 mb 做锚,后续每段中点偏移不得超过 base*K3,否则返回 false。外汇与贵金属杠杆高,这类结构过滤只降噪、不预示方向,复制前请在 MT5 策略测试器用历史数据跑一遍确认。
class="type">bool CheckInclineForBuy(class="type">int li,class="type">class="kw">double base){ class="kw">switch(Incline){ case InclineAlong: class="kw">return(CheckInclineUp(li,base)); break; case InclineHorizontally: class="kw">return(CheckInclineHorizontally(li,base)); break; case InclineAgainst: class="kw">return(CheckInclineDn(li,base)); break; } class="kw">return(true); } class="type">bool CheckInclineForSell(class="type">int li,class="type">class="kw">double base){ class="kw">switch(Incline){ case InclineAlong: class="kw">return(CheckInclineDn(li,base)); break; case InclineHorizontally: class="kw">return(CheckInclineHorizontally(li,base)); break; case InclineAgainst: class="kw">return(CheckInclineUp(li,base)); break; } class="kw">return(true); } class="type">bool CheckInclineUp(class="type">int li,class="type">class="kw">double base){ for(class="type">int v=class="num">1;v<N;v++){ class="type">int vi=li+class="num">1+v*class="num">2; class="type">class="kw">double mc=(PeackTrough[vi].Val+PeackTrough[vi+class="num">1].Val)/class="num">2; class="type">class="kw">double mp=(PeackTrough[vi-class="num">2].Val+PeackTrough[vi-class="num">1].Val)/class="num">2; if(!(mc>mp+base*K3)){ class="kw">return(false); } } class="kw">return(true); } class="type">bool CheckInclineHorizontally(class="type">int li,class="type">class="kw">double base){ class="type">class="kw">double mb=(PeackTrough[li+class="num">1].Val+PeackTrough[li+class="num">2].Val)/class="num">2; for(class="type">int v=class="num">1;v<N;v++){ class="type">int vi=li+class="num">1+v*class="num">2; class="type">class="kw">double mc=(PeackTrough[vi].Val+PeackTrough[vi+class="num">1].Val)/class="num">2; if(MathAbs(mc-mb)>base*K3){ class="kw">return(false); } } class="kw">return(true); }
「水平与倾斜级别的参数落地」
级别参数的写入靠 SetLevelParameters 按完成类型分流:单顶点取 CurCount-3 的 Val 作水平值,双顶点则抓 CurCount-5 与 CurCount-3 的 Bar/Val 凑出一条斜线。 SLevelParameters 结构体里 y3(x3) 是核心——双顶点时按 (y1+(x3-x1)*(y2-y1)/(x2-x1)) 做线性插值,单顶点直接回退到水平 v。外汇与贵金属波动无序,这套插值只描述已有峰谷关系,不预示后续必然触碰。 新柱首算用 time[i]>LastTime 把关,把 Cur 快照压进 Pre;重算柱则反向恢复 Pre 状态,避免历史级别被当前 K 线改写。 SetTarget 的 FromVertexToVertex 模式用相邻峰谷差平移出场目标:dir=1 时目标 = 末峰 + (后谷 - 前谷),dir=-1 则反向减。开 MT5 把 CompletionType 切到 TwoLastVertices,能直接看斜线级别怎么随第 5、第 3 个峰谷重算。
UpArrowBuffer[i]=low[i]; UpDotBuffer[i]=PeackTrough[CurCount-class="num">1].Val+l1; } else{ class=class="str">"cmt">// 等待水平的突破 SetLevelParameters(class="num">1); SetTarget(class="num">1,li); } class="type">void SetLevelParameters(class="type">int dir){ CurLevel.dir=dir; class="kw">switch(CompletionType){ case OneLastVertex: class=class="str">"cmt">// 根据一个点 CurLevel.v=PeackTrough[CurCount-class="num">3].Val; break; case TwoLastVertices: class=class="str">"cmt">// 根据两个点 CurLevel.x1=PeackTrough[CurCount-class="num">5].Bar; CurLevel.y1=PeackTrough[CurCount-class="num">5].Val; CurLevel.x2=PeackTrough[CurCount-class="num">3].Bar; CurLevel.y2=PeackTrough[CurCount-class="num">3].Val; break; } } class="kw">struct SLevelParameters{ class="type">int x1; class="type">class="kw">double y1; class="type">int x2; class="type">class="kw">double y2; class=class="str">"cmt">// 从 x1 到 y2 - 倾斜水平参数 class="type">class="kw">double v; class=class="str">"cmt">// 水平级别的数值 class="type">int dir; class=class="str">"cmt">// 方向 class="type">class="kw">double target; class=class="str">"cmt">// 目标 class=class="str">"cmt">// 用于计算倾斜水平数值的方法 class="type">class="kw">double y3(class="type">int x3){ if(CompletionType==TwoLastVertices){ class="kw">return(y1+(x3-x1)*(y2-y1)/(x2-x1)); } else{ class="kw">return(v); } } class=class="str">"cmt">// 用于初始化或者重置参数的方法 class="type">void Init(){ x1=class="num">0; y1=class="num">0; x2=class="num">0; y2=class="num">0; v=class="num">0; dir=class="num">0; } }; SLevelParameters CurLevel; SLevelParameters PreLevel; class="type">int start; if(prev_calculated==class="num">0){ class=class="str">"cmt">// 第一次计算全部柱 start=class="num">1; CurCount=class="num">0; PreCount=class="num">0; CurDir=class="num">0; PreDir=class="num">0; CurLevel.Init(); CurLevel.Init(); LastTime=class="num">0; } else{ class=class="str">"cmt">// 计算新柱和生成的柱 start=prev_calculated-class="num">1; } if(time[i]>LastTime){ class=class="str">"cmt">// 新柱的第一次计算 LastTime=time[i]; PreCount=CurCount; PreDir=CurDir; PreLevel=CurLevel; } else{ class=class="str">"cmt">// 重新计算柱 CurCount=PreCount; CurDir=PreDir; CurLevel=PreLevel; } class="type">void SetTarget(class="type">int dir,class="type">int li){ class="kw">switch(Target){ case FromVertexToVertex: class=class="str">"cmt">// &class="macro">#x27;从顶点到顶点&class="macro">#x27;的版本 if(dir==class="num">1){ CurLevel.target=PeackTrough[CurCount-class="num">1].Val+(PeackTrough[li+class="num">1].Val-PeackTrough[li].Val); } else if(dir==-class="num">1){ CurLevel.target=PeackTrough[CurCount-class="num">1].Val-(PeackTrough[li].Val-PeackTrough[li+class="num">1].Val); } break; case OneVertex: