一个为莫斯科交易所期货开发的点差策略实例·进阶篇
📈

一个为莫斯科交易所期货开发的点差策略实例·进阶篇

(2/3)· 接上篇概念铺垫,本篇在双期货真实相关性上落地三种点差交易逻辑与回测优化

案例拆解 第 2/3 篇
把 Si 和 RTS 当独立品种裸做对冲的人,常忽略两者毫秒级订单间隔带来的点差漂移。直接用价格差开仓而不做回归校正,回测漂亮实盘漏单。本文用构造序列把相关性量化后再动手。

◍ 用回归斜率捕捉点差的趋势拐点

点差图上的线性回归写成 Y=A*X+B,A 就是那条拟合线的斜率。A>0 代表点差近段在扩张,A<0 则是收敛,拐点出现时前后两根柱的 A 值乘积会小于 0。 附件里的 EA 取最近 100 根柱做回归(LR_length=100),另用 500 根(Spread_length=500)算点差本身,默认跑在 M5 周期。它只在 BarsCalculated 与 Bars 对齐后才读缓冲,避免跨品种数据没刷完就误触发。 核心判断在 OnTick:CopyBuffer 取索引 1、2 的回归值,用 LRvalues[1]-LRvalues[0] 当当前 A,和历史 Spread_A_prev 相乘。若结果 <0,且无持仓就按 strategy 参数双向进场;已有持仓则反转。若方向没变,就盯着 profit/balance*100 是否越过 profit_percent(默认 10%),到了就双向平掉。 外汇与贵金属价差受流动性影响大,这种拐点策略在高波动时段可能频繁假突破,实盘前务必在 MT5 用历史数据步进回测。把 strategy 设成 SELL_AND_BUY_ON_UP 或 BUY_AND_SELL_ON_UP,直接 F5 调试就能看 PrintFormat 打的斜率翻转日志。

MQL5 / C++
class="macro">#include <Trade\Trade.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 点差策略类型                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum SPREAD_STRATEGY
  {
   BUY_AND_SELL_ON_UP,   class=class="str">"cmt">// 买入第一个,卖出第二个
   SELL_AND_BUY_ON_UP,   class=class="str">"cmt">// 卖出第一个,买入第二个
  };
class=class="str">"cmt">//---
class="kw">input class="type">int        LR_length = class="num">100 ;                     class=class="str">"cmt">// 用于计算点差回归的柱数
class="kw">input class="type">int        Spread_length = class="num">500 ;                 class=class="str">"cmt">// 用于计算点差的柱数
class="kw">input ENUM_TIMEFRAMES   period = PERIOD_M5 ;           class=class="str">"cmt">// 时段
class="kw">input class="type">class="kw">string     symbol1 = "Si-class="num">12.16" ;                 class=class="str">"cmt">// 配对的第一个交易品种
class="kw">input class="type">class="kw">string     symbol2 = "RTS-class="num">12.16" ;               class=class="str">"cmt">// 配对的第二个交易品种
class="kw">input class="type">class="kw">double     profit_percent = class="num">10 ;                 class=class="str">"cmt">// 锁定的利润百分比
class="kw">input SPREAD_STRATEGY strategy =SELL_AND_BUY_ON_UP; class=class="str">"cmt">// 点差策略的类型
class=class="str">"cmt">//--- 指标句柄
class="type">int ind_spreadLR,ind,ind_2_symbols;
class=class="str">"cmt">//--- 交易操作的类
CTrade trade;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA交易订单函数                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 线性回归在点差图表上的斜率系数A, Y(X)=A*X+B
   class="kw">static class="type">class="kw">double Spread_A_prev= class="num">0 ;
   if (isNewBar())
       PrintFormat( "新柱 %s 在 %s 上开启" , _Symbol , TimeToString( TimeCurrent(), TIME_DATE | TIME_SECONDS ));
class=class="str">"cmt">//--- 等待指标数据刷新,因为它要在两个交易品种上工作
   if ( BarsCalculated(ind_spreadLR)== Bars( _Symbol , _Period ))
     {
       class=class="str">"cmt">//--- 在点差图表中获取索引为1和2(“昨天”和“前天”)的线性回归数值
       class="type">class="kw">double LRvalues[];
       class="type">class="kw">double Spread_A_curr;
       class="type">int copied= CopyBuffer(ind_spreadLR, class="num">1 , class="num">1 , class="num">2 ,LRvalues);
       if (copied!=- class="num">1 )
        {
         class=class="str">"cmt">//--- 在最近关闭 ("昨天") 的柱上的线性回归系数
         Spread_A_curr=LRvalues[ class="num">1 ]-LRvalues[ class="num">0 ];
         class=class="str">"cmt">//--- 如果线性回归的斜率方向改变,则当前和前一个数值的乘积小于0
         if (Spread_A_curr*Spread_A_prev< class="num">0 )
           {
             PrintFormat( "LR 斜率放向已改变, Spread_A_curr=%.2f, Spread_A_prev=%.2f: %s" ,
                        Spread_A_curr,Spread_A_prev, TimeToString( TimeCurrent(), TIME_SECONDS ));
             class=class="str">"cmt">//--- 如果我们没有持仓,就在两个交易品种中进场
             if ( PositionsTotal()== class="num">0 )
               DoTrades(Spread_A_curr-Spread_A_prev> class="num">0 , strategy , symbol1 , class="num">1 , symbol2 , class="num">1 );
             class=class="str">"cmt">//--- 如果有持仓,进行反转交易
             else
               ReverseTrades( symbol1 , symbol2 );
           }
         class=class="str">"cmt">//--- LR 倾斜方向没有变化,检查浮赢 - 是时候平仓吗?
         else
           {
             class="type">class="kw">double profit= AccountInfoDouble( ACCOUNT_PROFIT );
             class="type">class="kw">double balance= AccountInfoDouble( ACCOUNT_BALANCE );
             if (profit/balance* class="num">100 >= profit_percent )
              {
               class=class="str">"cmt">//--- 已经达到了所需的浮赢,获利
               trade.PositionClose( symbol1 );
               trade.PositionClose( symbol2 );
              }
           }
         class=class="str">"cmt">//--- 记住趋势的方向,用于和开启的新柱做比较
         Spread_A_prev=Spread_A_curr;
        }
     }
  }

用可视化调试跑通第一套策略

MT5 的可视化测试最适合做 EA 调试而非实盘验证。在「工具-设置-调试」里要逐项指定:交易品种、时段、测试间隔、执行模式、存款额、分时生成模式。 对于外汇和贵金属这类场外品种,分时生成建议选「基于真实分时的每一分时」;交易所品种同理。该模式下 EA 会回放真实记录的历史分时,结果更接近真实交易条件,但外汇/贵金属仍属高风险,回测贴近不等于实盘概率可控。 MT5 交易服务器会自动收集并存储从交易所收到的全部分时,终端首次请求便下发完整订单分时历史,不需自己拼数据。 开启调试模式后可在可视化界面单步跑策略,必要时打断点看任意变量数值;EA 引用的指标会自动加载到图表,不用手动挂。借这个过程顺手把参数调一遍,比盲改高效得多。

「只调利润比例看两种点差策略成色」

Strategy1_AngleChange_EA.mq5 留了四个外部参数,黄色高亮那几个都能进优化器。本次只动 profit_percent,把 strategy 钉死,范围扫 0.2% 到 3.0%,目的不是找最优解,而是横向比两种斜率翻转规则的轮廓。 [CODE]input int LR_length = 100 ; // 点差回归计算中的柱数 input int Spread_length = 500 ; // 用于计算点差的柱数 input double profit_percent = 10 ; // 锁定的利润百分比 input SPREAD_STRATEGY strategy =SELL_AND_BUY_ON_UP; // 点差策略的类型[/CODE] 上面四行里,LR_length=100 决定回归线用最近 100 根柱算斜率,Spread_length=500 管点差统计窗口,profit_percent 初始给的 10 实际远超本次扫描上限,得手动压进 3% 以内。 BUY_AND_SELL_ON_UP(买一卖二)在斜率由负转正时进场,优化结果稀烂。两个月实测连亏多笔,这种翻转触发方式本身就不像能打的样子。 SELL_AND_BUY_ON_UP(卖一买二)稍体面:15 轮优化里 5 轮出了点利润。测试区间是 2016.8.1–9.30 的历史两根月线,外汇与贵金属价差策略在此样本高风险,两种规则整体都偏弱。 滞后嫌疑最大的是 LR_length=100 这根斜率线——百柱回归天然慢半拍,进场参数可能选错了。下一版直接换斜率算法。

MQL5 / C++
class="kw">input class="type">int        LR_length = class="num">100 ;                     class=class="str">"cmt">// 点差回归计算中的柱数
class="kw">input class="type">int        Spread_length = class="num">500 ;                 class=class="str">"cmt">// 用于计算点差的柱数
class="kw">input class="type">class="kw">double     profit_percent = class="num">10 ;                 class=class="str">"cmt">// 锁定的利润百分比
class="kw">input SPREAD_STRATEGY strategy =SELL_AND_BUY_ON_UP; class=class="str">"cmt">// 点差策略的类型

◍ 拿已完成柱的点差正负翻转做信号

这套打法只看已收盘的柱:新柱一开,才去比对前两根柱的点差符号。若前天柱点差为负的、昨天柱变成正的,就判定点差方向翻转向上;反过来亦成立。EA 里用 strategy 参数切换只做某一侧翻转,不写死方向。 下面这段来自实盘 EA 的核心 OnTick 逻辑,先把点差缓冲读出来再判符号。static 变量 Spread_prev 跨 tick 保留,等 isNewBar 后拿索引 1、2 的已完成柱值做乘积累积判断。 回测层面不乐观:在可视化测试里按 profit_percent 跑优化,'卖一买二'规则全程亏损;'买一卖二'在全部测试轮次里也是净亏。所以原文作者紧接着做了第三版策略。 外汇与贵金属点差受流动性跳变影响大,符号翻转只是概率性线索,实盘前务必在 MT5 策略测试器用历史数据复算,高杠杆品种回撤可能超预期。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA 交易订单函数                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 之前的点差值是 Symbol1 和 Y(Symbol2)=A*Symbol2+B 之间的差异
   class="kw">static class="type">class="kw">double Spread_prev=class="num">0;
   if(isNewBar())
       PrintFormat("新柱 %s 在 %s 开启",_Symbol,TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS));
class=class="str">"cmt">//--- 等待指标数据刷新,因为它工作于两个交易品种
   if(BarsCalculated(ind_spreadLR)==Bars(_Symbol,_Period))
     {
       class=class="str">"cmt">//--- 取得索引为1和2 ("昨天" 和 "前天") 的点差值
       class="type">class="kw">double SpreadValues[];
       class="type">int copied=CopyBuffer(ind_spreadLR,class="num">0,class="num">1,class="num">2,SpreadValues);
       class="type">class="kw">double Spread_curr=SpreadValues[class="num">1];
       if(copied!=-class="num">1)
        {
         class=class="str">"cmt">//--- 如果点差符号改变,当前和之前数值相乘会小于0
         if(Spread_curr*Spread_prev<class="num">0)
           {
             PrintFormat("点差符号改变, Spread_curr=%.2f, Spread_prev=%.2f: %s",
                        Spread_curr,Spread_prev,TimeToString(TimeCurrent(),TIME_SECONDS));
             class=class="str">"cmt">//--- 如果我们没有持仓,就在两个交易品种中进场
             if(PositionsTotal()==class="num">0)
               DoTrades(Spread_curr>class="num">0,strategy,symbol1,class="num">1,symbol2,class="num">1);
             class=class="str">"cmt">//--- 有持仓,就反转交易
             else
               ReverseTrades(symbol1,symbol2);
           }
         class=class="str">"cmt">//--- 点差符号改变,检查浮盈 - 是时候平仓吗?
         else
           {
             class="type">class="kw">double profit=AccountInfoDouble(ACCOUNT_PROFIT);
             class="type">class="kw">double balance=AccountInfoDouble(ACCOUNT_BALANCE);
             if(profit/balance*class="num">100>=profit_percent)
              {
               class=class="str">"cmt">//--- 达到了要求的浮盈水平,获利平仓
               trade.PositionClose(symbol1);
               trade.PositionClose(symbol2);
              }
           }
         class=class="str">"cmt">//--- 记住点差值以在新柱开启时做比较
         Spread_prev=Spread_curr;
        }
     }
  }

在当根柱内抓点差符号翻转

前两个策略都只在整根柱闭合后才动手,等于放弃了柱内行情。这一节把战场移到当前未完成柱:逐笔 tick 监控点差符号,只要当根柱的点差符号和上一根已完成柱相反,就先假定方向切换已经发生。 光靠一次翻转不够稳,原文用了一个外部参数 ticks_for_trade=10 做过滤——要求最近 10 个订单分时的点差符号一致地反向,才认定不是噪点。比如上一根柱点差为正、近 10 个 tick 点差持续为负,EA 才进场;无持仓则双向建仓,有持仓则反转。 EA 里挂了 ShowLastTicksComment() 把最近 N 个分时的双品种数值直接打印在图表上,你能以毫秒精度肉眼核对信号时刻,不必盲信回测。把这套逻辑接进 MT5 后,沿用前两步的“买一卖二 / 卖一买二”优化,实测结果并没有明显抬升——说明单纯靠点差符号翻转+10 tick 确认,在外汇与贵金属这类高杠杆品种上仍只是弱信号,实盘前务必用小资金验证风险。

MQL5 / C++
class="type">void OnTick()
  {
   if(isNewBar())
       PrintFormat("新柱 %s 在 %s 开启", _Symbol, TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS));
   class=class="str">"cmt">//--- 等待指标数据的刷新,因为它工作于两个交易品种上
   if(BarsCalculated(ind_spreadLR)==Bars(_Symbol,_Period))
     {
      class=class="str">"cmt">//--- 取得当前(今天)和之前(昨天)的柱的点差值
      class="type">class="kw">double SpreadValues[];
      class="type">int copied=CopyBuffer(ind_spreadLR,class="num">0,class="num">0,class="num">2,SpreadValues);
      class="type">class="kw">double Spread_curr=SpreadValues[class="num">1]; class=class="str">"cmt">// 当前未完成的柱上的点差
      class="type">class="kw">double Spread_prev=SpreadValues[class="num">0]; class=class="str">"cmt">// 之前完成柱上的点差
      if(copied!=-class="num">1)
        {
         class=class="str">"cmt">//--- 如果点差符号改变在最近的 ticks_for_trade 分时中是稳定的
         if(SpreadSignChanged(Spread_curr,Spread_prev,ticks_for_trade))
           {
             PrintFormat("点差符号改变了,Spread_curr=%.2f, Spread_prev=%.2f: %s",
                        Spread_curr,Spread_prev,TimeToString(TimeCurrent(),TIME_SECONDS));
             class=class="str">"cmt">//--- 在图表上显示两个交易品种的最近 ticks_for_trade 的值
            ShowLastTicksComment(ticks_for_trade);
             class=class="str">"cmt">//--- 如果我们没有持仓,在两个交易品种中进场
             if(PositionsTotal()==class="num">0)
               DoTrades(Spread_curr>class="num">0, strategy, symbol1, class="num">1, symbol2, class="num">1);
             class=class="str">"cmt">//--- 有持仓,则反转交易
             else
               ReverseTrades(Spread_curr>class="num">0,positionstype, symbol1, symbol2);
           }
         class=class="str">"cmt">//--- 点差符号没有改变,检查浮赢 - 是时候平仓吗?
         else
           {
             class="type">class="kw">double profit=AccountInfoDouble(ACCOUNT_PROFIT);
             class="type">class="kw">double balance=AccountInfoDouble(ACCOUNT_BALANCE);
             if(profit/balance*class="num">100 >= profit_percent)
              {
               class=class="str">"cmt">//--- 达到了所需的浮赢水平,获利
               trade.PositionClose(symbol1);
               trade.PositionClose(symbol2);
               positionstype=class="num">0;
              }
           }
        }
     }
   }
把回归计算交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到双合约点差通道与斜率预警,你把精力留在策略参数调校上。

常见问题

RTS 指数成分股以卢布计价,USD/RUR 波动会反向传导到美元表示的指数,形成负相关的统计特征,但属概率性而非绝对。
为确保点差在同源时间轴上对齐,避免某一侧旧柱造成的偏差,提升后续回归与通道评估的精确度。
小布已内置相关品种页的诊断视图,可显示点差柱形与回归通道,但策略逻辑需自行在终端部署,外汇贵金属同样属高风险品类。
斜率方向反映点差近期扩张或收敛倾向,可作为策略 #1 的触发参考,但需结合成交分时确认,避免假突破。
前者用闭合柱确认降低噪声,后者抓实时反转但需 N 个订单分时过滤,延迟与胜率权衡不同。