English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Dottor Tradelove o come ho smesso di preoccuparmi e ho creato un Expert Advisor di auto-formazione

Dottor Tradelove o come ho smesso di preoccuparmi e ho creato un Expert Advisor di auto-formazione

MetaTrader 5Trading | 11 gennaio 2022, 15:16
97 0
Roman Zamozhnyy
Roman Zamozhnyy

Concetto

Dopo aver creato l'Expert Advisor, ricorriamo tutti all'utilizzo dello Strategy Tester integrato per selezionare i parametri ottimali. Dopo la selezione di questi, eseguiamo l'Expert Advisor e, una volta che si verifica un cambiamento significativo in esso, l'Expert Advisor viene quindi fermato e ottimizzato più e più volte utilizzando lo Strategy Tester e così via.

Possiamo assegnare il processo decisionale di riottimizzazione e la riottimizzazione come processo all'Expert Advisor senza interrompere naturalmente il suo lavoro?

Una delle soluzioni a questo problema è stata proposta da Quantum nel suo articolo "Adaptive Trading Systems and Their Use in MetaTrader5 Terminal", dedicato all'utilizzo di un vero e proprio sistema di trading affiancato da alcune strategie di trading virtuale (illimitate in numero) tra le quali è stata selezionata una strategia che fino ad ora aveva portato il massimo profitto. La decisione di cambiare la strategia di trading viene adottata dopo che un certo valore di barra fissa è stato superato.

Propongo di utilizzare un codice di algoritmo genetico (GA) stabilito da joo nell'articolo "Genetic Algorithms - It's Easy!". Diamo un'occhiata all'implementazione di questo Expert Advisor (uno degli esempi seguenti è un EA proposto per la partecipazione all'Automated Trading Championship 2011).


Lavori in corso

Quindi dobbiamo definire cosa deve essere in grado di fare l'Expert Advisor. In primo luogo, e va da sé, fare trading utilizzando la strategia selezionata. In secondo luogo, prendere una decisione: se è il momento di ristabilire (per eseguire una nuova ottimizzazione dei parametri di input). E in terzo luogo, per riottimizzare utilizzando GA. Per cominciare, esamineremo la più semplice riottimizzazione: esiste una strategia e selezioniamo solo i nuovi parametri. Vedremo quindi se possiamo, utilizzando GA, selezionare un'altra strategia in un ambiente di mercato cambiato e, in caso affermativo, come farlo.

Inoltre, per facilitare la simulazione nella funzione fitness, prendiamo la decisione di scambiare solo barre completate in uno strumento. Non ci saranno aggiunte di posizioni e chiusure parziali. Coloro che preferiscono utilizzare stop e take fissi e trailing stop, sono invitati a fare riferimento all'articolo "Tick Generation Algorithm in MetaTrader5 Strategy Tester" al fine di implementare i controlli degli ordini Stop Loss e Take Profit nella funzione fitness. Mi dilungherò ulteriormente sulla seguente frase intelligente:

Nella funzione fitness simulo una modalità di test nota nel Tester come "Open Prices Only". MA! Ciò non significa che questa sia l'unica simulazione del processo di test possibile nella funzione fitness. Le persone più scrupolose potrebbero voler implementare un test di funzione di fitness utilizzando la modalità "Every Tick". Per non reinventare la ruota o inventare "every tick", vorrei attirare la loro attenzione su un algoritmo esistente sviluppato da MetaQuotes. In altre parole, dopo aver letto questo articolo si sarà in grado di simulare la modalità "Every Tick" nella funzione fitness, una condizione necessaria per una corretta simulazione di stop e prese in FF.

Prima di procedere al punto principale, l'implementazione della strategia, esaminiamo brevemente i tecnicismi e implementiamo le funzioni ausiliarie che definiscono l'apertura di una nuova barra e l'apertura e la chiusura delle posizioni:

//+------------------------------------------------------------------+
//| Define whether a new bar has opened                             |
//+------------------------------------------------------------------+
bool isNewBars()
  {
   CopyTime(s,tf,0,1,curBT);
   TimeToStruct(curBT[0],curT);
   if(tf==PERIOD_M1||
      tf==PERIOD_M2||
      tf==PERIOD_M3||
      tf==PERIOD_M4||
      tf==PERIOD_M5||
      tf==PERIOD_M6||
      tf==PERIOD_M10||
      tf==PERIOD_M12||
      tf==PERIOD_M15||
      tf==PERIOD_M20||
      tf==PERIOD_M30)
      if(curT.min!=prevT.min)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_H1||
      tf==PERIOD_H2||
      tf==PERIOD_H3||
      tf==PERIOD_H4||
      tf==PERIOD_H6||
      tf==PERIOD_H8||
      tf==PERIOD_M12)
      if(curT.hour!=prevT.hour)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_D1||
      tf==PERIOD_W1)
      if(curT.day!=prevT.day)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_MN1)
      if(curT.mon!=prevT.mon)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   return(false);
  }
//+------------------------------------------------------------------+
//|  ClosePosition                                                   |
//+------------------------------------------------------------------+
void ClosePosition()
  {
   request.action=TRADE_ACTION_DEAL;
   request.symbol=PositionGetSymbol(0);
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) request.type=ORDER_TYPE_SELL; 
   else request.type=ORDER_TYPE_BUY;
   request.type_filling=ORDER_FILLING_FOK;
   if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
      SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      request.sl=NULL;
      request.tp=NULL;
      request.deviation=100;
     }
   while(PositionsTotal()>0)
     {
      request.volume=NormalizeDouble(MathMin(PositionGetDouble(POSITION_VOLUME),SymbolInfoDouble(PositionGetSymbol(0),SYMBOL_VOLUME_MAX)),2);
      if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
     }
  }
//+------------------------------------------------------------------+
//|  OpenPosition                                                    |
//+------------------------------------------------------------------+
void OpenPosition()
  {
   double vol;
   request.action=TRADE_ACTION_DEAL;
   request.symbol=s;
   request.type_filling=ORDER_FILLING_FOK;
   if(SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
      SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      request.sl=NULL;
      request.tp=NULL;
      request.deviation=100;
     }
   vol=MathFloor(AccountInfoDouble(ACCOUNT_FREEMARGIN)*optF*AccountInfoInteger(ACCOUNT_LEVERAGE)
       /(SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(s,SYMBOL_VOLUME_STEP)))*SymbolInfoDouble(s,SYMBOL_VOLUME_STEP);
   vol=MathMax(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_MIN));
   vol=MathMin(vol,GetPossibleLots()*0.95);
   if(SymbolInfoDouble(s,SYMBOL_VOLUME_LIMIT)!=0) vol=NormalizeDouble(MathMin(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_LIMIT)),2);
   request.volume=NormalizeDouble(MathMin(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_MAX)),2);
   while(PositionSelect(s)==false)
     {
      if(SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID); 
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
      PositionSelect(s);
     }
   while(PositionGetDouble(POSITION_VOLUME)<vol)
     {
      request.volume=NormalizeDouble(MathMin(vol-PositionGetDouble(POSITION_VOLUME),SymbolInfoDouble(s,SYMBOL_VOLUME_MAX)),2);
      if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
      PositionSelect(s);
     }
  }
//+------------------------------------------------------------------+

Dopo un'attenta considerazione, è possibile notare tre parametri significativi nella funzione di apertura della posizione: le variabili s e optF e la chiamata alla funzione GetPossibleLots():

  • s - strumento di trading, una delle variabili ottimizzate da GA,
  • optF - parte del deposito da utilizzare per il trading (un'altra variabile ottimizzata da GA), 
  • Funzione GetPossibleLots() - restituisce la parte del deposito da utilizzare per il trading:
//+------------------------------------------------------------------+
//|  GetPossibleLots                                                 |
//+------------------------------------------------------------------+
double GetPossibleLots()
  {
   request.volume=1.0;
   if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
   else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
   OrderCheck(request,check);
   return(NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)/check.margin,2));
  }

Rompendo leggermente l'ordine della narrazione, introduciamo altre due funzioni comuni a tutti gli Expert Advisor ed essenziali nella fase due:

//+------------------------------------------------------------------+
//|  InitRelDD                                                       |
//+------------------------------------------------------------------+
void InitRelDD()
  {
   ulong DealTicket;
   double curBalance;
   prevBT[0]=D'2000.01.01 00:00:00';
   TimeToStruct(prevBT[0],prevT);
   curBalance=AccountInfoDouble(ACCOUNT_BALANCE);
   maxBalance=curBalance;
   HistorySelect(D'2000.01.01 00:00:00',TimeCurrent());
   for(int i=HistoryDealsTotal();i>0;i--)
     {
      DealTicket=HistoryDealGetTicket(i);
      curBalance=curBalance+HistoryDealGetDouble(DealTicket,DEAL_PROFIT);
      if(curBalance>maxBalance) maxBalance=curBalance;
     }
  }
//+------------------------------------------------------------------+
//|  GetRelDD                                                        |
//+------------------------------------------------------------------+
double GetRelDD()
  {
   if(AccountInfoDouble(ACCOUNT_BALANCE)>maxBalance) maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);
   return((maxBalance-AccountInfoDouble(ACCOUNT_BALANCE))/maxBalance);
  }

Cosa possiamo vedere qui? La prima funzione determina il valore massimo del saldo del conto, la seconda funzione calcola il relativo prelievo corrente del conto. Le loro peculiarità saranno esposte in dettaglio nella descrizione della seconda fase.

Passiamo agli Expert Advisor in quanto tali. Poiché siamo solo principianti, non otterremo un Expert Advisor per selezionare una strategia, ma implementeremo rigorosamente due Expert Advisor con le seguenti strategie:
  • una fa trading utilizzando intersezioni di medie mobili (Golden Cross, compriamo uno strumento, Death Cross, vendiamo);
  • l'altra è una semplice rete neurale che riceve variazioni di prezzo nell'intervallo di [0..1] nelle ultime cinque sessioni di trading.

Algoritmicamente, il lavoro di un Expert Advisor auto-ottimizzante può essere esemplificato come segue:

  1. Inizializzazione delle variabili utilizzate dall'Expert Advisor: definire e inizializzare i buffer degli indicatori o impostare la topologia della rete neurale (numero di layer/neuroni in un layer; una semplice rete neurale in cui il numero di neuroni è lo stesso in tutti gli strati è dato come esempio), impostare il periodo di tempo di lavoro. Inoltre, probabilmente il passo più importante - chiamiamo la funzione di ottimizzazione genetica che a sua volta affronta la funzione principale: la funzione di fitness (di seguito FF).

    IMPORTANTE! C'è un nuovo FF per ogni strategia di trading, cioè viene creato ogni volta di nuovo, ad esempio FF per una singola media mobile è completamente diverso da FF per due medie mobili e differisce significativamente dalla rete neurale FF.

    Il risultato delle prestazioni FF nei miei Expert Advisor è un saldo massimo, a condizione che il relativo drawdown non abbia superato il valore critico impostato come variabile esterna (nei nostri esempi - 0,5). In altre parole, se la successiva esecuzione GA dà il saldo di 100.000, mentre il drawdown del saldo relativo è -0,6, allora FF = 0,0. Nel tuo caso, mio caro lettore, il risultato di FF può far emergere criteri completamente diversi.

    Raccogli i risultati delle prestazioni dell'Algoritmo Genetico: per l'intersezione delle medie mobili si tratterà ovviamente di periodi di media mobile, nel caso di una rete neurale ci saranno pesi sinapsi, e il risultato comune per entrambi (e per gli altri miei Expert Advisor) è uno strumento da scambiare fino alla prossima riottimizzazione e già familiare a noi, optF, cioè una parte del deposito da utilizzare per il trading. Sei libero di aggiungere parametri ottimizzati al tuo FF a tua discrezione, ad esempio puoi anche selezionare un intervallo di tempo o altri parametri...

    L'ultimo passo nell'inizializzazione è scoprire il valore massimo del saldo del conto. Perché è importante? Perché questo è un punto di partenza per il processo decisionale di riottimizzazione.

    IMPORTANTE! Come viene presa la decisione sulla riottimizzazione: una volta che il relativo drawdown BALANCE raggiunge un certo valore critico impostato come variabile esterna (nei nostri esempi - 0,2), dobbiamo riottimizzare. Per non ottenere un Expert Advisor per implementare la riottimizzazione ad ogni barra al raggiungimento del drawdown critico, il valore massimo del saldo viene sostituito con un valore corrente.

    Tu, mio caro lettore, potresti avere un criterio totalmente diverso per l'attuazione della riottimizzazione.

  2. Trading in corso.

  3. Ad ogni posizione chiusa controlliamo se il drawdown del saldo ha raggiunto il valore critico. Se il valore critico è stato raggiunto, eseguiamo GA e raccogliamo i risultati delle sue prestazioni (questa è la riottimizzazione!)

  4. E stiamo aspettando una chiamata dal direttore forex che chiede di non mandare in bancarotta il mondo o (ciò che avviene più spesso) per Stop Out, Margin Call, ambulanza di emergenza ...

Di seguito è riportata un'implementazione programmata di quanto sopra per l'Expert Advisor utilizzando la strategia delle medie mobili (è disponibile anche il codice sorgente) e utilizzando la rete neurale, il tutto come codice sorgente.

Il codice è fornito sotto termini e condizioni di licenza GPL.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   tf=Period();
//---for bar-to-bar test...
   prevBT[0]=D'2001.01.01';
//---... long ago
   TimeToStruct(prevBT[0],prevT);
//--- historical depth (should be set since the optimisation is based on historical data)
   depth=10000;
//--- copies at a time (should be set since the optimisation is based on historical data)
   count=2;
   ArrayResize(LongBuffer,count);
   ArrayResize(ShortBuffer,count);
   ArrayInitialize(LongBuffer,0);
   ArrayInitialize(ShortBuffer,0);
//--- calling the neural network genetic optimisation function
   GA();
//--- getting the optimised neural network parameters and other variables
   GetTrainResults();
//--- getting the account drawdown
   InitRelDD();
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(isNewBars()==true)
     {
      bool trig=false;
      CopyBuffer(MAshort,0,0,count,ShortBuffer);
      CopyBuffer(MAlong,0,0,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         if(PositionsTotal()>0)
           {
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               ClosePosition();
               trig=true;
              }
           }
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         if(PositionsTotal()>0)
           {
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               ClosePosition();
               trig=true;
              }
           }
        }
      if(trig==true)
        {
         //--- if the account drawdown has exceeded the allowable value:
         if(GetRelDD()>maxDD)
           {
            //--- calling the neural network genetic optimisation function
            GA();
            //--- getting the optimised neural network parameters and other variables
            GetTrainResults();
            //--- readings of the drawdown will from now on be based on the current balance instead of the maximum balance
            maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);
           }
        }
      CopyBuffer(MAshort,0,0,count,ShortBuffer);
      CopyBuffer(MAlong,0,0,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         request.type=ORDER_TYPE_SELL;
         OpenPosition();
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         request.type=ORDER_TYPE_BUY;
         OpenPosition();
        }
     };
  }
//+------------------------------------------------------------------+
//| Preparing and calling the genetic optimizer                      |
//+------------------------------------------------------------------+
void GA()
  {
//--- number of genes (equal to the number of optimised variables), 
//--- all of them should be specified in the FitnessFunction())
   GeneCount      =OptParamCount+2;    
//--- number of chromosomes in a colony
   ChromosomeCount=GeneCount*11;
//--- minimum search range
   RangeMinimum   =0.0;
//--- maximum search range
   RangeMaximum   =1.0;
//--- search pitch
   Precision      =0.0001;
//--- 1 is a minimum, anything else is a maximum
   OptimizeMethod =2;                                                 
   ArrayResize(Chromosome,GeneCount+1);
   ArrayInitialize(Chromosome,0);
//--- number of epochs without any improvement
   Epoch          =100;                                               
//--- ratio of replication, natural mutation, artificial mutation, gene borrowing, 
//--- crossingover, interval boundary displacement ratio, every gene mutation probabilty, %
   UGA(100.0,1.0,1.0,1.0,1.0,0.5,1.0);                                
  }
//+------------------------------------------------------------------+
//| Fitness function for neural network genetic optimizer:           | 
//| selecting a pair, optF, synapse weights;                         |
//| anything can be optimised but it is necessary                    |
//| to carefully monitor the number of genes                         |
//+------------------------------------------------------------------+
void FitnessFunction(int chromos)
  {
   int    b;
//--- is there an open position?
   bool   trig=false;
//--- direction of an open position
   string dir="";
//--- opening price
   double OpenPrice=0;
//--- intermediary between a gene colony and optimised parameters
   int    z;
//--- current balance
   double t=cap;
//--- maximum balance
   double maxt=t;
//--- absolute drawdown
   double aDD=0;
//--- relative drawdown
   double rDD=0.000001;
//--- fitness function proper
   double ff=0;
//--- GA is selecting a pair
   z=(int)MathRound(Colony[GeneCount-1][chromos]*12);
   switch(z)
     {
      case  0: {s="AUDUSD"; break;};
      case  1: {s="AUDUSD"; break;};
      case  2: {s="EURAUD"; break;};
      case  3: {s="EURCHF"; break;};
      case  4: {s="EURGBP"; break;};
      case  5: {s="EURJPY"; break;};
      case  6: {s="EURUSD"; break;};
      case  7: {s="GBPCHF"; break;};
      case  8: {s="GBPJPY"; break;};
      case  9: {s="GBPUSD"; break;};
      case 10: {s="USDCAD"; break;};
      case 11: {s="USDCHF"; break;};
      case 12: {s="USDJPY"; break;};
      default: {s="EURUSD"; break;};
     }
   MAshort=iMA(s,tf,(int)MathRound(Colony[1][chromos]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   MAlong =iMA(s,tf,(int)MathRound(Colony[2][chromos]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   dig=MathPow(10.0,(double)SymbolInfoInteger(s,SYMBOL_DIGITS));
   
//--- GA is selecting the optimal F
   optF=Colony[GeneCount][chromos];                                   
   
   leverage=AccountInfoInteger(ACCOUNT_LEVERAGE);
   contractSize=SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE);
   b=MathMin(Bars(s,tf)-1-count-MaxMAPeriod,depth);
   
//--- for a neural network using historical data - where the data is copied from
   for(from=b;from>=1;from--) 
     {
      CopyBuffer(MAshort,0,from,count,ShortBuffer);
      CopyBuffer(MAlong,0,from,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         if(trig==false)
           {
            CopyOpen(s,tf,from,count,o);
            OpenPrice=o[1];
            dir="SELL";
            trig=true;
           }
         else
           {
            if(dir=="BUY")
              {
               CopyOpen(s,tf,from,count,o);
               if(t>0) t=t+t*optF*leverage*(o[1]-OpenPrice)*dig/contractSize; else t=0;
               if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t;
               if((maxt>0) && (aDD/maxt>rDD)) rDD=aDD/maxt;
               OpenPrice=o[1];
               dir="SELL";
               trig=true;
              }
           }
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         if(trig==false)
           {
            CopyOpen(s,tf,from,count,o);
            OpenPrice=o[1];
            dir="BUY";
            trig=true;
           }
         else
           {
            if(dir=="SELL")
              {
               CopyOpen(s,tf,from,count,o);
               if(t>0) t=t+t*optF*leverage*(OpenPrice-o[1])*dig/contractSize; else t=0;
               if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t;
               if((maxt>0) && (aDD/maxt>rDD)) rDD=aDD/maxt;
               OpenPrice=o[1];
               dir="BUY";
               trig=true;
              }
           }
        }
     }
   if(rDD<=trainDD) ff=t; else ff=0.0;
   AmountStartsFF++;
   Colony[0][chromos]=ff;
  }

//+---------------------------------------------------------------------+
//| getting the optimized neural network parameters and other variables |
//| should always be equal to the number of genes                       |
//+---------------------------------------------------------------------+
void GetTrainResults()
  {
//---  intermediary between a gene colony and optimised parameters
   int z;                                                             
   MAshort=iMA(s,tf,(int)MathRound(Chromosome[1]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   MAlong =iMA(s,tf,(int)MathRound(Chromosome[2]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   CopyBuffer(MAshort,0,from,count,ShortBuffer);
   CopyBuffer(MAlong,0,from,count,LongBuffer);
//--- save the best pair
   z=(int)MathRound(Chromosome[GeneCount-1]*12);                      
   switch(z)
     {
      case  0: {s="AUDUSD"; break;};
      case  1: {s="AUDUSD"; break;};
      case  2: {s="EURAUD"; break;};
      case  3: {s="EURCHF"; break;};
      case  4: {s="EURGBP"; break;};
      case  5: {s="EURJPY"; break;};
      case  6: {s="EURUSD"; break;};
      case  7: {s="GBPCHF"; break;};
      case  8: {s="GBPJPY"; break;};
      case  9: {s="GBPUSD"; break;};
      case 10: {s="USDCAD"; break;};
      case 11: {s="USDCHF"; break;};
      case 12: {s="USDJPY"; break;};
      default: {s="EURUSD"; break;};
     }
//--- saving the best optimal F
   optF=Chromosome[GeneCount];                                        
  }
//+------------------------------------------------------------------+

Esaminiamo la funzione principale dell'algoritmo: la funzione fitness.

L'intera idea alla base di un Expert Advisor auto-ottimizzante si basa sulla simulazione del processo di trading (come nel Tester standard di MetaQuotes) entro un periodo di tempo (ad esempio, una cronologia di 10000 barre) nella funzione fitness, la quale riceve un input di variabili ottimizzate dall'algoritmo genetico (funzione GA ()). Nel caso di un algoritmo basato sull'intersezione di medie mobili, le variabili ottimizzate includono:

  • strumento (nel forex - una coppia di valute); sì, è un tipico Expert Advisor multivaluta e l'Algoritmo Genetico seleziona uno strumento (poiché il codice è stato preso dall'Expert Advisor proposto per la partecipazione al Campionato, le coppie che ha corrispondono alle coppie di valute del Campionato; in generale può esserci qualsiasi strumento quotato da un broker).
    Nota: sfortunatamente, l'Expert Advisor non può ottenere l'elenco delle coppie dalla finestra MarketWatch in modalità test (lo abbiamo chiarito qui grazie agli utenti di MetaQuotes - Assolutamente no!). Pertanto, se si desidera eseguire l'Expert Advisor nel Tester separatamente per il forex e le azioni, è necessario specificare i propri strumenti nella funzione FF e GetTrainResults().
  • Parte del deposito da utilizzare per il trading;
  • periodi di due medie mobili.

Gli esempi seguenti mostrano una variante di un Expert Advisor PER I TEST!

Il codice REAL TRADE può essere notevolmente semplificato utilizzando l'elenco degli strumenti dalla finestra Market Watch.

Per fare questo in FF e nella funzione GetTrainResults() con commenti, "//--- GA is selecting a pair" e "//--- saving the best pair", basta scrivere:

//--- GA is selecting a pair
  z=(int)MathRound(Colony[GeneCount-1][chromos]*(SymbolsTotal(true)-1));
  s=SymbolName(z,true);

Quindi, all'inizio di FF specifichiamo e inizializziamo, dove necessario, le variabili per la simulazione del trading basato sulla cronologia. Nella fase successiva, raccogliamo diversi valori di variabili ottimizzate dall'Algoritmo Genetico, ad esempio da questa riga "optF=Colony[GeneCount][chromos], il valore della parte di deposito viene trasferito a FF da GA.

Controlliamo ulteriormente il numero disponibile di barre nella cronologia e, partendo dalla 10000esima barra o dalla prima barra disponibile, simuliamo il processo di ricezione delle quotazioni nella modalità "Open prices only" e prendendo decisioni di trading:

  • Copia i valori delle medie mobili nei buffer;
  • Controlla se si tratta di una Death Cross.
  • Se c'è una Death Cross e nessuna posizione aperta (if(trig==false)), apri una posizione SELL virtuale (ricorda solo il prezzo di apertura e la direzione);
  • Se c'è una Death Cross e una posizione BUY aperta (if(dir=="BUY")), prendi il prezzo di apertura della barra e presta attenzione alle tre linee molto importanti come segue:
  1. Simulare la chiusura di una posizione e la variazione del saldo: il saldo corrente viene aumentato di un valore del saldo corrente, il quale viene moltiplicato per la parte del deposito da negoziare, moltiplicato per la differenza tra i prezzi di apertura e di chiusura e moltiplicato per il prezzo pip (orientativo);
  2. Verifica se il saldo corrente ha raggiunto il massimo nel corso della cronologia della simulazione di trading; in caso contrario, calcola il prelievo massimo del saldo in denaro;
  3. Converti il drawdown precedentemente calcolato in denaro in relativo drawdown del saldo;
  • Apri una posizione SELL virtuale (basta ricordare il prezzo e la direzione aperti);
  • Fai controlli e calcoli simili per una Golden Cross.

Dopo aver esaminato l'intera storia disponibile e la simulazione del trading virtuale, calcola il valore FF finale: se il drawdown del saldo relativo calcolato è inferiore a quello impostato per il test, allora FF = saldo, altrimenti FF = 0. L'algoritmo genetico mira alla massimizzazione della funzione fitness!

Dopotutto, dando vari valori degli strumenti, parti di deposito e periodi di medie mobili, l'Algoritmo Genetico troverà i valori che massimizzano il saldo al minimo (il minimo è impostato dall'utente) relativo drawdown.


Conclusione

Ecco una breve conclusione: è facile creare un Expert Advisor auto-formativo, la parte difficile è trovare cosa inserire (l'importante è un'idea, l'implementazione è solo una questione tecnica).

In previsione della domanda dei pessimisti - "Funziona?", ho una risposta: sì; la mia parola agli ottimisti: questo non è il Santo Graal.

Qual è la differenza fondamentale tra il metodo proposto e quello di Quantum? Può essere esemplificato al meglio confrontando gli Expert Advisor utilizzando gli MA:

  1. La decisione sui periodi di MA in Adaptive Trading System deve essere presa prima della compilazione e rigorosamente codificata e la selezione è possibile solo su questo numero limitato di varianti; non prendiamo alcuna decisione sui periodi precedenti la compilazione in Genetically Optimised Expert Advisor, questa decisione sarà presa da GA e il numero di varianti è limitato solo dal buon senso.
  2. Il trading virtuale in Adaptive Trading System è barra a barra; è raramente così in Genetically Optimised Expert Advisor e quindi solo al verificarsi di condizioni per la riottimizzazione. Le prestazioni del computer sul numero crescente di strategie, parametri, strumenti possono essere un fattore limitante per Adaptive Trading System.


Annettere

Ecco cosa otteniamo se la rete neurale viene eseguita nel Tester senza alcuna ottimizzazione e basata su grafici giornalieri a partire dal 01.01.2010:

Rapporto dello strategy tester
MetaQuotes-Demo (Versione 523)
Expert Advisor: ANNExample
Simbolo: EURUSD
Periodo: Giornaliero(2010.01.01 - 2011.09.30)
Parametri di Input: trainDD=0,9
maxDD=0,1
Broker: Alpari NZ Limited
Valuta: USD
Deposito iniziale; 10 000,00
Leva finanziaria: 1:100
Risultati
Qualità della cronologia: 100%
Barre: 454 Tick: 2554879
Utile netto totale: -9 094,49 Profitto lordo: 29 401,09 Perdita lorda: -38 495,58
Fattore di profitto: 0,76 Profitto previsto: -20,53 Livello di margine: 732,30%
Fattore di recupero: -0,76 Indice di Sharpe: -0,06 Risultato OnTester: 0
Drawdown del saldo:
Drawdown assoluto del saldo: 9 102,56 Drawdown massimo del saldo: 11 464,70 (92,74%) Drawdown relativo del saldo: 92,74% (11 464,70)
Prelievo di capitale:
Drawdown assoluto di capitale: 9 176,99 Drawdown massimo di capitale: 11 904,00 (93,53%) Drawdown relativo di capitale: 93,53% (11 904,00)
Totale operazioni: 443 Short trades (vincita, %): 7 (14,29%) Long trades (vincita, %): 436 (53,44%)
Totale offerte: 886 Operazioni di profitto (% del totale): 234 (52,82%) Operazioni di perdita (% del totale): 209 (47,18%)
Il più grande profitto nel trading: 1 095,57 La più grande perdita di trading: -1 438,85
Trading con profitto medio: 125,65 Perdita media di trading: -184,19
Vittorie consecutive massime (profitto in denaro): 8 (397,45) Perdite consecutive massime (perdita di denaro): 8 (-1 431,44)
Profitto consecutivo massimo (conteggio delle vincite): 1 095,57 (1) Perdita consecutiva massima (conteggio delle perdite): -3 433,21 (6)
Media vittorie consecutive: 2 Media perdite consecutive: 2

e di seguito sono tre varianti di riottimizzazione tra cui scegliere:

primo...

Ora Operazione Simbolo Tipo Direzione Volume Prezzo Ordine Swap Profitto Saldo
2010.01.01 00:00 1   saldo         0,00 10 000,00 10 000,00
2010.01.04 00:00 2 AUDUSD acquista in entrata 0,90 0,89977 2 0,00 0,00 10 000,00
2010.01.05 00:00 3 AUDUSD vendita in uscita 0,90 0,91188 3 5,67 1 089,90 11 095,57
2010.01.05 00:00 4 AUDUSD acquista in entrata 0,99 0,91220 4 0,00 0,00 11 095,57
2010.01.06 00:00 5 AUDUSD vendita in uscita 0,99 0,91157 5 6,24 -62,37 11 039,44
2010.01.06 00:00 6 AUDUSD acquista in entrata 0,99 0,91190 6 0,00 0,00 11 039,44
2010.01.07 00:00 7 AUDUSD vendita in uscita 0,99 0,91924 7 18,71 726,66 11 784,81


secondo...

Ora Operazione Simbolo Tipo Direzione Volume Prezzo Ordine Commissione Swap Profitto Saldo
2010.05.19 00:00 189 AUDUSD vendita in uscita 0,36 0,86110 189 0,00 2,27 -595,44 4 221,30
2010.05.19 00:00 190 EURAUD vendita in entrata 0,30 1,41280 190 0,00 0,00 0,00 4 221,30
2010.05.20 00:00 191 EURAUD acquista in uscita 0,30 1,46207 191 0,00 7,43 -1 273,26 2 955,47
2010.05.20 00:00 192 AUDUSD acquista in entrata 0,21 0,84983 192 0,00 0,00 0,00 2 955,47


terzo

Ora Operazione Simbolo Tipo Direzione Volume Prezzo Ordine Swap Profitto Saldo
2010.06.16 00:00 230 GBPCHF acquista in entrata 0,06 1,67872 230 0,00 0,00 2 128,80
2010.06.17 00:00 231 GBPCHF vendita in uscita 0,06 1,66547 231 0,13 -70,25 2 058,68
2010.06.17 00:00 232 GBPCHF acquista in entrata 0,06 1,66635 232 0,00 0,00 2 058,68
2010.06.18 00:00 233 GBPCHF vendita in uscita 0,06 1,64705 233 0,04 -104,14 1 954,58
2010.06.18 00:00 234 AUDUSD acquista in entrata 0,09 0,86741 234 0,00 0,00 1 954,58
2010.06.21 00:00 235 AUDUSD vendita in uscita 0,09 0,87184 235 0,57 39,87 1 995,02
2010.06.21 00:00 236 AUDUSD acquista in entrata 0,09 0,88105 236 0,00 0,00 1 995,02
2010.06.22 00:00 237 AUDUSD vendita in uscita 0,09 0,87606 237 0,57 -44,91 1 950,68
2010.06.22 00:00 238 AUDUSD acquista in entrata 0,09 0,87637 238 0,00 0,00 1 950,68
2010.06.23 00:00 239 AUDUSD vendita in uscita 0,09 0,87140 239 0,57 -44,73 1 906,52
2010.06.23 00:00 240 AUDUSD acquista in entrata 0,08 0,87197 240 0,00 0,00 1 906,52
2010.06.24 00:00 241 AUDUSD vendita in uscita 0,08 0,87385 241 1,51 15,04 1 923,07
2010.06.24 00:00 242 AUDUSD acquista in entrata 0,08 0,87413 242 0,00 0,00 1 923,07
2010.06.25 00:00 243 AUDUSD vendita in uscita 0,08 0,86632 243 0,50 -62,48 1 861,09
2010.06.25 00:00 244 AUDUSD acquista in entrata 0,08 0,86663 244 0,00 0,00 1 861,09
2010.06.28 00:00 245 AUDUSD vendita in uscita 0,08 0,87375 245 0,50 56,96 1 918,55
2010.06.28 00:00 246 AUDUSD acquista in entrata 0,08 0,87415 246 0,00 0,00 1 918,55
2010.06.29 00:00 247 AUDUSD vendita in uscita 0,08 0,87140 247 0,50 -22,00 1 897,05
2010.06.29 00:00 248 AUDUSD acquista in entrata 0,08 0,87173 248 0,00 0,00 1 897,05
2010.07.01 00:00 249 AUDUSD vendita in uscita 0,08 0,84053 249 2,01 -249,60 1 649,46
2010.07.01 00:00 250 EURGBP vendita in entrata 0,07 0,81841 250 0,00 0,00 1 649,46
2010.07.02 00:00 251 EURGBP acquista in uscita 0,07 0,82535 251 -0,04 -73,69 1 575,73
2010.07.02 00:00 252 EURGBP vendita in entrata 0,07 0,82498 252 0,00 0,00 1 575,73
2010.07.05 00:00 253 EURGBP acquista in uscita 0,07 0,82676 253 -0,04 -18,93 1 556,76
2010.07.05 00:00 254 EURGBP vendita in entrata 0,06 0,82604 254 0,00 0,00 1 556,76
2010.07.06 00:00 255 EURGBP acquista in uscita 0,06 0,82862 255 -0,04 -23,43 1 533,29


P.S. Come compito per casa: non selezionare solo i parametri di un determinato sistema, ma anche il sistema che meglio si adatta al mercato in un dato momento (suggerimento: dalla banca dei sistemi).


Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/334

File allegati |
anntrainlib.mqh (9.76 KB)
matrainlib.mqh (8.94 KB)
ugalib.mqh (33.26 KB)
annexample.mq5 (4.32 KB)
maexample.mq5 (4.22 KB)
musthavelib.mqh (8.14 KB)
La strategia Forex Tutto o niente La strategia Forex Tutto o niente
Lo scopo di questo articolo è quello di creare la strategia di trading più semplice che implementa il principio di gioco "Tutto o niente". Non vogliamo creare un Expert Advisor redditizio: l'obiettivo è aumentare il deposito iniziale più volte con la massima probabilità possibile. È possibile vincere il jackpot su ForEx o perdere tutto senza sapere nulla di analisi tecnica e senza utilizzare alcun indicatore?
Come aggiungere nuove lingue dell'interfaccia utente alla piattaforma MetaTrader 5 Come aggiungere nuove lingue dell'interfaccia utente alla piattaforma MetaTrader 5
L'interfaccia utente della piattaforma MetaTrader 5 è tradotta in diverse lingue. Non preoccuparti se la tua lingua madre non è tra quelle supportate. Puoi facilmente completare la traduzione utilizzando la speciale utility MetaTrader 5 MultiLanguage Pack offerta da MetaQuotes Software Corp. gratuitamente a tutti i partecipanti. In questo articolo mostreremo alcuni esempi di come aggiungere una nuova interfaccia utente lingue alla piattaforma MetaTrader 5.
Creazione di Expert Advisor MQL5 in pochi minuti utilizzando EA Tree: Prima parte Creazione di Expert Advisor MQL5 in pochi minuti utilizzando EA Tree: Prima parte
EA Tree è il primo generatore di Expert Advisor MetaTrader MQL5 drag and drop. È possibile creare MQL5 complessi utilizzando un'interfaccia utente grafica molto facile da usare. In EA Tree, gli Expert Advisor vengono creati collegando i riquadri insieme. Le caselle possono contenere funzioni MQL5, indicatori tecnici, indicatori personalizzati o valori. Utilizzando l'"albero dei riquadri", EA Tree genera il codice MQL5 dell'Expert Advisor.
Come sviluppare un Expert Advisor utilizzando gli strumenti UML Come sviluppare un Expert Advisor utilizzando gli strumenti UML
Questo articolo discute la creazione di Expert Advisor utilizzando il linguaggio grafico UML, usato per la modellazione visiva di sistemi software orientati agli oggetti. Il vantaggio principale di questo approccio è la visualizzazione del processo di modellazione. L'articolo contiene un esempio che mostra la modellazione della struttura e delle proprietà di un Expert Advisor utilizzando il software Ideas Modeler.