第十六节 R-Breaker策略

接下来我们来编写一些经典的策略,首先推荐给大家一本叫《Futures Truth》的杂志,这本杂志是专门给程序化交易者提供参考的,里面都是各种程序化交易系统的实盘排名。其中R-Breaker策略就是这款杂志中出来的经典策略,长期占据排名前十的榜单,很长一段时间都是非常有效的策略。那么来看以下这个策略到底是怎么运行的。

1 策略原理

R-Breaker策略是一个日内策略,该策略结合了趋势和反转两种思路,交易的机会比较多。

这个策略首先会根据昨日的开高低收四个价格计算出6个价位,分别是:

观察卖出价(Ssetup)=High+a*(Close-Low);

观察买入价(Bsetup)=Low-a*(High-Close);

反转卖出价(Senter)=b/2*(High+Low)-c*Low;

反转买入价(Benter)= b/2*(High+Low) -c*High;

突破卖出价(Sbreak)= Ssetup-d*(Ssetup-Bsetup);

突破买入价(Bbreak)= Bsetup+d*(Ssetup-Bsetup)。

以上的a、b、c、d均为策略的参数,默认参数为a=0.35,b=1.07,c=0.07,d=0.25。

策略交易逻辑如下:

1.当价格突破突破买入价,平空开多;

2.当价格超过观察卖出价,之后反转跌破反转卖出价,平多开空;

3.当价格突破突破卖出价,平多开空;

4.当价格超过观察买入价,之后反转升破反转买入价,平空开多。

5.收盘前平仓

策略逻辑可以参照下图:

从以上原理可以看出这款策略是突破策略,但是和突破类策略不同的是,它结合了反转策略,这也是它的亮点,震荡策略和趋势策略结合起来后会使资金曲线相对来说更平滑,当然,前提是两种策略都表现不错。所以R-Breaker策略的效果不一定十分好,但是思路值得我们借鉴。

那么为了更快捷地完成R-Breaker策略的编写,我们把已经编写好的开单模块、K线信号控制模块、平仓模块直接拿过来使用。其实编写EA到后面会发现很多模块都是通用的,有些只需要稍微改一下就可以使用了,所以写EA到后面速度会快一点,有些甚至一两个小时就可以完成建模。

2 画线模块

这款策略需要注意的地方是它是日内的策略,在收盘之前需要平仓,所以有必要在图上把每天收盘的那根K线标出来。我们以15min图表来运行策略,首先,每天开盘的时候,在开盘的那根K线上画一根竖线,然后把策略的6个价格计算出来,并在图表上画出来。同时由于这个模块可以识别日线的开盘,所以我们将日线开盘平仓的代码也加入到里面来作为一个保底的功能,防止在某些情况下出现市场的收盘时间的异样而导致订单没有平仓。代码如下:

int day=0;
int bar=0;
datetime ti=0;
datetime timeopen=0;
datetime timeclose_Friday=0;
datetime timeclose_otherday=0;
int magicnumber=1333;
double r1;
double s1;
double r2;
double s2;
double r3;
double s3;
void drawline(string sym,int peri,int peri1)
{
   r1=1.07/2*(iHigh(sym,peri1,1)+iLow(sym,peri1,1))-0.07*iLow(sym,peri1,1);
   s1=1.07/2*(iHigh(sym,peri1,1)+iLow(sym,peri1,1))-0.07*iHigh(sym,peri1,1);
   r2=iHigh(sym,peri1,1)+0.35*(iClose(sym,peri1,1)-iLow(sym,peri1,1));
   s2=iLow(sym,peri1,1)-0.35*(iHigh(sym,peri1,1)-iClose(sym,peri1,1));
   r3=r2+0.25*(r2-s2);
   s3=s2-0.25*(r2-s2);
   int i;
   int check;
   if(ti!=iTime(sym,peri1,0))
   {
      ti=iTime(sym,peri1,0);
      day=day+1;
      for(i=OrdersTotal()-1;i>=0;i--)
      {
         if(OrderSelect(i,SELECT_BY_POS))
         {
            if(OrderSymbol()==sym  && OrderMagicNumber()==magicnumber)
            {
               if(OrderType()==OP_BUY || OrderType()==OP_SELL)
               {
                  ordercl(sym,OrderTicket());
               }
               if(OrderType()==OP_BUYSTOP || OrderType()==OP_SELLSTOP)
               {
                  check=OrderDelete(OrderTicket(),clrAliceBlue);
               }
            }
         }
      }
      timeopen=TimeCurrent();
      ObjectCreate(0,'VLINE'+(string)day,OBJ_VLINE,0,TimeCurrent(),0);
      if(DayOfWeek()==1)
      {
         timeclose_Friday=iTime(sym,peri,1);
      }
      else
      {
         timeclose_otherday=iTime(sym,peri,1);
      }
   }
   if(Barjudge()==1)
   {
      ObjectDelete(0,'HLINE'+(string)day+”r1”);
      ObjectDelete(0,'HLINE'+(string)day+”r2”);
      ObjectDelete(0,'HLINE'+(string)day+”r3”);
      ObjectDelete(0,'HLINE'+(string)day+”s1”);
      ObjectDelete(0,'HLINE'+(string)day+”s2”);
      ObjectDelete(0,'HLINE'+(string)day+”s3”);

ObjectCreate(0,' HLINE'+(string)day+'r1',OBJ_TREND, 0,timeopen,r1,
iTime(sym,peri,0),r1);
      ObjectSetInteger(0,'HLINE'+(string)day+”r1”,OBJPROP_RAY_RIGHT,0);
      ObjectSetInteger(0,'HLINE'+(string)day+”r1”,OBJPROP_COLOR, clrGold);

      ObjectCreate(0,'HLINE'+(string)day+'r2',OBJ_TREND,0,timeopen,r2,
iTime(sym,peri,0),r2);
      ObjectSetInteger(0,'HLINE'+(string)day+'r2',OBJPROP_RAY_RIGHT,0);
      ObjectSetInteger(0,”HLINE”+(string)day+'r2',OBJPROP_COLOR, clrGreen);

      ObjectCreate(0,”HLINE”+(string)day+'r3',OBJ_TREND,0,timeopen,r3,
iTime(sym,peri,0),r3);
      ObjectSetInteger(0,”HLINE”+(string)day+”r3”,OBJPROP_RAY_RIGHT,0);
      ObjectSetInteger(0,”HLINE”+(string)day+”r3”,OBJPROP_COLOR,clrBlue);

      ObjectCreate(0,”HLINE”+(string)day+”s1”,OBJ_TREND,0,timeopen,s1,
iTime(sym,peri,0),s1);
      ObjectSetInteger(0,”HLINE”+(string)day+”s1”,OBJPROP_RAY_RIGHT,0);
      ObjectSetInteger(0,”HLINE”+(string)day+”s1”,OBJPROP_COLOR, clrGold);

      ObjectCreate(0,”HLINE”+(string)day+”s2”,OBJ_TREND,0,timeopen,s2,
iTime(sym,peri,0),s2);
      ObjectSetInteger(0,”HLINE”+(string)day+”s2”,OBJPROP_RAY_RIGHT,0);
      ObjectSetInteger(0,”HLINE”+(string)day+”s2”,OBJPROP_COLOR, clrGreen);

      ObjectCreate(0,”HLINE”+(string)day+”s3”,OBJ_TREND,0,timeopen,s3,
iTime(sym,peri,0),s3);
      ObjectSetInteger(0,”HLINE”+(string)day+”s3”,OBJPROP_RAY_RIGHT,0);
      ObjectSetInteger(0,”HLINE”+(string)day+”s3”,OBJPROP_COLOR,clrRed);
   } 
}	

这里把画线的模块分成几个部分来给大家讲解一下,每个部分功能都不一样,以后大家去学习其他人的程序也可以参照这个方法来做。

第一部分的内容,计算6个价位,很简单,按照公式计算即可;

第二部分,如果ti这个全局变量不等于今天的开盘时间,那么把今天的开盘时间赋值给ti,然后用于统计自系统运行以来的运行天数的变量day会增加1。再接下来,遍历订单,把订单全部平仓,把挂单全部删除。也就是说每天一开盘就要空仓,如前所述这是为了避免一些原因导致收盘时间改变而订单没有平仓的问题。

然后把当前的时间(也就是今天的开盘时间)赋值给timeopen这个变量,并在当前K线上面画一根竖直线,这就实现了在图标上把每天的K线划分出来的功能。

接下来,如果当天是周一,那么把上一个K线的开盘时间(也就是周五最后一根K线的开盘时间)赋值给timeclose_Friday这个变量,然后把其他天的最后一根K线开盘时间赋值给timeclose_otherday。做这一步主要是因为很多市场周五的收盘时间会和周一到周四的收盘时间不同,所以必须分开。

第三部分,如果K线信号控制模块返回1,也就是说有当前周期的K线开盘,那么就把原来根据6个价格画出的水平线删除,并重新画出这6个价位的水平线。这一部分看似很多,实际上都是重复的东西,创建完画线对象之后再设置对象格式而已。

我们把主函数加进去然后测试一下:

可以看到EA在每天开盘的K线上画了一根竖线以作区分,并且6个价格线都在图上画了出来,其中蓝色的线是突破买入价,红色的线是突破卖出价,绿色的线是观察卖出价和观察买入价,金色的线是反转卖出和反转买入价。

由此可以看出,价格要突破突破买入价或者突破卖出价确实是相当不容易的,反转却相对来说容易一些。

不管怎么说,有了画线模块之后就可以很直观地观察系统开单和平仓是否正确了。

3 开单及平仓逻辑模块

接下来要解决的就是策略的核心模块,开单和平仓逻辑模块了,由于这个系统开单和平仓总是同时进行的,所以我们这里把这两者放到一个模块中。依照以上策略原理,其代码如下:

bool reswitch_sell=false;
bool reswitch_buy=false;
void logic(string sym,int peri)
{
   double lot=0.5;
   int i;
   int hour_Friday=TimeHour(timeclose_Friday);
   int minute_Friday=TimeMinute(timeclose_Friday);
   int hour_otherday=TimeHour(timeclose_otherday);
   int minute_otherday=TimeMinute(timeclose_otherday);
   bool buy_switch=true;
   bool sell_switch=true;
   for(i=OrdersTotal()-1;i>=0;i--)
   {
      if(OrderSelect(i,SELECT_BY_POS))
      {
         if(OrderSymbol()==sym  && OrderMagicNumber()==magicnumber && 
			OrderType()==OP_BUY)
         {
            buy_switch=false;
         }
      }
   }
   for(i=OrdersTotal()-1;i>=0;i--)
   {
      if(OrderSelect(i,SELECT_BY_POS))
      {
         if(OrderSymbol()==sym  && OrderMagicNumber()==magicnumber && 
			OrderType()==OP_SELL)
         {
            sell_switch=false;
         }
      }
   }
   if((DayOfWeek()==5 && TimeHour(iTime(sym,peri,0))<hour_Friday) ||

      (DayOfWeek()==5 && TimeHour(iTime(sym,peri,0))==hour_Friday && 
		TimeMinute(iTime(sym,peri,0))<minute_Friday) ||

      (DayOfWeek()!=5 && TimeHour(iTime(sym,peri,0))<hour_otherday) ||

      (DayOfWeek()!=5 && TimeHour(iTime(sym,peri,0))==hour_otherday && 
		TimeMinute(iTime(sym,peri,0))<minute_otherday))
   {
      if(MarketInfo(sym,MODE_ASK)>r3 && buy_switch==true)
      {
         orderopen(sym,”BUY”,lot,0,0,magicnumber,”R-Breaker”);
         for(i=OrdersTotal()-1;i>=0;i--)
         {
            if(OrderSelect(i,SELECT_BY_POS))
            {
               if(OrderSymbol()==sym  && 
				OrderMagicNumber()==magicnumber && OrderType()==OP_SELL)
               {
                  ordercl(sym,OrderTicket());
               }
            }
         }
      }
      if(MarketInfo(sym,MODE_BID)<s3 && sell_switch==true)
      {
         orderopen(sym,”SELL”,lot,0,0,magicnumber,”R-Breaker”);
         for(i=OrdersTotal()-1;i>=0;i--)
         {
            if(OrderSelect(i,SELECT_BY_POS))
            {
               if(OrderSymbol()==sym  && 
				OrderMagicNumber()==magicnumber && OrderType()==OP_BUY)
               {
                  ordercl(sym,OrderTicket());
               }
            }
         }
      }
      if(MarketInfo(sym,MODE_ASK)>r2)
      {
         reswitch_sell=true;
      }
      if(MarketInfo(sym,MODE_BID)<s2)
      {
         reswitch_buy=true;
      }
      if(MarketInfo(sym,MODE_BID)<r1 && reswitch_sell==true && 
		sell_switch==true)
      {
         orderopen(sym,”SELL”,lot,0,0,magicnumber,”R-Breaker”);
         reswitch_sell=false;
         for(i=OrdersTotal()-1;i>=0;i--)
         {
            if(OrderSelect(i,SELECT_BY_POS))
            {
               if(OrderSymbol()==sym  && 
				OrderMagicNumber()==magicnumber && OrderType()==OP_BUY)
               {
                  ordercl(sym,OrderTicket());
               }
            }
         }
      }
      if(MarketInfo(sym,MODE_ASK)>s1 && reswitch_buy==true && 
		buy_switch==true)
      {
         orderopen(sym,”BUY”,lot,0,0,magicnumber,”R-Breaker”);
         reswitch_buy=false;
         for(i=OrdersTotal()-1;i>=0;i--)
         {
            if(OrderSelect(i,SELECT_BY_POS))
            {
               if(OrderSymbol()==sym  && 
				OrderMagicNumber()==magicnumber && OrderType()==OP_SELL)
               {
                  ordercl(sym,OrderTicket());
               }
            }
         }
      }
   }
   else
   {
      for(i=OrdersTotal()-1;i>=0;i--)
      {
         if(OrderSelect(i,SELECT_BY_POS))
         {
            if(OrderSymbol()==sym && OrderMagicNumber()==magicnumber && 
				(OrderType()==OP_BUY || OrderType()==OP_SELL))
            {
               ordercl(sym,OrderTicket());
            }
         }
      }
      reswitch_sell=false;
      reswitch_buy=false;
      return;
   }
}

同样分部给大家讲解一下,第一部分,做了一个遍历订单,如果存在买单那么买单的开关就会被关闭,接下来就不会开启买单,同样如果存在卖单那么卖单的开关就会关闭,这个开关是为了防止重复下单。

第二部分专门讲一下这个if语句,这个语句用于判断当前时间是否符合交易条件,如果这一天是周五,那么当现在时间的小时数小于周五收盘时间的小时数,或者是现在的小时数正好是收盘时间的小时数,但是分钟数小于周五收盘那根K线的开盘时间分钟数(也就是当天最后一根K线开盘的分钟数),那么条件成立,第二种情况是现在的时间是周一到周四,和周五的判定类似。满足第二部分的时间判断后才可以执行3、4、5部分。

第三部分,当价格大于了突破买入价r3,那么开一个多单,然后遍历订单把空单平仓,如果价格小于突破卖出价s3,那么开一个空单,把多单平仓。

第四部分,如果价格大于了观察卖出价r2,那么反转开空单的开关就会打开,同样如果价格小于了观察买入价s2那么反转买入的开关就会打开。

第五部分,如果价格小于了反转卖出价r1,而且此时反转卖出的开关是打开的,卖单的开关也是打开的,那么就开一个空单,将反转卖出的开关重置复位,将买单全部平仓。同样如果买入的开关和反转买入的开关都是打开的,而且价格大于了反转买入价s1,那么就开一个多单,将反转买入的开关重置复位,然后将卖单全部平仓

第六部分,如果时间的条件不满足,那么对所有的订单进行平仓,然后将反转开关复位。

4 主函数部分

主函数就相对比较简单了,只要把这两个模块加进去就可以了,如下:

void OnTick()
  {
      string sym=Symbol();
      int peri=PERIOD_CURRENT;
      int peri1=PERIOD_D1;
      drawline(sym,peri,peri1);
      logic(sym,peri);
  }

5 回测

采用EURUSD对其回测,点差设为20,回测结果如下:

可以看到测试期间效果还是可以的,曲线保持向上升,但是这里不能就断言说这个交易系统就有正期望了,还是需要做更长时间的测试以及实盘检验的,回测是实际能够盈利的必要条件而不是充分条件,所以只能说我们写出了一个短期内可以赚钱的系统,至于长期下来测试结果如何,就留给大家自己去测试吧。

6 小结

这节课的EA编写实际上把之前的一部分内容和EA的编写揉在了一起,需要把之前的内容掌握好,也是对前面内容的一个复习。

另外提醒大家,写EA策略一定要把所有的规则都量化出来,不然是写不出来的,比如说有些交易系统,在价格下跌靠近长期均线的时候做多,那问题就来了,靠近,多近才算近?入场是突破入场还是在均线挂单入场?止损要怎么设置?出场又要有什么条件才能出场?你会发现写一款EA会帮你发现交易策略中的一些盲点,而这些盲点或许在未来会成为你致命的弱点。

本节课R-Breaker的策略是一个比较好的示范,它制定了入场离场的规则以及止损的规则,对交易的时间做了一个限制,这是非常值得学习的地方,但是也有不足,那就是这款策略没有仓位控制的模块,所以如果仓位太大,有可能会出现巨亏的风险,那么仓位模块要怎么设置?下节课的海龟交易法则会详细给大家介绍海龟模型仓位控制的方法。