10 种横盘交易策略的比较分析·进阶篇
📘

10 种横盘交易策略的比较分析·进阶篇

第 2/3 篇

「双均线配布林带的进出场判定」

这套逻辑把慢快均线交叉与布林通道位置揉在一起:当慢线在快线下方且布林上轨低于收盘价时,SellSignal 返回 true,并把布林下轨作为止盈位 tp。外汇与贵金属杠杆高,信号触发也只是概率倾向,不等于稳向下。 买入侧换了一套参考:wwc_low 高于收盘价且 fdi 指标值低于阈值 Inp_FdiThreshold 时,BuySignal 成立,tp 取 wwc_up。两个信号函数都只比当前 0 号柱,不回溯,适合做即时触发类 EA。 GetIndValue 用 CopyBuffer 拉取 2 根柱的布林上/下轨、双均线以及收盘价,任一返回值 <=0 就放弃本轮计算。OnTick 里先查魔数订单是否已开,未开才取数并择一方向开仓,止损用 Inp_StopLoss*_Point 偏移,属于典型的轻量轮询框架。 别把信号函数当独立策略:SellSignal 里 bb_up[0]<close[0] 意味着价格已破上轨外侧,实际回测中这种穿刺后反转的概率随品种波动率变化,建议先开 MT5 用 EURUSD 的 H1 跑一遍看信号密度。

MQL5 / C++
class="type">bool SellSignal()
  {
   if(ma_slow[class="num">0]<ma_fast[class="num">0] && bb_up[class="num">0]<close[class="num">0])
     {
      tp=bb_low[class="num">0];
      class="kw">return true;
     }
   else
      class="kw">return class="kw">false;
  }
class="type">bool GetIndValue()
  {
   class="kw">return(CopyBuffer(InpInd_Handle1,class="num">1,class="num">0,class="num">2,bb_up)<=class="num">0  ||
          CopyBuffer(InpInd_Handle1,class="num">2,class="num">0,class="num">2,bb_low)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">0,class="num">0,class="num">2,ma_slow)<=class="num">0 ||
          CopyBuffer(InpInd_Handle3,class="num">0,class="num">0,class="num">2,ma_fast)<=class="num">0 ||
          CopyClose(Symbol(),PERIOD_CURRENT,class="num">0,class="num">2,close)<=class="num">0
          )?class="kw">false:true;
  }
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 检查以前由该 EA 开立的订单
   if(!Trade.IsOpenedByMagic(Inp_MagicNum))
     {
      class=class="str">"cmt">//--- 获取数据进行计算
      if(!GetIndValue())
         class="kw">return;
      class=class="str">"cmt">//--- 如果有买入信号,则开单
      if(BuySignal())
         Trade.BuyPositionOpen(Symbol(),Inp_Lot,close[class="num">0]-Inp_StopLoss*_Point,tp,Inp_MagicNum,Inp_EaComment);
      class=class="str">"cmt">//--- 如果有卖出信号,则开单
      if(SellSignal())
         Trade.SellPositionOpen(Symbol(),Inp_Lot,close[class="num">0]+Inp_StopLoss*_Point,tp,Inp_MagicNum,Inp_EaComment);
     }
  }
class="type">bool BuySignal()
  {
   if(wwc_low[class="num">0]>close[class="num">0] && fdi[class="num">0]<Inp_FdiThreshold)
     {
      tp=wwc_up[class="num">0];
      class="kw">return true;
     }
   else
      class="kw">return class="kw">false;
  }

信号函数与 OnTick 的实盘触发链

这套 EA 把开仓逻辑拆成了几个布尔函数,核心在 SellSignal 与 BuySignal,二者都依赖外部指标拷贝进来的数组。SellSignal 要求 wwc_up[0] 小于当前收盘价,且 fdi[0] 低于阈值 Inp_FdiThreshold,满足条件就把 wwc_low[0] 赋给 tp 并返回 true,否则直接 false。 GetIndValue 负责把指标缓冲拉进本地数组:用 CopyBuffer 取句柄1的第3、2号缓冲到 wwc_up、wwc_low,句柄2的第0号缓冲到 fdi,再用 CopyClose 取最近2根收盘价到 close。任一拷贝返回小于等于0就整体返回 false,阻断后续计算。 OnTick 里先判断 Trade.IsOpenedByMagic 确认没有同魔法码持仓;若为空仓且 GetIndValue 通过,就先后测 BuySignal 与 SellSignal。BuySignal 条件为 pcc_low[0] 大于 close[0] 且 tr_flat[0] 大于 tr_range[0],命中则 tp 取 pcc_up[0]。 开仓时止损用 Inp_StopLoss*_Point 偏移:买单调 close[0]-偏移,卖单调 close[0]+偏移,tp 来自信号函数。外汇与贵金属波动剧烈,这类条件单在高滑点时段可能触发后偏离预期,建议先在 MT5 策略测试器用历史数据跑一遍验证缓冲索引是否与你加载的指标一致。

MQL5 / C++
class=class="str">"cmt">//| 卖出条件   
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SellSignal()
  {
   if(wwc_up[class="num">0]<close[class="num">0] && fdi[class="num">0]<Inp_FdiThreshold)
    {
      tp=wwc_low[class="num">0];
      class="kw">return true;
    }
   else
      class="kw">return class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 获取指标的当前值
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool GetIndValue()
  {
   class="kw">return(CopyBuffer(InpInd_Handle1,class="num">3,class="num">0,class="num">2,wwc_up)<=class="num">0  ||
          CopyBuffer(InpInd_Handle1,class="num">2,class="num">0,class="num">2,wwc_low)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">0,class="num">0,class="num">2,fdi)<=class="num">0 ||
          CopyClose(Symbol(),PERIOD_CURRENT,class="num">0,class="num">2,close)<=class="num">0
          )?class="kw">false:true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 检查以前由该 EA 开立的订单
   if(!Trade.IsOpenedByMagic(Inp_MagicNum))
    {
      class=class="str">"cmt">//--- 获取数据进行计算
      if(!GetIndValue())
         class="kw">return;
      class=class="str">"cmt">//--- 如果有买入信号,则开单
      if(BuySignal())
         Trade.BuyPositionOpen(Symbol(),Inp_Lot,close[class="num">0]-Inp_StopLoss*_Point,tp,Inp_MagicNum,Inp_EaComment);
      class=class="str">"cmt">//--- 如果有卖出信号,则开单
      if(SellSignal())
         Trade.SellPositionOpen(Symbol(),Inp_Lot,close[class="num">0]+Inp_StopLoss*_Point,tp,Inp_MagicNum,Inp_EaComment);
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 买入条件
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool BuySignal()
  {
   if(pcc_low[class="num">0]>close[class="num">0] && tr_flat[class="num">0]>tr_range[class="num">0])
    {
      tp=pcc_up[class="num">0];
      class="kw">return true;
    }
   else
      class="kw">return class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 信号函数与 OnTick 的实盘接管逻辑

这段 EA 核心把「信号判定」和「订单执行」拆成了独立函数,开 MT5 后你能直接照抄进自己的 mq5 文件里跑。SellSignal 里用 pcc_up[0]<close[0] 且 tr_flat[0]>tr_range[0] 作为空单触发条件,命中就把 pcc_low[0] 设为止盈价 tp 并返回 true,否则返回 false。 GetIndValue 负责把两个自定义指标的缓冲区和当前收盘价拉进数组,CopyBuffer 分别取 InpInd_Handle1 的 0/2 号线与 InpInd_Handle2 的 1/2 号线,各取 2 根 K 线;任意一处返回值 ≤0 就整体返回 false,阻断后续计算。 OnTick 只在 Trade.IsOpenedByMagic 确认当前魔术码无持仓时才动作。先调 GetIndValue 拿到数据,再并行判断 BuySignal / SellSignal,开仓时止损用 close[0]±Inp_StopLoss*_Point 动态算出,止盈接 tp 变量。外汇与贵金属杠杆高,这种自动接管逻辑回测胜率会随参数漂移,实盘前务必用策略测试器跑至少 3 个月 tick 数据。 BuySignal 的判定是 pc_low[0]>close[0] 且 rbvi[0]<=Inp_level,达标则 tp=pc_up[0]。注意原文 SellSignal 在文末被重复声明了一次,编译时留一个即可,避免重定义报错。

MQL5 / C++
class="type">bool SellSignal()
  {
   if(pcc_up[class="num">0]<close[class="num">0] && tr_flat[class="num">0]>tr_range[class="num">0])
     {
       tp=pcc_low[class="num">0];
       class="kw">return true;
     }
   else
       class="kw">return class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 获取指标的当前值                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool GetIndValue()
  {
   class="kw">return(CopyBuffer(InpInd_Handle1,class="num">0,class="num">0,class="num">2,pcc_up)<=class="num">0  ||
          CopyBuffer(InpInd_Handle1,class="num">2,class="num">0,class="num">2,pcc_low)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">1,class="num">0,class="num">2,tr_flat)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">2,class="num">0,class="num">2,tr_range)<=class="num">0 ||
          CopyClose(Symbol(),PERIOD_CURRENT,class="num">0,class="num">2,close)<=class="num">0
          )?class="kw">false:true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 检查以前由该 EA 开立的订单
   if(!Trade.IsOpenedByMagic(Inp_MagicNum))
     {
       class=class="str">"cmt">//--- 获取数据进行计算
       if(!GetIndValue())
         class="kw">return;
       class=class="str">"cmt">//--- 如果有买入信号,则开单
       if(BuySignal())
         Trade.BuyPositionOpen(Symbol(),Inp_Lot,close[class="num">0]-Inp_StopLoss*_Point,tp,Inp_MagicNum,Inp_EaComment);
       class=class="str">"cmt">//--- 如果有卖出信号,则开单
       if(SellSignal())
         Trade.SellPositionOpen(Symbol(),Inp_Lot,close[class="num">0]+Inp_StopLoss*_Point,tp,Inp_MagicNum,Inp_EaComment);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 买入条件                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool BuySignal()
  {
   if(pc_low[class="num">0]>close[class="num">0] && rbvi[class="num">0]<=Inp_level)
     {
       tp=pc_up[class="num">0];
       class="kw">return true;
     }
   else
       class="kw">return class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 卖出条件                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SellSignal()
  {

「信号判定与 OnTick 调度的代码骨架」

这段逻辑把指标取值、信号函数和主循环拆得很干净。GetIndValue 一次性拉取两个自定义指标缓冲区和收盘价,任意 CopyBuffer/CopyClose 返回 ≤0 就直接判 false,避免脏数据进信号判断。 BuySignal 要求 wpr[0] < -80 且 adx[0] < Inp_FlatLevel,也就是商品通道类指标进入超卖、同时 ADX 低于震荡阈值才视为多头倾向;SellSignal 对称地用 wpr[0] >= -20 抓超买。注意这里 ADX 被当成平盘过滤器,并非趋势强度开关。 OnTick 里先用 Trade.IsOpenedByMagic 拦掉同魔法码已持仓的情况,再走 GetIndValue → BuySignal/SellSignal 的开仓链路。若你接的是自己的指标句柄,把 InpInd_Handle1/2 换成实际 Handle,并确认 pc_up、pc_low、rbvi 的缓冲序号与指标输出一致,否则可能永远不触发。 外汇与贵金属杠杆高,这类震荡+平盘过滤组合在趋势段可能连续踏空,实盘前务必在 MT5 策略测试器用至少 3 个月 Tick 数据验证信号频率。

MQL5 / C++
if(pc_up[class="num">0]<close[class="num">0] && rbvi[class="num">0]<=Inp_level)
    {
      tp=pc_low[class="num">0];
      class="kw">return true;
    }
  else
      class="kw">return class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 获取指标的当前值                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool GetIndValue()
  {
   class="kw">return(CopyBuffer(InpInd_Handle1,class="num">0,class="num">0,class="num">2,pc_up)<=class="num">0  ||
          CopyBuffer(InpInd_Handle1,class="num">1,class="num">0,class="num">2,pc_low)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">0,class="num">0,class="num">2,rbvi)<=class="num">0 ||
          CopyClose(Symbol(),PERIOD_CURRENT,class="num">0,class="num">2,close)<=class="num">0
          )?class="kw">false:true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 检查以前由该 EA 开立的订单
   if(!Trade.IsOpenedByMagic(Inp_MagicNum))
     {
      class=class="str">"cmt">//--- 获取数据进行计算
       if(!GetIndValue())
         class="kw">return;
      class=class="str">"cmt">//--- 如果有买入信号,则开单
       if(BuySignal())
         Trade.BuyPositionOpen(Symbol(),Inp_Lot,Inp_StopLoss,Inp_TakeProfit,Inp_MagicNum,Inp_EaComment);
      class=class="str">"cmt">//--- 如果有卖出信号,则开单
       if(SellSignal())
         Trade.SellPositionOpen(Symbol(),Inp_Lot,Inp_StopLoss,Inp_TakeProfit,Inp_MagicNum,Inp_EaComment);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 买入条件                                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool BuySignal()
  {
   class="kw">return(wpr[class="num">0]<-class="num">80 && adx[class="num">0]<Inp_FlatLevel)?true:class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 卖出条件                                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SellSignal()
  {
   class="kw">return(wpr[class="num">0]>=-class="num">20 && adx[class="num">0]<Inp_FlatLevel)?true:class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 获取指标的当前值                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+

用 Keltner 通道和动量过滤开仓信号

这段逻辑把指标取数和下单判断拆得很干净。GetIndValue 负责把 Keltner 通道上下轨(kc_up、kc_low)和动量数组(mt)通过 CopyBuffer 拉进本地,每次取 2 根柱(起始 0、数量 2),任一缓冲区拷贝返回值 ≤0 就视为取数失败,直接返回 false 阻断后续。 OnTick 里先查当前 Magic 号下有没有未平仓位,没有才继续。取数成功后分别测 BuySignal 与 SellSignal,两者互斥靠信号函数自身条件约束,不在这里做排他。 买入条件写得极简:当根 Keltner 下轨高于收盘价,且动量值 mt[0]==mt[1](动量走平)时,把上轨 kc_up[0] 设为止盈位并返回 true。卖出反过来,上轨低于收盘价且动量走平,止盈挂下轨。外汇与贵金属波动大,这种通道+动量走平组合在震荡市可能提高胜率,但趋势行情中容易连续假信号,实盘前务必在 MT5 策略测试器跑至少 3 个月 tick 数据。 让小布替你跑这套 把 Inp_StopLoss 从默认 300 点调到 150 点,在 XAUUSD 的 H1 上回测,观察 2023 年 Q4 的回撤是否从 18% 降到 9% 附近,再决定跟不跟。

MQL5 / C++
class="type">bool GetIndValue()
  {
   class="kw">return(CopyBuffer(InpInd_Handle1,class="num">0,class="num">0,class="num">2,wpr)<=class="num">0  ||
          CopyBuffer(InpInd_Handle2,class="num">0,class="num">0,class="num">2,adx)<=class="num">0)?class="kw">false:true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 检查以前由该 EA 开立的订单
   if(!Trade.IsOpenedByMagic(Inp_MagicNum))
     {
class=class="str">"cmt">//--- 获取数据进行计算
      if(!GetIndValue())
         class="kw">return;
class=class="str">"cmt">//--- 如果有买入信号,则开单
      if(BuySignal())
         Trade.BuyPositionOpen(Symbol(),Inp_Lot,close[class="num">0]-Inp_StopLoss*_Point,tp,Inp_MagicNum,Inp_EaComment);
class=class="str">"cmt">//--- 如果有卖出信号,则开单
      if(SellSignal())
         Trade.SellPositionOpen(Symbol(),Inp_Lot,close[class="num">0]+Inp_StopLoss*_Point,tp,Inp_MagicNum,Inp_EaComment);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 买入条件                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool BuySignal()
  {
   if(kc_low[class="num">0]>close[class="num">0] && mt[class="num">0]==mt[class="num">1])
     {
      tp=kc_up[class="num">0];
      class="kw">return true;
     }
   else
      class="kw">return class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 卖出条件                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool SellSignal()
  {
   if(kc_up[class="num">0]<close[class="num">0] && mt[class="num">0]==mt[class="num">1])
     {
      tp=kc_low[class="num">0];
      class="kw">return true;
     }
   else
      class="kw">return class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 获取指标的当前值                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool GetIndValue()
  {
   class="kw">return(CopyBuffer(InpInd_Handle1,class="num">0,class="num">0,class="num">2,kc_up)<=class="num">0  ||
          CopyBuffer(InpInd_Handle1,class="num">2,class="num">0,class="num">2,kc_low)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">0,class="num">0,class="num">2,mt)<=class="num">0 ||

◍ 信号判定与开单的执行骨架

EA 的主循环放在 OnTick 里:先用 Trade.IsOpenedByMagic 确认当前 Magic 号下没有已开仓位,避免重复下单。若为空仓,则调用 GetIndValue 拉取通道上下轨与状态数组,任一缓冲区拷贝失败(返回值 ≤0)就直接 return,不进场。 买入逻辑看 BuySignal:当最新下轨 dc_low[0] 高于收盘价 close[0],且状态量 ti[0]==0 时,把止盈 tp 设为上轨 dc_up[0] 并返回 true。卖出则反过来,dc_up[0] 低于收盘价且 ti[0]==0 时,tp 取下轨 dc_low[0]。这两个条件是互斥设计,同一根 K 线不会双向触发。 实际下单时止损按 Inp_StopLoss*_Point 偏移:买单价减该值做止损,卖单价加该值。外汇与贵金属杠杆高,这种固定点数止损在跳空时可能滑点到更远,回测之外需用模拟盘验证。 把下面这段直接塞进 mq5 文件就能跑通骨架,参数句柄 InpInd_Handle1/2 需在 OnInit 里用 iCustom 拿到,否则 CopyBuffer 全返回 0。

MQL5 / C++
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 检查以前由该 EA 开立的订单
   if(!Trade.IsOpenedByMagic(Inp_MagicNum))
     {
      class=class="str">"cmt">//--- 获取数据进行计算
       if(!GetIndValue())
         class="kw">return;
       class=class="str">"cmt">//--- 如果有买入信号,则开单
       if(BuySignal())
         Trade.BuyPositionOpen(Symbol(),Inp_Lot,close[class="num">0]-Inp_StopLoss*_Point,tp,Inp_MagicNum,Inp_EaComment);
      class=class="str">"cmt">//--- 如果有卖出信号,则开单
       if(SellSignal())
         Trade.SellPositionOpen(Symbol(),Inp_Lot,close[class="num">0]+Inp_StopLoss*_Point,tp,Inp_MagicNum,Inp_EaComment);
     }
  }

class="type">bool BuySignal()
  {
   if(dc_low[class="num">0]>close[class="num">0] && ti[class="num">0]==class="num">0)
     {
      tp=dc_up[class="num">0];
      class="kw">return true;
     }
   else
      class="kw">return class="kw">false;
  }

class="type">bool SellSignal()
  {
   if(dc_up[class="num">0]<close[class="num">0] && ti[class="num">0]==class="num">0)
     {
      tp=dc_low[class="num">0];
      class="kw">return true;
     }
   else
      class="kw">return class="kw">false;
  }

class="type">bool GetIndValue()
  {
   class="kw">return(CopyBuffer(InpInd_Handle1,class="num">0,class="num">0,class="num">2,dc_up)<=class="num">0  ||
          CopyBuffer(InpInd_Handle1,class="num">1,class="num">0,class="num">2,dc_low)<=class="num">0 ||
          CopyBuffer(InpInd_Handle2,class="num">0,class="num">0,class="num">2,ti)<=class="num">0     ||
          CopyClose(Symbol(),PERIOD_CURRENT,class="num">0,class="num">2,close)<=class="num">0
          )?class="kw">false:true;
  }

常见问题

看价格触碰布林带外轨后,双均线是否未张口;若均线粘合且价格回靠中轨,倾向视为假突破,不宜追单。
在 OnTick 中先调用信号函数做判定,返回 true 再走执行骨架开仓;把过滤条件前置,减少无效下单。
小布可加载对应品种页,实时标注双均线配布林带的进出场判定,并提示 Keltner 加动量过滤后的开仓信号,省去手动刷图。
设动量阈值过滤小幅波动,仅当价格破通道且动量超阈值才开仓,实盘概率上能压低噪音单。
在代码骨架里加交易时段判断,非活跃时段直接跳过 OnTick 调度,可规避流动性差时的异常触发。