środa, 14 stycznia 2015

Robot w "Realu", czyli dla czego jeszcze nie jestem forexowym milionerem

We wcześniejszych dwóch wpisach dotyczących automatów (automaty cz.1 i automaty cz.2) opisywałem pierwsze moje kroki w tej dziedzinie. Testując kolejne wersje i dziesiątki ustawień parametrów, czułem się trochę jak włamywacz szukający właściwej kombinacji cyfr otwierającej skarbiec. Jak pisałem wcześniej główną bolączką był brak wystarczającej historii danych dla różnych instrumentów. Osiągałem rewelacyjne wyniki, ale w krótkich okresach czasowych. Szybko się zorientowałem, że dopasowuję automat do kawałka wykresu – w największym uproszczeniu: jeżeli stworzę automat, który będzie tylko dokonywał transakcji zakupu (bez znaczenia kryterium otwarcia pozycji), to przy dynamicznym rynku wzrostowym będzie zawsze zarabiał. Analogicznie przy tendencji spadkowej tracił, a nie o to chodziło. Kolejnym weryfikującym ten problem działaniem było testowanie obiecujących robotów na platformie MT4 u alternatywnego brokera (dla danych historycznych). I tutaj niemiła niespodzianka. Dla tego samego robota i tego samego okresu czasowego testy dawały mocno odmienne wyniki! Okazał się, że wykresy tych samych instrumentów są różnie "rysowane" przez brokerów MM. Ogólnie dane są te same, ale ponieważ pobierane od innych dostawców to jednak się różnią. Dodatkowo brokerzy stosują inne "spread'y", oraz czasami rozpoczynają i kończą notowania o rożnych porach. Tak więc dla automatu działającego np. na interwale 15-sto minutowym wykres, a za nim wyniki są inne. No ale jak Osiołek wyruszył, to wpół drogi nie zawróci (pomimo, że zna już tę pierwszą połówkę). Cel nadal pozostawał ten sam – Perpetuum Constitues czyli robot wiecznie zarabiający:)


W dalszych pracach skupiłem się na parze EURUSD, gdyż jest to instrument uniwersalny, notowany 24 godziny na dobę, a wykresy są najbardziej zbliżone u różnych brokerów (choć nie identyczne). Dodatkowo udało mi się zdobyć najdłuższą, prawie trzy letnią historię notowań nadająca się do testów dla dwóch niezależnych brokerów. Parę ładnych nocek zarwałem śledząc działanie robota na danych historycznych. Ustawiałem prędkość testów tak, abym mógł "na żywo" obserwować otwieranie i zamykanie pozycji na wykresie. Wyłapywałem błędy i ulepszałem automat. I tak narodził się robot: OMA SL 3Phases Extended

W testach historycznych krzywa kapitału pięła się raz wolniej raz szybciej do góry, ale bez żadnych większych tąpnięć. Robot był tak zaprogramowany, żeby wyłapywać dłuższe zmiany trendu – łowić grube ryby, a w trendzie bocznym nie tracić za wiele. Testy za trzy lata było zadowalające i to u różnych brokerów:



Nie zostało już nic innego jak ożywić naszego przyszłego super robota!



Na początku uruchomiłem go na domowym PC, ale szybko napotkałem trudności nie do przejścia. "Albo przestanie mi tu szumieć całą noc ten komputer, albo wylecisz z nim na wycieraczkę" – ultimatum żony brzmiało groźnie:) Uruchomienie go w firmie też nie było dobrym wyjściem. Najpierw wyłączyła mi go tradycyjnie sprzątaczka, po za tym potrafił się zresetować od czasu do czasu (jak to PC). I tu uświadomiła mi się kolejna słaba strona tej zabawy. Żeby robot działał zgodnie z założeniem, to musi być cały czas uruchomiona platforma MT4 oraz zapewniony dostęp do sieci Internet. Każda przerwa w działaniu, to zgubienie synchronizacji robota. Potem zajmowało mu kilka dni powrót na właściwe tory. Tak więc trzeba było podejść do tematu profesjonalnie. Złożyłem porządny serwer z macierzą dyskową; software oparłem o Windows Server 2003 i taki zestaw w sierpniu powędrował do firmowej serwerowni.

Ustawiłem wielkość transakcji na 0,2 lota, a wartość portfela wyniosła 5 tys. złotych. Robot na "raz" otwierał tylko jedna pozycję. Już za trzecim razem wykonał transakcję, którą utrzymywał przez przeszło trzy tygodnie i złowił swoją pierwszą tłustą rybkę: 3.237 zł – zgodnie z ustawieniami max. 450% wartości depozytu. Niestety do końca roku nie powtórzył już tego sukcesu i częściej tracił niż zarabiał. Na początku roku zwiększyłem wolumen do 0,3 lota i w lutym udał się kolejny połów, choć trochę mniejszy: 2.955 zł. To zachęciło mnie do kolejnego zwiększania wolumenu aż do 1 lota. W sumie przez prawie 8 miesięcy ciągłej pracy robot wygenerował 4.070 zł zysku. Szczegółowe zestawienie transakcji poniżej.



I końcowe podsumowanie



Sukces czy porażka?

Patrząc pod kontem osiągnięcia dodatniego salda przez dłuższy okres – na pewno sukces. Z kolei patrząc pod kontem wstępnych wyników opartych na danych historycznych – na pewno poniżej poziomu oczekiwań. Dodatkowo biorąc pod uwagę nakład czasu pracy, zakup i obsługę serwera, zużyty prąd itd. to niestety szału nie było. Tak więc w marcu robot został odłączony od "Matrixa":) Nie miałem już więcej czasu na dalszą zabawę w zestawieniu z zaniedbanymi projektami, które generowały znacznie poważniejsze stopy zwrotu. Mimo wszystko sądzę, że jeszcze kiedyś wrócę do tego tematu, bogatszy o dotychczasowe doświadczenia i z nowymi pomysłami.

Na końcu wpisu kod źródłowy mojego "Championa"

PS1: Jak ktoś na bazie moich doświadczeń i kodów stanie się milionerem to prośba o tantiemy za prawa autorskie:)

PS2: Zająłem krótką pozycję na PKN Orlen (51 zł). Z jednej strony kurs rósł mocno do góry, ze względu na rosnące marże - spadek cen ropy szybszy niż spadek cen na stacjach. Ale z drugiej strony mocno rośnie USDPLN, co za chwilę powinno się przełożyć na spadek marży i w konsekwencji na obniżkę notowań. Dodatkowo założony blisko stoploss na poziomie 53 zł.


PS3. Z kolei osłabienie złotego powinno poprawiać wyniki eksporterów w tym po części JSW. Ale to w dłuższym terminie. Na razie rządzą emocje i możemy oglądać rollercoaster na wykresie tego waloru. Dlatego też zająłem pozycję na tradycyjnym rynku, a nie na leawrowanym, co by tylko upadłość spółki zakończyła tę inwestycję. 

Opis i Kod robota:

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

// Otwieranie Moving Average
// Przesuwanie StopLoss trójfazowe
// Dodatkowo definiowane łowienie "chudszych" ryb w 2. fazie

#define MAGICOMASL3PE 20120823

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

// parametry moving average
extern int MovingPeriod = 12;
extern int MovingShift = 6;

// 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 int Phase2IndependentMovingMode = 2; // 0, 1 lub 2
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()==MAGICOMASL3PE)
        {
         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;
   ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//---- sell conditions
   if(Open[1]>ma && Close[1]<ma)  
     {
      r = Ask/AccountLeverage();
      res=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,Ask+(r*MaximumLoss*0.01),0,"",MAGICOMASL3PE,0,Red);
      return;
     }
//---- buy conditions
   if(Open[1]<ma && Close[1]>ma)  
     {
      r = Bid/AccountLeverage();
      res=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,Bid-(r*MaximumLoss*0.01),0,"",MAGICOMASL3PE,0,Blue);
      return;
     }
//----

}

void CheckForModify() {
   bool modify;
   double StopLoss;
   double granicznyStopLoss1;
   double granicznyStopLoss2;
   int phase;
   double minChange = NormalizeDouble(MathPow(0.1, Digits), Digits);
   for(int i=0;i<OrdersTotal();i++) {
     modify = false;
     if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
     if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICOMASL3PE && 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)));
               if (Phase2IndependentMovingMode == 1) {
                 if (Volume[0]==1) StopLoss = MathMin(StopLoss, OrderStopLoss()-minChange);
               }
               else if (Phase2IndependentMovingMode == 2) {
                 if (Volume[0]==1 && Open[1]>Close[1]) StopLoss = MathMin(StopLoss, OrderStopLoss()-minChange);
               }
               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)));
               if (Phase2IndependentMovingMode == 1) {
                 if (Volume[0]==1) StopLoss = MathMax(StopLoss, OrderStopLoss()+minChange);
               }
               else if (Phase2IndependentMovingMode == 2) {
                 if (Volume[0]==1 && Close[1]>Open[1]) StopLoss = MathMax(StopLoss, OrderStopLoss()+minChange);
               }
               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 < MovingPeriod+MovingShift+5) return(0);
   if (IsTradeAllowed() == false) return(0);
   if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
   else CheckForModify();
//----
   return(0);
  }
//+------------------------------------------------------------------+

2 komentarze:

  1. Gratulację za wytrwanie w tworzeniu robota.
    Rozumiem że krótką sprzedaż otwarłeś na zasadzie kontraktu CFD.
    Można wiedzieć u jakiego brokera ? Bo zazwyczaj mają spore prowizje za otwarcie :(

    OdpowiedzUsuń
  2. Co do brokerów to na nich oceny przyjdzie jeszcze czas. Generalnie każdy poważny broker ma już teraz instrumenty CFD na Polskę i świat. Ze względu na mocno spekulacyjny charakter tej zagrywki pozycja jest nieduża 450 lotów; depozyt ok. 3.500 tys.; spread (czyli prowizja) zmienny, zależny od momentu zawierania transakcji. W moim przypadku wyniósł ok. 20 zł.

    OdpowiedzUsuń