形态与示例(第一部分):多顶·进阶篇
📐

形态与示例(第一部分):多顶·进阶篇

(2/3)· 双顶概念太窄?这篇把多顶渲染、识别与过滤写成通用框架,留给策略测试器去证伪

实战向 第 2/3 篇

接上篇,我们继续深挖逆转形态的机器实现。多顶在网上被讲烂了,但绝大多数教程只给肉眼看图,不给可证伪的统计。把形态写进代码,才知道它到底靠不靠谱。

用类封装多重顶的判定逻辑

做形态识别别盯着即时报价跑,按已收盘的柱线逐根推进能少掉一大半冗余计算。把搜索逻辑封进一个独立的类里,所有检测步骤都作为实例方法执行,后面要加随机选顶、扩成头肩族都只用改这个类,不用动外层调度。 判定顶部不套用分形,而是自己写半波逻辑:同一方向连续 MinimumSeriesBarsM 根柱线才认作一段走势,三段衔接(走势1→走势2→反向走势3)确认一个极值。底部反向同理由独立函数实现,结果丢进容器逐步附加,最后统一输出给绘图层。 顶部找齐只是过渡,机器没法像人眼那样挑‘好看’的肩,当前策略选最贴近行情的顶(变体0和1)。后续若开 bRandomExtremumsM 做随机选顶,就能模拟多个查找实例,自动模式扫形态更密。 方向按最贴近行情的极值类型给优先级:贴近的是底就倾向看涨多重底,是顶则倾向多重顶。过滤规则要求多选顶之间必须有一个顶低于所选最低顶(多重底反之),否则形态无效。 颈部我选‘最高/低点水平投影’这种变体:取两个极值顶的索引,在首末顶之间搜最低价画水平线,锚点时间对齐极值顶。目标位尺寸 = 头到颈垂直距离(点),离颈最远顶定边界,再做两次不稳定度检查。 垂直过滤量化成 K = (Max-Min)/Min,K <= RelativeUnstabilityM 才留;水平过滤同理用柱线索引算 K <= RelativeUnstabilityTimeM。这两道能砍掉大量假形态,但别指望代码比眼睛强,逻辑贴近现实即可停手。 最后两道:零号烛条必须穿颈线(价格出形态又折返的不算有效右交叉),且颈线左侧要有大于等于形态本身的前期走势。MetaTrader 5 可视测试器里三重顶画线和双顶参数切换已验证原型可用,外汇贵金属高波动下这类形态假突破概率不低,实盘前先跑历史可视化。

MQL5 / C++
class ExtremumsPatternFamilySearcherclass=class="str">"cmt">// class simulating an independent pattern search
  {
  class="kw">private:
  class="type">int BarsM;class=class="str">"cmt">// how many bars on chart to use
  class="type">int MinimumSeriesBarsM;class=class="str">"cmt">// the minimum number of bars in a row to detect a top
  class="type">int TopsM;class=class="str">"cmt">// number of tops in the pattern
  class="type">int PointsPessimistM;class=class="str">"cmt">// minimum distance in points to the nearest target
  class="type">class="kw">double RelativeUnstabilityM;class=class="str">"cmt">// maximum excess of the head size relative to the minimum shoulder
  class="type">class="kw">double RelativeUnstabilityMinM;class=class="str">"cmt">// minimum excess of the head size relative to the minimum shoulder
  class="type">class="kw">double RelativeUnstabilityTimeM;class=class="str">"cmt">// maximum excess of head and shoulders sizes
  class="type">bool bAbsolutelyHeadM;class=class="str">"cmt">// whether a pronounced head is required
  class="type">bool bRandomExtremumsM;class=class="str">"cmt">// random selection of extrema
  
  class="kw">struct Topclass=class="str">"cmt">// top data
    {
    class="type">class="kw">datetime Datetime0;class=class="str">"cmt">// time of the candlestick closest to the market
    class="type">class="kw">datetime Datetime1;class=class="str">"cmt">// time of the next candlestick
    class="type">int Index0;class=class="str">"cmt">// index of the candlestick closest to the market

「结构体的字段与趋势线斜率计算」

价格形态识别的第一步是把顶底和连线抽象成数据结构。下面这组结构体定义了单个顶(Top)和一条趋势线(Line)的内存布局,MT5 里直接塞进数组就能批量管理多组极值。 Top 结构体存了下一根 K 线索引、极值时间、极值索引、极值价格,以及一个 bActive 开关——bActive 为 false 时代表该顶当前不存在,避免在未成形阶段误判形态。Line 结构体则绑定了近端和远端两根 K 线的价格与时间,外加 X 点时间和左右边缘索引。 趋势线不是画完就不动的。CalculateKC() 用两端点时间差求斜率 K,若 Time0 == Time1 则强制 K=0.0 防止除零;常数项 C 由 Price1 - K*Time1 推出。Price(T) 函数随后按 K*T+C 返回任意时刻的线价,回测时可直接拿它和实时报价比大小。 参数化构造函数把 BarsM、MinimumSeriesBarsM、TopsM 等 9 个外部变量落进成员,最后跑一次 bFindPattern() 给 bPatternFinded 赋值。外汇与贵金属波动剧烈,这类形态识别仅作概率参考,实盘前务必在 MT5 策略测试器用历史数据验证误报率。

MQL5 / C++
class="type">int Index1;class=class="str">"cmt">// index of the next candlestick
class="type">class="kw">datetime DatetimeExtremum;class=class="str">"cmt">// time of the top
class="type">int IndexExtremum;class=class="str">"cmt">// index of the top
class="type">class="kw">double Price;class=class="str">"cmt">// price of the top
class="type">bool bActive;class=class="str">"cmt">// if the top is active(if not, then it does not exist)
};

 class="kw">struct Lineclass=class="str">"cmt">// line
  {
  class="type">class="kw">double Price0;class=class="str">"cmt">// price of the candlestick closest to the market, to which the line is bound
  class="type">class="kw">datetime Time0;class=class="str">"cmt">// time of the candlestick closest to the market, to which the line is bound
  class="type">class="kw">double Price1;class=class="str">"cmt">// price of the farthest candlestick to which the line is bound
  class="type">class="kw">datetime Time1;class=class="str">"cmt">// time of the farthest candlestick to which the line is bound
  class="type">class="kw">datetime TimeX;class=class="str">"cmt">// time of the X point
  class="type">int Index1;class=class="str">"cmt">// index of the left edge
  class="type">bool DirectionOfFormation;class=class="str">"cmt">// direction
  class="type">class="kw">double C;class=class="str">"cmt">// free coefficient in the equation
  class="type">class="kw">double K;class=class="str">"cmt">// aspect ratio

  class="type">void CalculateKC()class=class="str">"cmt">// find unknowns in the equation
    {
    if ( Time0 != Time1 ) K=class="type">class="kw">double(Price0-Price1)/class="type">class="kw">double(Time0-Time1);
    else K=class="num">0.0;
    C=class="type">class="kw">double(Price1)-K*class="type">class="kw">double(Time1);
    }

  class="type">class="kw">double Price(class="type">class="kw">datetime T)class=class="str">"cmt">// function of line depending on time
    {
    class="kw">return K*T+C;
    }
  };

 class="kw">public:  

 ExtremumsPatternFamilySearcher(class="type">int BarsI,class="type">int MinimumSeriesBarsI,class="type">int TopsI,class="type">int PointsPessimistI, class="type">class="kw">double RelativeUnstabilityI,
 class="type">class="kw">double RelativeUnstabilityMinI,class="type">class="kw">double RelativeUnstabilityTimeI,class="type">bool bAbsolutelyHeadI,class="type">bool bRandomExtremumsI)class=class="str">"cmt">// parametric constructor
  {
  BarsM=BarsI;
  MinimumSeriesBarsM=MinimumSeriesBarsI;
  TopsM=TopsI;
  PointsPessimistM=PointsPessimistI;
  RelativeUnstabilityM=RelativeUnstabilityI;
  RelativeUnstabilityMinM=RelativeUnstabilityMinI;
  RelativeUnstabilityTimeM=RelativeUnstabilityTimeI;
  bAbsolutelyHeadM=bAbsolutelyHeadI;
  bRandomExtremumsM=bRandomExtremumsI;
  bPatternFinded=bFindPattern();
  }

  class="type">int FormationDirection;class=class="str">"cmt">// direction of the formation(multiple top or bottom, or none at all) ( -class="num">1,class="num">1,class="num">0 )    
  class="type">bool bPatternFinded;class=class="str">"cmt">// if the pattern was found during formation
  Top TopsUp[];class=class="str">"cmt">// required upper extrema
  Top TopsDown[];class=class="str">"cmt">// required lower extrema
  Top TopsUpAll[];class=class="str">"cmt">// all upper extrema
  Top TopsDownAll[];class=class="str">"cmt">// all lower extrema
  class="type">int RandomIndexUp[];class=class="str">"cmt">// array for the random selection of the tops index
  class="type">int RandomIndexDown[];class=class="str">"cmt">// array for the random selection of the bottoms index
  Top StartTop;class=class="str">"cmt">// where the formation starts(top farthest from the market)

◍ 双顶双底识别类的内部结构与接口划分

在 MT5 自定义指标里做形态识别,通常会把形态的关键几何元素先声明成成员变量:最贴近行情的顶(EndTop)、颈线(Neck)、离颈线最远的顶(FarestTop,用来判定头肩或量度形态高度)、乐观目标线、悲观目标线、边界线以及平行于趋势阻力的辅助线。这些变量一旦赋值,后续绘制和信号判定都直接引用,不必重复计算。 私有方法负责全部识别逻辑:从 SearchFirstUps / SearchFirstDowns 找顶底,到 CalculateMaximum / CalculateMinimum 在两根 K 线间取极值,再到 PrepareExtremums 与一组 bBalanced* 方法做极值平衡——例如 bBalancedExtremumsTime 要求各极值在时间上不能离最小间距太远,bBalancedHead 在超过三个顶时强制头不能排第一或最后。 颈线修正分左右两侧:CorrectNeckUpRight 返回 int,用于找价格与颈线在右侧或当前位置的交叉点,这直接决定进场位;左侧修正(CorrectNeckUpLeft)则用于确认形态前存在逆向趋势。SearchLineOptimist 与 bCalculatePessimistic 分别给出乐观/悲观目标线,bWasTrend 会判断形态前是否有趋势,若有则乐观线视作趋势起点延伸。 公开接口只暴露清理与绘制:CleanAll 释放对象,DrawPoints / DrawNeck / DrawOptimist 等七个方法负责把识别结果画到图表。实战上你复制这套类结构后,只要改 bBalancedExtremums 里的容差阈值(比如把极值高度差从 2% 调到 5%),就能在 XAUUSD 的 H1 上少抓很多噪声双顶,但也可能漏掉紧凑整理后的真突破,贵金属杠杆高、假突破止损常被扫,参数务必先在策略测试器跑一遍。

MQL5 / C++
  Top EndTop; class=class="str">"cmt">// where the formation ends(top closest to the market)
  Line Neck; class=class="str">"cmt">// neck
  Top FarestTop; class=class="str">"cmt">// top farthest from the neck(will be used to determine the head or the formation size) or the same as the head
  Line OptimistLine; class=class="str">"cmt">// line of optimistic forecast
  Line PessimistLine; class=class="str">"cmt">// line of pessimistic forecast
  Line BorderLine; class=class="str">"cmt">// line at the edge of the pattern
  Line ParallelLine; class=class="str">"cmt">// line parallel to the trend resistance

  class="kw">private:
  class="type">void SetTopsSize(); class=class="str">"cmt">// setting sizes for arrays with tops
  class="type">bool SearchFirstUps(); class=class="str">"cmt">// search for tops
  class="type">bool SearchFirstDowns(); class=class="str">"cmt">// search for bottoms
  class="type">void CalculateMaximum(Top &T,class="type">int Index0,class="type">int Index1); class=class="str">"cmt">// calculate the maximum price between two bars
  class="type">void CalculateMinimum(Top &T,class="type">int Index0,class="type">int Index1); class=class="str">"cmt">// calculate the minimum price between two bars
  class="type">bool PrepareExtremums(); class=class="str">"cmt">// prepare extrema
  class="type">bool IsExtremumsAbsolutely(); class=class="str">"cmt">// control the priority of tops
  class="type">void DirectionOfFormation(); class=class="str">"cmt">// determine the direction of the formation
  class="type">void FindNeckUp(Top &TStart,Top &TEnd); class=class="str">"cmt">// find neck for the bullish pattern
  class="type">void FindNeckDown(Top &TStart,Top &TEnd); class=class="str">"cmt">// find neck for the bearish pattern
  class="type">void SearchFarestTop(); class=class="str">"cmt">// find top farthest from the neck
  class="type">bool bBalancedExtremums(); class=class="str">"cmt">// initial balancing of extrema(so that they do not differ much)
  class="type">bool bBalancedExtremumsHead(); class=class="str">"cmt">// if a pattern has more than class="num">2 tops, we can check for a pronounced head
  class="type">bool bBalancedExtremumsTime(); class=class="str">"cmt">// require that the extrema be not very far in time relative to the minimum distance
  class="type">bool bBalancedHead(); class=class="str">"cmt">// balance the head(in other words, require that it be neither the first nor the last one on the list of tops, if there are more than three of them)
  class="type">bool CorrectNeckUpLeft(); class=class="str">"cmt">// adjust the neck so as to find the intersection of price and neck(this creates prerequisites for the previous trend) 
  class="type">bool CorrectNeckDownLeft(); class=class="str">"cmt">// similarly for the bottom
  class="type">int CorrectNeckUpRight(); class=class="str">"cmt">// adjust the neck so as to find the intersection of price and neck on the right or at the current price position, which is the same(to determine the entry point)
  class="type">int CorrectNeckDownRight(); class=class="str">"cmt">// similarly for the bottom
  class="type">void SearchLineOptimist(); class=class="str">"cmt">// calculate the optimistic forecast line
  class="type">bool bWasTrend(); class=class="str">"cmt">// determine whether a trend preceded the pattern definition(in this case the optimistic target line is considered as the trend beginning)
  class="type">void SearchLineBorder(); class=class="str">"cmt">// determine trend resistance or support(usually a sloping line)
  class="type">void CalculateParallel(); class=class="str">"cmt">// determine a line parallel to support or resistance(crosses the neck at the pattern low or high)
  class="type">bool bCalculatePessimistic(); class=class="str">"cmt">// calculate the line of the pessimistic target
  class="type">bool bFindPattern(); class=class="str">"cmt">// perform all the above actions
  class="type">int iFindEnter(); class=class="str">"cmt">// find intersection with the neck
  class="kw">public:
  class="type">void CleanAll(); class=class="str">"cmt">// clean up objects
  class="type">void DrawPoints(); class=class="str">"cmt">// draw points
  class="type">void DrawNeck(); class=class="str">"cmt">// draw the neck
  class="type">void DrawLineBorder(); class=class="str">"cmt">// line at the border
  class="type">void DrawParallel(); class=class="str">"cmt">// line parallel to the border
  class="type">void DrawOptimist(); class=class="str">"cmt">// line of optimistic forecast
  class="type">void DrawPessimist(); class=class="str">"cmt">// line of pessimistic forecast
  };

顶部识别里的连续阴线判定

在极值模式搜索器里,找顶部(ups)的第一步不是看最高价,而是先确认一段连续看跌蜡烛。代码用 bDown 作辅助布尔,只有当 MinimumSeriesBarsM 根蜡烛全部满足 Open[j]-Close[j] >= 0(即收盘价不高于开盘价)时才保留为候选顶。 这段逻辑意味着:如果窗口内混进任意一根阳线,bDown 立刻置 false 并 break,当前 i 位置直接失去成为顶的资格。MinimumSeriesBarsM 是前置参数,默认取值偏小(常见 2~3)时假顶会偏多,调大则漏顶概率上升。 外汇与贵金属属高杠杆高风险品种,这种纯形态过滤不保证后续反转,仅作结构标注用。开 MT5 把 MinimumSeriesBarsM 从 2 改 4,回看 XAUUSD 的 M15,能直观比对候选顶数量收缩。

MQL5 / C++
class="type">bool ExtremumsPatternFamilySearcher::SearchFirstUps()class=class="str">"cmt">// find tops
  {
  class="type">int NumUp=class="num">0;class=class="str">"cmt">// the number of found tops
  class="type">int NumDown=class="num">0;class=class="str">"cmt">// the number of found bottoms
  class="type">bool bDown=class="kw">false;class=class="str">"cmt">// an auxiliary boolean which shows if a segment of bearish candlesticks has been found
  class="type">bool bUp=class="kw">false;class=class="str">"cmt">// an auxiliary boolean which shows if a segment of bullish candlesticks has been found
  class="type">bool bNextUp=true;class=class="str">"cmt">// can we move on to searching for the next top
  class="type">bool bNextDown=true;class=class="str">"cmt">// can we move on to searching for the next bottom
  
  for(class="type">int i=class="num">0;i<ArraySize(TopsUp);i++)class=class="str">"cmt">// before search, set all necessary tops to an inactive state
    {
    TopsUp[i].bActive=class="kw">false;
    }
  for(class="type">int i=class="num">0;i<ArraySize(TopsUpAll);i++)class=class="str">"cmt">// before search, set all tops to an inactive state
    {
    if (!TopsUpAll[i].bActive) class="kw">break;
    TopsUpAll[i].bActive=class="kw">false;
    }
    
  for(class="type">int i=class="num">0;i<BarsM;i++)
    {
    if ( i+MinimumSeriesBarsM-class="num">1 < BarsM )class=class="str">"cmt">// if remaining bars are enough to determine the extremum and we can start searching for the next top
      {
      if ( bNextUp )class=class="str">"cmt">// if it is allowed to search for the next top
        {
        bDown=true;
        for(class="type">int j=i;j<i+MinimumSeriesBarsM;j++)class=class="str">"cmt">// determine the first extrema for upper tops
          {
          if ( Open[j]-Close[j] < class="num">0 )class=class="str">"cmt">// if at least one of the selected candlesticks was upward
            {
            bDown=class="kw">false;
            class="kw">break;
            }
          }
        if ( bDown )
          {
          TopsUpAll[NumUp].Datetime0=Time[i+MinimumSeriesBarsM-class="num">1];
          TopsUpAll[NumUp].Index0=i+MinimumSeriesBarsM-class="num">1;
          bNextUp=class="kw">false;
          }
        }       
      }
    if ( MinimumSeriesBarsM+i < BarsM && bDown )class=class="str">"cmt">// if the remaining bars are enough to determine the second half of the extremum and the previous half has been found
      {
      bUp=true;          
      for(class="type">int j=i;j<MinimumSeriesBarsM+i;j++)class=class="str">"cmt">//determine further candlesticks in the opposite direction
        {

「顶底识别里的布尔开关与循环退出」

这段逻辑是形态搜索类的核心:用一组布尔量标记「是否找到空头段 / 多头段」「能否继续找下一个顶或底」,一旦在窗口内撞到反向 K 线就 break 退出内层循环。 以顶部搜索为例,内层 for(j=i;j<i+MinimumSeriesBarsM;j++) 扫描连续 K 线,只要出现 Open[j]-Close[j] > 0(即任一根为下跌 K 线),就把 bUp 置 false 并 break,说明这段不满足「连续看涨顶」的 precondition。 若 bDown && bUp 同时为真,才调用 CalculateMaximum 在两根索引间算极值,把该形态登记为活跃顶,随后 NumUp++bNextUp=true 复位标记进入下一轮。当 NumUp >= TopsM 直接 return true,否则 false——这就是「找够所需顶数才确认」的硬门槛。 底部搜索 SearchFirstDowns 镜像处理:开局先把 TopsDownTopsDownAll 全部 bActive=false,再逐 bar 跑。注意 bNextUp 初值设为 true、bNextDown 也为 true,意味着顶底搜索在起始阶段是并行允许的,靠后续命中反向段来互相锁死。

MQL5 / C++
if ( Open[j]-Close[j] > class="num">0 )class=class="str">"cmt">//if at least one of the selected candlesticks was downward
  {
  bUp=class="kw">false;
  class="kw">break;
  }
  }
  if ( bUp )
  {
  TopsUpAll[NumUp].Datetime1=Time[i];
  TopsUpAll[NumUp].Index1=i;
  TopsUpAll[NumUp].bActive=true;
  bNextUp=class="kw">false;
  }
  }
  class=class="str">"cmt">// after that, register the found formation as a top, if it is a top
  if ( bDown && bUp )
  {
  CalculateMaximum(TopsUpAll[NumUp],TopsUpAll[NumUp].Index0,TopsUpAll[NumUp].Index1);class=class="str">"cmt">// calculate extremum between two bars
  bNextUp=true;
  bDown=class="kw">false;
  bUp=class="kw">false;
  NumUp++;
  }
  }
  if ( NumUp >= TopsM ) class="kw">return true;class=class="str">"cmt">// if the required number of tops have been found
  else class="kw">return class="kw">false;
  }
class="type">bool ExtremumsPatternFamilySearcher::SearchFirstDowns()class=class="str">"cmt">// find bottoms
  {
  class="type">int NumUp=class="num">0;
  class="type">int NumDown=class="num">0;
  class="type">bool bDown=class="kw">false;class=class="str">"cmt">// an auxiliary boolean which shows if a segment of bearish candlesticks has been found
  class="type">bool bUp=class="kw">false;class=class="str">"cmt">// an auxiliary boolean which shows if a segment of bullish candlesticks has been found
  class="type">bool bNextUp=true;class=class="str">"cmt">// can we move on to searching for the next top
  class="type">bool bNextDown=true;class=class="str">"cmt">// can we move on to searching for the next bottom
  for(class="type">int i=class="num">0;i<ArraySize(TopsDown);i++)class=class="str">"cmt">// before search, set all necessary bottoms to an inactive state
  {
  TopsDown[i].bActive=class="kw">false;
  }
  for(class="type">int i=class="num">0;i<ArraySize(TopsDownAll);i++)class=class="str">"cmt">// before search, set all bottoms to an inactive state
  {
  if (!TopsDownAll[i].bActive) class="kw">break;
  TopsDownAll[i].bActive=class="kw">false;
  }
  for(class="type">int i=class="num">0;i<BarsM;i++)
  {
  if ( i+MinimumSeriesBarsM-class="num">1 < BarsM )class=class="str">"cmt">// if remaining bars are enough to determine the extremum and we can start searching for the next top
  {
  if ( bNextDown )class=class="str">"cmt">// if it is allowed to search for the next bottom
  {
  bUp=true;
  for(class="type">int j=i;j<i+MinimumSeriesBarsM;j++)class=class="str">"cmt">// determine the first extrema for upper tops

◍ 双段K线方向校验锁定底部形态

这段逻辑在做一件事:把前面找出的前半段向下K线组合,与后半段向上K线组合拼起来,确认一个完整的底部极值结构。前半段要求 MinimumSeriesBarsM 根里每根都是阴线(Open[j]-Close[j] > 0),只要有一根不是就 bUp=false 并 break,说明前半段不成立。 后半段在 MinimumSeriesBarsM+i < BarsM 且 bUp 为真时才进入,反向扫描同样数量的K线,要求每根都是阳线(Open[j]-Close[j] < 0),否则 bDown=false。两个布尔同时为真,才调用 CalculateMinimum 在 Index0 到 Index1 之间算极值,并把该底部标记为 bActive=true、NumDown 自增。 函数出口很干脆:NumDown 等于预设的 TopsM 就 return true,意味着指定数量的底部已全部找齐;否则 return false。外汇与贵金属行情里这种形态误判率不低,实盘前建议在 MT5 用不同 MinimumSeriesBarsM(例如 3 与 5)跑同一段代码比对命中数。

MQL5 / C++
         {
         if ( Open[j]-Close[j] > class="num">0 )class=class="str">"cmt">//if at least one of the selected candlesticks was downward
           {
           bUp=class="kw">false;
           class="kw">break;
           }
         }
         if ( bUp )
           {
           TopsDownAll[NumDown].Datetime0=Time[i+MinimumSeriesBarsM-class="num">1];
           TopsDownAll[NumDown].Index0=i+MinimumSeriesBarsM-class="num">1;
           bNextDown=class="kw">false;
           }
         }               
         }
      if ( MinimumSeriesBarsM+i < BarsM && bUp )class=class="str">"cmt">// if the remaining bars are enough to determine the second half of the extremum and the previous half has been found
        {     
        bDown=true;                      
        for(class="type">int j=i;j<MinimumSeriesBarsM+i;j++)class=class="str">"cmt">//determine further candlesticks in the opposite direction
          {
          if ( Open[j]-Close[j] < class="num">0 )class=class="str">"cmt">// if at least one of the selected candlesticks was upward
            {
            bDown=class="kw">false;
            class="kw">break;
            }
          }
        if ( bDown )
          {
          TopsDownAll[NumDown].Datetime1=Time[i];
          TopsDownAll[NumDown].Index1=i;
          TopsDownAll[NumDown].bActive=true;
          bNextDown=class="kw">false;             
          }
        }
       class=class="str">"cmt">// after that, register the found formation as a bottom, if it is a bottom
       if ( bDown && bUp )
         {
         CalculateMinimum(TopsDownAll[NumDown],TopsDownAll[NumDown].Index0,TopsDownAll[NumDown].Index1);class=class="str">"cmt">// calculate extremum between two bars
         bNextDown=true;
         bDown=class="kw">false;
         bUp=class="kw">false;             
         NumDown++;
         }
       }

   if ( NumDown == TopsM ) class="kw">return true;class=class="str">"cmt">//if the required number of bottoms have been found
   else class="kw">return class="kw">false;
把形态扫描交给小布盯盘
这些多顶识别与过滤逻辑,小布盯盘的 AIGC 已内置到对应品种页,打开即可看到实时形态标注,你只管判断概率和风控。

常见问题

多顶本质是一组局部峰值的排列,MQL5 可把峰值数量参数化,从而覆盖双顶、三顶及头肩混合体,突破肉眼只认固定个数的局限。
可以。小布盯盘在品种页内置了形态扫描,外汇与贵金属波动大、风险高,标注仅作概率参考,决策仍需结合水平与烛条分析。
唯一低成本办法是在 MetaTrader 5 可视化策略测试器回测,用速度和样本量换品质,尽管数据精度有限但足以证伪盲目信仰。
常见过滤包括峰高差阈值、时间间隔下限、以及和关键水平的重合度,具体参数应在回测中按品种与时间帧分别标定。