研究烛条分析技术(第四部分):形态分析器的更新和补充·综合运用
🕯️

研究烛条分析技术(第四部分):形态分析器的更新和补充·综合运用

(3/3)· 从界面重构到多时间帧筛选,一套能直接生成指标和EA的形态实验室如何落地

含代码示例实战向 第 3/3 篇
很多交易者装了形态分析器却只拿它看单周期信号,浪费了多时间帧联合筛选的能力。新版本把日期范围、趋势阈值和点数参数前移,不会用这些开关等于只开了半套系统。

「锤子线计数与烛条类型抓取的实现细节」

在形态扫描逻辑里,锤子线(Hammer)的判定直接走 cand1.type==CAND_HAMMER 分支,命中后对该时间框架的计数器 m_hummer_total[j] 自增,并调用 GetCategory 传入偏移量 3、系数与阈值,把分类结果写回表格行。 当扫描总量 count>0 时,代码会删掉占位的总计行 m_total_row,随后 m_table1.Update(true) 连同纵向滚动指针一起刷新;若 count==0 则按 m_lang_index 弹俄文或英文报错框,提示未选形态。这套交互说明面板渲染必须依赖实际命中数据,空跑会直接拦在 MessageBox。 GetCandleType 负责把某根烛条的结构捞出来:先用 aver_period=5 做均值周期,按日历与时间编辑框拼出 start,再叠 PeriodSeconds(timeframe)*shift 做偏移。CopyRates 要求至少拷贝 6 根(aver_period+1),不足就 Print 报错并返回 false,因此低于 6 根的历史深度在该函数里必然拿不到烛体。 实盘接这段代码时,把 aver_period 调到 10 以上,能降低短周期噪点导致的锤子误计;外汇与贵金属波动大、滑点高,形态命中只代表概率倾向,须自测 MT5 历史深度是否撑得住。

MQL5 / C++
if(cand1.type==CAND_HAMMER) class=class="str">"cmt">// 检查 "锤子"
  {
   m_hummer_total[j]++;
   GetCategory(symbol,i+class="num">3,hummer_coef[j],m_cur_timeframes1[j],m_threshold_value1);
  }
 }
AddRow(m_table1,"Hammer",hummer_coef[j],m_hummer_total[j],m_cur_timeframes1[j]);
}
}
class=class="str">"cmt">//---
if(count>class="num">0)
 {
 class=class="str">"cmt">//---
 m_table1.DeleteRow(m_total_row);
 class=class="str">"cmt">//--- 刷新表格
 m_table1.Update(true);
 m_table1.GetScrollVPointer().Update(true);
 }
 else
 {
  if(m_lang_index==class="num">0)
    MessageBox("Вы не выбрали паттерн!","Ошибка",MB_OK);
  else if(m_lang_index==class="num">1)
    MessageBox("You have not chosen a pattern!","Error",MB_OK);
 }
 class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 烛条类型识别                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::GetCandleType(class="kw">const class="type">class="kw">string symbol,CANDLE_STRUCTURE &res,ENUM_TIMEFRAMES timeframe,class="kw">const class="type">int shift)
  {
   class="type">MqlRates rt[];
   class="type">int aver_period=class="num">5;
   class="type">class="kw">double aver=class="num">0.0;
   class="type">class="kw">datetime start=TimeCurrent();
   SymbolSelect(symbol,true);
   class=class="str">"cmt">//--- 从范围中获取开始日期,具体取决于形态的类型
   if(m_table_number==class="num">1)
     start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(class="type">class="kw">string)m_time_edit1.GetHours()+":"+(class="type">class="kw">string)m_time_edit1.GetMinutes()+":class="num">00");
   else if(m_table_number==class="num">2)
     start=StringToTime(TimeToString(m_calendar3.SelectedDate(),TIME_DATE)+" "+(class="type">class="kw">string)m_time_edit3.GetHours()+":"+(class="type">class="kw">string)m_time_edit3.GetMinutes()+":class="num">00");
class=class="str">"cmt">//--- 偏移日期 
   start+=PeriodSeconds(timeframe)*shift;
   class="type">int copied=CopyRates(symbol,timeframe,start,aver_period+class="num">1,rt);
   if(copied<class="num">6)
     {
      Print(start,": Not enough data for calculation — ",GetLastError());
     }
class=class="str">"cmt">//--- 获取前一根烛条的详细信息
   if(copied<aver_period)
     class="kw">return(class="kw">false);
class=class="str">"cmt">//---
   res.open=rt[aver_period].open;
   res.high=rt[aver_period].high;
   res.low=rt[aver_period].low;
   res.close=rt[aver_period].close;
class=class="str">"cmt">//--- 判断趋势方向
   for(class="type">int i=class="num">0;i<aver_period;i++)

◍ 用代码给烛条自动贴标签

这段逻辑干的事很直接:先拿平均收盘价定趋势方向,再按实体和影线比例把当前烛条归到具体形态里。平均价低于收盘价判为 UPPER(偏多),高于则 DOWN(偏空),相等为 FLAT;res.bull 用开盘小于收盘判定多头烛条,bodysize 取实体绝对值。 之前 aver_period 根烛条的平均实体高度 sum 是关键基准。若 bodysize 大于 sum*m_long_coef 且为多头,标 CAND_LONG_BULL;小于 sum*m_short_coef 则 CAND_SHORT_BULL,空头同理。实体占整根幅度比 bodysize<HL*m_doji_coef 判十字星,影线都极短且无零实体则是 marubozu。 锤子与逆锤子靠上下影线不对称区分:下影大于实体*m_hummer_coef2 且上影小于*m_hummer_coef1 为 HAMMER,反过来是 INVERT_HAMMER。短实体且上下影都超过实体*m_spin_coef 归为 SPIN_TOP(纺锤)。 GetCategory 里用 CopyRates 取从 start 往后 4 根数据,start 由日历与时间编辑框拼出再叠加 PeriodSeconds(timeframe)*shift。开 MT5 把 m_long_coef、m_doji_coef 等系数调小一点,形态识别会明显更敏感,外汇与贵金属波动大,误判概率会随参数收紧而上升,属高风险验证。

MQL5 / C++
  aver+=rt[i].close;
  aver/=aver_period;
  if(aver<res.close)
    res.trend=UPPER;
  if(aver>res.close)
    res.trend=DOWN;
  if(aver==res.close)
    res.trend=FLAT;
class=class="str">"cmt">//--- 判断它是多头还是空头烛条
  res.bull=res.open<res.close;
class=class="str">"cmt">//--- 获得烛条实体的绝对大小
  res.bodysize=MathAbs(res.open-res.close);
class=class="str">"cmt">//--- 获取阴影的大小
  class="type">class="kw">double shade_low=res.close-res.low;
  class="type">class="kw">double shade_high=res.high-res.open;
  if(res.bull)
    {
      shade_low=res.open-res.low;
      shade_high=res.high-res.close;
    }
  class="type">class="kw">double HL=res.high-res.low;
class=class="str">"cmt">//--- 计算之前烛条的平均实体高度
  class="type">class="kw">double sum=class="num">0;
  for(class="type">int i=class="num">1; i<=aver_period; i++)
    sum+=MathAbs(rt[i].open-rt[i].close);
  sum/=aver_period;
class=class="str">"cmt">//--- 判断烛条类型  
  res.type=CAND_NONE;
class=class="str">"cmt">//--- 长实体 
  if(res.bodysize>sum*m_long_coef && res.bull)
    res.type=CAND_LONG_BULL;
class=class="str">"cmt">//--- 短实体 
  if(res.bodysize<sum*m_short_coef && res.bull)
    res.type=CAND_SHORT_BULL;
class=class="str">"cmt">//--- 长实体空头
  if(res.bodysize>sum*m_long_coef && !res.bull)
    res.type=CAND_LONG_BEAR;
class=class="str">"cmt">//--- 短实体空头
  if(res.bodysize<sum*m_short_coef && !res.bull)
    res.type=CAND_SHORT_BEAR;
class=class="str">"cmt">//--- 十字星
  if(res.bodysize<HL*m_doji_coef)
    res.type=CAND_DOJI;
class=class="str">"cmt">//--- marubozu
  if((shade_low<res.bodysize*m_maribozu_coef && shade_high<res.bodysize*m_maribozu_coef) && res.bodysize>class="num">0)
    res.type=CAND_MARIBOZU;
class=class="str">"cmt">//--- 锤子
  if(shade_low>res.bodysize*m_hummer_coef2 && shade_high<res.bodysize*m_hummer_coef1)
    res.type=CAND_HAMMER;
class=class="str">"cmt">//--- 逆锤子
  if(shade_low<res.bodysize*m_hummer_coef1 && shade_high>res.bodysize*m_hummer_coef2)
    res.type=CAND_INVERT_HAMMER;
class=class="str">"cmt">//--- 顶部纺锤
  if((res.type==CAND_SHORT_BULL || res.type==CAND_SHORT_BEAR) && shade_low>res.bodysize*m_spin_coef && shade_high>res.bodysize*m_spin_coef)
    res.type=CAND_SPIN_TOP;
class=class="str">"cmt">//---
  ArrayFree(rt);
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 判定利润类别                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::GetCategory(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">int shift,RATING_SET &rate,ENUM_TIMEFRAMES timeframe,class="type">int threshold)
  {
  class="type">MqlRates rt[];
  class="type">class="kw">datetime start=TimeCurrent();
  if(m_table_number==class="num">1)
    start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(class="type">class="kw">string)m_time_edit1.GetHours()+":"+(class="type">class="kw">string)m_time_edit1.GetMinutes()+":class="num">00");
  else if(m_table_number==class="num">2)
    start=StringToTime(TimeToString(m_calendar3.SelectedDate(),TIME_DATE)+" "+(class="type">class="kw">string)m_time_edit3.GetHours()+":"+(class="type">class="kw">string)m_time_edit3.GetMinutes()+":class="num">00");
  start+=PeriodSeconds(timeframe)*shift;
  class="type">int copied=CopyRates(symbol,timeframe,start,class="num">4,rt);

三根烛条的距离阈值怎么判趋势

这段逻辑先卡住数据完整性:若复制到的烛条数不足 4 根,直接返回 false,后续计算根本不会跑。也就是说,任何小于 4 根的历史样本在统计里会被直接丢弃,回测时若图表未加载足够 Bar 数,结果会偏空。 接着把当前收盘 rt[0].close 与前面三根的高、低分别取出,并拿到品种 point 值。趋势判定完全靠「距离 / point」取整后是否 ≥ threshold:比如 high1 距收盘够阈值就记 a_uptrend++,否则看 high2、high3;下行对称处理。threshold 是外部传入参数,EURUSD 的 point 通常是 1e-5,设 20 即要求 20 点以上空间才认趋势。 统计落表时,sum1 / sum2 是三类上行、下行计数之和,p1、p2 用 sum/found*100 并 NormalizeDouble 到两位小数,得出该形态出现后上行、下行的历史百分比。k1、k2 则按 m_k1~m_k3 权重对 a/b/c 类分别加权再除以 found,保留三位小数,权重倾斜会明显改变评分。 表格翻页时若 row 超出 total_patterns,用 row - total_patterns*Floor(row/total_patterns) 回绕到组合数组下标,避免越界。开 MT5 把 threshold 从 20 调到 50,同一品种形态命中率可能掉一半,但噪音信号会减少。外汇与贵金属杠杆高,历史统计只代表概率倾向,实盘须自担风险。

MQL5 / C++
if(copied<class="num">4)
  {
    class="kw">return(class="kw">false);
  }
  class="type">class="kw">double high1,high2,high3,low1,low2,low3,close0,point;
  close0=rt[class="num">0].close;
  high1=rt[class="num">1].high;
  high2=rt[class="num">2].high;
  high3=rt[class="num">3].high;
  low1=rt[class="num">1].low;
  low2=rt[class="num">2].low;
  low3=rt[class="num">3].low;
  if(!SymbolInfoDouble(symbol,SYMBOL_POINT,point))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 检查它是否为上行趋势
  if((class="type">int)((high1-close0)/point)>=threshold)
    {
      rate.a_uptrend++;
    }
  else if((class="type">int)((high2-close0)/point)>=threshold)
    {
      rate.b_uptrend++;
    }
  else if((class="type">int)((high3-close0)/point)>=threshold)
    {
      rate.c_uptrend++;
    }
class=class="str">"cmt">//--- 检查它是否为下行趋势
  if((class="type">int)((close0-low1)/point)>=threshold)
    {
      rate.a_dntrend++;
    }
  else if((class="type">int)((close0-low2)/point)>=threshold)
    {
      rate.b_dntrend++;
    }
  else if((class="type">int)((close0-low3)/point)>=threshold)
    {
      rate.c_dntrend++;
    }
  class="kw">return(true);
}
class="type">void CProgram::AddRow(CTable &table,class="type">class="kw">string pattern_name,RATING_SET &rate,class="type">int found,ENUM_TIMEFRAMES timeframe)
  {
   class="type">int row=m_total_row;
   class="type">int total_patterns=ArraySize(m_total_combination);
   class="type">class="kw">double p1,p2,k1,k2;
   class="type">int sum1=class="num">0,sum2=class="num">0;
   sum1=rate.a_uptrend+rate.b_uptrend+rate.c_uptrend;
   sum2=rate.a_dntrend+rate.b_dntrend+rate.c_dntrend;
   p1=(found>class="num">0)?NormalizeDouble((class="type">class="kw">double)sum1/found*class="num">100,class="num">2):class="num">0;
   p2=(found>class="num">0)?NormalizeDouble((class="type">class="kw">double)sum2/found*class="num">100,class="num">2):class="num">0;
   k1=(found>class="num">0)?NormalizeDouble((m_k1*rate.a_uptrend+m_k2*rate.b_uptrend+m_k3*rate.c_uptrend)/found,class="num">3):class="num">0;
   k2=(found>class="num">0)?NormalizeDouble((m_k1*rate.a_dntrend+m_k2*rate.b_dntrend+m_k3*rate.c_dntrend)/found,class="num">3):class="num">0;
   table.AddRow(row);
   if(m_table_number==class="num">1)
      table.SetValue(class="num">0,row,pattern_name);
   else if(m_table_number==class="num">2)
     {
      if(row<total_patterns)
         table.SetValue(class="num">0,row,m_total_combination[row]);
      else if(row>=total_patterns)
        {
         class="type">int i=row-class="type">int(total_patterns*MathFloor(class="type">class="kw">double(row)/total_patterns));
         table.SetValue(class="num">0,row,m_total_combination[i]);
        }
     }

「把组合字符串还原成蜡烛结构」

在扫描完所有形态组合后,GetPatternType 负责把字符串形式的组合还原成可供比对的蜡烛结构数组。它先按 total_patterns 的数量给 cand1/cand2/cand3 及计数数组做 ArrayResize,保证内存一次就位,避免循环里反复扩容。 组合字符串里带方括号,例如 "[3,7]",函数用 StringReplace 把 [ 和 ] 清掉,再按逗号切到 elements 数组。m_pattern_size 决定切几段:为 1 时直接把整串转整型喂给 IndexToPatternType;为 2 或 3 时则分段还原到对应 cand 结构。 还原完毕后调用 GetTimeframes 拿到当前生效的周期列表,用 ArraySize(m_cur_timeframes2) 取总数。若 total<1 说明没加载到任何周期,后续比对直接失去基准——这时若 m_lang_index==0 会走英文提示分支,实盘前务必确认周期数组非空。外汇与贵金属波动受杠杆放大,周期缺失可能导致信号漏判,属高风险操作。

MQL5 / C++
  }
  table.SetValue(class="num">1,row,(class="type">class="kw">string)found);
  table.SetValue(class="num">2,row,TimeframeToString(timeframe));
  table.SetValue(class="num">3,row,(class="type">class="kw">string)p1,class="num">2);
  table.SetValue(class="num">4,row,(class="type">class="kw">string)p2,class="num">2);
  table.SetValue(class="num">5,row,(class="type">class="kw">string)k1,class="num">2);
  table.SetValue(class="num">6,row,(class="type">class="kw">string)k2,class="num">2);
  ZeroMemory(rate);
  m_total_row++;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool                GetPatternType(class="kw">const class="type">class="kw">string symbol,class="type">class="kw">string &total_combination[]);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::GetPatternType(class="kw">const class="type">class="kw">string symbol,class="type">class="kw">string &total_combination[])
  {
  CANDLE_STRUCTURE cand1[],cand2[],cand3[],cur_cand,prev_cand,prev_cand2;
  RATING_SET ratings;
  class="type">int total_patterns,m_pattern_total[];
  class="type">class="kw">string elements[];
class=class="str">"cmt">//---
  total_patterns=ArraySize(total_combination);
  ArrayResize(cand1,total_patterns);
  ArrayResize(cand2,total_patterns);
  ArrayResize(cand3,total_patterns);
  ArrayResize(m_pattern_total,total_patterns);
  ArrayResize(elements,m_pattern_size);
class=class="str">"cmt">//---
  for(class="type">int i=class="num">0;i<total_patterns;i++)
    {
     StringReplace(total_combination[i],"[","");
     StringReplace(total_combination[i],"]","");
     if(m_pattern_size>class="num">1)
       {
        class="type">class="kw">ushort sep=StringGetCharacter(",",class="num">0);
        StringSplit(total_combination[i],sep,elements);
       }
     m_pattern_total[i]=class="num">0;
     if(m_pattern_size==class="num">1)
        IndexToPatternType(cand1[i],(class="type">int)total_combination[i]);
     else if(m_pattern_size==class="num">2)
       {
        IndexToPatternType(cand1[i],(class="type">int)elements[class="num">0]);
        IndexToPatternType(cand2[i],(class="type">int)elements[class="num">1]);
       }
     else if(m_pattern_size==class="num">3)
       {
        IndexToPatternType(cand1[i],(class="type">int)elements[class="num">0]);
        IndexToPatternType(cand2[i],(class="type">int)elements[class="num">1]);
        IndexToPatternType(cand3[i],(class="type">int)elements[class="num">2]);
       }
    }
class=class="str">"cmt">//---
  GetTimeframes(m_timeframes1,m_cur_timeframes2);
  class="type">int total=ArraySize(m_cur_timeframes2);
  if(total<class="num">1)
    {
     if(m_lang_index==class="num">0)

◍ 多周期下形态命中的批量扫描逻辑

这段逻辑跑在用户点了「计算」之后,先清掉旧表:m_table2.DeleteAllRows() 把上一次结果全删,m_total_row 归零、m_table_number 固定为 2,保证每次重算不从脏数据起步。 时间窗由界面上的两个日历加两个时间编辑框拼出来:start 和 end 都用 StringToTime(TimeToString(SelectedDate(),TIME_DATE)+" HH:MM:00") 合成。若 start>end 或 end>TimeCurrent(),直接弹窗报错并返回 false——这是防止拿未来时间或倒置区间去扫历史。 当 m_pattern_size==1 时进入单形态扫描:外层按 m_cur_timeframes2 里的每个周期,CopyRates 拉取该周期在 [start,end] 内的所有 K 线(copied 根)。内层再套两层循环——total_patterns 个形态模板 × copied 根 K 线,对每根调用 GetCandleType 取类型与多空方向。 命中条件是 cur_cand.type 与 cand1[j].type 相同且 bull 标志一致,此时 m_pattern_total[j] 自增,并调用 GetCategory(symbol,k+3,ratings,...,m_threshold_value2) 去统计该形态后第 3 根起的分类评级。外汇与贵金属市场的高波动特性下,这种跨周期命中数只能作为概率参考,不构成方向判断。 让小布替你跑这套 把 m_threshold_value2 从默认阈值调小 20%,同区间同形态的 m_pattern_total 往往明显上升,可借此观察阈值对稀疏形态的灵敏度。

MQL5 / C++
      MessageBox("Вы не выбрали рабочий таймфрейм!","Ошибка",MB_OK);
      else if(m_lang_index==class="num">1)
         MessageBox("You have not selected a working timeframe!","Error",MB_OK);
      class="kw">return(class="kw">false);
   }
   m_total_row=class="num">0;
   m_table_number=class="num">2;
class=class="str">"cmt">//--- 删除所有行
   m_table2.DeleteAllRows();
class=class="str">"cmt">//---
   class="type">class="kw">datetime start=StringToTime(TimeToString(m_calendar3.SelectedDate(),TIME_DATE)+" "+(class="type">class="kw">string)m_time_edit3.GetHours()+":"+(class="type">class="kw">string)m_time_edit3.GetMinutes()+":class="num">00");
   class="type">class="kw">datetime end=StringToTime(TimeToString(m_calendar4.SelectedDate(),TIME_DATE)+" "+(class="type">class="kw">string)m_time_edit4.GetHours()+":"+(class="type">class="kw">string)m_time_edit4.GetMinutes()+":class="num">00");
class=class="str">"cmt">//---
   if(start>end || end>TimeCurrent())
      {
         if(m_lang_index==class="num">0)
            MessageBox("Неправильно выбран диапазон дат!","Ошибка",MB_OK);
         else if(m_lang_index==class="num">1)
            MessageBox("Incorrect date range selected!","Error",MB_OK);
         class="kw">return(class="kw">false);
      }
class=class="str">"cmt">//---
   if(m_pattern_size==class="num">1)
      {
         ZeroMemory(cur_cand);
         class=class="str">"cmt">//--- 按时间帧计算
         for(class="type">int i=class="num">0;i<total;i++)
            {
               class="type">MqlRates rt[];
               ZeroMemory(rt);
               ZeroMemory(ratings);
               class="type">int copied=CopyRates(symbol,m_cur_timeframes2[i],start,end,rt);
               class=class="str">"cmt">//--- 按形态计算
               for(class="type">int j=class="num">0;j<total_patterns;j++)
                  {
                     class=class="str">"cmt">//--- 按日期范围计算
                     for(class="type">int k=class="num">0;k<copied;k++)
                        {
                           class=class="str">"cmt">//--- 计算当前烛条类型
                           GetCandleType(symbol,cur_cand,m_cur_timeframes2[i],k);      class=class="str">"cmt">// current candlestick
                           class=class="str">"cmt">//---
                           if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull)
                              {
                                 m_pattern_total[j]++;
                                 GetCategory(symbol,k+class="num">3,ratings,m_cur_timeframes2[i],m_threshold_value2);
                              }
                        }
                    }
            }
      }

三K线形态的双层遍历写法

当 m_pattern_size 设为 3 时,引擎要同时比对当前、前一根、前两根三根烛条的类型与多空属性,因此比双 K 线版本多声明了一个 prev_cand2 缓冲区。 外层先按时间帧循环,用 CopyRates 把指定 symbol 在 m_cur_timeframes2[i] 上从 start 到 end 的行情拉进 rt 数组,copied 返回实际拷贝根数;若 copied 为 0 则内层不会触发任何计数。 内层对每个形态 j 再遍历 k,调用 GetCandleType 分别填充 cur_cand(k)、prev_cand(k+1)、prev_cand2(k+2),只有三根属性同时匹配 cand1/cand2/cand3 的描述,m_pattern_total[j] 才加一,并取 k+5 位置做 GetCategory 分类评级。 这种三层 for 在 5 个时间帧 × 20 个形态 × 3000 根 K 线规模下,单次扫描开销可能达到百万级比较,建议先在策略测试器用 EURUSD 的 H1 小样本跑通再放大。外汇与贵金属杠杆品种波动剧烈,形态统计仅反映历史概率,实盘须自担风险。

MQL5 / C++
else if(m_pattern_size==class="num">3)
  {
   ZeroMemory(cur_cand);
   ZeroMemory(prev_cand);
   ZeroMemory(prev_cand2);
   class=class="str">"cmt">//--- 按时间帧计算
   for(class="type">int i=class="num">0;i<total;i++)
     {
      class="type">MqlRates rt[];
      ZeroMemory(ratings);
      class="type">int copied=CopyRates(symbol,m_cur_timeframes2[i],start,end,rt);
      class=class="str">"cmt">//--- 按形态计算
      for(class="type">int j=class="num">0;j<total_patterns;j++)
        {

「三烛组合在指定周期上的批量匹配」

这段逻辑跑在按日期范围回看的历史数据上,外层已通过 CopyRates 拿到 copied 根烛条,k 从 0 循环到 copied-1,逐根当作「当前烛」去比对。 每次循环先调三次 GetCandleType:k+2 是往前第二根、k+1 是往前一根、k 是当前根,分别填进 prev_cand2、prev_cand、cur_cand 三个结构。注意偏移写死,意味着模式识别只认连续三根的相对位置关系。 匹配条件要求三根烛的类型与多空方向同时等于预设的 cand1[j]、cand2[j]、cand3[j]。命中后 m_pattern_total[j] 自增,并调 GetCategory(symbol,k+5,...) 去统计该模式出现后第 5 根(即未来 5 根偏移)的分类表现,m_threshold_value2 控制归类阈值。 遍历完当前时间帧后 AddRow 把该帧的统计行写进 m_table2,并清零计数器;全部帧跑完删掉预留的空行再 Update 刷新。外汇与贵金属波动受杠杆与消息影响大,这类历史命中率仅代表样本内概率,实盘需自行在 MT5 用策略测试器验算。

MQL5 / C++
   class=class="str">"cmt">//--- 按日期范围计算
   for(class="type">int k=class="num">0;k<copied;k++)
     {
      class=class="str">"cmt">//--- 计算当前烛条类型
      GetCandleType(symbol,prev_cand2,m_cur_timeframes2[i],k+class="num">2);      class=class="str">"cmt">// 之前的烛条
      GetCandleType(symbol,prev_cand,m_cur_timeframes2[i],k+class="num">1);       class=class="str">"cmt">// 之前的烛条
      GetCandleType(symbol,cur_cand,m_cur_timeframes2[i],k);          class=class="str">"cmt">// 当前的烛条
      class=class="str">"cmt">//---
      if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull &&
         prev_cand.type==cand2[j].type && prev_cand.bull==cand2[j].bull &&
         prev_cand2.type==cand3[j].type && prev_cand2.bull==cand3[j].bull)
        {
         m_pattern_total[j]++;
         GetCategory(symbol,k+class="num">5,ratings,m_cur_timeframes2[i],m_threshold_value2);
        }
     }
   AddRow(m_table2,"",ratings,m_pattern_total[j],m_cur_timeframes2[i]);
   m_pattern_total[j]=class="num">0;
     }
   }
 }
class=class="str">"cmt">//---
 m_table2.DeleteRow(m_total_row);
class=class="str">"cmt">//--- 刷新表格
 m_table2.Update(true);
 m_table2.GetScrollVPointer().Update(true);
 class="kw">return(true);
}

◍ 把工具栏装进你的终端根目录

这套烛条形态分析工具以 MQL5.zip(495.94 KB)打包,内含 PatternAnalyzer.mq5 图形界面工具栏,以及 MainWindow.mqh、Program.mqh 两个代码库。部署时只需把压缩包里的 MQL5 文件夹整体丢进终端根目录,不必手动归位各子目录。 在 MT5 里按 Ctrl+Shift+D 可直接调出终端根目录窗口,比从菜单层层点开更快。有用户反馈在调试模式删掉 Combobox 相关代码才能跑通,说明 GUI 依赖在不同 build 下可能冲突,装之前先确认终端版本。 外汇与贵金属行情受杠杆和跳空影响,这类形态工具只负责把肉眼难辨的 K 线结构标出来,信号出现后仍需你自判胜率,别当成下单指令。 真要验证效果,下载 ZIP 解压后覆盖进根目录,加载 PatternAnalyzer 看工具栏能否正常弹出,比读十遍文档都实在。

让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到多周期形态评分,不必自己手动切时间帧比对。把重复劳动交给小布,你专注决策。

常见问题

旧版选卡在顶部占用横向空间且无法扩展,左侧垂直排列释放了右上区域,能加更多功能分区并提升可读性。
日期范围允许精确框选某段行情做形态回测,而不是只能从当前柱向前数固定根数,更适配事件驱动类验证。
该参数控制形态识别对趋势过滤的敏感度,前置后可在分析时即时调参,不必来回切 Settings 选项卡。
可以,小布盯盘品种页已集成多时间帧形态诊断,自动输出评分与阈值参考,省去本地部署终端的麻烦。
前文封装的函数库可把自定义形态直接编译成指标或EA,本篇综合运用就是把界面筛选结果与该函数库对接导出。