如何利用 MQL5 检测趋势和图表形态·进阶篇
用双底点画趋势线并判方向
这段逻辑先取两个低点价格并规范到 5 位小数,再删掉旧的趋势线对象,用 OBJ_TREND 连起前一低点和当前低点,向右延伸射线,颜色绿、线宽 3。注意 lowVal1 与 lowVal2 的比较是后续方向判断的硬依据,而非单纯看价格绝对值。 当 lowVal1>lowVal2 且 highVal1>highVal2 时,判定为 Uptrend;反之为 Downtrend;其余情况归 Sideways。Comment 会把当前/前一根高低点都打印到图表左上,方便肉眼复核 EA 的判断是否和你的价格行为读图一致。 getmove 与 getNextMove 是两个辅助定位函数:getmove 从 startPos 起循环调用 getNextMove,直到返回的极值柱号等于当前柱,相当于在指定回看窗内锁定某一侧极值。getNextMove 对 startPos<0 做了保护,把负偏移折算进 count 再从 0 柱算起,避免边界报错。外汇与贵金属波动剧烈,这类自动画线仅作辅助,信号失效概率不低,实盘前务必在 MT5 策略测试器用历史数据验证。
lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),class="num">5); lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),class="num">5); ObjectDelete(class="num">0,"bottomLine"); ObjectCreate(class="num">0,"bottomLine",OBJ_TREND,class="num">0,iTime(Symbol(),Period(),low2),iLow(Symbol(),Period(),low2),iTime(Symbol(),Period(),low1),iLow(Symbol(),Period(),low1)); ObjectSetInteger(class="num">0,"bottomLine",OBJPROP_COLOR,clrGreen); ObjectSetInteger(class="num">0,"bottomLine",OBJPROP_WIDTH,class="num">3); ObjectSetInteger(class="num">0,"bottomLine",OBJPROP_RAY_RIGHT,true); if(lowVal1>lowVal2&&highVal1>highVal2) { Comment("Uptrend", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else if(highVal1<highVal2&&lowVal1<lowVal2) { Comment("Downtrend", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else { Comment("Sideways", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } } class="type">int getmove(class="type">int move, class="type">int count, class="type">int startPos) { if(move!=MODE_HIGH && move!=MODE_LOW) class="kw">return (-class="num">1); class="type">int currentBar=startPos; class="type">int moveReturned=getNextMove(move,count*class="num">2+class="num">1,currentBar-count); while(moveReturned!=currentBar) { currentBar=getNextMove(move,count,currentBar+class="num">1); moveReturned=getNextMove(move,count*class="num">2+class="num">1,currentBar-count); } class="kw">return(currentBar); } class="type">int getNextMove(class="type">int move, class="type">int count, class="type">int startPos) { if(startPos<class="num">0) { count +=startPos; startPos =class="num">0; } class="kw">return((move==MODE_HIGH)? iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos):
◍ 用双高低点判趋势状态
在 MT5 脚本里,先把前后两段 K 线区间的高低点分别抓出来,再用 NormalizeDouble 统一到 5 位小数,避免浮点误差导致比较失真。 上面这段逻辑定义了 high1/high2/low1/low2 四个索引位,以及对应的 highVal1/highVal2/lowVal1/lowVal2 数值。当前高与前高、当前低与前低同时抬高,就倾向判为 Uptrend;同时压低则倾向 Downtrend;其余情况归为 Sideways。 实盘里外汇和贵金属波动受杠杆与消息面影响,高低点结构只反映概率性偏向,不代表后续必沿该方向运行,属于高风险品种下的参考信号。把 Comment 输出的内容接到小布面板上,能省掉手动比对两根 K 线的高低。
iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos)); } class="type">int high1, high2, low1, low2; class="type">class="kw">double highVal1, highVal2, lowVal1, lowVal2; highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),class="num">5); highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),class="num">5); lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),class="num">5); lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),class="num">5); if(lowVal1>lowVal2&&highVal1>highVal2) { Comment("Uptrend", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else if(highVal1<highVal2&&lowVal1<lowVal2) { Comment("Downtrend", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else { Comment("Sideways", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); }
「用 EA 抓双顶的两个变种」
双顶形态不一定非要两个高点完全相等。实战里更常见的是后一个高点略低于或等于前一个,而中间低点的情况决定它是「潜在」还是「确认」:低点抬高倾向暗示购买力还在挣扎,低点走低则下跌概率更大。 下面这套 MQL5 逻辑只检查最近 5 根内的两个高/低结构(checkBars=5),用 getmove 回溯前高前低,再比对 highVal 与 lowVal 的关系。外汇和贵金属波动大,这种形态识别只作辅助,误判风险高。 判定规则很直白:若 highVal1<=highVal2 且 lowVal1>lowVal2,图表注释打「潜在的双顶」;若 highVal1<=highVal2 且 lowVal1<lowVal2,打「双顶」。不匹配就不输出,避免噪音。 编译挂上 MT5 后,你能在 EURUSD 的 M15 上翻到不少样例——比如等高点配更高低点出潜在信号,等高点配更低低点出确认信号。直接复制代码改 checkBars 到 8,回看更高周期过滤假突破。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| DT patternFinder.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class="type">int checkBars= class="num">5; class="type">int high1, high2, low1, low2; class="type">class="kw">double highVal1, highVal2, lowVal1, lowVal2; high1=getmove(MODE_HIGH,checkBars,class="num">0); high2=getmove(MODE_HIGH,checkBars,high1+class="num">1); highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),class="num">5); highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),class="num">5); ObjectDelete(class="num">0,"topLine"); ObjectCreate(class="num">0,"topLine",OBJ_TREND,class="num">0,iTime(Symbol(),Period(),high2),iHigh(Symbol(),Period(),high2),iTime(Symbol(),Period(),high1),iHigh(Symbol(),Period(),high1)); ObjectSetInteger(class="num">0,"topLine",OBJPROP_COLOR,clrRed); ObjectSetInteger(class="num">0,"topLine",OBJPROP_WIDTH,class="num">3); ObjectSetInteger(class="num">0,"topLine",OBJPROP_RAY_RIGHT,true); low1=getmove(MODE_LOW,checkBars,class="num">0); low2=getmove(MODE_LOW,checkBars,low1+class="num">1); lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),class="num">5); lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),class="num">5); ObjectDelete(class="num">0,"bottomLine");
双顶识别与趋势线绘制的具体实现
这段逻辑在 MT5 上把双顶的判定和可视化拆成了两步:先画一条连接两个低点的绿色趋势线,再根据高低点关系用 Comment 输出形态提示。外汇与贵金属波动剧烈,双顶仅为概率性反转信号,须结合仓位控制。 ObjectCreate 用 OBJ_TREND 在图表上拉出名为 bottomLine 的线段,两端锚定在 low2 与 low1 对应的 K 线时间和国价低点;随后用 ObjectSetInteger 把颜色设为 clrGreen、线宽设为 3、并开启 OBJPROP_RAY_RIGHT 向右延伸,方便肉眼跟踪颈线支撑。 判定部分靠 highVal1<=highVal2 配合 lowVal1 与 lowVal2 的大小写分支:若低点抬高则标 Potential Double Top,若低点也下移则直接标 Double Top,其余情况清空提示。getmove 与 getNextMove 是两个辅助函数,前者循环调用后者在指定窗口内定位第 N 个极值 Bar,后者通过 iHighest / iLowest 按 MODE_HIGH 或 MODE_LOW 取区间极值,startPos 为负时会自动把 count 补齐并从 0 重算。 把这些函数直接塞进 EA 的 OnCalculate,把 low1、low2、highVal1、highVal2 换成你自己的极值变量,就能在 EURUSD 的 M15 上实时看到双顶标注。复盘时建议把 count 参数从默认 2 调到 3,过滤掉小周期噪音。
ObjectCreate(class="num">0,"bottomLine",OBJ_TREND,class="num">0,iTime(Symbol(),Period(),low2),iLow(Symbol(),Period(),low2),iTime(Symbol(),Period(),low1),iLow(Symbol(),Period(),low1)); ObjectSetInteger(class="num">0,"bottomLine",OBJPROP_COLOR,clrGreen); ObjectSetInteger(class="num">0,"bottomLine",OBJPROP_WIDTH,class="num">3); ObjectSetInteger(class="num">0,"bottomLine",OBJPROP_RAY_RIGHT,true); if(highVal1<=highVal2&&lowVal1>lowVal2) { Comment("Potential Double Top", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else if(highVal1<=highVal2&&lowVal1<lowVal2) { Comment("Double Top", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else Comment(" "); } class="type">int getmove(class="type">int move, class="type">int count, class="type">int startPos) { if(move!=MODE_HIGH && move!=MODE_LOW) class="kw">return (-class="num">1); class="type">int currentBar=startPos; class="type">int moveReturned=getNextMove(move,count*class="num">2+class="num">1,currentBar-count); while(moveReturned!=currentBar) { currentBar=getNextMove(move,count,currentBar+class="num">1); moveReturned=getNextMove(move,count*class="num">2+class="num">1,currentBar-count); } class="kw">return(currentBar); } class="type">int getNextMove(class="type">int move, class="type">int count, class="type">int startPos) { if(startPos<class="num">0) { count +=startPos; startPos =class="num">0; } class="kw">return((move==MODE_HIGH)? iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos): iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos)); } if(highVal1<=highVal2&&lowVal1>lowVal2) { Comment("Potential Double Top", "\nCurrent High ",highVal1,"\nPrevious High ",highVal2, "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2); } else if(highVal1<=highVal2&&lowVal1<lowVal2)
◍ 把双顶信号打到图表上
识别到双顶结构后,如果只是内部变量存着,交易者肉眼很难快速确认。把关键极值直接打印在图表左上角,是低成本的自检办法。 下面这段代码在条件满足时调用 Comment,把形态名称和四个价格一并输出:当前高点 highVal1、前高 highVal2、当前低点 lowVal1、前低 lowVal2;若不满足则清空注释,避免旧信息干扰。
{
Comment("Double Top",
"\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
"\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
}
else
Comment(" ");
{
Comment("Double Top",
"\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
"\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
}
else
Comment(" ");「用EA在MT5里抓双底雏形」
双底和双顶互为镜像:两段大致相等的低点暗示卖方动能衰减,价格后续向上突破两低点间颈线(前高)时才算形态确认。外汇与贵金属杠杆高,形态失效是常态,任何信号都只是概率倾向,不是方向保证。 写个EA连续比对最近两段高低点即可。若当前低点≥前低点、且当前高点低于前高点,标为「潜在的双底」;若当前低点≥前低点、当前高点也高于前高点,则判为「双底」。这样略高/略低的实战变形也能兜住,不只认死板的绝对等高。 下面这段MQL5在每跳刷新时取5根内两段极值,画红线连前高,并准备比低。编译零报错后挂上回测,图表注释会直接吐出当前/前高、当前/前低四值,你照着改阈值就能跑。 代码逐行拆解: #property段声明版权与版本,无关逻辑可忽略。 void OnTick() 每笔报价触发。 int checkBars=5 限定回溯5根K线找极值。 high1/getmove找最近一段高点索引,high2找再前一段。 iHigh取对应高价,NormalizeDouble精度截到5位。 ObjectDelete+ObjectCreate画红色趋势线连两高,RAY_RIGHT向右延伸。 low1/low2同理取两段低点索引,iLow取低价并规范化。 后续ObjectDelete("bottomLine")是为清旧低线,留待下一屏补画与条件注释。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| DB patternFinder.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class="type">int checkBars= class="num">5; class="type">int high1, high2, low1, low2; class="type">class="kw">double highVal1, highVal2, lowVal1, lowVal2; high1=getmove(MODE_HIGH,checkBars,class="num">0); high2=getmove(MODE_HIGH,checkBars,high1+class="num">1); highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),class="num">5); highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),class="num">5); ObjectDelete(class="num">0,"topLine"); ObjectCreate(class="num">0,"topLine",OBJ_TREND,class="num">0,iTime(Symbol(),Period(),high2),iHigh(Symbol(),Period(),high2),iTime(Symbol(),Period(),high1),iHigh(Symbol(),Period(),high1)); ObjectSetInteger(class="num">0,"topLine",OBJPROP_COLOR,clrRed); ObjectSetInteger(class="num">0,"topLine",OBJPROP_WIDTH,class="num">3); ObjectSetInteger(class="num">0,"topLine",OBJPROP_RAY_RIGHT,true); low1=getmove(MODE_LOW,checkBars,class="num">0); low2=getmove(MODE_LOW,checkBars,low1+class="num">1); lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),class="num">5); lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),class="num">5); ObjectDelete(class="num">0,"bottomLine");