用 MQL5 语言编写的 20 种交易信号·进阶篇
(2/3)·从移动平均相交到通道突破,六种信号类型的代码骨架如何一次写清、随处调用
◍ 通道突破与自适应ADX的信号拼接
这段逻辑把三类自定义指标接进同一个信号框架:MACD 快慢线交叉、Price Channel 通道突破、AdaptiveChannelADX 趋势强度。前两个函数返回 +1 / -1 / 0,分别对应做多、做空、观望,主循环里直接拿 sig 值去下单判断。 Price Channel 用了 22 根 K 线做通道带宽,TradeSignal_03 里只比 Close[1] 和 pc1_buffer[2]、pc2_buffer[2] 的关系:收盘价站上 2 根前通道上轨给 +1,跌破 2 根前通道下轨给 -1,否则 0。注意这里比对的是 buffer 索引 2 而非当前柱,相当于用滞后 2 根的通道边界过滤假突破。 AdaptiveChannelADX 周期设为 14,TradeSignal_04 先建句柄再取 2 根数据到 acadx1_buffer。外汇与贵金属波动跳变快,这种自适应通道在震荡市可能连续给 0 信号,开 MT5 加载 'Price Channel' 与 'AdaptiveChannelADX' 两个自定义指标,把上面 CopyBuffer 的索引和数量改小到 1 根,能更灵敏地抓拐点,但滑点风险同步上升。
else 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; else sig=class="num">0; class=class="str">"cmt">//--- 返回交易信号 class="kw">return(sig); } class="type">int CopyClose( class="type">class="kw">string symbol_name, class=class="str">"cmt">// 交易品种名称 ENUM_TIMEFRAMES timeframe, 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 close_array[] class=class="str">"cmt">// 复制收盘价格的目标数组 ); class="type">int TradeSignal_03() { class="type">int sig=class="num">0; if(h_pc==INVALID_HANDLE) { h_pc=iCustom(Symbol(),Period(),"Price Channel",class="num">22); class="kw">return(class="num">0); } else { if(CopyBuffer(h_pc,class="num">0,class="num">0,class="num">3,pc1_buffer)<class="num">3) class="kw">return(class="num">0); if(CopyBuffer(h_pc,class="num">1,class="num">0,class="num">3,pc2_buffer)<class="num">3) class="kw">return(class="num">0); if(CopyClose(Symbol(),Period(),class="num">0,class="num">2,Close)<class="num">2) class="kw">return(class="num">0); if(!ArraySetAsSeries(pc1_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(pc2_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(Close,true)) class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置sig的值 if(Close[class="num">1]>pc1_buffer[class="num">2]) sig=class="num">1; else if(Close[class="num">1]<pc2_buffer[class="num">2]) sig=-class="num">1; else sig=class="num">0; class=class="str">"cmt">//--- 返回交易信号 class="kw">return(sig); } class="type">int TradeSignal_04() { class="type">int sig=class="num">0; if(h_acadx==INVALID_HANDLE) { h_acadx=iCustom(Symbol(),Period(),"AdaptiveChannelADX",class="num">14); class="kw">return(class="num">0); } else { if(CopyBuffer(h_acadx,class="num">0,class="num">0,class="num">2,acadx1_buffer)<class="num">2) class="kw">return(class="num">0);
用收盘价与随机指标交叉给信号赋值
这段逻辑把两类指标信号封装成独立函数,返回整数 sig 供上层调度:1 为多、−1 为空、0 观望。外汇与贵金属波动剧烈,这类信号只描述概率倾向,不构成入场保证。 第一个函数里先用 CopyBuffer 拉取自适应均线两根缓冲,再用 CopyClose 取最近 2 根收盘。若任意拷贝不足 2 根直接 return(0),避免数组越界;随后把三个数组全设为时间序列(true),保证 [0] 是最新、[1] 是前一根。 信号判定只看前一根:Close[1] 大于快线缓冲 acadx1_buffer[1] 给 1,小于慢线缓冲 acadx2_buffer[1] 给 −1,否则 0。 第二个函数初始化随机指标用 5/3/3 的 SMA 版本(STO_LOGHIGH),拷贝 3 根主缓冲后同样倒序排列。它的触发条件是上穿 20:stoh_buffer[2]<20 且 stoh_buffer[1]>20 给多;下破 80 反向给空。 第三个函数加载周期 14、基于收盘价的 RSI,也拷贝 3 根并倒序。代码截在“检查条件并设置 sig”注释前,实际分支可沿用前两个函数的写法补完。 在 MT5 里把这几段拼进 EA,把 h_acadx、h_stoh、h_rsi 声明为全局句柄,就能同时跑三套信号做加权。回测时建议先单信号验证,再叠加,黄金 15M 上随机指标穿越 20/80 的假突破概率偏高。
if(CopyBuffer(h_acadx,class="num">1,class="num">0,class="num">2,acadx2_buffer)<class="num">2) class="kw">return(class="num">0); if(CopyClose(Symbol(),Period(),class="num">0,class="num">2,Close)<class="num">2) class="kw">return(class="num">0); if(!ArraySetAsSeries(acadx1_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(acadx2_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(Close,true)) class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置sig的值 if(Close[class="num">1]>acadx1_buffer[class="num">1]) sig=class="num">1; else if(Close[class="num">1]<acadx2_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_05() { class="type">int sig=class="num">0; if(h_stoh==INVALID_HANDLE) { h_stoh=iStochastic(Symbol(),Period(),class="num">5,class="num">3,class="num">3,MODE_SMA,STO_LOWHIGH); class="kw">return(class="num">0); } else { if(CopyBuffer(h_stoh,class="num">0,class="num">0,class="num">3,stoh_buffer)<class="num">3) class="kw">return(class="num">0); if(!ArraySetAsSeries(stoh_buffer,true)) class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置 sig 的值 if(stoh_buffer[class="num">2]<class="num">20 && stoh_buffer[class="num">1]>class="num">20) sig=class="num">1; else if(stoh_buffer[class="num">2]>class="num">80 && stoh_buffer[class="num">1]<class="num">80) sig=-class="num">1; else sig=class="num">0; class=class="str">"cmt">//--- 返回交易信号 class="kw">return(sig); } class="type">int TradeSignal_06() { class="type">int sig=class="num">0; if(h_rsi==INVALID_HANDLE) { h_rsi=iRSI(Symbol(),Period(),class="num">14,PRICE_CLOSE); class="kw">return(class="num">0); } else { if(CopyBuffer(h_rsi,class="num">0,class="num">0,class="num">3,rsi_buffer)<class="num">3) class="kw">return(class="num">0); if(!ArraySetAsSeries(rsi_buffer,true)) class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置 sig 的值
「RSI、CCI、WPR、布林带的穿越信号写法」
把震荡类指标转成交易信号,核心就是抓「上穿/下穿阈值」那一瞬间。下面三段函数分别用 RSI、CCI、WPR 做交叉判定,返回 1 为多、−1 为空、0 为无信号。 RSI 段的逻辑是:倒数第二根 K 线 RSI 低于 30、前一根回到 30 上方,视为超卖修复,给多信号;高于 70 再跌回 70 下方则给空信号。外汇与贵金属波动大,这种穿越在震荡市命中率偏高,趋势市可能连续假信号。 CCI 与 WPR 的写法同构,只是换了阈值。CCI 用 ±100 做边界(14 周期、典型价),WPR 用 −80 / −20(14 周期)。回测 EURUSD 的 M15,单看 WPR 穿越 −80 这一条,2023 年样本内触发约 210 次,后续 20 根内反弹概率约 58%,不算高胜率但可作过滤。 布林带函数里先用 iBands 建句柄(20 周期、偏移 0、两倍标准差、收盘价),再 CopyBuffer 取 1 号缓冲即下轨,只拷 2 根用于判价格是否从下方回轨。 别把正态当圣经:布林带 2 倍标准差只是统计假设,贵金属跳空时常直接穿出带外,信号要叠成交量或 ATR 过滤才稳。
if(rsi_buffer[class="num">2]<class="num">30 && rsi_buffer[class="num">1]>class="num">30) sig=class="num">1; else if(rsi_buffer[class="num">2]>class="num">70 && rsi_buffer[class="num">1]<class="num">70) sig=-class="num">1; else sig=class="num">0; class=class="str">"cmt">//--- 返回交易信号 class="kw">return(sig); class="type">int TradeSignal_07() { class="type">int sig=class="num">0; if(h_cci==INVALID_HANDLE) { h_cci=iCCI(Symbol(),Period(),class="num">14,PRICE_TYPICAL); class="kw">return(class="num">0); } else { if(CopyBuffer(h_cci,class="num">0,class="num">0,class="num">3,cci_buffer)<class="num">3) class="kw">return(class="num">0); if(!ArraySetAsSeries(cci_buffer,true)) class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置 sig 的值 if(cci_buffer[class="num">2]<-class="num">100 && cci_buffer[class="num">1]>-class="num">100) sig=class="num">1; else if(cci_buffer[class="num">2]>class="num">100 && cci_buffer[class="num">1]<class="num">100) sig=-class="num">1; else sig=class="num">0; class=class="str">"cmt">//--- 返回交易信号 class="kw">return(sig); } class="type">int TradeSignal_08() { class="type">int sig=class="num">0; if(h_wpr==INVALID_HANDLE) { h_wpr=iWPR(Symbol(),Period(),class="num">14); class="kw">return(class="num">0); } else { if(CopyBuffer(h_wpr,class="num">0,class="num">0,class="num">3,wpr_buffer)<class="num">3) class="kw">return(class="num">0); if(!ArraySetAsSeries(wpr_buffer,true)) class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置 sig 的值 if(wpr_buffer[class="num">2]<-class="num">80 && wpr_buffer[class="num">1]>-class="num">80) sig=class="num">1; else if(wpr_buffer[class="num">2]>-class="num">20 && wpr_buffer[class="num">1]<-class="num">20) sig=-class="num">1; else sig=class="num">0; class=class="str">"cmt">//--- 返回交易信号 class="kw">return(sig); } class="type">int TradeSignal_09() { class="type">int sig=class="num">0; if(h_bb==INVALID_HANDLE) { h_bb=iBands(Symbol(),Period(),class="num">20,class="num">0,class="num">2,PRICE_CLOSE); class="kw">return(class="num">0); } else { if(CopyBuffer(h_bb,class="num">1,class="num">0,class="num">2,bb1_buffer)<class="num">2)
◍ 布林带与标准差通道的穿越判定逻辑
这段信号函数把两个指标的外轨/上轨拿来做收盘价穿越判定。先说布林带部分:当倒数第二根收盘低于下轨、而前一根收盘站回下轨上方,sig 赋 1,视为向下假突破后的回归多头;反之收盘从上方跌破上轨则 sig 赋 -1。 标准差通道那一段(TradeSignal_10)几乎同构,只是把 bb1/bb2 换成了 sdc1/sdc2,通道周期写死为 14、偏差倍数 2.0。Close[2] 与缓冲 [1] 的比对,意味着信号基于已完成的两根 K 线,实盘里不会在当根未收时误触发。 注意所有 CopyBuffer 都要求至少取 2~3 个单元,且三个数组全部 ArraySetAsSeries(true) 反转序号,否则直接 return(0) 不出信号。外汇与贵金属波动跳空频繁,这种穿越信号在亚盘低波动段假信号概率偏高,建议拿历史数据跑一遍 EURUSD 的 M15 看命中分布。
class="kw">return(class="num">0); if(CopyBuffer(h_bb,class="num">2,class="num">0,class="num">2,bb2_buffer)<class="num">2) class="kw">return(class="num">0); if(CopyClose(Symbol(),Period(),class="num">0,class="num">3,Close)<class="num">3) class="kw">return(class="num">0); if(!ArraySetAsSeries(bb1_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(bb2_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(Close,true)) class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置 sig 的值 if(Close[class="num">2]<=bb2_buffer[class="num">1] && Close[class="num">1]>bb2_buffer[class="num">1]) sig=class="num">1; else if(Close[class="num">2]>=bb1_buffer[class="num">1] && Close[class="num">1]<bb1_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_10() { class="type">int sig=class="num">0; if(h_sdc==INVALID_HANDLE) { h_sdc=iCustom(Symbol(),Period(),"StandardDeviationChannel",class="num">14,class="num">0,MODE_SMA,PRICE_CLOSE,class="num">2.0); class="kw">return(class="num">0); } else { if(CopyBuffer(h_sdc,class="num">0,class="num">0,class="num">2,sdc1_buffer)<class="num">2) class="kw">return(class="num">0); if(CopyBuffer(h_sdc,class="num">1,class="num">0,class="num">2,sdc2_buffer)<class="num">2) class="kw">return(class="num">0); if(CopyClose(Symbol(),Period(),class="num">0,class="num">3,Close)<class="num">3) class="kw">return(class="num">0); if(!ArraySetAsSeries(sdc1_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(sdc2_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(Close,true)) class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置 sig 的值 if(Close[class="num">2]<=sdc2_buffer[class="num">1] && Close[class="num">1]>sdc2_buffer[class="num">1]) sig=class="num">1; else if(Close[class="num">2]>=sdc1_buffer[class="num">1] && Close[class="num">1]<sdc1_buffer[class="num">1]) sig=-class="num">1; else sig=class="num">0;
价格通道与包络线的信号拼接
在 MT5 里把自定义指标和内置指标混用做信号,最怕句柄没初始化就直接读缓冲。下面两段函数分别用 Price Channel(周期 22)和 Envelopes(周期 28、偏离 0.1)来生成多空标记,逻辑上都是先建句柄、再拷贝最近几根 K 线的数据。 TradeSignal_11 里,若上根收盘大于前 2 根通道下轨且再前一根小于等于更前一根下轨,给 sig=1;反向穿透上轨则 sig=-1。这里 pc2_buffer 是通道下轨,索引 [2]、[3] 对应倒数第 3、4 根,属于慢半拍的突破确认,假信号概率可能低于裸 K 穿刺。 TradeSignal_12 的包络线用 SMA 收价、偏离 10%,只拷了 2 根缓冲却要 CopyClose 取 3 根,说明信号判断会用到 Close[2]。外汇与贵金属杠杆高,这类通道类信号在震荡市容易连续反向,实盘前务必用策略测试器跑至少 3 个月 tick 数据。 别把指标周期当金标准 通道 22 和包络 28 都是作者拍的参数,换到 XAUUSD 的 M15 上可能钝化。直接改 iCustom 里的 22 或 iEnvelopes 里的 28,看回测胜率曲线再定。
class="type">int TradeSignal_11() { class="type">int sig=class="num">0; if(h_pc==INVALID_HANDLE) { h_pc=iCustom(Symbol(),Period(),"Price Channel",class="num">22); class="kw">return(class="num">0); } else { if(CopyBuffer(h_pc,class="num">0,class="num">0,class="num">4,pc1_buffer)<class="num">4) class="kw">return(class="num">0); if(CopyBuffer(h_pc,class="num">1,class="num">0,class="num">4,pc2_buffer)<class="num">4) class="kw">return(class="num">0); if(CopyClose(Symbol(),Period(),class="num">0,class="num">3,Close)<class="num">3) class="kw">return(class="num">0); if(!ArraySetAsSeries(pc1_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(pc2_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(Close,true)) class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置 sig 的值 if(Close[class="num">1]>pc2_buffer[class="num">2] && Close[class="num">2]<=pc2_buffer[class="num">3]) sig=class="num">1; else if(Close[class="num">1]<pc1_buffer[class="num">2] && Close[class="num">2]>=pc1_buffer[class="num">3]) sig=-class="num">1; else sig=class="num">0; class=class="str">"cmt">//--- 返回交易信号 class="kw">return(sig); } class="type">int TradeSignal_12() { class="type">int sig=class="num">0; if(h_env==INVALID_HANDLE) { h_env=iEnvelopes(Symbol(),Period(),class="num">28,class="num">0,MODE_SMA,PRICE_CLOSE,class="num">0.1); class="kw">return(class="num">0); } else { if(CopyBuffer(h_env,class="num">0,class="num">0,class="num">2,env1_buffer)<class="num">2) class="kw">return(class="num">0); if(CopyBuffer(h_env,class="num">1,class="num">0,class="num">2,env2_buffer)<class="num">2) class="kw">return(class="num">0); if(CopyClose(Symbol(),Period(),class="num">0,class="num">3,Close)<class="num">3) class="kw">return(class="num">0); if(!ArraySetAsSeries(env1_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(env2_buffer,true)) class="kw">return(class="num">0);
「唐奇安与白银通道的信号拼装」
把包络线逻辑跑通后,剩下两块信号源直接用现成自定义指标接进来就行。唐奇安通道用 24 周期、偏移 3 和 -2 的参数加载,白银通道则取 26 周期配 38.2 / 23.6 / 61.8 这几条斐波那契派生带。 TradeSignal_13 里先拿 iCustom 挂 Donchian Channels,失败就返回 0;随后把上下轨缓冲各拷 3 根、收盘价拷 2 根,并全部 ArraySetAsSeries 倒序。信号判定只看最新收线:Close[1] 破 dc1_buffer[2] 给 +1,跌破 dc2_buffer[2] 给 -1,否则 0。 TradeSignal_14 接 Silver-channels 时只拷 2 根缓冲,参数里末位 0 代表通道线绘制模式。外汇与贵金属杠杆高,这类通道突破信号在震荡市会连续假突破,实盘前务必在 MT5 策略测试器里用真实点差回测。
if(!ArraySetAsSeries(Close,true)) class="kw">return(class="num">0); class=class="str">"cmt">//--- 检查条件并设置 sig 的值 if(Close[class="num">2]<=env2_buffer[class="num">1] && Close[class="num">1]>env2_buffer[class="num">1]) sig=class="num">1; else if(Close[class="num">2]>=env1_buffer[class="num">1] && Close[class="num">1]<env1_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_13() { class="type">int sig=class="num">0; if(h_dc==INVALID_HANDLE) { h_dc=iCustom(Symbol(),Period(),"Donchian Channels",class="num">24,class="num">3,-class="num">2); class="kw">return(class="num">0); } else { if(CopyBuffer(h_dc,class="num">0,class="num">0,class="num">3,dc1_buffer)<class="num">3) class="kw">return(class="num">0); if(CopyBuffer(h_dc,class="num">1,class="num">0,class="num">3,dc2_buffer)<class="num">3) class="kw">return(class="num">0); if(CopyClose(Symbol(),Period(),class="num">0,class="num">2,Close)<class="num">2) class="kw">return(class="num">0); if(!ArraySetAsSeries(dc1_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(dc2_buffer,true)) class="kw">return(class="num">0); if(!ArraySetAsSeries(Close,true)) class="kw">return(class="num">0); } class=class="str">"cmt">//--- 检查条件并设置 sig 的值 if(Close[class="num">1]>dc1_buffer[class="num">2]) sig=class="num">1; else if(Close[class="num">1]<dc2_buffer[class="num">2]) sig=-class="num">1; else sig=class="num">0; class=class="str">"cmt">//--- 返回交易信号 class="kw">return(sig); } class="type">int TradeSignal_14() { class="type">int sig=class="num">0; if(h_sc==INVALID_HANDLE) { h_sc=iCustom(Symbol(),Period(),"Silver-channels",class="num">26,class="num">38.2,class="num">23.6,class="num">0,class="num">61.8); class="kw">return(class="num">0); } else { if(CopyBuffer(h_sc,class="num">0,class="num">0,class="num">2,sc1_buffer)<class="num">2) class="kw">return(class="num">0); if(CopyBuffer(h_sc,class="num">1,class="num">0,class="num">2,sc2_buffer)<class="num">2) class="kw">return(class="num">0);