用 MQL5 语言编写的 20 种交易信号(基础篇)
「MQL5 里能直接跑的 20 类信号」
MT5 终端内用 MQL5 写交易信号,不是纸上谈兵。2013 年 10 月 4 日有作者放出过一套合集,覆盖 20 种信号逻辑,发布当年就累积 13 336 次查看、49 条讨论,说明实战圈对“可编码信号”需求很硬。 这套合集的价值不在指标多花哨,而在每一条信号都能编译进 EA 或指标,让你在 MT5 策略测试器里直接回测。 想验证的人,打开 MT5 的 MetaEditor,新建一个信号类模板,把其中任意一条逻辑抄进去编译,就能看它在历史数据上的触发密度。外汇和贵金属波动大,这类信号只作概率参考,别当确定性依据。
◍ 交易信号到底在告诉您什么
做系统化交易的人,本质是在找价格变动里可重复的结构,并把它落成“什么时候该看一眼图表”的触发条件。全自动系统不靠盘感,靠的是把这类触发写成代码——也就是交易信号。 信号只是提示“这里可能出现有利点位”,不代表每个都要进场。真实账户里,光有信号不够,还得有过滤层把噪音剔掉,否则来回扫损会比想象中快。 MQL5 里常见的入场逻辑,基本能归为六类:均线相交、区间突破、随机动量脱离超买超卖区、通道边界反弹、通道边界突破、趋势反转。后面几节会逐一拆这些类型的写法。 外汇和贵金属杠杆高、滑点跳空频繁,同一套信号在欧美盘和重大数据夜表现可能完全两样,代码跑通不等于能直接上实盘。
把信号逻辑收进可复用包含文件
在 MT5 里硬写信号,很多人习惯直接堆在 OnTick() 或 OnStart() 里。问题是同一段判断逻辑往往要在多个 EA 或脚本里重复敲,改一处得翻好几份源码,效率偏低。更干净的做法是把通用判断抽成自定义函数,再塞进 MQL5\Include 下的独立头文件,比如叫 SignalTrade,任何程序一句 #include 就能挂上。 信号本质只有三种可能状态:1 代表买入倾向,-1 代表卖出倾向,0 代表无信号。用枚举固化这三态,比散落各处的魔法数字好维护,也方便后续接 AIGC 标注模块做分类。 下面这个函数骨架就是 SignalTrade 的核心原型。开头先声明并初始化 sig=0,随后做指标句柄校验、数据复制与倒序索引设置,任一步出错就直接 return,避免脏数据往下传。真正的开仓条件留空在「检查条件并设置 sig 的值」处,按你自己的价格行为规则填。 把这类模块抽出来后,你下次写新策略只需调 TradeSignal(),不用重写指标抓取与防错流程。外汇与贵金属波动剧烈,这类隔离能降低误触发概率,但任何信号都只是概率倾向,不是确定性入口。
class="type">int TradeSignal() { class=class="str">"cmt">//--- 0表示信号不存在 class="type">int sig=class="num">0; class=class="str">"cmt">//--- 检查指标句柄 class=class="str">"cmt">//--- 如果所有句柄都无效,创建它们 class=class="str">"cmt">//--- 如果句柄有效, 从指标中复制数值 class=class="str">"cmt">//--- 检查复制的数据 class=class="str">"cmt">//--- 如果复制有错, 从函数中退出 class=class="str">"cmt">//--- 把数组索引方式设为倒序 class=class="str">"cmt">//--- 如果索引方式有错,退出函数 class=class="str">"cmt">//--- 检查条件并设置 sig 的值 class=class="str">"cmt">//--- 返回交易信号 class="kw">return(sig); }
「双均线交叉信号的句柄与复制细节」
在 MT5 里拿指标数据,第一步是用 iMA、iMACD 这类函数建副本并拿句柄;相同参数已存在时只增引用计数,不重复建。句柄建完别马上读,指标算值要时间,所以正经做法是在 OnInit() 里创建,避免 CopyBuffer() 时扑空。 以快线周期 8、慢线周期 16 的双均线交叉为例,TradeSignal_01() 只回传信号类型:0 无、1 买、-1 卖。函数开头先声明局部变量 sig 并置 0;若 h_ma1 是 INVALID_HANDLE 就重建并直接退出,把等待计算的时间留给下一 tick。 句柄有效后才用 CopyBuffer() 拉数据:快线取最后 3 根、慢线取最后 2 根足够判断。复制后先查实际拿到的是否少于需要量,少了就退;再用数组时间序列索引化函数设方向,设错也退,否则会读歪。 信号只在已收盘的第 1、2 根柱上判,过滤当前柱抖动。买:快 MA(柱2) < 慢 MA(柱1) 且 快 MA(柱1) > 慢 MA(柱1),即快线向上穿;卖反过来。两个条件都不成立回 0。外汇和贵金属杠杆高,这类交叉在震荡市可能连续假突破,开 MT5 把周期改成 8/16 跑 EURUSD 的 M15 能直接看命中率。 其余 19 个信号按同原型编号:MACD 主线/信号线交叉、价格/ADX/布林/标准差/包络/唐奇安/银/加拉赫通道突破或反弹、RSI(30/70)、CCI(±100)、随机(%K%D 的 20/80)、威廉(-80/-20) 超买超卖退出、NRTR 与 AMA 与鳄鱼与 Ichimoku 趋势变向、动量振荡柱变色等,均靠各自句柄 + CopyBuffer() 取数。自定义指标用 iCustom() 最多 512 缓冲,也能同样处理。
class="type">int TradeSignal_01() { class=class="str">"cmt">//--- 0说明没有信号 class="type">int sig=class="num">0; class=class="str">"cmt">//--- 检查指标的句柄 if(h_ma1==INVALID_HANDLE)class=class="str">"cmt">//--- 如果句柄无效 { class=class="str">"cmt">//--- 再次创建它 h_ma1=iMA(Symbol(),Period(),class="num">8,class="num">0,MODE_SMA,PRICE_CLOSE); class=class="str">"cmt">//--- 退出函数
◍ 双均线句柄校验与交叉信号判定
这段逻辑先兜底两条均线的指标句柄:若 h_ma1 或 h_ma2 等于 INVALID_HANDLE,就当场用 iMA 重建——第一条周期 8、第二条周期 16,都是收盘价 SMA,重建完直接 return(0) 让当前 tick 跳过,避免空句柄读缓冲崩 EA。 句柄有效后,CopyBuffer 分别拉 3 根和 2 根数据进 ma1_buffer / ma2_buffer;复制量不足就退出。随后 ArraySetAsSeries 把数组设成倒序,使 [0] 对应最新 K 线。注意原文对 ma2 分支误写了 ma1_buffer,实盘抄代码要改成 ma2_buffer,否则第二条均线索引方式没翻过来。 信号判定只看最近两期:ma1_buffer[2]<ma2_buffer[1] 且 ma1_buffer[1]>ma2_buffer[1] 给 sig=1(快线由下向上穿越慢线,偏多);反向则 sig=-1;其余 sig=0。外汇与贵金属杠杆高,这种穿越信号在震荡市可能连续假突破,只作概率参考,务必上 MT5 用策略测试器跑 EURUSD 15M 验证。
class="kw">return(class="num">0); } else class=class="str">"cmt">//--- 如果句柄有效 { class=class="str">"cmt">//--- 把指标值复制到数组中 if(CopyBuffer(h_ma1,class="num">0,class="num">0,class="num">3,ma1_buffer)<class="num">3) class=class="str">"cmt">//--- 如果数组数据少于所需 class=class="str">"cmt">//--- 退出函数 class="kw">return(class="num">0); class=class="str">"cmt">//--- 设置数组索引方式为倒序 if(!ArraySetAsSeries(ma1_buffer,true)) class=class="str">"cmt">//--- 如果索引出错,退出函数 class="kw">return(class="num">0); } if(h_ma2==INVALID_HANDLE)class=class="str">"cmt">//--- 如果句柄是无效的 { class=class="str">"cmt">//--- 再次创建它 h_ma2=iMA(Symbol(),Period(),class="num">16,class="num">0,MODE_SMA,PRICE_CLOSE); class=class="str">"cmt">//--- 退出函数 class="kw">return(class="num">0); } else class=class="str">"cmt">//--- 如果句柄有效 { class=class="str">"cmt">//--- 把指标值复制到数组中 if(CopyBuffer(h_ma2,class="num">0,class="num">0,class="num">2,ma2_buffer)<class="num">2) class=class="str">"cmt">//--- 如果数据少于所需 class=class="str">"cmt">//--- 退出函数 class="kw">return(class="num">0); class=class="str">"cmt">//--- 设置数组索引方式为倒序 if(!ArraySetAsSeries(ma1_buffer,true)) class=class="str">"cmt">//--- 如果索引出错,退出函数 class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置sig的值 if(ma1_buffer[class="num">2]<ma2_buffer[class="num">1] && ma1_buffer[class="num">1]>ma2_buffer[class="num">1]) sig=class="num">1; else if(ma1_buffer[class="num">2]>ma2_buffer[class="num">1] && ma1_buffer[class="num">1]<ma2_buffer[class="num">1]) sig=-class="num">1; else sig=class="num">0; class=class="str">"cmt">//--- 返回交易信号 class="kw">return(sig); } class="type">int sig=class="num">0; if(h_ma1==INVALID_HANDLE)class=class="str">"cmt">//--- 如果句柄无效 { class=class="str">"cmt">//--- 再次创建它 h_ma1=iMA(Symbol(),Period(),class="num">8,class="num">0,MODE_SMA,PRICE_CLOSE); class=class="str">"cmt">//--- 退出函数 class="kw">return(class="num">0); } class="type">int CopyBuffer(
双指标信号函数的取数细节
把指标缓冲区搬进 EA 之前,得先认清楚 CopyBuffer 的参数排布:indicator_handle 是指标句柄,buffer_num 选第几个输出缓冲,start_pos 从哪根 K 线起算,count 是要复制的根数,buffer[] 是落地用的 double 数组。少一根都别硬跑,代码里写了 <3 就直接 return(0) 退出,避免拿半截数据算信号。
ArraySetAsSeries 的第二个参数设 true,等于告诉 MT5 把数组当成时间序列——buffer[0] 是最新一根,buffer[2] 是两根前。这个倒序设定若返回 false,同样立刻 return(0),否则下标对不上,金叉死叉会整反。
TradeSignal_01 里用 ma1_buffer[2]<ma2_buffer[1] 且 ma1_buffer[1]>ma2_buffer[1] 判多头交叉,sig 给 1;反之为 -1,否则 0。TradeSignal_02 则对 MACD 下手:iMACD 用 12/26/9 默认值,主缓冲取 2 根、信号缓冲取 3 根,macd2_buffer[2]>macd1_buffer[1] 且 macd2_buffer[1]<macd1_buffer[1] 时 sig=1,属柱线从下穿信号线转上的概率倾向。外汇与贵金属杠杆高,这类交叉信号假突破频繁,实盘前请在 MT5 策略测试器用历史数据验一遍复制逻辑。
class="type">int indicator_handle, class=class="str">"cmt">// 指标句柄 class="type">int buffer_num, class=class="str">"cmt">// 指标缓冲区序号 class="type">int start_pos, class=class="str">"cmt">// 从哪里开始 class="type">int count, class=class="str">"cmt">// 要复制的数量 class="type">class="kw">double buffer[] class=class="str">"cmt">// 复制数据的目标数组 ); class="type">bool ArraySetAsSeries( class="type">void array[], class=class="str">"cmt">// 数组 class="type">bool set class=class="str">"cmt">// true 说明索引顺序为倒序 ); else class=class="str">"cmt">//--- 如果句柄有效 { class=class="str">"cmt">//--- 把指标值复制到数组中 if(CopyBuffer(h_ma1,class="num">0,class="num">0,class="num">3,ma1_buffer)<class="num">3) class=class="str">"cmt">//--- 如果数据少于所需 class=class="str">"cmt">//--- 退出函数 class="kw">return(class="num">0); class=class="str">"cmt">//--- 设置数组索引类似于时间序列(倒序) if(!ArraySetAsSeries(ma1_buffer,true)) class=class="str">"cmt">//--- 如果索引出错,退出函数 class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置sig的值 if(ma1_buffer[class="num">2]<ma2_buffer[class="num">1] && ma1_buffer[class="num">1]>ma2_buffer[class="num">1]) sig=class="num">1; else if(ma1_buffer[class="num">2]>ma2_buffer[class="num">1] && ma1_buffer[class="num">1]<ma2_buffer[class="num">1]) sig=-class="num">1; else sig=class="num">0; class=class="str">"cmt">//--- 返回交易信号 class="kw">return(sig); class="type">int TradeSignal_02() { class="type">int sig=class="num">0; if(h_macd==INVALID_HANDLE) { h_macd=iMACD(Symbol(),Period(),class="num">12,class="num">26,class="num">9,PRICE_CLOSE); class="kw">return(class="num">0); } else { if(CopyBuffer(h_macd,class="num">0,class="num">0,class="num">2,macd1_buffer)<class="num">2) class="kw">return(class="num">0); if(CopyBuffer(h_macd,class="num">1,class="num">0,class="num">3,macd2_buffer)<class="num">3) class="kw">return(class="num">0); if(!ArraySetAsSeries(macd1_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(macd2_buffer,true)) class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置 sig 的值 if(macd2_buffer[class="num">2]>macd1_buffer[class="num">1] && macd2_buffer[class="num">1]<macd1_buffer[class="num">1]) sig=class="num">1;