第十六節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的策略是一個比較好的示範,它制定了入場離場的規則以及止損的規則,對交易的時間做了一個限制,這是非常值得學習的地方,但是也有不足,那就是這款策略沒有倉位控制的模塊,所以如果倉位太大,有可能會出現巨虧的風險,那麼倉位模塊要怎麼設置?下節課的海龜交易法則會詳細給大家介紹海龜模型倉位控制的方法。