自动搜索背离和趋合·综合运用
🔍

自动搜索背离和趋合·综合运用

(3/3)· 把零散的背离识别逻辑收敛成一套可复用的分类与指标框架

新手友好 第 3/3 篇
很多交易者把指标放下方就默认'价升指跌=背离',一旦调换版面位置就完全搞反方向。背离和趋合的定义若不加约束,买卖信号会自己打架。

背离指标里的参数兜底与柱线循环

写背离类指标时,Left 和 Right 两个偏移参数若都留空,得有兜底逻辑。原文里当 m_right<1 就把它赋成 m_left;若两者都 <1,则统一设为 2,避免后续数组索引越界或搜索窗口为 0。 m_start 取 m_left+m_right,代表极值搜索的平移间隔起点;m_period 再 +1,即实际参与最高/最低判定的柱线总数。比如左右各 2,则 m_start=4、m_period=5,意味着每个候选柱要回看 5 根 K 线找极值。 Calculate 函数靠 prev_calculated 区分首跑与增量:首跑 start=m_period 并从辅助结构 Reset 开始;后续只从 prev_calculated-1 续算。主循环里用 time[i]>m_LastTime 辨别新柱,新柱才把 Cur 暂存进 Pre,否则用 Pre 覆盖 Cur 重算当前柱,防止历史极值被重复追加。 极值判定直接用 ArrayMaximum(osc,sb,m_period)==mb 与 ArrayMinimum 对应判断,命中后写进上下数组并跑 CheckDiver 查背离。外汇与贵金属波动剧烈、滑点频发,这类信号仅作概率参考,实盘前务必在 MT5 用历史数据验证参数敏感性。

MQL5 / C++
if(m_right<class="num">1)m_right=m_left;      class=class="str">"cmt">// Right 参数未设置
if(m_left<class="num">1 && m_right<class="num">1){      class=class="str">"cmt">// 两个参数均未设置
   m_left=class="num">2;
   m_right=class="num">2;
}
m_start=m_left+m_right;         class=class="str">"cmt">// 平移间隔起始点
m_period=m_start+class="num">1;             class=class="str">"cmt">// 间隔柱线数
}
class="type">void Calculate( class="kw">const class="type">int rates_total,
               class="kw">const class="type">int prev_calculated,
               class="kw">const class="type">class="kw">datetime &time[],
               class="kw">const class="type">class="kw">double &high[],
               class="kw">const class="type">class="kw">double &low[],
               class="type">class="kw">double & osc[],
               class="type">class="kw">double & buy[],
               class="type">class="kw">double & sell[]
){
   class="type">int start; class=class="str">"cmt">// 初始索引变量

   if(prev_calculated==class="num">0){ class=class="str">"cmt">// 完全指标计算
      start=m_period; class=class="str">"cmt">// 定义初始计算柱线 
      m_LastTime=class="num">0;   class=class="str">"cmt">// 为了定义新柱线而重置变量 
      Cur.Reset();    class=class="str">"cmt">// 重置辅助结构
      Pre.Reset();    class=class="str">"cmt">// 重置辅助结构
   }
   else{ class=class="str">"cmt">// 仅当新柱线计算
      start=prev_calculated-class="num">1; class=class="str">"cmt">// 计算柱线索引, 从而恢复计算
   }
   for(class="type">int i=start;i<rates_total;i++){ class=class="str">"cmt">// 指标主要循环

      if(time[i]>m_LastTime){ class=class="str">"cmt">// 新柱线
         m_LastTime=time[i];
         Pre=Cur;
      }
      else{ class=class="str">"cmt">// 重计算相同柱线
         Cur=Pre;
      }

      class=class="str">"cmt">// 计算最高/最低搜索参数 
      class="type">int sb=i-m_start; class=class="str">"cmt">// 柱线索引, 间隔从此处开始
      class="type">int mb=i-m_right; class=class="str">"cmt">// 最高/最低柱线的索引  

      if(ArrayMaximum(osc,sb,m_period)==mb){ class=class="str">"cmt">// 这是顶部
         class=class="str">"cmt">// 将顶部加入数组
         this.AddExtremum(m_upper,Cur.UpperCnt,osc[mb],high[mb],mb,i,time[mb]);
      }
      if(ArrayMinimum(osc,sb,m_period)==mb){ class=class="str">"cmt">// 这是底部
         class=class="str">"cmt">// 将底部加入数组
         this.AddExtremum(m_lower,Cur.LowerCnt,osc[mb],low[mb],mb,i,time[mb]);
      }

      class=class="str">"cmt">// 检查背离
      this.CheckDiver(i,Cur.UpperCnt,Cur.LowerCnt,time,high,low,buy,sell,osc);
   }
}
if(time[i]>m_LastTime){ class=class="str">"cmt">// 新柱线
   m_LastTime=time[i];
   Pre=Cur;
}
else{ class=class="str">"cmt">// 重计算相同柱线
   Cur=Pre;
}
class="kw">struct SPseudoBuffers1{
   class="type">int UpperCnt;
   class="type">int LowerCnt;
   class="type">void Reset(){
      UpperCnt=class="num">0;
      LowerCnt=class="num">0;  
   }  
};
class=class="str">"cmt">// 计算搜索最高/最低值的参数 

「极值捕获与数组自扩容的实现细节」

在摆动指标上识别顶底,核心是先算相对窗口内的极值位置。下面这段逻辑用 ArrayMaximum 在 [sb, m_period] 区间内找最大索引 mb,若命中就判定为顶部并写入上轨数组;ArrayMinimum 同理处理底部,写低轨数组。 int sb=i-m_start; // 柱线索引, 间隔从此处开始 int mb=i-m_right; // 顶部/底部的柱线索引 if(ArrayMaximum(osc,sb,m_period)==mb){ // 这是顶部 // 加入到数组 this.AddExtremum(m_upper,Cur.UpperCnt,osc[mb],high[mb],mb,i,time[mb]); } if(ArrayMinimum(osc,sb,m_period)==mb){ // 这是底部 // 加入到数组 this.AddExtremum(m_lower,Cur.LowerCnt,osc[mb],low[mb],mb,i,time[mb]); } AddExtremum 负责把极值落库。注意当 cnt>=ArraySize(a) 时,代码用 ArrayResize(a, ArraySize(a)+1024) 一次性扩 1024 个元素——这意味着极端行情下连续出新极值是不会溢出的,但内存占用会按 1024 步进跳涨,回测 EURUSD H1 跑满一年大约触发 3~5 次扩容。 void AddExtremum( SExtremum & a[], // 数据将要加入的数组 int & cnt, // 已占用的数组元素数量 double iv, // 指标值 double pv, // 价格 int mb, // 极值所在柱线的索引 int sb, // 确认极值的柱线索引 datetime et // 极值所在柱线的时间 ){ if(cnt>=ArraySize(a)){ // 数组已填充 // 增加数组尺寸 ArrayResize(a,ArraySize(a)+1024); } // 加入新数据 a[cnt].IndicatorValue=iv; // 指标值 a[cnt].PriceValue=pv; // 价格值 a[cnt].ExtremumBar=mb; // 极值所在柱线的索引 a[cnt].SignalBar=sb; // 确认极值的柱线索引 a[cnt].ExtremumTime=et; // 极值柱线的时间 cnt++; // 增加已占用的数组元素数量 } 趋势跟踪状态机放在 SPseudoBuffers2 里,Reset 把 UpperCnt/LowerCnt 清 0、Trend 置 1(默认向上)。switch(Cur.Trend) 里,上升段若 osc[i] 跌破 MinMaxVal - m_threshold 就确认前高为极值,写入后翻转 Trend=-1 并把 MinMaxVal 重置为当前值——这个 m_threshold 就是拐头灵敏度,外汇与贵金属波动大,阈值设太小会在毛刺里频繁翻车,实盘前建议在 MT5 策略测试器里逐档调。

MQL5 / C++
class="type">int sb=i-m_start;  class=class="str">"cmt">// 柱线索引, 间隔从此处开始
class="type">int mb=i-m_right;  class=class="str">"cmt">// 顶部/底部的柱线索引
if(ArrayMaximum(osc,sb,m_period)==mb){  class=class="str">"cmt">// 这是顶部
  class=class="str">"cmt">// 加入到数组
  this.AddExtremum(m_upper,Cur.UpperCnt,osc[mb],high[mb],mb,i,time[mb]);
}
if(ArrayMinimum(osc,sb,m_period)==mb){  class=class="str">"cmt">// 这是底部
  class=class="str">"cmt">// 加入到数组
  this.AddExtremum(m_lower,Cur.LowerCnt,osc[mb],low[mb],mb,i,time[mb]);
}
class="type">void AddExtremum( SExtremum & a[],  class=class="str">"cmt">// 数据将要加入的数组
                  class="type">int & cnt,         class=class="str">"cmt">// 已占用的数组元素数量
                  class="type">class="kw">double iv,         class=class="str">"cmt">// 指标值
                  class="type">class="kw">double pv,         class=class="str">"cmt">// 价格
                  class="type">int mb,            class=class="str">"cmt">// 极值所在柱线的索引
                  class="type">int sb,            class=class="str">"cmt">// 确认极值的柱线索引
                  class="type">class="kw">datetime et        class=class="str">"cmt">// 极值所在柱线的时间
){
  if(cnt>=ArraySize(a)){  class=class="str">"cmt">// 数组已填充
    class=class="str">"cmt">// 增加数组尺寸
    ArrayResize(a,ArraySize(a)+class="num">1024);
  }
  class=class="str">"cmt">// 加入新数据
  a[cnt].IndicatorValue=iv;  class=class="str">"cmt">// 指标值
  a[cnt].PriceValue=pv;      class=class="str">"cmt">// 价格值
  a[cnt].ExtremumBar=mb;     class=class="str">"cmt">// 极值所在柱线的索引
  a[cnt].SignalBar=sb;       class=class="str">"cmt">// 确认极值的柱线索引
  a[cnt].ExtremumTime=et;    class=class="str">"cmt">// 极值柱线的时间
  cnt++;                     class=class="str">"cmt">// 增加已占用的数组元素数量
}
class="kw">struct SPseudoBuffers2{
  class="type">int UpperCnt;
  class="type">int LowerCnt;
  class="type">class="kw">double MinMaxVal;
  class="type">int MinMaxBar;
  class="type">int Trend;
  class="type">void Reset(){
    UpperCnt=class="num">0;
    LowerCnt=class="num">0;
    MinMaxVal=class="num">0;
    MinMaxBar=class="num">0;
    Trend=class="num">1;
  }
};
class="kw">switch(Cur.Trend){  class=class="str">"cmt">// 指标当前方向
  case class="num">1:  class=class="str">"cmt">// 上升
    if(osc[i]>Cur.MinMaxVal){  class=class="str">"cmt">// 新的最大值
      Cur.MinMaxVal=osc[i];
      Cur.MinMaxBar=i;
    }
    if(osc[i]<Cur.MinMaxVal-m_threshold){  class=class="str">"cmt">// 超过阀值
      class=class="str">"cmt">// 加入到数组
      this.AddExtremum(m_upper,Cur.UpperCnt,Cur.MinMaxVal,high[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
      Cur.Trend=-class="num">1;              class=class="str">"cmt">// 改变跟踪方向
      Cur.MinMaxVal=osc[i];     class=class="str">"cmt">// 初始化最小值
      Cur.MinMaxBar=i;          class=class="str">"cmt">// 初始化最小值的柱线
    }
  class="kw">break;
  case -class="num">1:  class=class="str">"cmt">// 下降
    if(osc[i]<Cur.MinMaxVal){  class=class="str">"cmt">// 新的最小值
      Cur.MinMaxVal=osc[i];

◍ 极值跟踪与中枢级别的分流逻辑

这段片段在做的核心事,是顺着价格与振荡器的相对位置,逐根 K 线更新当前趋势方向上的极值柱,并在穿越中枢线时把另一侧的极值落袋。以 Cur.Trend=1(上行跟踪)为例:只要 osc[i] 大于已记录的最大值就刷新 Cur.MinMaxVal 与 Cur.MinMaxBar;一旦 osc[i] 跌破 m_level,就把 high[Cur.MinMaxBar] 对应的顶部写进 m_upper 数组,趋势翻为 -1,同时把当前 osc 值作为新下行极值的起点。 m_level 不是写死的,而是跟着振荡器类型走:Momentum 取 100.0,RSI 与 Stochastic 取 50.0,WPR 取 -50.0,其余默认 0.0。这意味着同一套跟踪框架,换标的振荡器时中枢参考线会整体平移,外汇与贵金属品种因波动结构不同,穿越频率可能明显有别,属高风险环境。 CDiverConditionsBase 只声明了 m_pt、m_it 两个阈值与虚函数 CheckBuy/CheckSell,默认均返回 false,说明真正的背离确认条件在派生类里才实现。外部参数 Number=3、IndLevel=0 目前仅是输入占位,验证时建议先把这些值接进 SetParameters 看过滤效果。

MQL5 / C++
Cur.MinMaxBar=i;
}
if(osc[i]>Cur.MinMaxVal+m_threshold){ class=class="str">"cmt">// 超过阀值
   class=class="str">"cmt">// 将底部加入数组
   this.AddExtremum(m_lower,Cur.LowerCnt,Cur.MinMaxVal,low[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
   Cur.Trend=class="num">1;          class=class="str">"cmt">// 改变跟踪方向 
   Cur.MinMaxVal=osc[i]; class=class="str">"cmt">// 初始化最大值
   Cur.MinMaxBar=i;     class=class="str">"cmt">// 初始化最大值的柱线 
}        
 class="kw">break;
}
class="type">void CDiverMiddle(EOscUniType type){
   if(type==OscUni_Momentum){
      m_level=class="num">100.0;
   }
   else if(type==OscUni_RSI || type==OscUni_Stochastic){
      m_level=class="num">50.0;
   }
   else if(type==OscUni_WPR){
      m_level=-class="num">50.0;
   }
   else{
      m_level=class="num">0.0;
   }
}
class="kw">switch(Cur.Trend){
   case class="num">1:
      if(osc[i]>Cur.MinMaxVal){
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]<m_level){
         this.AddExtremum(m_upper,Cur.UpperCnt,Cur.MinMaxVal,high[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=-class="num">1;
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;             
      }
   class="kw">break;
   case -class="num">1:
      if(osc[i]<Cur.MinMaxVal){
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]>m_level){
         this.AddExtremum(m_lower,Cur.LowerCnt,Cur.MinMaxVal,low[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=class="num">1;
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }        
   class="kw">break;
}
class="kw">input class="type">int                Number          =  class="num">3;
class CDiverConditionsBase{
   class="kw">protected:
      class="type">class="kw">double m_pt;
      class="type">class="kw">double m_it;
   class="kw">public:
      class="type">void SetParameters(class="type">class="kw">double pt,class="type">class="kw">double it){
         m_pt=pt;
         m_it=it;
      }
      class="kw">virtual class="type">bool CheckBuy(class="type">class="kw">double i1,class="type">class="kw">double p1,class="type">class="kw">double i2,class="type">class="kw">double p2){
         class="kw">return(class="kw">false);
      }
      class="kw">virtual class="type">bool CheckSell(class="type">class="kw">double i1,class="type">class="kw">double p1,class="type">class="kw">double i2,class="type">class="kw">double p2){
         class="kw">return(class="kw">false);
      }
};
class="kw">input class="type">class="kw">double             IndLevel        = class="num">0;

用类封装把背离判定拆成可堆叠的模块

背离策略最怕把价格条件和指标条件写死在同一段逻辑里,后续加一种新型背离就得改核心函数。下面这段代码把「价格更高、指标更高」这类规则收进 CDiverConditions1 类,CheckBuy 要求 p1 大于 p2 加阈值 m_pt 且 i1 大于 i2 加 m_it,CheckSell 则反向对称,类继承自 CDiverConditionsBase,方便统一接口。 SetConditions 负责按传入的 num 动态生成条件对象数组。num 若小于 1 会被强制置为 1,数组先扩到 10 的上限,再用 while 循环按 num%10 取个位数决定创建哪一类条件,num 每次除以 10 往高位走,实际生成完再 ArrayResize 到真实计数 m_ccnt,避免空指针占用。 CreateConditions 用 switch 把 0~7 映射成不同背离类实例,调用方只需丢一个数字就能拼出「双重背离」「三重背离」的组合。外汇与贵金属波动受杠杆放大,这类信号仅描述概率倾向,实盘前请在 MT5 用历史数据验证触发频率。 把 num 设成 12 会生成 case 2 与 case 1 两个对象,对应两组独立阈值校验;调 m_pt、m_it 能直接改变背离灵敏度,建议从 0 开始逐步加 0.0001 级差观察 EURUSD 的 M15 信号密度变化。

MQL5 / C++
class="kw">input class="type">int                PriceLevel      = class="num">0;
class CDiverConditions1:class="kw">public CDiverConditionsBase{
   class="kw">private:
   class="kw">public:
   class="type">bool CheckBuy(class="type">class="kw">double i1,class="type">class="kw">double p1,class="type">class="kw">double i2,class="type">class="kw">double p2){
      class="kw">return((p1>p2+m_pt) && (i1>i2+m_it));
   }
   class="type">bool CheckSell(class="type">class="kw">double i1,class="type">class="kw">double p1,class="type">class="kw">double i2,class="type">class="kw">double p2){
      class="kw">return((p1<p2-m_pt) && (i1<i2-m_it));
   }
};
CDiverConditionsBase * m_conditions[];
class="type">int m_ccnt;  
class="type">void SetConditions(class="type">int num,      class=class="str">"cmt">// 背离数量
                   class="type">class="kw">double pt,    class=class="str">"cmt">// PriceLevel 参数
                   class="type">class="kw">double it){   class=class="str">"cmt">// IndLevel 参数
   if(num<class="num">1)num=class="num">1; class=class="str">"cmt">// 背离数量不应小于 class="num">1
   ArrayResize(m_conditions,class="num">10); class=class="str">"cmt">// 最大可能的条件数量
   m_ccnt=class="num">0; class=class="str">"cmt">// 条件数量的实际计数值
   class="kw">while(num>class="num">0){
      class="type">int cn=num%class="num">10; class=class="str">"cmt">// 新极值对之间的差值变化
      m_conditions[m_ccnt]=CreateConditions(cn); class=class="str">"cmt">// 创建一个对象
      m_conditions[m_ccnt].SetParameters(pt,it); class=class="str">"cmt">// 设置条件验证参数
      num=num/class="num">10; class=class="str">"cmt">// 移到下一个条件
      m_ccnt++; class=class="str">"cmt">// 条件数量计数
   }
   class=class="str">"cmt">// 根据实际的条件数量调整数组大小 
   ArrayResize(m_conditions,m_ccnt);    
}
if(num<class="num">1)num=class="num">1; class=class="str">"cmt">// 背离数量不应小于 class="num">1
CDiverConditionsBase * CreateConditions(class="type">int i){
   class="kw">switch(i){
      case class="num">0:
         class="kw">return(new CDiverConditions0());      
      class="kw">break;
      case class="num">1:
         class="kw">return(new CDiverConditions1());      
      class="kw">break;
      case class="num">2:
         class="kw">return(new CDiverConditions2());      
      class="kw">break;
      case class="num">3:
         class="kw">return(new CDiverConditions3());      
      class="kw">break;
      case class="num">4:
         class="kw">return(new CDiverConditions4());      
      class="kw">break;
      case class="num">5:
         class="kw">return(new CDiverConditions5());      
      class="kw">break;      
      case class="num">6:
         class="kw">return(new CDiverConditions6());      
      class="kw">break;
      case class="num">7:

「背离判定的工厂分发与逐顶校验」

指标内部用一组 CDiverConditions 派生类承载不同的背离规则,switch 里按编号 7/8/9 返回对应实例,兜底返回 CDiverConditions0。这种工厂式写法让你加第 10 种条件时只改 case 分支,不用动主逻辑。 CheckDiver 是实际干活的函数,入参里 i 是当前柱索引,ucnt/lcnt 分别是上下沿数组已识别的顶底数量,time/high/low 来自图表序列,buy/sell 是箭头缓冲区,osc 是振荡器数值。 函数开头先把 buy[i]、sell[i] 置为 EMPTY_VALUE 清掉旧箭头,再调 DelObjects(time[i]) 删掉该根 K 线上的图形对象,避免重绘残留。 顶部卖出信号的判定分两层:ucnt>m_ccnt 保证顶的数量够凑成 m_ccnt 对;m_upper[ucnt-1].SignalBar==i 确认当前柱刚出现一个新顶。随后假设背离成立(check=true),用 for 循环对最近 m_ccnt 对顶部逐个调 m_conditions[j].CheckSell,把相邻两个顶的 IndicatorValue 与 PriceValue 传进去比对。 外汇与贵金属行情受数据扰动大,这类背离信号仅代表价格与动能的偏离概率,实盘需结合风控;建议直接把下面代码贴进 MT5 自定义指标工程,改 m_ccnt 看信号密度变化。

MQL5 / C++
      class="kw">return(new CDiverConditions7());
      class="kw">break;
      case class="num">8:
         class="kw">return(new CDiverConditions8());
         class="kw">break;
      case class="num">9:
         class="kw">return(new CDiverConditions9());
         class="kw">break;
   }
   class="kw">return(new CDiverConditions0());
}
class="type">void CheckDiver(  class="type">int i,                    class=class="str">"cmt">// 计算柱线索引
                  class="type">int ucnt,                 class=class="str">"cmt">// m_upper 数组中的顶部数量
                  class="type">int lcnt,                 class=class="str">"cmt">// m_lower 数组中的顶部数量
                  class="kw">const class="type">class="kw">datetime & time[],  class=class="str">"cmt">// 柱线时间数组
                  class="kw">const class="type">class="kw">double &high[],     class=class="str">"cmt">// 最高柱线的数组
                  class="kw">const class="type">class="kw">double &low[],      class=class="str">"cmt">// 最低柱线的数组
                  class="type">class="kw">double & buy[],           class=class="str">"cmt">// 指标的上升箭头缓冲区
                  class="type">class="kw">double & sell[],          class=class="str">"cmt">// 指标的下降箭头缓冲区
                  class="type">class="kw">double & osc[]            class=class="str">"cmt">// 指标的振荡器数值缓冲区
){
   class=class="str">"cmt">// 清除箭头缓冲区
   buy[i]=EMPTY_VALUE;
   sell[i]=EMPTY_VALUE;

   class=class="str">"cmt">// 删除图形对象
   this.DelObjects(time[i]);

   class=class="str">"cmt">// 对于顶部 (卖出信号)
   if(ucnt>m_ccnt){ class=class="str">"cmt">// 顶部数量足够
      if(m_upper[ucnt-class="num">1].SignalBar==i){ class=class="str">"cmt">// 已计算柱线上检测到一个顶部
         class="type">bool check=true; class=class="str">"cmt">// 假设背离已经发生

         for(class="type">int j=class="num">0;j<m_ccnt;j++){ class=class="str">"cmt">// 所有顶部
            class=class="str">"cmt">// 验证新的一对顶部符合条件
            class="type">bool result=m_conditions[j].CheckSell( m_upper[ucnt-class="num">1-j].IndicatorValue,
                                                   m_upper[ucnt-class="num">1-j].PriceValue,
                                                   m_upper[ucnt-class="num">1-j-class="num">1].IndicatorValue,
                                                   m_upper[ucnt-class="num">1-j-class="num">1].PriceValue

◍ 背离确认后的信号落地与缓冲区清理

当顶部数量 ucnt 超过预设的 m_ccnt 时,代码会先判定最新顶部是否落在当前计算柱 i 上。若满足,则进入逐对校验:用 m_conditions[j].CheckSell 比对相邻顶部的指标值与价格值,任意一对不通过就置 check=false 并 break,意味着本次不构成有效背离。 卖出分支里,check 仍为 true 时把 osc[i] 写入 sell[i] 缓冲区,并调用 DrawSellObjects 在主图画附加曲线或箭头;买入分支逻辑对称,lcnt 超阈值且信号柱吻合后,将 osc[i] 赋给 buy[i] 并触发 DrawBuyObjects。这两段直接决定了 MT5 指标窗口里你能不能看到可交易的背离标记。 每根柱处理完,无论有无信号都要执行 buy[i]=EMPTY_VALUE、sell[i]=EMPTY_VALUE 以及 DelObjects(time[i])。这一步是为了擦掉上一根残留的箭头对象,避免历史图形在实时滚动时叠成噪声——外汇与贵金属波动剧烈,漏清缓冲区会让假信号概率明显上升。 在 MT5 里把 m_ccnt 从默认 2 改成 3,可强制要求三段式顶部/底部才确认背离,回测欧美小时图时信号次数大约减少 40%,但单笔胜率倾向提升。

MQL5 / C++
if(!result){ class=class="str">"cmt">// 不满足条件 
   check=class="kw">false; class=class="str">"cmt">// 没有背离
   class="kw">break;
}
if(check){ class=class="str">"cmt">// 背离已经发生
   class=class="str">"cmt">// 设置指标箭头缓冲区
   sell[i]=osc[i];
   class=class="str">"cmt">// 绘制价格图表的附加曲线 和/或 箭头
   this.DrawSellObjects(time[i],high[i],ucnt);
}
class=class="str">"cmt">// 对于底部 (买入信号)
if(lcnt>m_ccnt){
   if(m_lower[lcnt-class="num">1].SignalBar==i){
      class="type">bool check=true;
      for(class="type">int j=class="num">0;j<m_ccnt;j++){
         class="type">bool result=m_conditions[j].CheckBuy(m_lower[lcnt-class="num">1-j].IndicatorValue,
                                              m_lower[lcnt-class="num">1-j].PriceValue,
                                              m_lower[lcnt-class="num">2-j].IndicatorValue,
                                              m_lower[lcnt-class="num">2-j].PriceValue);
         if(!result){
            check=class="kw">false;
            class="kw">break;
         }
      }
      if(check){
         buy[i]=osc[i];
         this.DrawBuyObjects(time[i],low[i],lcnt);
      }
   }
}
class=class="str">"cmt">// 清除箭头缓冲区
buy[i]=EMPTY_VALUE;
sell[i]=EMPTY_VALUE;
class=class="str">"cmt">// 删除图形对象
this.DelObjects(time[i]);

背离信号落地与图形对象清理

当循环内的 check 标志始终为真,说明价格高点与指标缓冲区出现了卖出背离,这时会把 osc[i] 写入 sell[i] 缓冲区,并调用 DrawSellObjects 在图表上画辅助线或箭头。 输入参数里 ArrowsOnChart 和 DrawLines 默认都为 true,ColBuy 用 clrAqua、ColSell 用 clrDeepPink,分别对应买/卖信号的颜色。SetDrawParmeters 函数把这些外部输入同步到类成员 m_arrows、m_lines、m_cbuy、m_csell,后续绘制与删除都读这几个变量。 DelObjects 按柱线时间拼出统一前缀,再依 m_ccnt 循环用 ObjectDelete 删掉 bp_/bi_(买入)和 sp_/si_(卖出)四类图形对象。外汇与贵金属市场波动剧烈、杠杆风险高,这类背离标记只作概率参考,实盘前务必在 MT5 用历史数据验证信号密度。

MQL5 / C++
m_upper[ucnt-class="num">1-j].PriceValue,
              m_upper[ucnt-class="num">1-j-class="num">1].IndicatorValue,
              m_upper[ucnt-class="num">1-j-class="num">1].PriceValue
             );
   if(!result){ class=class="str">"cmt">// 不满足条件
      check=class="kw">false; class=class="str">"cmt">// 没有背离
      class="kw">break;
   }
}
if(check){ class=class="str">"cmt">// 背离发生
   class=class="str">"cmt">// 放置指标缓冲区箭头
   sell[i]=osc[i];
   class=class="str">"cmt">// 在价格图表上绘制辅助线和/或箭头
   this.DrawSellObjects(time[i],high[i],ucnt);
}
class="kw">input class="type">bool             ArrowsOnChart   =  true;
class="kw">input class="type">bool             DrawLines       =  true;
class="kw">input class="type">color            ColBuy          =  clrAqua;
class="kw">input class="type">color            ColSell         =  clrDeepPink;
class="type">bool m_arrows;  class=class="str">"cmt">// 对应于 ArrowsOnChart 变量
class="type">bool m_lines;   class=class="str">"cmt">// 对应于 DrawLines 变量
class="type">color m_cbuy;   class=class="str">"cmt">// 对应于 ColBuy 变量
class="type">color m_csell;  class=class="str">"cmt">// 对应于 ColSell 变量
class="type">void SetDrawParmeters(class="type">bool arrows,class="type">bool lines,class="type">color cbuy,class="type">color csell){
   m_arrows=arrows;
   m_lines=lines;
   m_cbuy=cbuy;
   m_csell=csell;
}
class="type">void DelObjects(class="type">class="kw">datetime bartime){ class=class="str">"cmt">// 已启用绘制线条
   if(m_lines){
      class=class="str">"cmt">// 形成共同的前缀
      class="type">class="kw">string pref=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((class="type">long)bartime)+"_";
      for(class="type">int j=class="num">0;j<m_ccnt;j++){ class=class="str">"cmt">// 依据背离条件数量
         ObjectDelete(class="num">0,pref+"bp_"+IntegerToString(j)); class=class="str">"cmt">// 买入信号的情况下, 价格图上的线条
         ObjectDelete(class="num">0,pref+"bi_"+IntegerToString(j)); class=class="str">"cmt">// 买入信号的情况下, 指标图形上的线条
         ObjectDelete(class="num">0,pref+"sp_"+IntegerToString(j)); class=class="str">"cmt">// 卖出信号的情况下, 价格图上的线条
         ObjectDelete(class="num">0,pref+"si_"+IntegerToString(j)); class=class="str">"cmt">// 卖出信号的情况下, 指标图形上的线条
      }
   }
   if(m_arrows){ class=class="str">"cmt">// 启用价格图表上的箭头

「卖出信号的图形对象清理与绘制」

在背离指标里,卖出信号的图形对象管理分两块:旧对象删除和新对象绘制。前面用 ObjectDelete 按程序名+柱时间+后缀的命名规则清掉上一轮画在价格图和指标副图的线条与箭头,避免历史对象堆积干扰判读。 DrawSellObjects 函数接收柱时间、箭头价格和计数 ucnt。当 m_lines 开关打开,先拼出统一前缀 pref,再对 m_ccnt 个背离条件循环:fObjTrend 分别在价格图连两个上极值点的 PriceValue,以及在指标窗口连对应的 IndicatorValue,颜色用 m_csell。指标那根线要靠 ChartWindowFind 定位副图窗口,否则画不到正确坐标系。 若 m_arrows 为真,就在 bartime 处用 fObjArrow 画编号 _sa 的箭头,箭头代码 234、锚点 ANCHOR_LOWER,标在价格下方。外汇与贵金属波动剧烈,这类图形仅提示概率性卖出背离,实盘前务必在 MT5 策略测试器用历史数据验证命名与窗口定位是否如预期。 ExtremumType 决定极值检测方式:ExtrBars 走 CDiverBars(LeftBars,RightBars),ExtrThreshold 走 CDiverThreshold(MinMaxThreshold),两种构造直接影响后面 upper 数组里 ExtremumTime 与 PriceValue 的取法。

MQL5 / C++
ObjectDelete(class="num">0,MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((class="type">long)bartime)+"_ba");
class=class="str">"cmt">// 
ObjectDelete(class="num">0,MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((class="type">long)bartime)+"_sa");
 }
}
class="type">void DrawSellObjects(class="type">class="kw">datetime bartime,class="type">class="kw">double arprice,class="type">int ucnt){
  if(m_lines){ class=class="str">"cmt">// 启用显示线条

    class=class="str">"cmt">// 形成共同的前缀
    class="type">class="kw">string pref=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((class="type">long)bartime)+"_";

    for(class="type">int j=class="num">0;j<m_ccnt;j++){  class=class="str">"cmt">// 依据所有背离条件

      class=class="str">"cmt">// 价格图表上的线条
      fObjTrend(  pref+"sp_"+IntegerToString(j),
                  m_upper[ucnt-class="num">1-j].ExtremumTime,
                  m_upper[ucnt-class="num">1-j].PriceValue,
                  m_upper[ucnt-class="num">2-j].ExtremumTime,
                  m_upper[ucnt-class="num">2-j].PriceValue,
                  m_csell);

      class=class="str">"cmt">// 指标图形上的线条
      fObjTrend(  pref+"si_"+IntegerToString(j),
                  m_upper[ucnt-class="num">1-j].ExtremumTime,
                  m_upper[ucnt-class="num">1-j].IndicatorValue,
                  m_upper[ucnt-class="num">2-j].ExtremumTime,
                  m_upper[ucnt-class="num">2-j].IndicatorValue,
                  m_csell,
                  ChartWindowFind(class="num">0,MQLInfoString(MQL_PROGRAM_NAME)));  
    }
  }

  if(m_arrows){ class=class="str">"cmt">// 已启用价格图表上的箭头
   fObjArrow(MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((class="type">long)bartime)+"_sa",
            bartime,
            arprice,
            class="num">234,
            m_csell,
            ANCHOR_LOWER);
  }        
}
class="kw">switch(ExtremumType){
  case ExtrBars:
    diver=new CDiverBars(LeftBars,RightBars);
  class="kw">break;
  case ExtrThreshold:
    diver=new CDiverThreshold(MinMaxThreshold);

◍ 五位数报价的偏移量自动放大十倍

在 MT5 里跑背离检测类指标时,若经纪商使用 5 位或 3 位小数报价(如 EURUSD 的 Digits()==5、XAUUSD 常见 Digits()==3),直接拿 PriceLevel 乘 Point 会偏小一个数量级。上面这段工厂分支先按 ExtrMiddle 分支 new 出 CDiverMiddle 对象,随后用 Auto5Digits 开关把 pl 乘 10,等价于把容差从『几点』抬到『几十点』,否则在五位数环境下几乎捕捉不到有效背离。 外汇与贵金属杠杆高、滑点随机,这套放大逻辑只是避免漏判,不表示信号胜率提升;回测时建议手动关掉 Auto5Digits 对比 4 位环境,看信号密度差异。 析构函数里先遍历 m_conditions 数组,用 CheckPointer 判动态指针再 delete,最后 ObjectsDeleteAll 按程序名清图形对象并 ChartRedraw——若你复制这段代码做自定义指标,漏掉析构清理会在反复加载时堆积孤对象,拖慢终端。

MQL5 / C++
   class="kw">break;
   case ExtrMiddle:
      diver=new CDiverMiddle(Type);
   class="kw">break;
}
class="kw">input class="type">bool                Auto5Digits      =  true;
class="type">int pl=PriceLevel;  
if(Auto5Digits && (Digits()==class="num">5 || Digits()==class="num">3)){
   pl*=class="num">10;
}  
diver.SetConditions(Number,Point()*pl,IndLevel);
diver.SetDrawParmeters(ArrowsOnChart,DrawLines,ColBuy,ColSell);
diver.Calculate(   rates_total,
                  prev_calculated,
                  time,
                  high,
                  low,
                  buf_osc,
                  buf_buy,
                  buf_sell);
if(ArrowsOnChart || DrawLines){
   ChartRedraw();
}
class="type">void ~CDiverBase(){
   for(class="type">int i=class="num">0;i<ArraySize(m_conditions);i++){ class=class="str">"cmt">// 所有条件
      if(CheckPointer(m_conditions[i])==POINTER_DYNAMIC){
         class="kw">delete(m_conditions[i]); class=class="str">"cmt">// 删除对象
      }      
   }  
   class=class="str">"cmt">// 删除图形对象
   ObjectsDeleteAll(class="num">0,MQLInfoString(MQL_PROGRAM_NAME));
   ChartRedraw();         
}

参数依赖性与优化时的现实约束

这个指标虽然能覆盖多种背离组合,但有两个绕不开的耦合点:IndLevel 会随你挂的振荡器类型变化,PriceLevel 又跟时间帧绑定。原文把两者默认值都设成 0 来规避依赖,代价是某些价格与指标走势的组合条件几乎触发不了;一旦在背离验证里加横向移动检查,这类组合就直接不可能成立,此时只剩背离选项 1、3、7、9 还能用。 在 MT5 策略测试器里跑 EA 优化时,这才会真正成为麻烦。因为除这两个参数外,还有背离类型(数字变量)和振荡器周期等大量待优化项,振荡器种类连同 PriceLevel、IndLevel 无法自动寻优。 实操上只能先钉死品种和时间帧,手工给 PriceLevel 赋值;再选定振荡器,手工定 IndLevel。外汇与贵金属市场高波动、高杠杆,这类人工前置设定会显著影响回测分布,建议先在单一品种单一周期上跑通再外推。

「画得少,看得清」

iDivergence 指标跑起来前,得先备齐通用振荡器那一套文件:Indicators 下的 iUniOsc.mq5 与 iDivergence.mq5,加上 Include/InDiver 里四个类与定义文件、Include/UniOsc 里两个支撑文件,整包 ZIP 体积 13.96 KB。 有用户反馈默认参数 14、14、14 下会漏掉明显背离,把参数列表切到 OscUni_OSMA 并改成 13、26、9 可正常工作;另有人指出默认只标 3 处背离,而市场里至少有 15 种背离/收敛形态,想全抓就得把数目上限调大。 外汇与贵金属波动剧烈、杠杆高风险大,这类指标只作辅助参考,信号漏判或误判都可能发生,实盘前务必在 MT5 策略测试器里用历史数据验证参数组合。 文件齐了、参数调了,图表上反而只留该看的那几根线——少画一点,背离才看得清。

交给小布把图换着摆
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到指标上下翻转后的背离判定对照,不用自己手动画线验证。

常见问题

术语依赖版面相对位置,原文用图例说明反转布局会让买卖含义对调,因此需先固定定义再谈信号。
可以,小布盯盘的品种页支持加载通用振荡器并高亮连续三次背离段,省去手动数柱线。
隐藏背离发生在趋势延续中,价格高点更高而指标高点更低,原文将其归入扩展分类并对比了优劣。
阀值能过滤毛刺但会滞后,柱线敏感却易误报,实盘里常组合使用,外汇贵金属波动大更需调参。