czwartek, 4 grudnia 2014

Automaty na forex; część 2

Część pierwszą dotyczącą automatów poświęciłem opisem rozpoczęcia prac związanych z Expert Advisor. Etap ten zakończyłem napisaniem pierwszego robota tego typu, instalacją go na platformie MT4, oraz krótkimi testami na danych historycznych. Od strony technicznej wszystko zaczęło działać – czas na poważniejsze prace – czyli poszukiwanie "Świętego Grala".

Pierwszym pomysłem przedstawionym wcześniej był robot SL Pending. Jego zadaniem było automatyczne otwieranie pozycji z wykorzystaniem średnich ruchomych i prowadzenie jej z procentowym zdefiniowanym poziomem stop loss. Kiedy kurs zmieniał się korzystnie poziom ten podążał w stałej "odległości" za "ceną "danego waloru, a pozostawał nieruchomy w przeciwnym razie. Pozycja była zamykana wtedy, kiedy niekorzystna zmiana kursu przecięła poziom stop loss.

Ewolucją tego pomysłu był robot SL Pending Hyperbolic. Modyfikacja polegała na zmianie sposobu prowadzenia poziomu stop loss. Nie podążał on już liniowo za kursem, tylko hiperbolicznie. To znaczy czym bardziej kurs "przesuwał się" w pożądanym kierunku tym szybciej linia obronna go goniła. Miało to na celu zmniejszenie ilości stratnych transakcji – pozytywna zmiana kierunku szybciutko przesuwała SL do punktu "break even". Dodatkowo, żeby w przypadku dalszej korzystnej zmiany kursu nie stracić części zysku, wprowadziłem nowy parametr "Noise" – czyli szum. Zadaniem tego parametru było zmienić sposób prowadzenia linii stop loss z wykładniczej znowu na liniową, tak żeby można było maksymalizować zysk.  Przykładowo, ustawiony parametr Noise na 20%, powoduje, że zbliżająca się szybko linia SL do aktualnego poziomu kursu danego waloru zatrzyma się w odległości 20% wielkości depozytu i dalej już będzie podążać liniowo (analogicznie do trailing stop), oczywiście tylko w przypadku korzystnej zmiany ceny instrumentu bazowego.

Dane o testach historycznych poniżej, a kod programu dostępny jest na końcu wpisu.



Zanim przejdę do kolejnych automatów, parę słów o ich testowaniu. Pierwszym i jak się okaże później poważnym problemem była dostępność danych historycznych. Standardowo wraz z platformą broker dostarczał informację o wykresach tylko za parę miesięcy wstecz. To stanowczo za mało, żeby móc wyciągać jakiekolwiek wnioski. Mogę dać przykłady automatów, które zarabiały kilkaset procent w krótkim okresie czasu, żeby równie szybko stracić drugie tyle w kolejnych miesiącach. Na zadane pytanie do brokera MM co do dostępności danych historycznych otrzymałem następującą odpowiedź: „Nie udostępniamy takich danych”, Można z tego wysnuć następujące wnioski:
- być może ich po prostu nie przechowują,
- być może testy automatów za okres kilkuletni pokazują ich nieskuteczność, co może w ogóle zniechęcać uczestników do gry na forex,
- a może bronią się w ten sposób przed napisaniem robota zarabiającego pieniądze w dłuższych okresach czasowych:) 

Generalnie jest to słaby punkt całej zabawy. Dzięki różnym zabiegom technicznym udało mi się pozyskać dane dla niektórych walorów za okres do jednego roku, a na najpopularniejszej parze EURUSD nawet za okres trzech lat. Dodatkowo przy testach historycznych pojawiał się parametr jakości modelowania, który wynosił w zależności od testu od 30 do 95%, co też nie było zbyt budujące.

Pomimo powyższych niedogodności prace trwały dalej. Jak już niektórzy pewnie zauważyli, duży nacisk kładłem na sposób prowadzenia pozycji. Osobiście z mojej praktyki uważam, że jest to jeden z najważniejszych czynników decydujący o naszej skuteczności i umiejętności zarabiania na rynku lewarowanym.

Kolejnymi wersjami robotów były OMA SL3Phaze i OMACD SL3Phaze.
Tutaj mamy już dość istotne zmiany w porównaniu do poprzednich rozwiązań. Po pierwsze mamy nowy inny sposób otwierania pozycji. W pierwszym przypadku wykorzystujemy przecinanie się średnich kroczących. W drugim rozwiązaniu do otwierania pozycji wykorzystujemy wskaźnik MACD. Został też dodany zaawansowany sposób prowadzenia pozycji. Oprócz parametrów SL i Nosie istnieje możliwość definiowania różnych prędkości przesuwania poziomu SL, w zależności od zmiany ceny naszego waloru. Przykładowo, kiedy w pierwszej fazie kurs zaczyna się korzystnie zmieniać automat szybko przesuwa SL do poziomu break even lub trochę powyżej. Dalej możemy ustawić np. prędkość liniową, żeby spokojnie holować naszą pozycję przy korzystnej zmianie kursu, tak aby drobne korekty nie wyrzuciły nas z rynku. Po osiągnięciu zakładanego zysku np. 3R (3x depozyt) możemy znowu zmienić prędkość podążania SL na wykładniczą tak aby nie stracić tego co wirtualnie zarobiliśmy (realny zysk będzie dopiero po zamknięciu pozycji), a jednocześnie dać szanse na powiększenie zysku przy gwałtownych korzystnych dla nas ruchach cen.  

Dane dla wybranych walorów oraz parametrów, które osiągały dodatnie wyniki poniżej w tabeli. Kod całego robota OMACD SL3Phaze na końcu wpisu.




Wyniki były coraz bardziej obiecujące, a automaty coraz bardziej zaawansowane. Ale to wszystko tylko na danych historycznych. Jeżeli chciałem się posuwać dalej, to zbliżał się czas „wypuszczenia robotów w świat”, czyli uruchomienia ich na rzeczywistym rachunku z prawdziwą gotówką. O kolejnych robotach pracujących już w „realu” zapraszam w kolejnym wpisie dotyczącym tego tematu. 

KOD SL Pending:

//+------------------------------------------------------------------+
//|                          OMA SLHyperbolicWithMinimalDistance.mq4 |
//|                                                 SR & WP |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "www.donkey.org.pl"
#property link      ""

/* Otwieranie wg Moving Average */
/* Zamykanie wg hiperbolicznego StopLoss z minimaln╣ odleg│oťci╣ */

#define MAGICOMASLH  20100719

extern int MovingPeriod = 12;
extern int MovingShift = 6;
extern double StopLossPercentage = 90.0; // jako procent wartoťci depozytu
extern double NoiseTolerancePercentage = 30.0; // jako procent wartoťci depozytu
double SLDist, NTDist;

//+------------------------------------------------------------------+
//| Calculate open positions                                         |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
  {
   int buys=0,sells=0;
//----
   for(int i=0;i<OrdersTotal();i++)
     {
      if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
      if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICOMASLH)
        {
         if(OrderType()==OP_BUY)  buys++;
         if(OrderType()==OP_SELL) sells++;
        }
     }
//---- return orders volume
   if(buys>0) return(buys);
   else       return(-sells);
  }
  
void CheckForOpen()
  {
   double ma;
   int    res;
//---- go trading only for first tiks of new bar
   if(Volume[0]>1) return;
//---- get Moving Average 
   ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//---- sell conditions
   if(Open[1]>ma && Close[1]<ma)  
     {
      SLDist = Ask*StopLossPercentage*0.01/AccountLeverage();
      NTDist = Ask*NoiseTolerancePercentage*0.01/AccountLeverage();
      res=OrderSend(Symbol(),OP_SELL,0.01,Bid,3,Ask+SLDist,0,"",MAGICOMASLH,0,Red);
      return;
     }
//---- buy conditions
   if(Open[1]<ma && Close[1]>ma)  
     {
      SLDist = Bid*StopLossPercentage*0.01/AccountLeverage();
      NTDist = Bid*NoiseTolerancePercentage*0.01/AccountLeverage();
      res=OrderSend(Symbol(),OP_BUY,0.01,Ask,3,Bid-SLDist,0,"",MAGICOMASLH,0,Blue);
      return;
     }
//----
  }
  
  void CheckForModify() {
   bool modify = false;
   double StopLoss;
   for(int i=0;i<OrdersTotal();i++)
     {
      modify = false;
      if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
      if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICOMASLH && OrderType() <= 1)
        {
         switch (OrderType()) {
           case OP_SELL:
             if (Ask < OrderOpenPrice()) {
               StopLoss = Ask+(SLDist*SLDist/(OrderOpenPrice()-Ask+SLDist));
               if (StopLoss < Ask+NTDist) {
                 StopLoss = Ask+NTDist;
               }
               if (StopLoss < OrderStopLoss()) modify = true;
             }
             break;
             
           case OP_BUY:
             if (Bid > OrderOpenPrice()) {
               StopLoss = Bid-(SLDist*SLDist/(Bid-OrderOpenPrice()+SLDist));
               if (StopLoss > Bid-NTDist) {
                 StopLoss = Bid-NTDist;
               }
               if (StopLoss > OrderStopLoss()) modify = true;
             }
             break;
         }
         if (modify) {
           OrderModify(OrderTicket(),OrderOpenPrice(),StopLoss,0,0);
         }
        }
     }
}

//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   if(Bars<MovingPeriod+MovingShift+5 || IsTradeAllowed()==false) return;
   if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
   else CheckForModify();
//----
   return(0);
  }
//+------------------------------------------------------------------+


KOD OMACD SL3Phaze:

//+------------------------------------------------------------------+
//|                                              OMACD SL3Phases.mq4 |
//|                                                           SR&WP|
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "www.donkey.org.pl"
#property link      ""

// Otwieranie MACD
// Przesuwanie StopLoss trójfazowe

#define MAGICOMACDSL3P 20120725

// parametr wielkości
extern double Lots = 0.01;

// parametry MACD
extern double MACDOpenLevel=3;
//extern double MACDCloseLevel=2;
extern int MATrendPeriod=26;

extern int FastEMA=12;
extern int SlowEMA=26;
extern int MACDSMA=9;

// parametry przesuwania StopLoss
extern double Noise = 30.0; // szum w procentach postawionej stawki - SL nigdy nie przesunie się bliżej aktualnego kursu
extern double MaximumLoss = 95.0; // maksymalna możliwa strata w procentach postawionej stawki
extern double Phase1Speed = 3.0; // prędkość zmiany StopLoss w fazie pierwszej;
//ile wynosi ta wartość, tylukrotnie zmniejszy się dystans między StopLoss a aktualnym kursem, jeśli aktualny kurs zmieni się o R na korzyść
extern double Phase1End = 55.0; // tyle procent ZYSKU oznacza moment końca pierwszej fazy
extern double Phase2Speed = 0.1; // o tyle jednostek przesunie się w drugiej fazie StopLoss, gdy aktualny kurs przesunie się o jedną jednostkę na korzyść
extern double Phase2End = 500.0; // tyle procent ZYSKU oznacza moment końca drugiej fazy
extern double Phase3Speed = 4.0; // jak Phase1Speed

double r;

//+------------------------------------------------------------------+
//| Calculate open positions                                         |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
  {
   int buys=0,sells=0;
//----
   for(int i=0;i<OrdersTotal();i++)
     {
      if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
      if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICOMACDSL3P)
        {
         if(OrderType()==OP_BUY)  buys++;
         if(OrderType()==OP_SELL) sells++;
        }
     }
//---- return orders volume
   if(buys>0) return(buys);
   else       return(-sells);
  }
  
void CheckForOpen() {
   double MacdCurrent, MacdPrevious, SignalCurrent;
   double SignalPrevious, MaCurrent, MaPrevious;
   int    res;
   
   //---- go trading only for first tiks of new bar
   //if(Volume[0]>1) return;
   MacdCurrent=iMACD(NULL,0,FastEMA,SlowEMA,MACDSMA,PRICE_CLOSE,MODE_MAIN,0);
   MacdPrevious=iMACD(NULL,0,FastEMA,SlowEMA,MACDSMA,PRICE_CLOSE,MODE_MAIN,1);
   SignalCurrent=iMACD(NULL,0,FastEMA,SlowEMA,MACDSMA,PRICE_CLOSE,MODE_SIGNAL,0);
   SignalPrevious=iMACD(NULL,0,FastEMA,SlowEMA,MACDSMA,PRICE_CLOSE,MODE_SIGNAL,1);
   MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
   MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);
//---- sell conditions
   if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && 
      MacdCurrent>(MACDOpenLevel*Point) && MaCurrent<MaPrevious)
     {
      r = Ask/AccountLeverage();
      res=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,Ask+(r*MaximumLoss*0.01),0,"",MAGICOMACDSL3P,0,Red);
      return;
     }
//---- buy conditions
   if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious &&
      MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious) 
     {
      r = Bid/AccountLeverage();
      res=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,Bid-(r*MaximumLoss*0.01),0,"",MAGICOMACDSL3P,0,Blue);
      return;
     }
//----

}

void CheckForModify() {
   bool modify;
   double StopLoss;
   double granicznyStopLoss1;
   double granicznyStopLoss2;
   int phase;
   for(int i=0;i<OrdersTotal();i++) {
     modify = false;
     if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
     if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICOMACDSL3P && OrderType() <= 1) {
       switch(OrderType()) {
         case OP_SELL:
           if (Bid >= OrderOpenPrice()) break;
           if (Bid <= OrderOpenPrice()-(r*Phase2End*0.01)) { phase=3; }
           else {
             if (Bid <= OrderOpenPrice()-(r*Phase1End*0.01)) { phase=2; }
             else { phase=1; }
           }
           switch (phase) {
             case 1:
               StopLoss = Bid+((r*MaximumLoss*0.01)/(MathPow(Phase1Speed,(OrderOpenPrice()-Bid)/r)));
               break;
               
             case 2:
               granicznyStopLoss1 = (OrderOpenPrice()-(r*Phase1End*0.01))+((r*MaximumLoss*0.01)/(MathPow(Phase1Speed,Phase1End*0.01)));
               StopLoss = granicznyStopLoss1-(Phase2Speed*(Bid-OrderOpenPrice()+(r*Phase1End*0.01)));
               break;
               
             case 3:
               granicznyStopLoss1 = (OrderOpenPrice()-(r*Phase1End*0.01))+((r*MaximumLoss*0.01)/(MathPow(Phase1Speed,Phase1End*0.01)));
               granicznyStopLoss2 = granicznyStopLoss1-(Phase2Speed*r*0.01*(Phase1End-Phase2End));
               StopLoss = Bid+(granicznyStopLoss2/(MathPow(Phase3Speed,(OrderOpenPrice()-Bid)/r)));
               break;
           }
           if (StopLoss < Ask+(r*Noise*0.01)) { StopLoss = Ask+(r*Noise*0.01); }
           if (StopLoss < OrderStopLoss()) { modify = true; }
           break;
           
         case OP_BUY:
           if (Ask <= OrderOpenPrice()) break;
           if (Ask >= OrderOpenPrice()+(r*Phase2End*0.01)) { phase=3; }
           else {
             if (Ask >= OrderOpenPrice()+(r*Phase1End*0.01)) { phase=2; }
             else { phase=1; }
           }
           switch (phase) {
             case 1:
               StopLoss = Ask-((r*MaximumLoss*0.01)/(MathPow(Phase1Speed,(Ask-OrderOpenPrice())/r)));
               break;
               
             case 2:
               granicznyStopLoss1 = (OrderOpenPrice()+(r*Phase1End*0.01))-((r*MaximumLoss*0.01)/(MathPow(Phase1Speed,Phase1End*0.01)));
               StopLoss = granicznyStopLoss1+(Phase2Speed*(OrderOpenPrice()-Ask+(r*Phase1End*0.01)));
               break;
               
             case 3:
               granicznyStopLoss1 = (OrderOpenPrice()+(r*Phase1End*0.01))-((r*MaximumLoss*0.01)/(MathPow(Phase1Speed,Phase1End*0.01)));
               granicznyStopLoss2 = granicznyStopLoss1+(Phase2Speed*r*0.01*(Phase1End-Phase2End));
               StopLoss = Ask-(granicznyStopLoss2/(MathPow(Phase3Speed,(Ask-OrderOpenPrice())/r)));
               break;
           }
           if (StopLoss > Bid-(r*Noise*0.01)) { StopLoss = Bid-(r*Noise*0.01); }
           if (StopLoss > OrderStopLoss()) { modify = true; }
           break;
       }
       if (modify) {
         OrderModify(OrderTicket(),OrderOpenPrice(),StopLoss,0,0);
       }
     }
   }
}

//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   if (Bars < 100) return(0);
   if (IsTradeAllowed() == false) return(0);
   if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
   else CheckForModify();
//----
   return(0);
  }
//+------------------------------------------------------------------+

2 komentarze:

  1. witam,

    po roku od swoich pierwszych doświadczeń z FX wracam do banalnie prostych strategii z SMA i EMA. Dodałem do tego przesunięcie SMA o 1 do 5 okresów i muszę stwierdzić, że otrzymuję ciekawe wyniki. Reszta to tylko umiejętne zarządzanie ryzykiem oraz odpowiednie wyjście z pozycji.
    Pozdrawiam.
    Antoni

    OdpowiedzUsuń
  2. Antoni możesz podać do siebie namiary chciałbym porozmawiać.

    OdpowiedzUsuń