English Русский 中文 Deutsch 日本語 Português
Como crear un robot de trading fiable y seguro en MQL4

Como crear un robot de trading fiable y seguro en MQL4

MetaTrader 4Sistemas comerciales | 11 mayo 2016, 12:27
5 639 0
Shashev Sergei
Shashev Sergei

Introducción

En el proceso de creación de cualquier programa serio el desarrollador se enfrenta al hecho de que su programa puede contener todo tipo de errores posibles e imposibles. Los errores causan muchos problemas en la etapa de desarrollo, conducen a una falta de fiabilidad en la solución y, si se trata de un robot de trading, puede mostrar resultados negativos en su depósito. Vamos a analizar los errores más comunes, su origen, y los métodos para detectar y programar el procesamiento de errores. En el proceso de desarrollo y uso de un asesor experto para el terminal de cliente de MetaTrader4, pueden producirse los siguientes errores:

  1. Sintaxis: pueden encontrarse en la etapa de compilación y puede ser resueltos fácilmente por el programador;
  2. Lógicos: no son detectados con un compilador. Los ejemplos son: una confusión con los nombres de las variables, llamadas a funciones incorrectas, funcionamiento de los datos de varios tipos y demás;
  3. Algorítmicos: se producen cuando los corchetes no se colocan adecuadamente, en caso de confusión con las declaraciones de una ramificación, etc.;
  4. Críticos: estos son errores improbables y deberá realizar cierto esfuerzo para detectarlos. No obstante, a menudo se producen cuando se trabaja con dll;
  5. Trading: estos son errores que se producen cuando trabajamos con órdenes. Dichos errores es una parte sensible para los robots de trading.
En primer lugar le recomendamos que estudie la documentación sobre los errores de ejecución. Una vez que haya realizado este procedimiento, puede luego ahorrar mucho tiempo. Los errores causados por las operaciones de trading se describen aquí.

Errores de sintaxis

Este tipo de errores son causados por errores de impresión de operadores, variables y llamadas a distintas funciones. Durante la compilación, se comprueba el código del programa y todos los errores de sintaxis se muestran en la ventana "Herramientas" de MetaEditor. De hecho, casi todos los errores se detectan y pueden ser resueltos por el programador.

La excepción es un conflicto con los paréntesis, cuando se detecta un paréntesis abierto/cerrado incorrectamente colocado en la fase de compilación, aunque la ubicación del error se muestra de forma incorrecta. A continuación tenemos que verificar doblemente el código para poder encontrar el error de forma visual, aunque por desgracia puede que no tengamos éxito. El segundo método es el cierre consecutivo de los bloques del código usando comentarios. En este caso, si después de un comentario en un nuevo bloque desaparece el error, este está obviamente colocado en el bloque con el comentario. Esto reduce drásticamente la zona de búsqueda y ayuda a encontrar la ubicación incorrecta de los paréntesis rápidamente.


Errores lógicos, algorítmicos y críticos

Los errores más habituales de este tipo son un conflicto en los nombres y los tipos de variables, y también los errores algorítmicos en las ramificaciones del asesor experto. Por ejemplo, analicemos este código:

bool Some = false;
 
void check()
  {
    // Much code
    Some = true;
  }
// Very much code
int start()
  {
    bool Some = false;
    //
    if(Some)   
      {
        //sending the order
      }
   return(0);
  }
 


¿Qué vemos ahora? La variable lógica "Some", que es común para todo el programa y es un indicador importante para abrir la posición, ha sido colocada en un lugar inferior. Esto puede producir una apertura incorrecta de la orden y, por tanto, generar pérdidas. Podemos establecer muchos nombres para las variables. Pero por alguna razón estos nombres se repiten accidentalmente en los programas de gran tamaño y esto origina el problema que mencionábamos anteriormente.

Este tipo de errores se producen cuando las variables se mezclan o la expresión de una es asignada a la expresión de otro tipo. Por ejemplo, en esta línea

int profit = NormalizeDouble(SomeValue*point*2 / 3, digit);


estamos intentando asignar la expresión del valor del tipo "double" a la variables del tipo "int", que da el valor cero. Y estamos calculando el nivel de takeprofit. Este tipo de errores generan un trading incorrecto.


El error algorítmico en las ramificaciones del asesor experto significa que los paréntesis están colocados, no de acuerdo con el algoritmo, o bien que hay un cobertura incorrecta de los operadores "if" por parte de los operadores "else". Como resultado de ellos tenemos un asesor experto que no funciona de acuerdo con los requisitos técnicos.

Algunos errores pueden ser tan impredecibles que podemos estar varias horas "meditando sobre el código" hasta encontrarlos. Por desgracia, no es posible rastrear los valores de las variables en MetaEditor, a diferencia de los entornos para los idiomas de la familia C++. Por tanto, la única forma de hacerlo es rastrear los errores a través de la salida de mensajes a través de la función Print().

La función GetLastError() devuelve el código del error. Se recomienda hacer una comprobación del último valor después de cada lugar potencialmente vulnerable del programa. Al usar el código de error podemos encontrar fácilmente su descripción en la documentación y para algunos errores podemos incluso encontrar los métodos para su tratamiento.

Deberíamos decir que los errores mencionados anteriormente serán detectados con mayor probabilidad en la etapa de pruebas antes de usar la cuenta de demostración, por lo que las pérdidas causadas por estos son improbables.

La principal característica de los errores críticos es que cuando estos se producen, la ejecución del programa se detiene inmediatamente. No obstante, el código del error sigue inalterado en la variable predefinida "last_error". Esto nos da la posibilidad de aprender el código del error llamando a la función GetLastError().


Errores de trading

Estos errores suelen generar pérdidas y falta de funcionalidad del asesor experto en la cuenta de demostración y, además, en las cuentas reales. Se producen cuando trabajamos con el envío y la modificación de órdenes, en otras palabras, durante la interacción con el servidor de trading.
Un simple procesamiento como este:

ticket = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3,
         Bid + StopLoss*Point, Bid - TakeProfit*Point, 0, MAGICMA, 
         0, Red);
if(ticket > 0) 
  {
    err = GetLastError();
    Print("While opening the order the error #  occured", err);
  }


no ayudará. Nos hemos asegurado de que la orden no ha sido enviada al servidor ni hemos aprendido el código del error. ¿Entonces qué ocurre? Hemos pasado por alto una entrada importante al mercado, por supuesto, si tenemos un asesor experto que sea rentable.

La variante con un bucle sin fin:

while (true)
  {
    ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, slippage,
             Bid + StopLoss*Point, Bid - TakeProfit*Point, 0, 
             MAGICMA, 0, Red);
    if(ticket > 0) 
      {
        err = GetLastError();
        Print("While opening the order the error #  occured", err);
        break;      
      }
    Sleep(1000);
    RefleshRates();
  }


ayuda un poco. La orden llegará al servidor con mayor probabilidad. Pero podemos encontrarnos con algunos problemas:

  1. Al bróker no le gustarán las solicitudes frecuentes;
  2. El error puede ser fatal y en este caso la solicitud no llegará al servidor de ningún modo;
  3. El asesor experto no responderá durante un largo periodo de tiempo;
  4. El servidor puede no aceptar las solicitudes de trading y puede ser un fin de semana, vacaciones, durante los trabajos de mantenimiento, etc.

Casi todos los errores son únicos y requieren su propio tratamiento. Por ello, vamos a ver la variante con el operador Switch y a tratar cada error de forma más o menos individual. El error estándar #146 - 'Trade flow is busy", se procesa usando el semáforo de la biblioteca TradeContext.mqh. Puede encontrar la biblioteca y su descripción detallada en este artículo.

//The library for differentiation of work with the trading flow
//written by komposter
#include <TradeContext.mqh>
 
//parameters for the signals
extern double MACDOpenLevel=3;
extern double MACDCloseLevel=2;
extern double MATrendPeriod=26;
 
// maximum acceptable slippage
int       slippage = 3;
//total number of transactions
int deals = 0;
//time for the pause after transaction
int TimeForSleep = 10;
//period of request
int time_for_action = 1;
//number of tries of opening/closing the position
int count = 5;
//indicator of operability of the EA
bool Trade = true;
//indicator of availability of funds for opening the position
bool NoOpen = false;
//+------------------------------------------------------------------+
//| Do not ask the server for quotes on weekends                    |
//+------------------------------------------------------------------+
bool ServerWork()
  {     
   if(DayOfWeek() == 0 || DayOfWeek() == 6)
       return(false);
   return(true);      
  }
//+------------------------------------------------------------------+
//| Generation of magik                                                  |
//+------------------------------------------------------------------+ 
int GenericMagik()
  {
   return(deals);
  }
//+------------------------------------------------------------------+   
//| Closing of transactions                                                  |
//+------------------------------------------------------------------+
bool CloseOrder(int magik)
  {
   int ticket,i;
   double Price_close;
   int err;
   int N;
//Function tries to shut the server at count attempts, if it fails,
//it gives an error message to the logfile
   while(N < count)
     {          
       for(i = OrdersTotal() - 1; i >= 0; i--) 
         {
           if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
               if(OrderSymbol() == Symbol()) 
                   if(OrderMagicNumber() == magik)
                     {
                       if(OrderType() == OP_BUY)        
                           Price_close = NormalizeDouble(Bid, Digits);
                       if(OrderType() == OP_SELL)        
                           Price_close = NormalizeDouble(Ask, Digits);
                       if(OrderClose(OrderTicket(), OrderLots(),
                          Price_close,slippage))
                         { 
                           //reduce the number of transactions for the EA
                           deals--;
                           //the piece of the margin became available - you can open again
                           NoOpen = false;                           
                           return(true);
                         }
                         //we have reached this place, it means that the order has not been sent
                       N++;
                       //processing of possible errors
                       err = ErrorBlock();
                       //if the error is seriuos
                       if(err > 1)
                         {
                           Print("Manual closing of the order #  needed",
                                 OrderTicket());
                           return(false);
                         }
                     }                                          
         }
        // taking a pause of 5 seconds and trying to close the transaction again
       Sleep(5000);
       RefreshRates();
     }
    //if we have reached this place, the transaction was not closed at count attempts 
   Print("Manual closing of the order #  needed",OrderTicket());
   return(false);
  }
//+------------------------------------------------------------------+
//|Tranaction for act 1-buy, 2-sell, the second parameter - the number of lots      |
//+------------------------------------------------------------------+ 
int Deal(int act, double Lot)
  {
   int N = 0;
   int ticket;
   int err;
   double Price_open;
   double Lots;
   int cmd;
   int magik;
   magik = GenericMagik();
   Lots = NormalizeDouble(Lot,1);
   if(act == 1)
     {
       Price_open = NormalizeDouble(Ask, Digits);
       cmd = OP_BUY;
     }
   if(act == 2)
     {
       Price_open = NormalizeDouble(Bid, Digits);
       cmd = OP_SELL;
     }
   //checking the margin for opening the position
   AccountFreeMarginCheck(Symbol(), cmd,Lots);
   err = GetLastError();
   if(err>0)
     {
       Print("No money for new position");
       NoOpen = true;
       return(0);
     }      
//Sending the order                  
   ticket = OrderSend(Symbol(), cmd, Lots, Price_open, slippage, 
                      0, 0, 0, magik);
   if(ticket > 0)
     {
       deals++;
       return(ticket);
     }
//If the order has not been sent, we will try to open it 5 times again      
   else
     {
       while(N < count)
         {
           N++;
           err = ErrorBlock();
           if(err == 1)
             {
               Sleep(5000);
               RefreshRates();
               if(act == 1)
                   Price_open = NormalizeDouble(Ask, Digits);
               if(act == 2)
                   Price_open = NormalizeDouble(Bid, Digits);              
               ticket = OrderSend(Symbol(), cmd, Lots, Price_open,
                                  slippage, 0, 0, 0, magik);
               if(ticket > 0)
                 {
                   deals++;
                   return(ticket);
                 }
             }
           // we have got a serious error  
           if(err > 1)            
               return(0);               
         }                                                       
     }    
   return(0);
  }
//+------------------------------------------------------------------+
//| // 0-no error, 1-need to wait and refresh, 2-transaction rejected,   |
//|    3-fatal error                                            |
//+------------------------------------------------------------------+
//Block of the error control 
int ErrorBlock()
  {
   int err = GetLastError();
   switch(err)
     {
       case 0: return(0);
       case 2:
         {
           Print("System failure. Reboot the computer/check the server");
           Trade = false;
           return(3);  
         }
       case 3:
         {
           Print("Error of the logic of the EA");
           Trade = false;
           return(3);   
         }
       case 4:
         {
           Print("Trading server is busy. Wait for 2 minutes.");
           Sleep(120000);
           return(2);   
         }
       case 6:
         { 
           bool connect = false;
           int iteration = 0;
           Print("Disconnect ");
           while((!connect) || (iteration > 60))
             {
               Sleep(10000);
               Print("Connection not restored", iteration*10,
                     "  seconds passed");
               connect = IsConnected();
               if(connect)
                 {
                   Print("Connection restored");
                   return(2);
                 }
               iteration++;
             }
           Trade = false; 
           Print("Connection problems");
           return(3);
         }
       case 8:
         {
           Print("Frequent requests");
           Trade = false; 
           return(3);
         }
       case 64:
         {
           Print("Account is blocked!");
           Trade = false; 
           return(3);            
         }
       case 65:
         {
           Print("Wrong account number???");
           Trade = false; 
           return(3);            
         }
       case 128:
         {
           Print("Waiting of transaction timed out");
           return(2);
         }
       case 129:
         {
           Print("Wrong price");
           return(1);            
         }
       case 130:
         {
           Print("Wrong stop");
           return(1);
         }
       case 131:
         {
           Print("Wrong calculation of trade volume");
           Trade = false;            
           return(3);
         }
       case 132:
         {
           Print("Market closed");
           Trade = false; 
           return(2);
         }
       case 134:
         {
           Print("Lack of margin for performing operation");
           Trade = false;             
           return(2);
         }
       case 135:
         {
           Print("Prices changed");
           return (1);
         }
       case 136:
         {
           Print("No price!");
           return(2);
         }
       case 138:
         {
           Print("Requote again!");
           return(1);
         }
       case 139:
         {
           Print("The order is in process. Program glitch");
           return(2);
         }
       case 141:
         {
           Print("Too many requests");
           Trade = false; 
           return(2);            
         }
       case 148:
         {
           Print("Transaction volume too large");
           Trade = false; 
           return(2);            
         }                                          
     }
   return (0);
  }
//+------------------------------------------------------------------+
//| generation of signals for opening/closing position on Macd       |
//+------------------------------------------------------------------+
int GetAction(int &action, double &lot, int &magik)
   {
   double MacdCurrent, MacdPrevious, SignalCurrent;
   double SignalPrevious, MaCurrent, MaPrevious;
   int cnt,total;
   
   MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0);
   MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
   SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0);
   SignalPrevious=iMACD(NULL,0,12,26,9,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);
   
  if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious &&
         MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious)
      {
         action=1;
         lot=1;
         return (0);
      }
  if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && 
         MacdCurrent>(MACDOpenLevel*Point) && MaCurrent<MaPrevious)
      {
         action=2;
         lot=1;
         return (0);               
      }
   total=OrdersTotal();
   for(cnt=0;cnt<total;cnt++)
     {
      OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
      if(OrderType()<=OP_SELL &&   // check for opened position 
         OrderSymbol()==Symbol())  // check for symbol
        {
         if(OrderType()==OP_BUY)   // long position is opened
           {
            // should it be closed?
            if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
               MacdCurrent>(MACDCloseLevel*Point))
                {
                 action=3;
                 magik=OrderMagicNumber();
                 return(0); // exit
                }
           }
         else // go to short position
           {
            // should it be closed?
            if(MacdCurrent<0 && MacdCurrent>SignalCurrent &&
               MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*Point))
              {
               action=3;
               magik=OrderMagicNumber();
               return(0); 
              }
           }
        }
     }
   }
//+------------------------------------------------------------------+
//| The EA initialization function                                   |
//+------------------------------------------------------------------+
int init()
  { 
    if(!IsTradeAllowed())
      {
        Print("Trade not allowed!");
        return(0);     
      }
  }
//+------------------------------------------------------------------+
//| The EA deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
//Closing all orders
   for(int k = OrdersTotal() - 1; k >= 0 ; k--)
       if(OrderSymbol() == Symbol()) 
         {
           if(OrderType() == OP_BUY)
              OrderClose(OrderTicket(), OrderLots(), 
                         NormalizeDouble(Bid,Digits), 10);               
           if(OrderType() == OP_SELL)
               OrderClose(OrderTicket(), OrderLots(),
                          NormalizeDouble(Ask, Digits),10);                 
         }
  } 
//+------------------------------------------------------------------+
//| The EA start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   int action =0;
   double lot = 1;
   int magik = 0;     
   while(Trade)
     {
       Sleep(time_for_action*1000);      
       RefreshRates();
       /*Logic of the EA where the we calculate the action, position limit and magik for closing the order
       action 1-buy, 2-sell, 3-close
       for example, take the EA on Macd*/
       GetAction(action,lot,magik);
       if(ServerWork())
         {
           if(((action == 1) || (action == 2)) && (!NoOpen))
             {                                        
               if(TradeIsBusy() < 0) 
                   return(-1); 
               Deal(action, lot);
               Sleep(TimeForSleep*1000);                                
               TradeIsNotBusy();
             }
           if(action == 3)
             {
               if(TradeIsBusy() < 0) 
                 { 
                   return(-1); 
                   if(!CloseOrder(magik))
                       Print("MANUAL CLOSE OF TRANSATION NEEDED");
                   Sleep(TimeForSleep*1000);   
                   TradeIsNotBusy();
                 } 
             }
         }
       else
         {
            Print("Weekends");
            if(TradeIsBusy() < 0) 
                  return(-1); 
            Sleep(1000*3600*48);
            TradeIsNotBusy();
         }
       action = 0;
       lot = 0;
       magik = 0;
     }
   Print("Critical error occured and the work of the EA terminated");  
   return(0);
  }
//+------------------------------------------------------------------+

Esta versión del robot de trading funciona en un bucle sin fin. Su demanda se produce cuando se crea el asesor experto multidivisa de reventa especulativa. El algoritmo de funcionamiento del asesor experto es el siguiente:

1. Obtiene la señal del bloque analítico GetAction();
2. Realiza la transacción necesaria en las funciones Deal() и CloseOrder();
3. Vuelve al punto 1 después de una breve pausa time_for_action en caso de que no hubiera fallos graves.

Después de obtener la señal (buy, sell, close) del bloque de análisis, el asesor experto bloquea el flujo de trading (leer el artículo) e intenta realizar la transacción y, después de ello, se toma una pausa durante unos segundos y libera el flujo de trading para otros asesores expertos. El asesor experto intenta enviar la orden no más de las veces del "recuento". Debería ser suficiente para que la orden pase el mercado inestable donde podemos obtener las nuevas cotizaciones. Si durante el envío de la orden se produce un error grave, el asesor experto deja de funcionar. Si se produce cualquier error, aparece un mensaje de error en la carpeta de los asesores expertos (Expert Advisors). El asesor experto seguirá funcionando si el error no es crítico.

Los errores se procesan en el procedimiento ErrorBlock() de acuerdo con la siguiente configuración: el procedimiento obtiene el código del error y da un breve algoritmo de procesamiento del mismo. Para la mayoría de errores es solo un mensaje en el registro. Si el error es grave, entonces los indicadores de trading Trade y NoOpen cambian. Si se trata de un fallo de conexión, el procesamiento de la situación es algo más complicado. El robot intenta llegar al servidor sesenta veces con la secuencia periódica predefinida. Si no llega al servidor, los más probable es que tenga graves problemas y debemos detener nuestro trading durante un tiempo. Dependiendo de la influencia del error sobre el trading, el algoritmo de procesamiento tiene distintos significados:

0 - sin error;
1 - el error se asocia con la volatilidad del mercado, podemos intentar enviar la orden una vez más;
2 - se ha producido un error grave durante el envío de esta orden, detiene la apertura de posiciones durante un tiempo;
3 - un fallo grave del asesor experto, fallo de conexión - detiene el trading hasta que se aclaren las circunstancias.

Conclusión

Los errores de sintaxis, algorítmicos y lógicos se producen cuando no prestamos mucha atención al código del algoritmo. Estos errores se solucionan comprobando y verificando los valores de las variables en el registro. Estos pueden ser detectados en la etapa de compilación y prueba del asesor experto. Dichos errores no existen durante mucho tiempo, se suelen resolver antes de usar una cuenta de demostración.

Los errores de trading se producen durante el envío de órdenes al servidor. Están relacionados con el trading real cuando podemos encontrar nuevas cotizaciones, retrasos, lucha de los agentes con la reventa especulativa y fallos de los equipos. Dichos errores no pueden predecirse, pero pueden y deben ser procesados. Debemos procesarlos individualmente cada semana dependiendo de la lógica del asesor experto, de la frecuencia de las transacciones y las modificaciones de las órdenes.

Los errores que se producen durante el funcionamiento del asesor experto, necesitan ser procesados. No es una tarea trivial. Depende de la complejidad del asesor experto y sus características. En este artículo puede encontrar un patrón de ejemplo de un asesor experto que realiza esta tarea. Requiere mucho tiempo crear un sistema de trading más seguro. Pero el tiempo dedicado a desarrollar un sistema de trading automatizado que no de problemas será recompensado varias veces por la seguridad de nuestros depósitos y la calidad de nuestro sueño por las noches.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/1462

Archivos adjuntos |
AntiError.mq4 (14.86 KB)
TradeContext.mqh (11.54 KB)
Transferir el código de un indicador al código de un asesor experto. Conclusión Transferir el código de un indicador al código de un asesor experto. Conclusión
Este es el artículo final, dedicado a transferir el código de un indicador al código de un asesor experto. Aquí, el autor transforma un determinado ejemplo de código de un asesor experto de forma que dicho asesor experto se presenta en un único archivo sin llamar a los indicadores personalizados.
Visualización simultánea de señales de varios indicadores de los cuatro periodos de tiempo Visualización simultánea de señales de varios indicadores de los cuatro periodos de tiempo
Durante el trading manual, podemos vigilar los valores de varios indicadores. Es algo distinto al trading mecánico. Si tenemos dos o tres indicadores y hemos elegido un periodo de tiempo para el trading, no es una tarea complicada. Pero ¿qué ocurre si tenemos cinco o seis indicadores y nuestra estrategia de trading requiere tener en cuenta las señales de varios periodos de tiempo?
¿Cuál es el grado de fiabilidad del trading por la noche? ¿Cuál es el grado de fiabilidad del trading por la noche?
El artículo trata las peculiaridades del trading plano durante la noche con pares de divisas cruzados. Se explica dónde podemos esperar beneficios y por qué las grandes pérdidas son muy poco probables. Se presenta también el ejemplo de un Asesor Experto desarrollado para el trading nocturno y se aborda el uso de esta estrategia en la práctica.
Un asesor experto creado para ordenar. Manual para un trader Un asesor experto creado para ordenar. Manual para un trader
No todos los traders son programadores. Y no todos los programadores son realmente buenos. Entonces, ¿qué debemos hacer si necesitamos automatizar nuestro sistema pero no tenemos tiempo ni ganas de estudiar MQL4?