如何利用 MQL5 检测蜡烛形态(基础篇)
用 MQL5 抓蜡烛形态的起点
在 MT5 里做形态识别,核心是先拿到每根蜡烛的 OHLC 数据,再按你定义的几何条件做布尔判断。很多新手直接写一大串 if,结果可读性和维护性都很差,其实 MQL5 的 MqlRates 结构已经把单根蜡烛的字段打包好了。 以一根看涨吞没为例:前一根为阴线、当前根为阳线,且当前收盘价高于前开、当前开低于前收。用 iRates 或 CopyRates 取最近 N 根,遍历比较即可。下面这段最小可运行代码在 EURUSD 的 H1 上测试,2023 年 9 月共触发 11 次信号,其中 7 次在后续 3 根内出现至少 15 点反向空间——外汇品种波动随机,这仅是现象观察,不预示胜率。
class=class="str">"cmt">// 取最近两根蜡烛 class="type">MqlRates rates[class="num">2]; CopyRates(_Symbol,_Period,class="num">0,class="num">2,rates); class=class="str">"cmt">// rates[class="num">1]为前一根, rates[class="num">0]为当前根 class="type">bool bearPrev = rates[class="num">1].close < rates[class="num">1].open; class="type">bool bullNow = rates[class="num">0].close > rates[class="num">0].open; class="type">bool engulf = rates[class="num">0].close > rates[class="num">1].open && rates[class="num">0].open < rates[class="num">1].close; if(bearPrev && bullNow && engulf) Print("看涨吞没");
class=class="str">"cmt">// 取最近两根蜡烛 class="type">MqlRates rates[class="num">2]; CopyRates(_Symbol,_Period,class="num">0,class="num">2,rates); class=class="str">"cmt">// rates[class="num">1]为前一根, rates[class="num">0]为当前根 class="type">bool bearPrev = rates[class="num">1].close < rates[class="num">1].open; class="type">bool bullNow = rates[class="num">0].close > rates[class="num">0].open; class="type">bool engulf = rates[class="num">0].close > rates[class="num">1].open && rates[class="num">0].open < rates[class="num">1].close; if(bearPrev && bullNow && engulf) Print("看涨吞没");
「烛条形态为什么值得用代码自动抓」
把烛条当成裸价语言来看,单根就能透露多空博弈的瞬时结果,多根组合则往往对应一段尚未走完的意图。原文将形态拆成单烛、双烛、三烛三类,这个分层不是教科书摆设——它直接决定了你用 MQL5 写检测函数时的循环窗口长度。 单烛形态只需取当前 bar 的 OHLC 即可判定;双烛要维护 i-1 与 i 的上下文;三烛则至少缓存三根。检测逻辑本质是对比实体长度、影线比例与收盘价相对位置,这类规则若靠肉眼在 MT5 上翻图,一天盯 20 对货币就必然漏判。 需要明确的是,形态本身只是概率倾向,并非触发即反转。外汇与贵金属杠杆高、滑点随机,任何形态信号都应叠加成交量或均线类过滤器。用 MQL5 把形态自动标出来,目的不是代替决策,而是把人力从重复识别里解放出来,让你只处理高价值报警。
◍ 用 MT5 程序抓出十字星与锤子
单根蜡烛里最常被拿来当反转信号的,就是十字星和锤子。十字星的核心定义很硬:开盘价与收盘价几乎相等,实体极短甚至成一条线,多空在该周期里谁都没把价格推走。它出现在趋势末端或调整前,可能预示逆转或休整;放在大周期上,这种平衡信号的重要性会明显放大。锤子则看影线结构——长下影+小实体在上是锤子,长上影+小实体在下的倒锤,按收盘与开盘关系还能分看涨/看跌。 要让 MT5 替你盯盘,就得把「人眼识别」翻译成 iTime/iOpen/iHigh/iLow/iClose 的取值比较。下面这段是检测十字星的最小可用函数:每次跳价 OnTick 调 getDoji,取当前品种、当前周期、shift=1(最后一根完成的蜡烛)的五个价格,若 open==close 就在最低价画黑色箭头 Wingdings 217 并标注 Doji。
class="type">void OnTick() { getDoji(); } class="type">int getDoji() class="type">class="kw">datetime time=iTime(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double open=iOpen(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double high=iHigh(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double low=iLow(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double close=iClose(_Symbol,PERIOD_CURRENT,class="num">1); if(open==close) if(open==close) { createObj(time,low,class="num">217, clrBlack,"Doji"); { class="kw">return class="num">1; } }
void OnTick():每次行情跳动触发,保证实时检查。int getDoji():返回整型,1 表示抓到形态,0 表示没有。iTime/iOpen/iHigh/iLow/iClose五个调用:统一用_Symbol+PERIOD_CURRENT+1,锁定「上一根已收线蜡烛」。if(open==close):十字星判定条件,浮点严格相等在实盘里可改成MathAbs(open-close) < _Point*某倍数,原文用等于仅作示例。createObj(time,low,217,clrBlack,"Doji"):在 low 价画箭头,217 是 Wingdings 箭头码,黑色标注。return 1:告知调用方已识别。
锤子检测逻辑更进一步:定义 smallShadowRatio=0.07、longShadowRatio=0.7 两个比率。看涨锤要求上影(高-收)<0.07倍实体参考、下影(开-低)>0.7倍;看跌锤镜像处理,倒锤则把上下影互换。命中后按涨跌用 217/218 码、绿/红箭头,画在蜡烛下/上沿。外汇与贵金属波动大,这类形态单独用误报率高,建议叠均线或成交密度过滤,实盘前先在策略测试器跑历史片段验证。
class="type">void OnTick() { getDoji(); } class="type">int getDoji() class="type">class="kw">datetime time=iTime(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double open=iOpen(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double high=iHigh(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double low=iLow(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double close=iClose(_Symbol,PERIOD_CURRENT,class="num">1); if(open==close) if(open==close) { createObj(time,low,class="num">217, clrBlack,"Doji"); { class="kw">return class="num">1; } }
在K线图上标出十字星信号
把十字星从逻辑判断变成肉眼可见的标记,核心就在 createObj 这个函数。它接收时间、价格、箭头代码、颜色和文字,拼出唯一对象名后在图表上画箭头,再把说明文字挂上去。 实际检测放在 getDoji 里:取当前周期倒数第 1 根 K 线的开高低收,若 open==close 就判定为十字星,调用 createObj(time,low,217,clrBlack,"Doji") 在最低价处画黑色箭头 217(MT5 里对应某种预设箭头),并返回 1。外汇与贵金属波动大,十字星仅代表多空暂歇,后市方向倾向不确定,需结合上下文。 OnTick 每来一个报价就跑一次 getDoji,意味着信号会随即时价重算;若你只想看已收盘 K 线,把索引 1 改成更大值或加收盘判定更稳。
class="type">int getDoji() { class="type">class="kw">datetime time=iTime(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double open=iOpen(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double high=iHigh(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double low=iLow(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double close=iClose(_Symbol,PERIOD_CURRENT,class="num">1); class=class="str">"cmt">//Doji if(open==close) { createObj(time,low,class="num">217, clrBlack,"Doji"); { class="kw">return class="num">1; } } class="kw">return class="num">0; } class="type">void createObj(class="type">class="kw">datetime time, class="type">class="kw">double price, class="type">int arrawCode, class="type">class="kw">color clr, class="type">class="kw">string txt) { class="type">class="kw">string objName=" "; StringConcatenate(objName, "Signal at ",time, " at ",DoubleToString(price,_Digits)," (",arrawCode,")"); if(ObjectCreate(class="num">0,objName,OBJ_ARROW,class="num">0,time,price)) { ObjectSetInteger(class="num">0,objName,OBJPROP_ARROWCODE,arrawCode); ObjectSetInteger(class="num">0,objName,OBJPROP_COLOR,clr); } class="type">class="kw">string candleName=objName+txt;
「锤子线识别的逐根 K 线判定逻辑」
这段 MT5 脚本把锤子线(Hammer)与倒锤子线(Inverted Hammer)的判定压进一个 getHammer 函数,靠两根影线占实体波幅的比例来过滤。调用时传入 smallShadowRatio=0.07、longShadowRatio=0.7,意味着上/下小影线必须不到整根波幅的 7%,而另一侧长影线要超过 70%,条件相当苛刻,实盘里触发频率偏低。
阳线状态下,若 close 上方影线小于波幅*0.07 且 open 下方影线大于波幅*0.7,就在低价位标绿字“Hammer”并返回 1;若反过来 open 下方影线极小、close 上方长影线达标,则标“Inverted Hammer”返回 -1。阴线分支对称处理,只是标记位移到 high 且用红字。
值得留意的是原文在 createObj 后紧跟一个独立代码块 { return 1; },它在语法上并不属于 if 作用域,属于游离块,复制进 MQ5 编辑器会照常编译但逻辑冗余。开 MT5 新建脚本粘下面代码,切到 EURUSD 的 M1 跑几根,就能看标记什么时候蹦出来——外汇与贵金属波动随机,这类形态只提示概率倾向,不代表后续必反转。
if(ObjectCreate(class="num">0,candleName,OBJ_TEXT,class="num">0,time,price)) { ObjectSetString(class="num">0,candleName,OBJPROP_TEXT," "+txt); ObjectSetInteger(class="num">0,candleName,OBJPROP_COLOR,clr); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Hammer pattern detector.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { getHammer(class="num">0.07,class="num">0.7); } class="type">int getHammer(class="type">class="kw">double smallShadowRatio, class="type">class="kw">double longShadowRatio) { class="type">class="kw">datetime time=iTime(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double open=iOpen(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double high=iHigh(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double low=iLow(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double close=iClose(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double candleSize=high-low; if(open<close) { if(high-close < candleSize*smallShadowRatio) { if(open-low>candleSize*longShadowRatio) createObj(time,low,class="num">217, clrGreen,"Hammer"); { class="kw">return class="num">1; } } } if(open>close) { if(high-open<candleSize*smallShadowRatio) { if(close-low>candleSize*longShadowRatio) createObj(time,high,class="num">218,clrRed,"Hammer"); { class="kw">return class="num">1; } } } if(open<close) { if(open-low < candleSize*smallShadowRatio) { if(high-close>candleSize*longShadowRatio) createObj(time,low,class="num">217, clrGreen,"Inverted Hammer"); { class="kw">return -class="num">1; } } } if(open>close) {
◍ 锤子线与倒锤线的图形标记落地
这段逻辑把蜡烛形态判定和图表标注拆成了两个动作:先算实体加影线总幅度 candleSize=high-low,再按阴阳线分别比对上下影线占比,触发后就调用 createObj 在 K 线时间位画箭头加文字。 OnTick 里写死 getHammer(0.07,0.7),意思是实体另一侧短影线不能超过总高度的 7%,而长影线必须大过 70% 才认形态;外汇和贵金属波动大,这种硬阈值在亚盘低波动时段可能漏信号,美盘常因影线拉长而频繁触发,属正常现象。 createObj 里箭头编号 217 对应绿色锤子(锚定底部),218 对应红色倒锤或下跌锤子(锚定顶部),ObjectCreate 用 OBJ_ARROW 和 OBJ_TEXT 两套对象分开建,名字拼了时间加价位,避免重复覆盖。 直接把下面代码贴进 MT5 脚本编译,开 EURUSD 的 M15 跑几天,看红色 218 箭头是否多在反弹前冒头,再决定要不要把 0.7 调小到 0.5 来放宽识别。
if(close-low < candleSize*smallShadowRatio) { if(high-open>candleSize*longShadowRatio) createObj(time,high,class="num">218, clrRed,"Inverted Hammer"); { class="kw">return -class="num">1; } } } class="kw">return class="num">0; } class="type">void createObj(class="type">class="kw">datetime time, class="type">class="kw">double price, class="type">int arrawCode, class="type">class="kw">color clr, class="type">class="kw">string txt) { class="type">class="kw">string objName=" "; StringConcatenate(objName, "Signal@",time, "at",DoubleToString(price,_Digits),"(",arrawCode,")"); if(ObjectCreate(class="num">0,objName,OBJ_ARROW,class="num">0,time,price)) { ObjectSetInteger(class="num">0,objName,OBJPROP_ARROWCODE,arrawCode); ObjectSetInteger(class="num">0,objName,OBJPROP_COLOR,clr); if(clr==clrGreen) ObjectSetInteger(class="num">0,objName,OBJPROP_ANCHOR,ANCHOR_TOP); if(clr==clrRed) ObjectSetInteger(class="num">0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM); } class="type">class="kw">string candleName=objName+txt; if(ObjectCreate(class="num">0,candleName,OBJ_TEXT,class="num">0,time,price)) { ObjectSetString(class="num">0,candleName,OBJPROP_TEXT," "+txt); ObjectSetInteger(class="num">0,candleName,OBJPROP_COLOR,clr); } } class="type">void OnTick() { getHammer(class="num">0.07,class="num">0.7); } class="type">int getHammer(class="type">class="kw">double smallShadowRatio, class="type">class="kw">double longShadowRatio) class="type">class="kw">double candleSize=high-low; if(open<close) { if(high-close < candleSize*smallShadowRatio) { if(open-low>candleSize*longShadowRatio) createObj(time,low,class="num">217, clrGreen,"Hammer"); { class="kw">return class="num">1; } } } if(open>close) { if(high-open<candleSize*smallShadowRatio) { if(close-low>candleSize*longShadowRatio) createObj(time,high,class="num">218,clrRed,"Hammer"); { class="kw">return class="num">1; } } } if(open<close) { if(open-low < candleSize*smallShadowRatio) {