研究烛条分析技术(第一部分):检查现存形态·进阶篇
📘

研究烛条分析技术(第一部分):检查现存形态·进阶篇

第 2/3 篇

形态识别里的趋势门禁与偏移取样

这段逻辑干的事很直接:先卡趋势方向,再认 K 线类型,命中才累加计数器并往回跳几根取系数。倒锤头在 DOWN 趋势里计为 invert_hummer,锤头在 DOWN 里又算 hummer 多头模型——同样一根锤头,趋势上下文不同,归类槽位就分开了。 射击之星要求 cand1、cand2 两段都为 UPPER 且 cand1.close<=cand2.open,命中后 GetCategory 回退 4 根(i-4)取 shooting_star_coef;而前面几类只回退 3 根(i-3)。这个 3 与 4 的差值,意味着双星模型需要多一根确认背景。 多头吞噬的门槛更硬:两段都 DOWN,cand1 为阳、cand2 为阴,且 cand1.bodysize>cand2.bodysize,收破前开。实体必须吞得下才计 engulfing_bull_total,回退也是 i-4。把这些判断直接贴进 MT5 的 OnCalculate 里跑一遍,能看清自己的品种在 H1 上哪种形态计数跑得最快。外汇与贵金属波动剧烈,形态命中只代表概率倾斜,不等于方向锁死。

MQL5 / C++
if(cand2.trend==DOWN &&
      cand2.type==CAND_INVERT_HAMMER)
   {
    m_invert_hummer_total++;
    GetCategory(symbol,i-class="num">3,invert_hummer_coef);
   }
class=class="str">"cmt">//--- 吊挂,空头
if(cand2.trend==UPPER &&
    cand2.type==CAND_HAMMER)
   {
    m_handing_man_total++;
    GetCategory(symbol,i-class="num">3,handing_man_coef);
   }
class=class="str">"cmt">//--- 锤头,多头模型
if(cand2.trend==DOWN &&
    cand2.type==CAND_HAMMER)
   {
    m_hummer_total++;
    GetCategory(symbol,i-class="num">3,hummer_coef);
   }
class=class="str">"cmt">//---
class=class="str">"cmt">//--- 射击之星,空头模型
if(cand1.trend==UPPER && cand2.trend==UPPER &&
    cand2.type==CAND_INVERT_HAMMER && cand1.close<=cand2.open)
   {
    m_shooting_star_total++;
    GetCategory(symbol,i-class="num">4,shooting_star_coef);
   }
class=class="str">"cmt">//--- 吞噬,多头模型
if(cand1.trend==DOWN && cand1.bull && cand2.trend==DOWN && !cand2.bull &&
   cand1.bodysize>cand2.bodysize &&
   cand1.close>=cand2.open && cand1.open<cand2.close)
   {
    m_engulfing_bull_total++;
    GetCategory(symbol,i-class="num">4,engulfing_bull_coef);
   }
class=class="str">"cmt">//--- 吞噬,空头模型

◍ 吞没与孕线的代码判定分支

这段逻辑在两根烛条 cand1、cand2 上同时做趋势方向和实体关系的双重过滤,只有全部命中才计入对应形态总数。外汇与贵金属市场波动剧烈,这类形态信号仅代表概率倾向,实际触发后仍需结合仓位与止损。 先看看空头吞没分支:cand1 处于上行趋势且为阳线,cand2 同处上行趋势但为阴线,且 cand1 实体小于 cand2,cand1 的收盘不高于 cand2 开盘、开盘高于 cand2 收盘,此时 m_engulfing_bear_total 加一,并取 i-4 位置做分类统计。 Harami 交叉多头要求 cand2 下行趋势且为阴线,形态是长烛或光头光脚,cand1 为十字星且完全落在 cand2 实体内(收低于开、开不低于收),命中则 m_harami_cross_bull_total 加一。反向的 Harami 交叉空头把趋势与阴阳对调,十字星需反向包裹于 cand2 实体中。 普通 Harami 多头去掉十字星限制:cand1 下行趋势阳线、cand2 阴线且为长实体,cand1 实体更小并嵌在 cand2 实体内才计数;空头分支则交换趋势与阴阳,并沿用长实体前置条件。把下面代码直接丢进 MT5 脚本逐行跑,能验证每种形态的边界条件是否如预期。

MQL5 / C++
if(cand1.trend==UPPER && cand1.bull && cand2.trend==UPPER && !cand2.bull && class=class="str">"cmt">// 检查趋势方向和烛条方向
      cand1.bodysize<cand2.bodysize &&
      cand1.close<=cand2.open && cand1.open>cand2.close)
      {
      m_engulfing_bear_total++;
      GetCategory(symbol,i-class="num">4,engulfing_bear_coef);
      }
class=class="str">"cmt">//--- Harami 交叉,多头
if(cand2.trend==DOWN && !cand2.bull &&                                      class=class="str">"cmt">// 检查趋势方向和烛条方向
      (cand2.type==CAND_LONG || cand2.type==CAND_MARIBOZU) && cand1.type==CAND_DOJI && class=class="str">"cmt">// 检查 "长烛条" 的第一根烛条和十字星烛条
      cand1.close<cand2.open && cand1.open>=cand2.close)                                      class=class="str">"cmt">// 十字星在第一根烛条实体内
      {
      m_harami_cross_bull_total++;
      GetCategory(symbol,i-class="num">4,harami_cross_bull_coef);
      }
class=class="str">"cmt">//--- Harami 交叉,空头模型
if(cand2.trend==UPPER && cand2.bull &&                                      class=class="str">"cmt">// 检查趋势方向和烛条方向
      (cand2.type==CAND_LONG || cand2.type==CAND_MARIBOZU) && cand1.type==CAND_DOJI && class=class="str">"cmt">// 检查 "长烛条" 和十字星
      cand1.close>cand2.open && cand1.open<=cand2.close)                                      class=class="str">"cmt">// 十字星在第一根烛条实体内
      {
      m_harami_cross_bear_total++;
      GetCategory(symbol,i-class="num">4,harami_cross_bear_coef);
      }
class=class="str">"cmt">//--- Harami, 多头
if(cand1.trend==DOWN && cand1.bull && !cand2.bull &&                                      class=class="str">"cmt">// 检查趋势方向和烛条方向
      (cand2.type==CAND_LONG || cand2.type==CAND_MARIBOZU)  &&                              class=class="str">"cmt">// 检查 "长烛条" 的第一根烛条
      cand1.type!=CAND_DOJI && cand1.bodysize<cand2.bodysize &&                              class=class="str">"cmt">// 第二根烛条不是十字星,第一根烛条实体比第二根烛条实体更大
      cand1.close<cand2.open && cand1.open>=cand2.close)                                      class=class="str">"cmt">// 第二根烛条实体位于第一根烛条实体内
      {
      m_harami_bull_total++;
      GetCategory(symbol,i-class="num">4,harami_bull_coef);
      }
class=class="str">"cmt">//--- Harami, 空头
if(cand1.trend==UPPER && !cand1.bull && cand2.bull &&                              class=class="str">"cmt">// 检查趋势方向和烛条方向
      (cand2.type==CAND_LONG || cand2.type==CAND_MARIBOZU) &&                              class=class="str">"cmt">// 检查 "长烛条" 的第一根烛条

「吞没与星线的判定分支怎么写」

这段逻辑在蜡烛形态识别里专门处理几类经典反转:看跌孕线、十字星反转以及穿刺 / 乌云盖顶。每一类都先用趋势方向和蜡烛方向做初筛,再卡实体尺寸与位置关系,命中才累加计数器并调用 GetCategory 做盈利归类。 看跌孕线要求第二根不是十字星且实体小于前一根,同时第二根实体完全包在第一根实体内(cand1.close>cand2.open 且 cand1.open<=cand2.close)。十字星多头则要求趋势向下、前一根为长实体或光头光脚,后一根为 Doji 且开盘价不高于前收。 穿刺线(多头)和乌云盖顶(空头)都强制两根均为长实体或 Marubozu,差别在收评价相对前一根中部的位置:穿刺要求后收高于前的中部 (cand1.close>(cand2.close+cand2.open)/2),乌云则低于。所有分支在命中后都对 i-4 号 K 线调用 GetCategory 取系数,说明归类窗口相对信号柱往前推了 4 根。 外汇与贵金属品种上跑这套识别要留心:形态命中只是概率倾向反转,杠杆品种波动可能直接扫掉止损,实盘前建议在 MT5 策略测试器用历史数据核对计数器增量。

MQL5 / C++
 cand1.type!=CAND_DOJI && cand1.bodysize<cand2.bodysize && class=class="str">"cmt">// 第二根烛条不是十字星,第一根烛条实体比第二根烛条实体更大
 cand1.close>cand2.open && cand1.open<=cand2.close) class=class="str">"cmt">// 第二根烛条实体位于第一根烛条实体内
 {
 m_harami_bear_total++;
 GetCategory(symbol,i-class="num">4,harami_bear_coef);
 }
class=class="str">"cmt">//--- 十字星,多头
if(cand1.trend==DOWN && !cand2.bull && class=class="str">"cmt">// 检查趋势方向和烛条方向
 (cand2.type==CAND_LONG || cand2.type==CAND_MARIBOZU) && cand1.type==CAND_DOJI && class=class="str">"cmt">// 检查第一个 "长" 烛条和第二个十字星
 cand1.close<=cand2.open) class=class="str">"cmt">// 十字星的开盘价低于或等于第一根烛条的收盘价 
 {
 m_doji_star_bull_total++;
 GetCategory(symbol,i-class="num">4,doji_star_bull_coef);
 }
class=class="str">"cmt">//--- 十字星,空头
if(cand1.trend==UPPER && cand2.bull && class=class="str">"cmt">// 检查趋势方向和烛条方向
 (cand2.type==CAND_LONG || cand2.type==CAND_MARIBOZU) && cand1.type==CAND_DOJI && class=class="str">"cmt">// 检查第一个 "长" 烛条和第二个十字星
 cand1.open>=cand2.close) class=class="str">"cmt">//十字星的开盘价高于或等于第一根烛条的收盘价
 {
 m_doji_star_bear_total++;
 GetCategory(symbol,i-class="num">4,doji_star_bear_coef);
 }
class=class="str">"cmt">//--- 穿孔,多头模型
if(cand1.trend==DOWN && cand1.bull && !cand2.bull && class=class="str">"cmt">// 检查趋势方向和烛条方向
 (cand1.type==CAND_LONG || cand1.type==CAND_MARIBOZU) && (cand2.type==CAND_LONG || cand2.type==CAND_MARIBOZU) && class=class="str">"cmt">// 检查 "长" 烛条
 cand1.close>(cand2.close+cand2.open)/class="num">2 && class=class="str">"cmt">// 第二根烛条收盘价高于第一根烛条的中部
 cand2.open>cand1.close && cand2.close>=cand1.open)
 {
 m_piercing_line_total++;
 GetCategory(symbol,i-class="num">4,piercing_line_coef);
 }
class=class="str">"cmt">//--- 乌云盖顶,空头
if(cand1.trend==UPPER && !cand1.bull && cand2.bull && class=class="str">"cmt">// 检查趋势方向和烛条方向
 (cand1.type==CAND_LONG || cand1.type==CAND_MARIBOZU) && (cand2.type==CAND_LONG || cand2.type==CAND_MARIBOZU) && class=class="str">"cmt">// 检查 "长" 烛条
 cand1.close<(cand2.close+cand2.open)/class="num">2 && class=class="str">"cmt">// 第二根烛条收盘价低于第一根烛条的中部
 cand1.close<cand2.open && cand2.close<=cand1.open)
 {
 m_dark_cloud_cover_total++;
 GetCategory(symbol,i-class="num">4,dark_cloud_cover_coef);
 }
 }

用前三根K线给趋势分类并算效率系数

这段逻辑干两件事:先把当前收盘价和前 3 根 K 线的高/低做比较,按阈值把走势塞进 6 个类别数组;再拿这些计数去算上行、下行的占比和加权系数。 GetCategory 里先 CopyRates 取 4 根(索引 0 是当前,1~3 是前三根)。若拷贝不足 4 根直接返 false。close0 取 rt[0].close,high1~high3 和 low1~low3 分别对应前三根的高低点,再用 SymbolInfoDouble 拿 SYMBOL_POINT 做最小变动单位。 分类规则很直白:上行看 (highN - close0)/point 是否 ≥ m_threshold_value,满足则 category[0/1/2] 对应加一;下行看 (close0 - lowN)/point,满足则 category[3/4/5] 加一。也就是说,只比「离当前收盘有多远」,不要求三根严格递升。 CoefCalculation 接着汇总:sum1 是 category[0..2] 之和(上行计数),sum2 是 category[3..5] 之和(下行计数)。p1、p2 是各自占 found 样本数的百分比,保留两位小数;k1、k2 是用 m_k1/m_k2/m_k3 对近、中、远三根分别加权后除以总样本,保留三位。外汇和贵金属波动跳点差异大,m_threshold_value 用裸点数会随品种点值失真,实盘前建议在 MT5 里按品种重算 point 再定阈值。 想验证就直接把这两段塞进 EA 的 CProgram 类,m_threshold_value 先设 20 点,跑 EURUSD 的 M15 看 category 分布;若发现 category[0] 长期为 0,说明阈值相对近根波动偏大,往下调到 10~15 可能更灵敏。

MQL5 / C++
class="type">bool CProgram::GetCategory(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">int shift,class="type">int &category[])
  {
   class="type">MqlRates rt[];
   class="type">int copied=CopyRates(symbol,m_timeframe,shift,class="num">4,rt);
   class="type">int rating=class="num">0;
class=class="str">"cmt">//--- 获取前一根烛条的详细信息
   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)>=m_threshold_value)
     {
      category[class="num">0]++;
     }
   else if((class="type">int)((high2-close0)/point)>=m_threshold_value)
     {
      category[class="num">1]++;
     }
   else if((class="type">int)((high3-close0)/point)>=m_threshold_value)
     {
      category[class="num">2]++;
     }
class=class="str">"cmt">//--- 检查它是否为下行趋势
   if((class="type">int)((close0-low1)/point)>=m_threshold_value)
     {
      category[class="num">3]++;
     }
   else if((class="type">int)((close0-low2)/point)>=m_threshold_value)
     {
      category[class="num">4]++;
     }
   else if((class="type">int)((close0-low3)/point)>=m_threshold_value)
     {
      category[class="num">5]++;
     }
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 计算效率评估系数                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CoefCalculation(class="kw">const class="type">int row,class="type">int &category[],class="type">int found)
  {
   class="type">class="kw">double p1,p2,k1,k2;
   class="type">int sum1=class="num">0,sum2=class="num">0;
   for(class="type">int i=class="num">0;i<class="num">3;i++)
     {
      sum1+=category[i];
      sum2+=category[i+class="num">3];
     }
class=class="str">"cmt">//---
   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*category[class="num">0]+m_k2*category[class="num">1]+m_k3*category[class="num">2])/found,class="num">3):class="num">0;
   k2=(found>class="num">0)?NormalizeDouble((m_k1*category[class="num">3]+m_k2*category[class="num">4]+m_k3*category[class="num">5])/found,class="num">3):class="num">0;
   m_table.SetValue(class="num">3,row,(class="type">class="kw">string)p1,class="num">2);
   m_table.SetValue(class="num">4,row,(class="type">class="kw">string)p2,class="num">2);
   m_table.SetValue(class="num">5,row,(class="type">class="kw">string)k1,class="num">2);

◍ 把计算结果刷进表格控件

这段收尾代码负责把算好的 k2 值写回 GUI 表格的第 6 列,并强制重绘滚动条与单元格。 调用 SetValue(6,row,(string)k2,2) 时,第 4 个参数 2 代表右对齐格式,适合数值类字段直接落表。 随后三行 Update(true) 分别刷新主表、垂直滚动条与水平滚动条指针;在 MT5 里若漏掉后两个,长表在拖动时可能残留旧帧。 最后 return(true) 表示该刷新函数执行成功,调用方可以继续下一行数据处理。

MQL5 / C++
  m_table.SetValue(class="num">6,row,(class="type">class="kw">string)k2,class="num">2);
class=class="str">"cmt">//--- 更新表格
  m_table.Update(true);
  m_table.GetScrollVPointer().Update(true);
  m_table.GetScrollHPointer().Update(true);
  class="kw">return(true);
  }

常见问题

取最近三根烛条,比较它们的收盘与开盘关系并算效率系数(同向收阳/阴占比),系数高于阈值才认定趋势成立,避免单根误判。
吞没看后一根实体完全包住前一根实体且方向相反;孕线看后一根实体被前一根包裹。用独立if分支分别判定,互不加else以免漏信号。
可以,小布在对应品种页直接标注吞没、孕线、星线并给出趋势门禁状态,你只需打开看盘页核对,不用手动跑脚本。
星线要求实体极小且影线明显,与吞没的实体包裹逻辑冲突,合并写会互相屏蔽信号,必须独立if分支单独输出。
检查控件列索引是否越界、刷新函数是否放在OnCalculate末尾,以及数组是否用了PlotIndexSetInteger绑定了表格而非图表。