形态与示例(第一部分):多顶·综合运用
(3/3)·当视觉形态遇上 MQL5 代码,双顶只是起点,多顶与头肩的混合识别才是算法交易的练手场
◍ 极值提取与形态方向的代码骨架
在 MT5 里做双顶双底识别,第一步是把两段索引之间的真实 High/Low 捞出来。CalculateMaximum 从 Index0 扫到 Index1,只要 High[i] 大于暂存值就刷新价格、时间和位置,最终写回 Top 结构的 Price 与 IndexExtremum。CalculateMinimum 逻辑对称,只是把比较符号翻成小于号找最低点。 PrepareExtremums 不干复杂活,直接把 TopsUpAll / TopsDownAll 的前 TopsM 个极值拷贝到工作数组,等于只保留离盘面最近的那几根。若 TopsM 设成 5,就只处理最近 5 个峰谷,老数据不进计算。 DirectionOfFormation 靠时间先后定方向:当下行极值时间晚于上行极值且末端谷仍激活,判为双底(FormationDirection=-1);反过来是双顶(=1)。两者都不满足就置 0,说明峰谷没凑齐,后续不宜硬判。 外汇与贵金属波动大,这类极值识别只解决“形状在不在”,不预示突破概率,实盘须叠加成交量或均线过滤。
class="type">void ExtremumsPatternFamilySearcher::CalculateMaximum(Top &T,class="type">int Index0,class="type">int Index1)class=class="str">"cmt">// if class="num">2 intermediate points are found, find High between them { class="type">class="kw">double MaxValue=High[Index0]; class="type">class="kw">datetime MaxTime=Time[Index0]; class="type">int MaxIndex=Index0; for(class="type">int i=Index0;i<=Index1;i++) { if ( High[i] > MaxValue ) { MaxValue=High[i]; MaxTime=Time[i]; MaxIndex=i; } } T.DatetimeExtremum=MaxTime; T.IndexExtremum=MaxIndex; T.Price=MaxValue; } class="type">void ExtremumsPatternFamilySearcher::CalculateMinimum(Top &T,class="type">int Index0,class="type">int Index1)class=class="str">"cmt">//if class="num">2 intermediate points are found, find Low between them { class="type">class="kw">double MinValue=Low[Index0]; class="type">class="kw">datetime MinTime=Time[Index0]; class="type">int MinIndex=Index0; for(class="type">int i=Index0;i<=Index1;i++) { if ( Low[i] < MinValue ) { MinValue=Low[i]; MinTime=Time[i]; MinIndex=i; } } T.DatetimeExtremum=MinTime; T.IndexExtremum=MinIndex; T.Price=MinValue; } class="type">bool ExtremumsPatternFamilySearcher::PrepareExtremums()class=class="str">"cmt">// assign the tops with which we will work { class="type">int Quantity;class=class="str">"cmt">// an auxiliary counter for random tops class="type">int PrevIndex;class=class="str">"cmt">// an auxiliary index for maintaining the order of indexes(increment only) for(class="type">int i=class="num">0;i<TopsM;i++)class=class="str">"cmt">// simply select the tops that are closest to the market { TopsUp[i]=TopsUpAll[i]; TopsDown[i]=TopsDownAll[i]; } class="kw">return true; } class="type">void ExtremumsPatternFamilySearcher::DirectionOfFormation()class=class="str">"cmt">// determine whether it is a class="type">class="kw">double top(class="num">1) or class="type">class="kw">double bottom(-class="num">1) (only if all tops and bottoms are found - if not found, then class="num">0) { if ( TopsDown[class="num">0].DatetimeExtremum > TopsUp[class="num">0].DatetimeExtremum && TopsDown[ArraySize(TopsDown)-class="num">1].bActive ) { StartTop=TopsDown[ArraySize(TopsDown)-class="num">1]; EndTop=TopsDown[class="num">0]; FormationDirection=-class="num">1; } else if ( TopsDown[class="num">0].DatetimeExtremum < TopsUp[class="num">0].DatetimeExtremum && TopsUp[ArraySize(TopsUp)-class="num">1].bActive ) { StartTop=TopsUp[ArraySize(TopsUp)-class="num">1]; EndTop=TopsUp[class="num">0]; FormationDirection=class="num">1; } else FormationDirection=class="num">0; } class="type">bool ExtremumsPatternFamilySearcher::IsExtremumsAbsolutely()class=class="str">"cmt">// require the selected extrema to be the most extreme ones { if ( bRandomExtremumsM )class=class="str">"cmt">// check only if we have a random selection of tops(in other case the check should be considered completed) { if ( FormationDirection == class="num">1 ) {
双方向极值间的颈线过滤逻辑
这段逻辑在形态识别里干一件事:给定随机抽出的上/下极值索引数组,校验两个选定极值之间是否还存在「更高(或更低)的毛刺」,从而判定该组极值能否构成经典多重顶/底的候选。 向上方向(FormationDirection==1)时,先取 RandomIndexUp[0] 为起点、数组末位为终点,遍历中间每根 K 线 i。若某根 i 的价格不低于已知 TopsUp 中任意顶,就再去比对 i 是否本就属于随机选中的极值集合;只要有一个 i 不在集合内,直接 return false,说明中间混入了非选定但更高的顶,形态不成立。 向下方向镜像处理:用 RandomIndexDown 框定区间,若中间 K 线价格不高于 TopsDown 中任意底且不在随机索引内,同样 return false。两种方向都通过才 return true;方向参数异常则返回 false。外汇与贵金属杠杆高,这类形态过滤只能降低假信号概率,不能排除突破失效风险。 后面 FindNeckUp 接收两个 Top 引用,初始化颈线最低价为起点极值处的 Low、时间为对应 Time,这是后续连线的基准点,开 MT5 把这两行打日志就能看到颈线锚定位置。
class="type">int StartIndex=RandomIndexUp[class="num">0]; class="type">int EndIndex=RandomIndexUp[ArraySize(RandomIndexUp)-class="num">1]; for(class="type">int i=StartIndex+class="num">1;i<EndIndex;i++)class=class="str">"cmt">// check all tops between the selected ones { for(class="type">int j=class="num">0;j<ArraySize(TopsUp);j++) { if ( TopsUpAll[i].Price >= TopsUp[j].Price ) { for(class="type">int k=class="num">0;k<ArraySize(RandomIndexUp);k++) { if ( i != RandomIndexUp[k] ) class="kw">return class="kw">false; } } } } class="kw">return true; } else if ( FormationDirection == -class="num">1 ) { class="type">int StartIndex=RandomIndexDown[class="num">0]; class="type">int EndIndex=RandomIndexDown[ArraySize(RandomIndexDown)-class="num">1]; for(class="type">int i=StartIndex+class="num">1;i<EndIndex;i++)class=class="str">"cmt">// check all tops between the selected ones { for(class="type">int j=class="num">0;j<ArraySize(TopsDown);j++) { if ( TopsDownAll[i].Price <= TopsDown[j].Price ) { for(class="type">int k=class="num">0;k<ArraySize(RandomIndexDown);k++) { if ( i != RandomIndexDown[k] ) class="kw">return class="kw">false; } } } } class="kw">return true; } else class="kw">return class="kw">false; } else { class="kw">return true; } } class="type">void ExtremumsPatternFamilySearcher::FindNeckUp(Top &TStart,Top &TEnd)class=class="str">"cmt">// find the neck line based on the two extreme tops(for the classic multiple top) { class="type">class="kw">double PriceMin=Low[TStart.IndexExtremum]; class="type">class="kw">datetime TimeMin=Time[TStart.IndexExtremum];
「颈线锚点与最远极值的定位逻辑」
多重顶/底形态里,颈线不是随手画的水平线,而是用两个极值拐点反推出来的。向上找颈线时,代码从起始顶的 K 线索引扫到结束顶的索引,逐根比对 Low 取最小价作为 PriceMin,同时记录 TimeMin;向下找颈线则比对 High 取最大价。 拿到锚点后,Neck 结构体会把 Price0、TimeX、Time0、Price1、Time1 全部赋值,再调 CalculateKC() 算直线方程。DirectionOfFormation 用 true/false 区分形态方向,这步直接决定后面过滤条件怎么走。 SearchFarestTop() 负责从已识别的极值数组里挑「离颈线最远」的那个顶或底。多重顶下用 TopsUp[i].Price - Neck.Price0 做距离判据,多重底下用 Neck.Price0 - TopsDown[i].Price。注意原代码在多重底分支里,更新 MaxTranslation 时误写了 TopsDown[0] 而非 TopsDown[i],这会导致最远底判定可能偏向首元素,开 MT5 跑回测时建议先修掉这行再验证。 外汇与贵金属杠杆高、波动突变频繁,这类形态识别只是概率辅助,实盘须配合止损。把上面逻辑复制到自己的 EA 里,改 FormationDirection 参数就能切换多顶/多底扫描。
for(class="type">int i=TStart.IndexExtremum;i>=TEnd.IndexExtremum;i--)class=class="str">"cmt">// define the lowest point { if ( Low[i] < PriceMin ) { PriceMin=Low[i]; TimeMin=Time[i]; } } class=class="str">"cmt">// define the parameters of the anchor point and all parameters of the line equation Neck.Price0=PriceMin; Neck.TimeX=TimeMin; Neck.Time0=Time[class="num">0]; Neck.Price1=PriceMin; Neck.Time1=TStart.DatetimeExtremum; Neck.DirectionOfFormation=true; Neck.CalculateKC(); } class="type">void ExtremumsPatternFamilySearcher::FindNeckDown(Top &TStart,Top &TEnd)class=class="str">"cmt">// find the neck line based on two extreme bottoms(for the classic multiple bottom) { class="type">class="kw">double PriceMax=High[TStart.IndexExtremum]; class="type">class="kw">datetime TimeMax=Time[TStart.IndexExtremum]; for(class="type">int i=TStart.IndexExtremum;i>=TEnd.IndexExtremum;i--)class=class="str">"cmt">// define the lowest point { if ( High[i] > PriceMax ) { PriceMax=High[i]; TimeMax=Time[i]; } } class=class="str">"cmt">// define the parameters of the anchor point and all parameters of the line equation Neck.Price0=PriceMax; Neck.TimeX=TimeMax; Neck.Time0=Time[class="num">0]; Neck.Price1=PriceMax; Neck.Time1=TStart.DatetimeExtremum; Neck.DirectionOfFormation=class="kw">false; Neck.CalculateKC(); } class="type">void ExtremumsPatternFamilySearcher::SearchFarestTop()class=class="str">"cmt">// define the farthest top { class="type">class="kw">double MaxTranslation;class=class="str">"cmt">// temporary variable to determine the highest top if ( FormationDirection == class="num">1 )class=class="str">"cmt">// if we deal with a multiple top { MaxTranslation=TopsUp[class="num">0].Price-Neck.Price0;class=class="str">"cmt">// temporary variable to determine the highest top FarestTop=TopsUp[class="num">0]; for(class="type">int i=class="num">1;i<ArraySize(TopsUp);i++) { if ( TopsUp[i].Price-Neck.Price0 > MaxTranslation ) { MaxTranslation=TopsUp[i].Price-Neck.Price0; FarestTop=TopsUp[i]; } } } if ( FormationDirection == -class="num">1 )class=class="str">"cmt">// if we deal with a multiple bottom { MaxTranslation=Neck.Price0-TopsDown[class="num">0].Price;class=class="str">"cmt">// temporary variable to determine the lowest bottom FarestTop=TopsDown[class="num">0]; for(class="type">int i=class="num">1;i<ArraySize(TopsDown);i++) { if ( Neck.Price0-TopsDown[i].Price > MaxTranslation ) { MaxTranslation=Neck.Price0-TopsDown[class="num">0].Price; FarestTop=TopsDown[i]; } } } } class="type">bool ExtremumsPatternFamilySearcher::bBalancedExtremums()class=class="str">"cmt">// balance the tops { class="type">class="kw">double Lowest;class=class="str">"cmt">// the lowest top for the multiple top
◍ 头肩形态的高度与时间对称性过滤
在 MT5 里自动识别多重顶/底(头肩类)形态时,光有极值点还不够,必须卡掉「头远大于肩」和「波宽失衡」的畸形结构。下面这段逻辑用两个阈值变量 RelativeUnstabilityM 与 RelativeUnstabilityTimeM 做硬性过滤,不达标直接 return false,形态判定作废。 对于多重顶(FormationDirection==1),先扫 TopsUp 数组找出最低的那个峰位 Lowest,算它到颈线 Neck.Price0 的垂直距离 AbsMin;若 AbsMin 为 0 说明贴着颈线,无意义。接着用 (最远峰到颈线距离 - AbsMin)/AbsMin 与 RelativeUnstabilityM 比较,比值超标就认为头部相对最小杠杆过大,形态不可信。多重底镜像处理,用 Highest 到颈线的距离做同样判定。 时间轴上,bBalancedExtremumsTime() 专门看相邻极值点的 IndexExtremum 间距。以多重顶为例,遍历 TopsUp 算每两段波宽,取最低 Lowest 与最高 Highest;若 (Highest-Lowest)/Lowest 大于 RelativeUnstabilityTimeM,说明某条腿过宽,水平方向不平衡,也一票否决。外汇与贵金属波动跳变频繁,这类过滤能显著降低假信号,但形态失败概率仍高,仅作辅助。 把 RelativeUnstabilityM 设成 0.5、RelativeUnstabilityTimeM 设成 1.0 这类经验值,挂上 EURUSD 的 M15 历史数据回测,能直观看到多少候选形态被砍掉。
class="type">class="kw">double Highest;class=class="str">"cmt">// the highest bottom for the multiple bottom class="type">class="kw">double AbsMin;class=class="str">"cmt">// distance from the neck to the nearest top if ( FormationDirection == class="num">1 )class=class="str">"cmt">// for the multiple top { Lowest=TopsUp[class="num">0].Price; for(class="type">int i=class="num">1;i<ArraySize(TopsUp);i++)class=class="str">"cmt">// find the lowest top { if ( TopsUp[i].Price < Lowest ) Lowest=TopsUp[i].Price; } AbsMin=Lowest-Neck.Price0;class=class="str">"cmt">// determine distance from the lowest top to the neck if ( AbsMin == class="num">0.0 ) class="kw">return class="kw">false; if ( ((FarestTop.Price - Neck.Price0)-AbsMin)/AbsMin >= RelativeUnstabilityM ) class="kw">return class="kw">false;class=class="str">"cmt">// if the head is too much bigger than the lowest leverage } else if ( FormationDirection == -class="num">1 )class=class="str">"cmt">// for the multiple bottom { Highest=TopsDown[class="num">0].Price; for(class="type">int i=class="num">1;i<ArraySize(TopsDown);i++)class=class="str">"cmt">// find the highest top { if ( TopsDown[i].Price > Highest ) Highest=TopsDown[i].Price; } AbsMin=Neck.Price0-Highest;class=class="str">"cmt">// determine distance from the highest top to the neck if ( AbsMin == class="num">0.0 ) class="kw">return class="kw">false; if ( ((Neck.Price0-FarestTop.Price)-AbsMin)/AbsMin >= RelativeUnstabilityM ) class="kw">return class="kw">false;class=class="str">"cmt">// if the head is too much bigger than the lowest leverage } else class="kw">return class="kw">false; class="kw">return true; } class="type">bool ExtremumsPatternFamilySearcher::bBalancedExtremumsTime()class=class="str">"cmt">// balance the sizes of shoulders and head along the horizontal axis { class="type">class="kw">double Lowest;class=class="str">"cmt">// minimum distance between the tops class="type">class="kw">double Highest;class=class="str">"cmt">// maximum distance between the tops if ( FormationDirection == class="num">1 )class=class="str">"cmt">// for the multiple top { Lowest=TopsUp[class="num">1].IndexExtremum-TopsUp[class="num">0].IndexExtremum; Highest=TopsUp[class="num">1].IndexExtremum-TopsUp[class="num">0].IndexExtremum; for(class="type">int i=class="num">1;i<ArraySize(TopsUp)-class="num">1;i++)class=class="str">"cmt">// find the lowest top { if ( TopsUp[i+class="num">1].IndexExtremum-TopsUp[i].IndexExtremum < Lowest ) Lowest=TopsUp[i+class="num">1].IndexExtremum-TopsUp[i].IndexExtremum; if ( TopsUp[i+class="num">1].IndexExtremum-TopsUp[i].IndexExtremum > Highest ) Highest=TopsUp[i+class="num">1].IndexExtremum-TopsUp[i].IndexExtremum; } if ( class="type">class="kw">double(Highest-Lowest)/class="type">class="kw">double(Lowest) > RelativeUnstabilityTimeM ) class="kw">return class="kw">false;class=class="str">"cmt">// if the width of one of the waves differs much } else if ( FormationDirection == -class="num">1 )class=class="str">"cmt">// for the multiple bottom {
双顶颈线左侧交截的过滤逻辑
这段 MT5 代码在做一件事:先算出双顶(或双底)相邻波峰之间的 K 线跨度极值,再拿相对不稳定阈值去卡掉变形太夸张的形态。Lowest 和 Highest 初值取最近两个顶的索引差,循环里不断刷新最小/最大跨度;若 (Highest-Lowest)/Lowest 超过 RelativeUnstabilityTimeM,直接 return false,说明某段波动宽度偏离太大,形态可信度倾向偏低。 CorrectNeckUpLeft 针对双顶:从 StartTop.Index1 往右扫到 BarsM,若某根 High 刺穿 FarestTop.Price 就判假突破返 false;只有 Close、Open、High、Low 四价同时低于 Neck.Price0,才把 Neck.Time1/Index1 钉在该根并返 true。双底镜像由 CorrectNeckDownLeft 处理,条件全部取反。 实盘里把 RelativeUnstabilityTimeM 设 0.5 意味着允许波宽差最多 50%,设 0.2 则只容 20% 偏差,过滤更严但可能漏掉真实宽幅双顶。外汇与贵金属杠杆高,这类形态识别只是概率辅助,进场前仍需看更大周期确认。
Lowest=TopsDown[class="num">1].IndexExtremum-TopsDown[class="num">0].IndexExtremum; Highest=TopsDown[class="num">1].IndexExtremum-TopsDown[class="num">0].IndexExtremum; for(class="type">int i=class="num">1;i<ArraySize(TopsDown)-class="num">1;i++)class=class="str">"cmt">// find the lowest top { if ( TopsDown[i+class="num">1].IndexExtremum-TopsDown[i].IndexExtremum < Lowest ) Lowest=TopsDown[i+class="num">1].IndexExtremum-TopsDown[i].IndexExtremum; if ( TopsDown[i+class="num">1].IndexExtremum-TopsDown[i].IndexExtremum > Highest ) Highest=TopsDown[i+class="num">1].IndexExtremum-TopsDown[i].IndexExtremum; } if ( class="type">class="kw">double(Highest-Lowest)/class="type">class="kw">double(Lowest) > RelativeUnstabilityTimeM ) class="kw">return class="kw">false;class=class="str">"cmt">// if the width of one of the waves differs much } else class="kw">return class="kw">false; class="kw">return true; } class="type">bool ExtremumsPatternFamilySearcher::CorrectNeckUpLeft()class=class="str">"cmt">// next the neck line must be corrected so that it finds an intersection with the price on the left { class="type">bool bCrossNeck=class="kw">false;class=class="str">"cmt">// indicates if the neck was crossed if ( Neck.DirectionOfFormation )class=class="str">"cmt">// if the neck is found for a class="type">class="kw">double top { for(class="type">int i=StartTop.Index1;i<BarsM;i++)class=class="str">"cmt">// define the intersection point { if ( High[i] >= FarestTop.Price )class=class="str">"cmt">// if the movement goes beyond the formation, then the formation is fake { class="kw">return class="kw">false; } if ( Close[i] < Neck.Price0 && Open[i] < Neck.Price0 && High[i] < Neck.Price0 && Low[i] < Neck.Price0 ) { Neck.Time1=Time[i]; Neck.Index1=i; class="kw">return true; } } } class="kw">return class="kw">false; } class="type">bool ExtremumsPatternFamilySearcher::CorrectNeckDownLeft()class=class="str">"cmt">// next the neck line must be corrected so that it finds an intersection with the price on the left { class="type">bool bCrossNeck=class="kw">false;class=class="str">"cmt">// indicates if the neck was crossed if ( !Neck.DirectionOfFormation )class=class="str">"cmt">// if the neck is found for a class="type">class="kw">double bottom { for(class="type">int i=StartTop.Index1;i<BarsM;i++)class=class="str">"cmt">// define the intersection point { if ( Low[i] <= FarestTop.Price )class=class="str">"cmt">// if the movement goes beyond the formation, then the formation is fake { class="kw">return class="kw">false; } if ( Close[i] > Neck.Price0 && Open[i] > Neck.Price0 && High[i] > Neck.Price0 && Low[i] > Neck.Price0 ) { Neck.Time1=Time[i]; Neck.Index1=i;
「颈线右侧交叉的修正与假突破判定」
双顶结构里,颈线找到后还得向右修正,确认价格和颈线在右侧有交点。代码用 CorrectNeckUpRight() 处理双顶:从 EndTop 极点索引向左扫到 i>1,若中途 High[i] 超过 FarestTop.Price 或 Low[i] 跌破 Neck.Price0,直接 return -1,说明形态失效概率高。 若最新收盘 Close[0] 已落在 Neck.Price0 下方,就把 Neck.Time0 设为当前时间并返回 1,代表右交点已成立。否则返回 0,等待后续 K 线推进。 双底镜像逻辑在 CorrectNeckDownRight():只有当 !Neck.DirectionOfFormation 才进入,循环里判 Low[i] < FarestTop.Price 或 High[i] > Neck.Price0 即 return -1。Close[0] >= Neck.Price0 时记录 Time0 并返回 1。 bWasTrend() 负责确认形态前驱趋势:FormationDirection==1 时从 Neck.Index1 向右到 BarsM,只要 High[i] > Neck.Price0 就 return false,意味着价格已刺穿颈线、双顶假设作废。外汇与贵金属波动大,这类刺穿在重大数据行情中常出现假信号,建议用 MT5 历史回放逐根验证上述 return 分支。
class="type">int ExtremumsPatternFamilySearcher::CorrectNeckUpRight()class=class="str">"cmt">// next the neck line must be corrected so that it finds an intersection with the price on the right { class="type">bool bCrossNeck=class="kw">false;class=class="str">"cmt">// indicates if the neck was crossed if ( Neck.DirectionOfFormation )class=class="str">"cmt">// if the neck is found for a class="type">class="kw">double top { for(class="type">int i=EndTop.IndexExtremum;i>class="num">1;i--)class=class="str">"cmt">// define the intersection point { if ( High[i] > FarestTop.Price || Low[i] < Neck.Price0 )class=class="str">"cmt">// if the movement goes beyond the formation, then the formation is fake { class="kw">return -class="num">1; } } } if ( Close[class="num">0] <= Neck.Price0 ) { Neck.Time0=Time[class="num">0]; class="kw">return class="num">1; } class="kw">return class="num">0; } class="type">int ExtremumsPatternFamilySearcher::CorrectNeckDownRight()class=class="str">"cmt">// next the neck line must be corrected so that it finds an intersection with the price on the right { class="type">bool bCrossNeck=class="kw">false;class=class="str">"cmt">// indicates if the neck was crossed if ( !Neck.DirectionOfFormation )class=class="str">"cmt">// if the neck is found for a class="type">class="kw">double bottom { for(class="type">int i=EndTop.IndexExtremum;i>class="num">1;i--)class=class="str">"cmt">// define the intersection point { if ( Low[i] < FarestTop.Price || High[i] > Neck.Price0 )class=class="str">"cmt">// if the movement goes beyond the formation, then the formation is fake { class="kw">return -class="num">1; } } } if ( Close[class="num">0] >= Neck.Price0 ) { Neck.Time0=Time[class="num">0]; class="kw">return class="num">1; } class="kw">return class="num">0; } class="type">bool ExtremumsPatternFamilySearcher::bWasTrend()class=class="str">"cmt">// did we find the movement preceding the formation(also move here the anchor point to the intersection) { class="type">bool bCrossOptimist=class="kw">false;class=class="str">"cmt">// denotes if the neck is crossed if ( FormationDirection == class="num">1 )class=class="str">"cmt">// if the optimistic forecast is at the class="type">class="kw">double top { for(class="type">int i=Neck.Index1;i<BarsM;i++)class=class="str">"cmt">// define the intersection point { if ( High[i] > Neck.Price0 )class=class="str">"cmt">// if the movement goes beyond the neck, then the formation is fake { class="kw">return class="kw">false; }
◍ 双底形态里乐观线的触发与证伪
在双底(FormationDirection == -1)的判定逻辑里,代码从颈线右侧第一根 K 线(Neck.Index1)扫到 BarsM 根为止,目的是确认价格是否真正刺穿乐观线。若期间任一根 Low[i] 跌破颈线 Price0,直接 return false——这意味着向下破位,双底失效,概率上倾向放弃多头设想。 反过来,只要 High[i] 上破 OptimistLine.Price0,就把该 K 线时间写进 OptimistLine.Time1 并返回 true,代表乐观目标被触及。外汇与贵金属这类高波动品种里,刺穿颈线后反向抽回的概率不低,因此用循环逐根校验比单根判断更稳。 下面这段是 MT5 里可直接粘去验证的核心片段,注意 Neek 与 OptimistLine 的结构体字段要和你自己的图形对象一致:
else if ( FormationDirection == -class="num">1 )class=class="str">"cmt">// if the optimistic forecast is at the class="type">class="kw">double bottom { for(class="type">int i=Neck.Index1;i<BarsM;i++)class=class="str">"cmt">// define the intersection point { if ( Low[i] < Neck.Price0 )class=class="str">"cmt">// if the movement goes beyond the neck, then the formation is fake { class="kw">return class="kw">false; } if ( High[i] > OptimistLine.Price0 ) { OptimistLine.Time1=Time[i]; class="kw">return true; } } } class="kw">return class="kw">false; }
头肩之外还能挖什么
当前这套检测逻辑只覆盖了头肩家族的基础阵型,后续计划把搜索质量整体抬一档,并让类能直接识别头肩形态本身。更值得做的是混合阵型——比如「N 个顶部加多个肩部」这种非标准组合,靠现有单形态匹配是漏掉的。 系列不会只盯头肩,还会补进其它有趣形态、以及用不同类别方法检测阵型的思路。一个明确落点是:基于历史数据做交易回放,并收集不同金融产品、不同时间帧的统计数据,这部分在 MT5 里用标准历史接口就能跑。 等级(rank)也会单独拎出来研究,因为它常用来辅助判断反转。外汇和贵金属波动大、杠杆高,任何形态识别都只是概率信号,实盘前请用策略测试器在对应品种多周期验证。
「别急着下结论」
从可视化策略测试器里能直接看到,几十行 MQL5 代码就能把复杂形态抓出来,不一定非得上神经网络或机器视觉那套重武器。语言本身的功能厚度足够,更重的算法也只是时间和想象力的成本。 下一篇作者计划把所有形态拉到一起回测,用统计表说话,而不是堆代码。对交易者来说,这意味着你手里的 MT5 先能验证简单逻辑,再决定是否加复杂度。 外汇和贵金属波动受杠杆与事件驱动,形态命中只是概率倾向,实盘前请用策略测试器跑一遍自己的品种与周期。