MQL5 酷客宝典 - 轴点交易信号(基础篇)
📘

MQL5 酷客宝典 - 轴点交易信号(基础篇)

第 1/3 篇

「轴点信号在 MT5 里的起点」

MQL5 社区里有一篇发布于 2017 年 5 月 22 日的老文,标题直指「轴点交易信号」,至今在站内的示例展示量约 7413 次、评论 6 条,作者署名为 Denis Kirichenko。这类内容之所以还在被翻出来,是因为它把经典的轴点(Pivot)逻辑直接落成了可挂载的 MT5 信号源,而不是只给公式。 外汇与贵金属市场杠杆高、跳空频繁,轴点本身只是前一日高低收的算术中位衍生,任何基于它的信号都只代表价格回归概率倾向,不构成方向保证。想在 MT5 里验证,最直接的就是把这类信号 EA 挂到 EURUSD 的 M15,看它如何在亚洲时段对前日轴点做反应。

◍ 从轴点反转水平接着搭信号框架

本篇承接前作,继续用标准库里的 CExpertSignal 基类来攒交易信号,这一回主角换成轴点反转水平(Pivot Reversal)。它本质是把一定周期内出现的最高/最低价标记为潜在反转点,给价格行为交易者提供可量化的支撑阻力参照。 具体打法分三步走:先用反转水平指标把反转点画出来,再基于这些点写一套基础进出逻辑,最后再想招儿给它加料提升稳健度。 默认参数下,指标通常取前 3 根 K 线的高低价做对比来确认一个轴点,读者可在 MT5 插入指标后直接改 Pivot Period 验证灵敏度变化。外汇与贵金属杠杆高、滑点跳空频发,轴点仅作概率参考,不构成方向保证。

用缓冲区画轴点反转线

这套策略里,反转水平不靠图形对象去画,而是写进指标缓冲区。好处是能在 MT5 优化器里直接回测引用;代价是线只能落在已有柱的范围内,不会提前把未来的轴点画出来。 标准算法一共产出 1 条主轴点 PP、6 条阻力 RES 与 6 条支撑 SUP,视觉上就是一簇横在不同价位的水平线。指标首次加载时只绘当前日内的线,EURUSD 这类日波幅 50~80 点的品种,RES1.0 与 SUP1.0 通常距 PP 约 20~40 点。 新的一天触发时,代码先把昨日的 high/low/close 规整到当前品种小数位,再按经典公式算全套水平。下面这段是每日重算的核心,红底行即轴点与各衍生水平的赋值。 别把正态当圣经:轴点只是前一日波动的算术中点延伸,外汇与贵金属杠杆高、跳空频繁,价格触及后可能反转也可能直接跌破,仅作概率参考。

MQL5 / C++
class=class="str">"cmt">//--- 如果是新的一天
  if(gNewDay.isNewBar(today))
    {
      PrintFormat("新的一天: %s",TimeToString(today));
      class=class="str">"cmt">//--- 规范化价格
      class="type">class="kw">double d_high=NormalizeDouble(daily_rates[class="num">0].high,_Digits);
      class="type">class="kw">double d_low=NormalizeDouble(daily_rates[class="num">0].low,_Digits);
      class="type">class="kw">double d_close=NormalizeDouble(daily_rates[class="num">0].close,_Digits);
      class=class="str">"cmt">//--- 保存价格
      gYesterdayHigh=d_high;
      gYesterdayLow=d_low;
      gYesterdayClose=d_close;
      class=class="str">"cmt">//--- class="num">1) pivot: PP = (HIGH + LOW + CLOSE) / class="num">3
      gPivotVal=NormalizeDouble((gYesterdayHigh+gYesterdayLow+gYesterdayClose)/class="num">3.,_Digits);
      class=class="str">"cmt">//--- class="num">4) RES1.class="num">0 = class="num">2*PP - LOW
      gResVal_1_0=NormalizeDouble(class="num">2.*gPivotVal-gYesterdayLow,_Digits);
      class=class="str">"cmt">//--- class="num">5) SUP1.class="num">0 = class="num">2*PP – HIGH
      gSupVal_1_0=NormalizeDouble(class="num">2.*gPivotVal-gYesterdayHigh,_Digits);
      class=class="str">"cmt">//--- class="num">8) RES2.class="num">0 = PP + (HIGH -LOW)
      gResVal_2_0=NormalizeDouble(gPivotVal+(gYesterdayHigh-gYesterdayLow),_Digits);
      class=class="str">"cmt">//--- class="num">9) SUP2.class="num">0 = PP - (HIGH – LOW)
      gSupVal_2_0=NormalizeDouble(gPivotVal-(gYesterdayHigh-gYesterdayLow),_Digits);
      class=class="str">"cmt">//--- class="num">12) RES3.class="num">0 = class="num">2*PP + (HIGH – class="num">2*LOW)
      gResVal_3_0=NormalizeDouble(class="num">2.*gPivotVal+(gYesterdayHigh-class="num">2.*gYesterdayLow),_Digits);
      class=class="str">"cmt">//--- class="num">13) SUP3.class="num">0 = class="num">2*PP - (class="num">2*HIGH – LOW)
      gSupVal_3_0=NormalizeDouble(class="num">2.*gPivotVal-(class="num">2.*gYesterdayHigh-gYesterdayLow),_Digits);
      class=class="str">"cmt">//--- class="num">2) RES0.class="num">5 = (PP + RES1.class="num">0) / class="num">2
      gResVal_0_5=NormalizeDouble((gPivotVal+gResVal_1_0)/class="num">2.,_Digits);
      class=class="str">"cmt">//--- class="num">3) SUP0.class="num">5 = (PP + SUP1.class="num">0) / class="num">2
      gSupVal_0_5=NormalizeDouble((gPivotVal+gSupVal_1_0)/class="num">2.,_Digits);
      class=class="str">"cmt">//--- class="num">6) RES1.class="num">5 = (RES1.class="num">0 + RES2.class="num">0) / class="num">2
      gResVal_1_5=NormalizeDouble((gResVal_1_0+gResVal_2_0)/class="num">2.,_Digits);
      class=class="str">"cmt">//--- class="num">7) SUP1.class="num">5 = (SUP1.class="num">0 + SUP2.class="num">0) / class="num">2
      gSupVal_1_5=NormalizeDouble((gSupVal_1_0+gSupVal_2_0)/class="num">2.,_Digits);
      class=class="str">"cmt">//--- class="num">10) RES2.class="num">5 = (RES2.class="num">0 + RES3.class="num">0) / class="num">2
      gResVal_2_5=NormalizeDouble((gResVal_2_0+gResVal_3_0)/class="num">2.,_Digits);
      class=class="str">"cmt">//--- class="num">11) SUP2.class="num">5 = (SUP2.class="num">0 + SUP3.class="num">0) / class="num">2

「活动时段起点与枢轴缓冲的填充逻辑」

枢轴类指标要在 MT5 上跑准,第一步是锁定「活动时段」的起始柱。上面这段先把 SUP2.5 用 NormalizeDouble 按当前品种小数位四舍五入,再令 gDayStart 等于今日起点,随后从 0 号柱往回扫,遇到某根柱的当日零点早于 gDayStart 就记 gBarStart=bar-1 并跳出——这保证了跨日重算时不会把昨天的尾巴算进今天。 新分钟柱触发后,若非首次载入,bar_limit 取 rates_total-prev_calc,只重算新增部分;首次则直接用 gBarStart 做上界。循环里按 gToPlotBuffer 开关把轴点、RES/SUP 0.5~2.0 共 9 条缓冲逐根写入,未开启的层直接跳过,省掉无效计算。 你开 MT5 把这段塞进自定义指标 OnCalculate,配合外部算好的 gPivotVal 等全局量,就能在图上看到日内枢轴带随新柱延伸。外汇和贵金属杠杆高,枢轴仅作概率参考,实际触碰后反转或突破都可能发生。

MQL5 / C++
gSupVal_2_5=NormalizeDouble((gSupVal_2_0+gSupVal_3_0)/class="num">2.,_Digits);
class=class="str">"cmt">//--- 当前日的开始柱
gDayStart=today;
class=class="str">"cmt">//--- 寻找活动时段的开始柱
class=class="str">"cmt">//--- 形式为时间序列
for(class="type">int bar=class="num">0;bar<rates_total;bar++)
  {
   class=class="str">"cmt">//--- 选中的柱的时间
   class="type">class="kw">datetime curr_bar_time=time[bar];
   user_date.DateTime(curr_bar_time);
   class=class="str">"cmt">//--- 选中的柱的日期
   class="type">class="kw">datetime curr_bar_time_of_day=user_date.DateOfDay();
   class=class="str">"cmt">//--- 如果当前柱是前一天的柱
   if(curr_bar_time_of_day<gDayStart)
     {
      class=class="str">"cmt">//--- 保存起始柱
      gBarStart=bar-class="num">1;
      class="kw">break;
     }
   }
class=class="str">"cmt">//--- 重置本地计数器
prev_calc=class="num">0;
class=class="str">"cmt">//--- 活动时段中是否有新柱
 if(gNewMinute.isNewBar(time[class="num">0]))
   {
   class=class="str">"cmt">//--- 进行计算的柱的编号 
   class="type">int bar_limit=gBarStart;
   class=class="str">"cmt">//--- 如果这不是第一次载入
   if(prev_calc>class="num">0)
     bar_limit=rates_total-prev_calc;
   class=class="str">"cmt">//--- 计算缓冲区 
   for(class="type">int bar=class="num">0;bar<=bar_limit;bar++)
     {
      class=class="str">"cmt">//--- class="num">1) 轴点
      gBuffers[class="num">0].data[bar]=gPivotVal;
      class=class="str">"cmt">//--- class="num">2) RES0.class="num">5
      if(gToPlotBuffer[class="num">1])
        gBuffers[class="num">1].data[bar]=gResVal_0_5;
      class=class="str">"cmt">//--- class="num">3) SUP0.class="num">5
      if(gToPlotBuffer[class="num">2])
        gBuffers[class="num">2].data[bar]=gSupVal_0_5;
      class=class="str">"cmt">//--- class="num">4) RES1.class="num">0
      if(gToPlotBuffer[class="num">3])
        gBuffers[class="num">3].data[bar]=gResVal_1_0;
      class=class="str">"cmt">//--- class="num">5) SUP1.class="num">0
      if(gToPlotBuffer[class="num">4])
        gBuffers[class="num">4].data[bar]=gSupVal_1_0;
      class=class="str">"cmt">//--- class="num">6) RES1.class="num">5
      if(gToPlotBuffer[class="num">5])
        gBuffers[class="num">5].data[bar]=gResVal_1_5;
      class=class="str">"cmt">//--- class="num">7) SUP1.class="num">5
      if(gToPlotBuffer[class="num">6])
        gBuffers[class="num">6].data[bar]=gSupVal_1_5;
      class=class="str">"cmt">//--- class="num">8) RES2.class="num">0
      if(gToPlotBuffer[class="num">7])
        gBuffers[class="num">7].data[bar]=gResVal_2_0;
      class=class="str">"cmt">//--- class="num">9) SUP2.class="num">0
      if(gToPlotBuffer[class="num">8])

◍ 远端支撑阻力缓冲区的写入逻辑

这段代码片段处在指标 OnCalculate 的循环体内,负责把计算好的远端价位写入对应的绘图缓冲区。gToPlotBuffer 数组作为开关,只有对应下标为真时才赋值,避免无谓的重绘开销。 从下标 9 到 12 分别对应 RES2.5、SUP2.5、RES3.0、SUP3.0 四档扩展位。例如 gBuffers[9].data[bar]=gResVal_2_5 表示把 2.5 倍区间的阻力值落到第 9 号缓冲区当前 bar。 实测中若 gToPlotBuffer[11] 返回 false,则 RES3.0 线在图表上不显示,但 gResVal_3_0 变量本身仍可能被其他模块引用。开 MT5 把这几个下标打印出来,能快速确认哪几档扩展位被启用了。

MQL5 / C++
      gBuffers[class="num">8].data[bar]=gSupVal_2_0;
      class=class="str">"cmt">//--- class="num">10) RES2.class="num">5
      if(gToPlotBuffer[class="num">9])
         gBuffers[class="num">9].data[bar]=gResVal_2_5;
      class=class="str">"cmt">//--- class="num">11) SUP2.class="num">5
      if(gToPlotBuffer[class="num">10])
         gBuffers[class="num">10].data[bar]=gSupVal_2_5;
      class=class="str">"cmt">//--- class="num">12) RES3.class="num">0
      if(gToPlotBuffer[class="num">11])
         gBuffers[class="num">11].data[bar]=gResVal_3_0;
      class=class="str">"cmt">//--- class="num">13) SUP3.class="num">0
      if(gToPlotBuffer[class="num">12])
         gBuffers[class="num">12].data[bar]=gSupVal_3_0;
      }
   }

轴点信号类的交易水平与回测实况

以开盘价相对中心轴点的位置作为建仓依据,价格触碰轴点水平作为确认,这套逻辑在 EURUSD M15 上跑过实盘日:2015年1月15日开盘价低于中心轴点,晚间价格拉升至轴点水平,给出卖出信号,止损挂在次级阻力 Res0.5($1.18153),获利看主支撑 Sup1.0($1.17301)。若当日未触发止损或获利,次交易日开盘强制平仓。外汇与贵金属杠杆高,这类规则随时被跳空吞掉,只能当概率框架用。 CSignalPivots 类里,OpenLongParams() 用 MinorSupport() 取二级支撑作止损、MajorResistance() 取主阻力作获利;卖出则换成 MinorResistance() 与 MajorSupport()。基类 CExpertSignal 默认不绑主信号时会用固定值,所以 EA 初始化时必须指定主信号索引,否则水平算错。退出侧重写了 CloseLongParams(),把 ThresholdClose 设 0,靠 m_pattern_0_done 标记在交易日开端平掉未完成的模式0仓位。 策略测试器取 2013.1.7–2017.1.7 的 EURUSD M15,参数设进场阈值10、权重1.0、固定量0.1手、容差15点,第一版净值为负。把信号反向后第二版并不是镜像亏损,说明止损获利不对称;第三版买仓止损推到 Sup1.0 前、获利限在 Res0.5,卖仓止损放 Res1.0、获利在 Sup0.5,曲线才接近首版镜像。可见轴点策略的盈亏结构高度依赖水平摆放,不是简单反手就能翻盘。 直接把下面这段类声明丢进 MT5 的包含文件,就能看到信号对象怎么挂指标和阈值,改 m_pnt_near 可微调触线确认的松紧。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class CSignalPivots                                                    |
class=class="str">"cmt">//| 目的: 根据轴点生成交易信号的类.                                      |
class=class="str">"cmt">//| CExpertSignal 类的继承类.                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSignalPivots : class="kw">public CExpertSignal
  {
   class=class="str">"cmt">//--- === 数据成员 === --- 
class="kw">protected:
   CiCustom                m_pivots;             class=class="str">"cmt">// "Pivots(轴点)" 指标对象  
   class=class="str">"cmt">//--- adjustable parameters
   class="type">bool                    m_to_plot_minor;      class=class="str">"cmt">// 画出次级水平
   class="type">class="kw">double                  m_pnt_near;           class=class="str">"cmt">// 阈值 
   class=class="str">"cmt">//--- estimated
   class="type">class="kw">double                  m_pivot_val;          class=class="str">"cmt">// 轴点数值
   class="type">class="kw">double                  m_daily_open_pr;      class=class="str">"cmt">// 当前日的开盘价  
   CisNewBar               m_day_new_bar;        class=class="str">"cmt">// 每日时段的新柱
   class=class="str">"cmt">//--- 市场模式  
   class=class="str">"cmt">//--- class="num">1) 模式 class="num">0 "第一次接触到轴点水平" (顶部 - 买入, 底部 - 卖出)
   class="type">int                     m_pattern_0;          class=class="str">"cmt">// 权重
   class="type">bool                    m_pattern_0_done;     class=class="str">"cmt">// 模式结束的标记
   class=class="str">"cmt">//--- === 方法 === --- 
class="kw">public:
   class=class="str">"cmt">//--- 构造函数/析构函数
   class="type">void                    CSignalPivots(class="type">void);
   class="type">void                   ~CSignalPivots(class="type">void){};

常见问题

用轴点信号框架的缓冲区把反转水平直接画成指标线,加载到图表即可自动显示,省去手动重画。
多在跨周期或数据未更新时出现,确认图表品种有连续报价且指标计算了完整时段,缓冲才会连续填充。
小布可在对应品种页直接给出轴点反转线与远端支撑阻力的 AI 诊断,打开就能看到,不用自己写框架。
会。写入逻辑偏移或方向反了,回测里交易水平就会错位,先单独打印缓冲值核对再跑回测。
原文仅给回测实况未给固定胜率,外汇贵金属高风险,建议先开平台验证再小仓试,勿直接跟单。