研究烛条分析技术(第一部分):检查现存形态·进阶篇
形态识别里的趋势门禁与偏移取样
这段逻辑干的事很直接:先卡趋势方向,再认 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 上哪种形态计数跑得最快。外汇与贵金属波动剧烈,形态命中只代表概率倾斜,不等于方向锁死。
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 脚本逐行跑,能验证每种形态的边界条件是否如预期。
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 策略测试器用历史数据核对计数器增量。
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 可能更灵敏。
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) 表示该刷新函数执行成功,调用方可以继续下一行数据处理。
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); }