下载MetaTrader 5

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

13 二月 2017, 09:04
MetaQuotes Software Corp.
0
4 217

MetaTrader 5 可以开发和测试同时交易多种金融资产的交易机器人。其内建的策略测试器能够从经纪商的服务器中下载所需的订单时刻历史,并会考虑到账户的合约规范,所以开发人会员不需要人工做这些工作了。这可以使交易环境条件的重建能够简单和可靠,包括乃至不同交易品种中订单来临之间毫秒级的间隔。在本文中,我们将演示在两种莫斯科交易所期货上开发和测试一种点差策略。


资产的负相关: Si 和 RTS

Si-M.Y 和 RTS-M.Y 期货是在莫斯科交易所进行交易的,这两种期货类型是紧密相关的。这里的 M.Y 意思是合约过期日期:

  • M — 月份的编号
  • Y — 年份的最后两位数字

Si 是美元/俄罗斯卢布的一种期货合约,RTS 是一种以美元表示的 RTS 指数的期货合约。RTS 指数包含了俄罗斯公司的股票,其价格是以卢布表示的,USD/RUR 的波动也会引起以美元表示的指数的波动。价格图表上显示,当一种资产上涨时,另一种通常就下跌。


为了更好地观察,我们已经在这些图表上画出了标准偏差的通道。


计算 Si 和 RTS 之间的线性回归

通过使用线性回归方程 Y(X)=A(X)+B,我们可以明显发现两种资产之间的关联。让我们创建一个脚本 CalcShowRegression_script.mq5, 它使用两个收盘价的数组计算系数,并在图表上直接使用回归线来显示分布图。

回归系数是使用一个ALGLIB函数来计算的,而数值的绘制使用的是标准库的图形类



在Si和构造序列之间绘制点差指标

我们已经得到了线性回归系数,并且可以绘制出类型为 Y(RTS) = A*RTS+B 的构造图表,让我们把资产源数据和构造的序列之间的差称为 "点差"。这个差距在每个柱可能都不同,数值可以是正的,也可以是负的。

为了看到点差,让我们创建TwoSymbolsSpread_Ind.mql5指标,它显示了最近500个柱上的点差的柱形图。正的数值以蓝色绘制,负的数值是黄色的。

当新柱开启时,指标会更新并把线性回归系数写到专家日志中,并且,它会等待两个资产,包括Si和RTS都有新的烛形才开始,这样,指标就能确保计算的正确性和精确度。


在点差通道的最近100个柱内创建一个线性回归通道

点差通道显示了Si期货和所构造的根据时间交易品种价格变化的差距,为了评估当前的点差,让我们创建SpreadRegression_Ind.mq5 指标 (含有线性回归的点差),它在点差图表上画了一条趋势线。线的参数是使用线性回归计算的,让我们在一个图表上载入两个指标用于调试。

红色趋势线的斜率依赖于最近100个柱的点差值。现在我们有了达到最低要求的数据,我们就可以创建一个交易系统。


策略 #1: 线性回归斜率在点差图表上变化

TwoSymbolsSpread_Ind.mql5 指标中的点差数值将根据 Si 和 Y(RTS)=A*RTS + B 之间的差来计算,您可以通过在调试 模式 (F5 按键)下运行指标来简单检查它。


让我们创建一个简单的 EA 交易来监控附加在点差图表上的线性回归的斜率的改变,线的斜率也就是公式中的 A 系数: Y=A*X+B. 如果在点差图表上的趋势是正的,A>0. 如果趋势是负的,A<0. 线性回归是使用点差图表上最近100个柱的值来计算的,这里是 EA 交易 Strategy1_AngleChange_EA.mq5代码的一部分。

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

为了避免在趋势改变时猜测买什么和卖什么,让我们加入一个外部参数来允许反转交易规则:

input SPREAD_STRATEGY strategy=SELL_AND_BUY_ON_UP; // 点差策略的类型

现在我们可以启动EA交易并开始调试了。


测试交易策略 #1

可视化测试模式最适合用于调试,使用菜单"工具-设置-调试"来设置所需的数据:

  1. 交易品种
  2. 时段
  3. 测试间隔
  4. 执行
  5. 存款
  6. 分时生成模式

交易所工具的推荐模式是 "基于真实分时的每一分时",在这种情况下,EA将使用记录的历史数据进行测试,而最终的结果将何真实交易条件非常接近。

MetaTrader 5 交易服务器自动收集和储存从交易所受到的所有分时数据并且根据第一个请求把整个订单分时历史发送给终端。

调试模式使得可以在可视化模式下执行测试过程,当有必要时使用断点来检查任何变量的数值。机器人中使用的指标将会自动载入到图表上,不需要人工附加它们。

当调试EA代码时,我们可以优化参数。


交易策略 #1 的优化

Strategy1_AngleChange_EA.mq5 EA交易有几个外部参数,可以通过优化来进行配置 (使用黄色突出显示):

input int       LR_length=100;                     // 点差回归计算中的柱数
input int       Spread_length=500;                 // 用于计算点差的柱数
input double    profit_percent=10;                 // 锁定的利润百分比

input SPREAD_STRATEGY strategy=SELL_AND_BUY_ON_UP; // 点差策略的类型

在本例中,我们将在两个版本的策略中只优化profit_percent,这样来了解它们之间是否有区别。换句话说,我们固定strategy参数的值,并基于profit_percent在 0.2 到 3.0% 的范围进行优化, 这样是为了看到两个趋势线斜率变化方法的整体印象。

对于BUY_AND_SELL_ON_UP 规则 (买入第一个资产,卖出第二个), 当线的斜率从负变正时,优化没有显示出好的结果。一般来说,这种进入市场的方法看起来不怎么样,我们在两个月的测试期间有很多亏损。


SELL_AND_BUY_ON_UP 规则 (卖出第一个资产,买入第二个) 给出了更好的优化结果: 15轮中的5轮有一些利润。


优化是在从2016年8月1日到9月30日之间的历史数据(两个月间隔)上进行的。大致看来,这两种交易策略看起来都不怎么样,也许问题出在我们用于进场的参数上,也就是说趋势线在最近100个柱上的斜率,是滞后的指标。让我们开发一个第二版本的策略。


策略 #2: 已完成柱上的点差符号改变

在第二种策略中,我们分析点差符号的改变。我们将只会分析已完成柱的数值,也就是说,我们将在 "今天的" 柱开启时做检查。如果"前天"的柱的点差是负的,而它在"昨天"的柱上是正的,我们就假定点差转向上升了。代码使得可以在点差改变的任何方向上进行交易,我们可以使用strategy参数来修改进场的方向,这里是一块来自Strategy2_SpreadSignChange_EA.mq5 的代码:

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

首先,我们在可视化测试模式下调试EA交易,并根据profit_percent运行优化,就像我们在第一个策略中做得那样。结果:



您可以看到,第二个策略中使用的"卖出第一个而买入第二个资产"规则也给出了令人失望的测试结果,"买入第一个而卖出第二个资产" 在所有测试伦中给出了更多的亏损。

让我们创建此策略的第三个版本。


策略 #3: 当前柱上点差符号改变,并且在N个订单分时中得到确认

两个之前的策略只在柱的开启时工作,也就是说,它们只分析完全完成的柱。现在,我们将尝试在当前柱内部工作。让我们分析每个订单时刻的点差改变,并且如果在完成柱上的点差符号和当前柱不同,我们就假定点差方向改变了。

另外,点差符号的改变在最近N个订单时刻中应该是稳定的,这将帮助过滤掉假信号。我们需要增加外部参数 ticks_for_trade=10 到我们的EA交易中。如果点差符号在最近10个分时中是负的,并且在之前的柱上是正的,EA就进入市场。这里是 Strategy3_SpreadSignOnTick_EA.mq5 EA交易的 OnTick()函数。

//+------------------------------------------------------------------+
//| EA 交易订单函数                           |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(isNewBar())
      PrintFormat("新柱 %s 在 %s 开启",_Symbol,TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS));
//--- 等待指标数据的刷新,因为它工作于两个交易品种上
   if(BarsCalculated(ind_spreadLR)==Bars(_Symbol,_Period))
     {
      //--- 取得当前(今天)和之前(昨天)的柱的点差值
      double SpreadValues[];
      int copied=CopyBuffer(ind_spreadLR,0,0,2,SpreadValues);
      double Spread_curr=SpreadValues[1]; // 当前未完成的柱上的点差
      double Spread_prev=SpreadValues[0]; // 之前完成柱上的点差
      if(copied!=-1)
        {
         //--- 如果点差符号改变在最近的 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));
            //--- 在图表上显示两个交易品种的最近 ticks_for_trade 的值
            ShowLastTicksComment(ticks_for_trade);
            //--- 如果我们没有持仓,在两个交易品种中进场
            if(PositionsTotal()==0)
               DoTrades(Spread_curr>0,strategy,symbol1,1,symbol2,1);
            //--- 有持仓,则反转交易
            else
               ReverseTrades(Spread_curr>0,positionstype,symbol1,symbol2);
           }
         //--- 点差符号没有改变,检查浮赢 - 是时候平仓吗?
         else
           {
            double profit=AccountInfoDouble(ACCOUNT_PROFIT);
            double balance=AccountInfoDouble(ACCOUNT_BALANCE);
            if(profit/balance*100>=profit_percent)
              {
               //--- 达到了所需的浮赢水平,获利
               trade.PositionClose(symbol1);
               trade.PositionClose(symbol2);
               positionstype=0;
              }
           }
        }
     }
   }

在这个EA交易中,我们已经加入了 ShowLastTicksComment() 函数,它在图表上显示当信号出现时两个交易品种最近N个分时的数值,这使我们可以可视化地测试策略并监控毫秒级精确度的时刻变化。

现在,我们开始应用在前两个策略中使用的优化选项,并得到如下结果:

"买入第一个资产而卖出第二个"


"卖出第一个资产而买入第二个"


这样简单的优化并没有使结果提升很多。


策略 4: 点差达到了预先设定的百分比数值

现在,让我们创建点差交易中的第四个也是最后一个策略,它将和前面三个策略一样简单: 当点差值达到第一种资产价格的指定的百分比— spread_delta时就出现交易信号,订单处理函数 OnTick() 有少许改动,这里就是它在 Strategy4_SpreadDeltaPercent_EA.mq5 文件中的样子。

//+------------------------------------------------------------------+
//| EA 交易订单函数                           |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(isNewBar())
      PrintFormat("新柱 %s 在 %s 开启",_Symbol,TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS));
//--- 等待指标数据的刷新,因为它工作于两个交易品种上
   if(BarsCalculated(ind_spreadLR)==Bars(_Symbol,_Period))
     {
      //--- 取得当前柱(今天)的点差数值
      double SpreadValues[];
      int copied=CopyBuffer(ind_spreadLR,0,0,1,SpreadValues);
      double Spread_curr=SpreadValues[0]; // 当前未完成的柱的点差
      if(copied!=-1)
        {
         MqlTick tick;
         SymbolInfoTick(symbol1,tick);
         double last=tick.last;
         double spread_percent=Spread_curr/last*100;
         //--- 如果点差 % 达到了 spread_delta 的数值
         if(MathAbs(spread_percent)>=spread_delta)
           {
            PrintFormat("点差达到了 %.1f%% (%G) %s",
                        spread_percent,TimeToString(TimeCurrent(),TIME_SECONDS),
                        Spread_curr);
            //--- 如果没有持仓,就在两个交易品种中进入市场
            if(PositionsTotal()==0)
               DoTrades(Spread_curr,strategy,symbol1,1,symbol2,1);
            //--- 有持仓,就反转交易
            else
               ReverseTrades(Spread_curr,positionstype,symbol1,symbol2);
           }
         //--- 点差在可接受的范围之内,检查浮赢 - 是时候平仓吗?
         else
           {
            double profit=AccountInfoDouble(ACCOUNT_PROFIT);
            double balance=AccountInfoDouble(ACCOUNT_BALANCE);
            if(profit/balance*100>=profit_percent)
              {
               //--- 达到了要求的浮盈,就获利平仓
               trade.PositionClose(symbol1);
               trade.PositionClose(symbol2);
               positionstype=0;
              }
           }
        }
     }
  }

当达到了指定的利润百分比 profit_percent=2 时,也会进行获利平仓,这次它是一个固定值,使用spread_delta参数在范围 0.1 到 1% 之内开始优化。

"买入第一个资产而卖出第二个"


"卖出第一个资产而买入第二个"


这一次,第一个 "买入第一个而卖出第二个" 的规则看起来比第二个规则好很多,您可以进一步使用其他参数进行优化。


MetaTrader 5 — 交易策略开发环境

在本文中,我们已经探讨了4种用于点差交易的简单策略,这些策略产生的测试和优化结果不应该用于作为行动的指导,因为它们只是在有限的时间段中获得的,并且在某种程度上可能是随机的。本文的初衷是展示,使用 MetaTrader 5来测试和调试交易策略是多么容易和方便。

MetaTrader 5 测试器为自动交易系统的开发人员提供了以下方便的功能:

  • 自动下载EA交易中使用的所有交易品种的分时历史
  • 可视化的指标和策略调试模式,包含了交易、交易历史和专家日志的可视化
  • 在可视化测试模式下,EA交易中使用的所有指标都会自动载入
  • 使用真实记录的历史数据并重现真实的交易环境来测试策略
  • 使用自定义目标函数来多线程优化参数
  • 使用了数以千计的测试代理以进行更快的优化
  • 根据自定义规则可视化地浏览优化的结果
  • 测试在多个资产中进行交易并进行毫秒级同步的策略
  • 在测试过程中直接调试策略 — 您可以设置断点来检查所需变量的值并一步一步运行测试。

在本文中,策略测试器用作研究工具来找到正确的方向,这是通过使用一个参数的优化来做到的,它可以快速得到有价值的结论。您可以加入新的规则,修改已有的再运行全面的 EA 优化。为了加快计算,可以使用特别为 MetaTrader 5平台设计的MQL5 云网络


关于策略的重要提示

通常,当搜索用于点差计算的交易品种时,会使用价格的增长而不是绝对价格的数值,意思是用 Delta[i]=Close[i]-Close[i-1] 来计算,而不是 Close[i] 序列。

对于平衡的交易,您应该选择每个点差交易品种的交易量,在本文中,我们只是对于每个交易品种使用了1手的交易量。

在测试中使用了 Si 和 RTS 合约规格的当前设定,很重要应该说明的是:

  • RTS-12.16 期货是基于美元的,
  • RTS-12.16 期货的价格在莫斯科交易所每天都会设置点值,
  • 点值等于 指示的 USD/RUB 汇率 的0.2。

指数的计算信息在莫斯科交易所网站上有,位于http://fs.moex.com/files/4856. 所以,您应该记住,在策略测试器中优化的结果依赖于测试期间的美元汇率。本文包含的优化结果的屏幕截图是2016年10月25日的。

代码的书写是在最佳运行条件下执行的:它并不包含对发送订单结果的处理,连接错误和失去连接的处理,它也没有考虑手续费和滑点。

期货的流动性和图表填充在合约过期后提高。代码没有包含对于这种情况的处理,即收到了一个交易品种的报价,而第二个交易品种在全部柱中都没有(在交易所因为某种原因没有交易)。然而,EA交易中使用的指标会等到这两个交易品种柱的同步,来计算点差,并把这些事件写到日志中。

本文没有包含对点差与平均值偏差的统计分析,而它在创建更加可靠的交易规则时是需要的。

没有使用市场深度分析,因为在 MetaTrader 5 策略测试器中没有模拟订单预订。

注意: 本文中使用的指标会为创建点差图标和趋势线而动态重新计算线性回归系数,所以,在测试结束之前,图表的外观和指标值与测试中显示的数值会有所不同,

可以在可视化测试模式下运行附件中的指标或者EA交易,来在实时观察过程。


相关文章:


文章中使用的程序:

#
 名称
类型
描述
1
CalcShowRegression_script.mq5
脚本
计算线性回归系数并使用趋势线绘制点图 (使用 CGraphic 类和 Alglib 函数)
2
TwoSymbolsSpread_Ind.mql5
指标
指标画出了两个交易品种的点差柱形图
3
SpreadRegression_Ind.mq5
指标 指标画出了点差图表并在其上画出了回归线
4
Strategy1_AngleChange_EA.mq5
EA 交易
策略 #1. 交易是基于在方程 Y=A*X+B 中的线性回归系数 A 的符号改变的。分析和进场只是在新柱开启时进行
5
Strategy2_SpreadSignChange_EA.mq5 EA 交易 策略 #2. 交易是基于点差值符号的改变进行的,分析和进场只是在新柱开启时进行
6
Strategy3_SpreadSignOnTick_EA.mq5
EA 交易
策略 #3. 交易是基于点差值符号的改变进行的,分析和进场是在当前柱中进行,符号的改变在最近的N个时刻中应该是稳定的
7 Strategy4_SpreadDeltaPercent_EA.mq5 
EA 交易
策略 #4. 交易是基于点差值达到某个百分比的,分析和进场时在当前柱的第一个时刻进行的

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/2739

附加的文件 |
MQL5.zip (22.9 KB)
交易货币篮子时可用的形态 交易货币篮子时可用的形态

跟随我们以前关于货币篮子交易原理的文章, 这里我们将分析交易者可以检测的形态。我们还将研究每种形态的优点和缺点, 并就其使用提供一些建议。基于威廉姆斯振荡器的指标将用作分析工具。

直方图形式的统计分布, 无需指标缓冲区和数组 直方图形式的统计分布, 无需指标缓冲区和数组

本文讨论当绘制市场条件的统计分布直方图时利用图形存储器的可能性, 而无需指标缓冲区和数组。描述了样本直方图的细节, 并展示了 MQL5 图形对象的 "隐藏" 功能。

图形界面 X: 时间控件, 复选框列表控件和表格排序 (集成编译 6) 图形界面 X: 时间控件, 复选框列表控件和表格排序 (集成编译 6)

创建图形界面的函数库开发续篇。这次会涵盖时间和复选框列表控件。此外, CTable 类现已提供按照升序或降序对数据排序的能力。

图形界面 X: 高级列表和表格管理。代码优化 (集成构建 7) 图形界面 X: 高级列表和表格管理。代码优化 (集成构建 7)

函数库的代码需要进行优化: 它应该更规范化, 这样可以 — 更具可读性并易于理解学习。此外, 我们将继续开发之前创建的控件: 列表, 表格和滚动条。