MQL5 酷客宝典 - 轴点交易信号·进阶篇
📉

MQL5 酷客宝典 - 轴点交易信号·进阶篇

(2/3)· 从指标缓冲区到建仓确认,手把手拆开轴点信号的工程化实现路径

案例拆解 第 2/3 篇
很多交易者把轴点水平当静态画线看,却没意识到图形对象无法在优化模式回测。用缓冲区绘制的反转水平才能进策略测试器,否则你打磨的“信号”只是肉眼错觉。

枢轴点信号类的接口与多头参数逻辑

在 MT5 里写自定义信号类时,把可调参数和确认流程拆成独立方法是避免后期改崩的关键。下面这段头文件声明给出了 CSignalPivots 的对外接口骨架:设置绘制的开关、邻近点数、市场模式权重,以及虚函数留给子类或框架去实现指标初始化与多空条件判定。 [CODE]void ToPlotMinor(const bool _to_plot) {m_to_plot_minor=_to_plot;} void PointsNear(const uint _near_pips); void Pattern_0(int _val) {m_pattern_0=_val;m_pattern_0_done=false;} virtual bool ValidationSettings(void); virtual bool InitIndicators(CIndicators *indicators); virtual int LongCondition(void); virtual int ShortCondition(void); virtual double Direction(void); virtual bool OpenLongParams(double &price,double &sl,double &tp,datetime &expiration); virtual bool OpenShortParams(double &price,double &sl,double &tp,datetime &expiration); protected: bool InitCustomIndicator(CIndicators *indicators); double Pivot(void) {return(m_pivots.GetData(0,0));} double MajorResistance(uint _ind); double MinorResistance(uint _ind); double MajorSupport(uint _ind); double MinorSupport(uint _ind);[/CODE] 逐行看:ToPlotMinor 直接写成员变量控制次级水平线是否绘制;PointsNear 用 uint 传 pips 数限定枢轴点附近多少点内算有效;Pattern_0 改权重时顺手把 m_pattern_0_done 置 false,强制下次重算。ValidationSettings 与 InitIndicators 是框架回调,LongCondition / ShortCondition 返回 int 而非 bool,说明多空信号是分级打分而非简单开关。 OpenLongParams 的实现片段暴露了实际挂单算法:若启用模式 0 且未完成,取 Ask 作基准,price 归一化到 base_price 减去 m_price_level 倍的 PriceLevelUnit()。此时 sl 被注释指向 Sup0.5 水平,意味着止损挂在次级支撑下方,具体数值由 MinorSupport 提供。外汇与贵金属波动大,这类基于枢轴点的挂单在数据行情中可能滑点超预期,验证时建议先用策略测试器跑 EURUSD 的 H1 看成交分布。

MQL5 / C++
class="type">void ToPlotMinor(class="kw">const class="type">bool _to_plot) {m_to_plot_minor=_to_plot;}
class="type">void PointsNear(class="kw">const class="type">uint _near_pips);
class="type">void Pattern_0(class="type">int _val) {m_pattern_0=_val;m_pattern_0_done=class="kw">false;}
class="kw">virtual class="type">bool ValidationSettings(class="type">void);
class="kw">virtual class="type">bool InitIndicators(CIndicators *indicators);
class="kw">virtual class="type">int LongCondition(class="type">void);
class="kw">virtual class="type">int ShortCondition(class="type">void);
class="kw">virtual class="type">class="kw">double Direction(class="type">void);
class="kw">virtual class="type">bool OpenLongParams(class="type">class="kw">double &price,class="type">class="kw">double &sl,class="type">class="kw">double &tp,class="type">class="kw">datetime &expiration);
class="kw">virtual class="type">bool OpenShortParams(class="type">class="kw">double &price,class="type">class="kw">double &sl,class="type">class="kw">double &tp,class="type">class="kw">datetime &expiration);
class="kw">protected:
class="type">bool InitCustomIndicator(CIndicators *indicators);
class="type">class="kw">double Pivot(class="type">void) {class="kw">return(m_pivots.GetData(class="num">0,class="num">0));}
class="type">class="kw">double MajorResistance(class="type">uint _ind);
class="type">class="kw">double MinorResistance(class="type">uint _ind);
class="type">class="kw">double MajorSupport(class="type">uint _ind);
class="type">class="kw">double MinorSupport(class="type">uint _ind);

◍ 用枢轴支撑阻力给多单定止损止盈

在 CExpertSignal 的买入参数函数里,止损先取 MinorSupport(0) 的 minor 支撑,若返回 DBL_MAX 说明当前没有有效支撑,直接 return false 放弃开仓。 拿到支撑价后必须用 m_symbol.NormalizePrice(sl) 按品种精度规整,否则部分外汇对(如 USDJPY 小数点后 3 位)会因报价精度不匹配被交易服务器拒单。 止盈则对接 MajorResistance(0) 的主要阻力,同样做 NormalizePrice 处理;过期时间叠加 m_expiration*PeriodSeconds(m_period) 秒数,最后把 params_set 和 m_pattern_0_done 置 true 表示模式 0 参数已就绪。 OpenLongParams 中若 m_general 为 -1(无基础过滤器),直接用 Ask 或 m_base_price 减 m_price_level*PriceLevelUnit 算挂单价,止损/止盈按 m_stop_level / m_take_level 对称偏移;外汇与贵金属杠杆高,枢轴位仅作概率参考,实盘需自验。 CSignalPivots 作为过滤器经 signal.AddFilter(filter0) 加入,并用 signal.General(0) 设为主过滤器,之后 LongCondition 会基于这些枢轴位判断多单触发条件。

MQL5 / C++
sl=this.MinorSupport(class="num">0);
if(sl==DBL_MAX)
   class="kw">return class="kw">false;
class=class="str">"cmt">//--- 如果设置了止损价格
sl=m_symbol.NormalizePrice(sl);
class=class="str">"cmt">//--- 获利价格 - Res1.class="num">0 水平

tp=this.MajorResistance(class="num">0);
if(tp==DBL_MAX)
   class="kw">return class="kw">false;
class=class="str">"cmt">//--- 如果设置了获利价格
tp=m_symbol.NormalizePrice(tp);
expiration+=m_expiration*PeriodSeconds(m_period);
class=class="str">"cmt">//--- 如果设置了价格
params_set=true;
class=class="str">"cmt">//--- 模式结束
m_pattern_0_done=true;
}
class=class="str">"cmt">//---
  class="kw">return params_set;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 侦测用于买入的水平                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CExpertSignal::OpenLongParams(class="type">class="kw">double &price,class="type">class="kw">double &sl,class="type">class="kw">double &tp,class="type">class="kw">datetime &expiration)
  {
  CExpertSignal *general=(m_general!=-class="num">1) ? m_filters.At(m_general) : NULL;
class=class="str">"cmt">//---
  if(general==NULL)
    {
    class=class="str">"cmt">//--- 如果没有明确指定基础价格,就是用当前市场价格
    class="type">class="kw">double base_price=(m_base_price==class="num">0.0) ? m_symbol.Ask() : m_base_price;
    price      =m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit());
    sl         =(m_stop_level==class="num">0.0) ? class="num">0.0 : m_symbol.NormalizePrice(price-m_stop_level*PriceLevelUnit());
    tp         =(m_take_level==class="num">0.0) ? class="num">0.0 : m_symbol.NormalizePrice(price+m_take_level*PriceLevelUnit());
    expiration+=m_expiration*PeriodSeconds(m_period);
    class="kw">return(true);
    }
class=class="str">"cmt">//---
  class="kw">return(general.OpenLongParams(price,sl,tp,expiration));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- CSignalPivots 过滤器
  CSignalPivots *filter0=new CSignalPivots;
  if(filter0==NULL)
    {
    class=class="str">"cmt">//--- 错误
    PrintFormat(__FUNCTION__+": 创建 filter0 出错");
    class="kw">return INIT_FAILED;
    }
  signal.AddFilter(filter0);
  signal.General(class="num">0);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 检查买入条件                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalPivots::LongCondition(class="type">void)
  {
  class="type">int result=class="num">0;
class=class="str">"cmt">//--- 如果没有使用模式 class="num">0 

「轴点接触判定与方向权重重置」

在自定义信号类里,模式 0 的触发逻辑依赖日线开盘价与轴点的相对位置。当 m_daily_open_pr 大于 m_pivot_val 且上一根柱最低价 last_low 落入轴点加阈值 m_pnt_near 的范围内,才认为价格从上方接触了轴点,并把 result 置为 m_pattern_0,同时向日志打印价格、轴点与阈值三位小数对齐的数值。 下方 Direction() 方法负责维护模式完成的生命周期。它通过 CopyRates(_Symbol, PERIOD_D1, 0, 1, daily_rates) 取 1 根日线,若返回小于 0 直接返回 0.0 避免误判。 若 m_pattern_0_done 为 true,且 isNewBar 识别到新日线,就把完成标记复位并返回 0.0,相当于每天只让模式 0 生效一次。未完成时则把 m_daily_open_pr 同步为当日开盘价,供上方接触判断使用。外汇与贵金属波动剧烈,这类轴点接触只是概率信号,实盘前请在 MT5 用策略测试器跑一遍观察触发频率。

MQL5 / C++
if(IS_PATTERN_USAGE(class="num">0))
   class=class="str">"cmt">//--- 如果模式 class="num">0 没有完成
   if(!m_pattern_0_done)
      class=class="str">"cmt">//--- 如果一天的开盘价高于轴点
      if(m_daily_open_pr>m_pivot_val)
         {
         class=class="str">"cmt">//--- 当前柱的最低价格
         class="type">class="kw">double last_low=m_low.GetData(class="num">1);
         class=class="str">"cmt">//--- 如果得到了价格
         if((last_low>WRONG_VALUE) && (last_low<DBL_MAX))
            class=class="str">"cmt">//--- 如果有上方的接触 (考虑到阈值)
            if(last_low<=(m_pivot_val+m_pnt_near))
               {
               result=m_pattern_0;
               class=class="str">"cmt">//--- 写到日志中
               Print("\n---== 价格在上方接触到轴点水平 ==---");
               PrintFormat("价格: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",last_low);
               PrintFormat("轴点: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_pivot_val);
               PrintFormat("阈值: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_pnt_near);
               }
         }
class=class="str">"cmt">//---
   class="kw">return result;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 定义 "有权重的" 方向                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignalPivots::Direction(class="type">void)
  {
   class="type">class="kw">double result=class="num">0.;
class=class="str">"cmt">//--- 接收每日历史数据
   class="type">MqlRates daily_rates[];
   if(CopyRates(_Symbol,PERIOD_D1,class="num">0,class="num">1,daily_rates)<class="num">0)
      class="kw">return class="num">0.;
class=class="str">"cmt">//--- 如果模式 class="num">0 已经完成
   if(m_pattern_0_done)
     {
     class=class="str">"cmt">//--- 检查新的一天
     if(m_day_new_bar.isNewBar(daily_rates[class="num">0].time))
       {
       class=class="str">"cmt">//--- 重设模式完成标记
       m_pattern_0_done=class="kw">false;
       class="kw">return class="num">0.;
       }
     }
class=class="str">"cmt">//--- 如果模式 class="num">0 没有完成
   else
     {
     class=class="str">"cmt">//--- 每日开盘价
     if(m_daily_open_pr!=daily_rates[class="num">0].open)
        m_daily_open_pr=daily_rates[class="num">0].open;
     class=class="str">"cmt">//--- 轴点

枢轴信号类的平仓与调度落点

CSignalPivots::CloseLongParams 只处理模式 0 未完成的场景:把平仓价直接设为当前 Bid,并往日志打印市场和价格。若 IS_PATTERN_USAGE(0) 成立且 m_pattern_0_done 为 false,函数返回 true,否则返回 false,意味着其余模式不走这套硬编码平仓逻辑。 CExpertSignal::CheckCloseLong 里有一行硬阈值判断:当 -m_direction >= m_threshold_close 时才置 result=true,并调用 CloseLongParams 取平仓价;若取价失败则回退为 false。这个 m_threshold_close 是关闭多单的方向强度门槛,调小它会让平仓信号更敏感。 主调度 CPivotsExpert::Processing 以分钟新柱为节拍:m_minute_new_bar.isNewBar() 不为真就直接 return false,不重复计算。新柱出现后先 m_signal.SetDirection(),无持仓走 CheckOpen,有持仓则进入后面的平仓分支(原文在此截断)。外汇与贵金属波动剧烈,这类基于枢轴的信号仅作概率参考,实盘前务必在 MT5 策略测试器用历史数据验证门槛参数。

MQL5 / C++
class="type">class="kw">double curr_pivot_val=this.Pivot();
if(curr_pivot_val<DBL_MAX)
  if(m_pivot_val!=curr_pivot_val)
    m_pivot_val=curr_pivot_val;
}
class=class="str">"cmt">//--- 结果
  result=m_weight*(this.LongCondition()-this.ShortCondition());
class=class="str">"cmt">//---
  class="kw">return result;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 定义买入时的交易水平                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSignalPivots::CloseLongParams(class="type">class="kw">double &price)
  {
  price=class="num">0.;
class=class="str">"cmt">//--- 如果使用了模式 class="num">0 
  if(IS_PATTERN_USAGE(class="num">0))
    class=class="str">"cmt">//--- 如果模式 class="num">0 没有完成
    if(!m_pattern_0_done)
      {
      price=m_symbol.Bid();
      class=class="str">"cmt">//--- 写到日志中
      Print("\n---== 关闭买入仓位的信号 ==---");
      PrintFormat("市场价格: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",price);
      class="kw">return true;
      }
class=class="str">"cmt">//--- 返回结果
  class="kw">return class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 生成关闭买入仓位的信号                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CExpertSignal::CheckCloseLong(class="type">class="kw">double &price)
  {
  class="type">bool   result   =class="kw">false;
class=class="str">"cmt">//--- "禁止"信号
  if(m_direction==EMPTY_VALUE)
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 检查是否超过阈值
  if(-m_direction>=m_threshold_close)
    {
    class=class="str">"cmt">//--- 有信号
    result=true;
    class=class="str">"cmt">//--- 尝试取得平仓水平
    if(!CloseLongParams(price))
      result=class="kw">false;
    }
class=class="str">"cmt">//--- 基础价格清零
  m_base_price=class="num">0.0;
class=class="str">"cmt">//--- 返回结果
  class="kw">return(result);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 主模块                                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CPivotsExpert::Processing(class="type">void)
  {
class=class="str">"cmt">//--- 新的分钟柱
  if(!m_minute_new_bar.isNewBar())
    class="kw">return class="kw">false;
class=class="str">"cmt">//--- 计算方向
  m_signal.SetDirection();
class=class="str">"cmt">//--- 如果没有仓位
  if(!this.SelectPosition())
    {
    class=class="str">"cmt">//--- 仓位开启模块
    if(this.CheckOpen())
      class="kw">return true;
    }
class=class="str">"cmt">//--- 如果有仓位 

◍ 轴点下方接触的多头信号判定

在 CSignalPivots 类的 LongCondition 方法里,模式 0 的多头触发逻辑依赖三个前置条件:当日开盘价低于轴点值、模式 0 尚未完成标记、且当前柱(索引 1)最高价向上触碰轴点减去阈值 m_pnt_near 的区间。 代码用 m_high.GetData(1) 取上一根已完成 K 线的最高价,并校验其处于 (WRONG_VALUE, DBL_MAX) 有效区间;只有 last_high >= m_pivot_val - m_pnt_near 时才将 result 置为 m_pattern_0,同时向日志打印价格、轴点与阈值三位小数对齐的现场数据,方便在 MT5 Experts 标签页核对接触精度。 OpenLongParams 负责回填具体下单参数,初始把 sl 和 tp 都预置为 WRONG_VALUE,仅在 IS_PATTERN_USAGE(0) 且 !m_pattern_0_done 时继续填充价格。外汇与贵金属杠杆品种波动剧烈,轴点接触仅代表概率倾斜,实盘前应在策略测试器用至少 3 个月 Tick 数据验证阈值敏感性。

MQL5 / C++
else
    {
      class=class="str">"cmt">//--- 仓位关闭模块
      if(this.CheckClose())
        class="kw">return true;
    }
class=class="str">"cmt">//--- 如果没有交易操作
   class="kw">return class="kw">false;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 检查卖出条件                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CSignalPivots::LongCondition(class="type">void)
  {
   class="type">int result=class="num">0;
class=class="str">"cmt">//--- 如果没有使用模式 class="num">0 
   if(IS_PATTERN_USAGE(class="num">0))
      class=class="str">"cmt">//--- 如果模式 class="num">0 没有完成
      if(!m_pattern_0_done)
         class=class="str">"cmt">//--- 如果一天的开盘价低于轴点
         if(m_daily_open_pr<m_pivot_val)
           {
            class=class="str">"cmt">//--- 当前柱的最高价
            class="type">class="kw">double last_high=m_high.GetData(class="num">1);
            class=class="str">"cmt">//--- 如果得到了价格
            if((last_high>WRONG_VALUE) && (last_high<DBL_MAX))
               class=class="str">"cmt">//--- 如果有上方的接触 (考虑到阈值)
               if(last_high>=(m_pivot_val-m_pnt_near))
                 {
                  result=m_pattern_0;
                  class=class="str">"cmt">//--- 写到日志中
                  Print("\n---== 价格从下方接触到轴点水平 ==---");
                  PrintFormat("Price: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",last_high);
                  PrintFormat("轴点: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_pivot_val);
                  PrintFormat("阈值: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_pnt_near);
                 }
           }
class=class="str">"cmt">//---
   class="kw">return result;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 定义买入交易的水平                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSignalPivots::OpenLongParams(class="type">class="kw">double &price,class="type">class="kw">double &sl,class="type">class="kw">double &tp,class="type">class="kw">datetime &expiration)
  {
   class="type">bool params_set=class="kw">false;
   sl=tp=WRONG_VALUE;
class=class="str">"cmt">//--- 如果使用了模式 class="num">0 
   if(IS_PATTERN_USAGE(class="num">0))
      class=class="str">"cmt">//--- 如果模式 class="num">0 没有完成
      if(!m_pattern_0_done)
         {
          class=class="str">"cmt">//--- 开盘价 - 市场

「用支撑阻力给挂单定止损止盈」

这段逻辑落在挂单参数组装的收口处:先抓当前 Ask 作基准,再按价位单位往下推挂单价,随后把止损锚在 MajorSupport(0)、止盈锚在 MinorResistance(0)。 double base_price=m_symbol.Ask(); price=m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit()); sl=this.MajorSupport(0); if(sl==DBL_MAX) return false; sl=m_symbol.NormalizePrice(sl); tp=this.MinorResistance(0); if(tp==DBL_MAX) return false; tp=m_symbol.NormalizePrice(tp); expiration+=m_expiration*PeriodSeconds(m_period); params_set=true; m_pattern_0_done=true; 两个取值都做了 DBL_MAX 守卫——拿不到有效支撑或阻力就直接 return false,不会带着空止损去下单。NormalizePrice 调用保证价格符合品种最小变动位数,否则 MT5 会拒单。 expiration 用 PeriodSeconds(m_period) 把周期换算成秒再累加,避免手动填错时间尺度。params_set 与 m_pattern_0_done 置 true 后函数把 params_set 返回,外层据此判断能否发单。外汇与贵金属杠杆高,支撑阻力被扫后反转概率不低,实盘前建议在策略测试器用历史数据跑一遍该分支。

MQL5 / C++
class="type">class="kw">double base_price=m_symbol.Ask();
price=m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit());
class=class="str">"cmt">//--- 止损价格e - Sup1.class="num">0 水平
sl=this.MajorSupport(class="num">0);
if(sl==DBL_MAX)
   class="kw">return class="kw">false;
class=class="str">"cmt">//--- 如果设置了止损价格
sl=m_symbol.NormalizePrice(sl);
class=class="str">"cmt">//--- 获利价格 - Res0.class="num">5 水平
tp=this.MinorResistance(class="num">0);
if(tp==DBL_MAX)
   class="kw">return class="kw">false;
class=class="str">"cmt">//--- 如果设置了获利价格
tp=m_symbol.NormalizePrice(tp);
expiration+=m_expiration*PeriodSeconds(m_period);
class=class="str">"cmt">//--- 如果设置了价格
params_set=true;
class=class="str">"cmt">//--- 模式结束
m_pattern_0_done=true;
}
class=class="str">"cmt">//---
  class="kw">return params_set;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
把重复劳动交给小布
这些轴点水平的日内重算与信号扫描,小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时反转区,你只需判断触碰后的反应。

常见问题

图形对象不能在优化模式下被策略参考,且无法超出已知柱预测;缓冲区方式允许回测与优化,但只画已发生时段的水平线。
以开盘价在 PP 下方且后续价格触碰轴点水平作为确认,触发卖出;反向则考虑买入,并在次日开局平仓若未止盈止损。
因为轴点水平按日重算,必须清空已算缓冲计数,让指标从头填充当日柱,否则会沿用跨日旧值导致水平错位。
可以,小布在品种页内置了轴点水平计算与触碰提示,省去自己在 MT5 写指标的过程,但外汇贵金属波动剧烈,仍属高风险。
它比主阻力更贴近进场价,能收紧风险;若价格反向穿过则证明信号失效,比固定点数止损更贴合结构。