Discusión sobre el artículo "Evaluación visual y ajuste comercial en MetaTrader 5"

 

Artículo publicado Evaluación visual y ajuste comercial en MetaTrader 5:

En el simulador de estrategias no solo es posible optimizar los parámetros de un robot comercial. Hoy le mostraremos cómo evaluar post-facto la historia comercial de su cuenta y realizar ajustes en el trading en el simulador cambiando el tamaño de las órdenes stop para las posiciones abiertas.

Imaginemos una situación: en cualquier cuenta durante bastante tiempo se da un comercio más o menos activo en diferentes instrumentos por parte de diferentes asesores expertos e incluso manualmente. Y ahora, pasado un tiempo, queremos ver los resultados del trabajo. Naturalmente, podemos consultar los informes estándar sobre transacciones en el terminal pulsando la combinación de teclas Alt+E. O podemos cargar los iconos de transacciones en el gráfico y ver las entradas y salidas de las posiciones. Pero, ¿y si queremos ver en dinámica cómo se ha negociado, dónde y cómo se han abierto y cerrado las posiciones? O por ejemplo, visualizar por separado para cada símbolo, o para todos a la vez, la apertura y el cierre de posiciones, a qué niveles se han colocado las órdenes stop y si su tamaño está justificado. ¿Y si nos preguntamos "qué pasaría si..." (y aquí hay muchas opciones: otros stops, utilizar diferentes algoritmos y criterios, utilizar posiciones finales, o mover los stops hasta el punto de equilibrio, etc.); y luego probamos todos nuestros "si" con un resultado claramente visible? ¿Cómo habría cambiado el comercio si?...

Pues resulta que ya está todo preparado para abordar estas cuestiones. Todo lo que deberemos hacer es cargar la historia de la cuenta en un archivo, es decir, todas las transacciones ejecutadas, y luego ejecutar el asesor experto en el simulador de estrategias, que leerá las transacciones del archivo y abrirá/cerrará posiciones en el simulador de estrategias del terminal cliente. Con un asesor experto de este tipo, podemos añadirle código para cambiar las condiciones de salida de las posiciones y comparar cómo ha cambiado el comercio con ello, y qué habría pasado si...

¿Qué ganamos nosotros? Otra herramienta para encontrar los mejores resultados y hacer ajustes en las transacciones ya realizadas en la cuenta durante algún tiempo. Las pruebas visuales nos permitirán ver en dinámica si las posiciones se han abierto correctamente en tal o cual instrumento, y si se han cerrado en el momento adecuado, etc. Y lo principal es que podremos simplemente añadir un nuevo algoritmo al código del asesor experto, probarlo, obtener resultados y hacer ajustes a los asesor expertos que trabajan en esta cuenta.


Autor: Artyom Trishkin

 

Tengo un enfoque ligeramente diferente. Como cada robot escribe un registro de sus operaciones, basta con hacer un script. Cuando lo ejecuto en un gráfico de símbolos, pide el número del robot (mágico) y desde el archivo con este símbolo y este número en el nombre carga las operaciones en el gráfico - todas o sólo largas/sólo cortas (puede ser útil separarlas). Este enfoque permite trabajar con la compensación, que es fundamentalmente importante para mí. E incluso con los robots de comercio en el modo de prueba - sin hacer operaciones, pero sólo simular, calcular y registrar los resultados en un archivo.

 
Muchas gracias por el artículo - Definitivamente voy a probar y ver....
Sólo necesito fragmentos de código para registrar valores e indicadores de transacciones en estructuras.
 
//+------------------------------------------------------------------+
//|| Estructura de transacciones. Se utiliza para crear un archivo de historial de transacciones.
//+------------------------------------------------------------------+
struct SDeal
  {
   ulong             ticket;                 // Billete de oferta
   long              order;                  // Orden de apertura de la operación
   long              pos_id;                 // Identificador de posición
   long              time_msc;               // Tiempo en milisegundos
   datetime          time;                   // Hora.
   double            volume;                 // Volumen
   double            price;                  // Precio
   double            profit;                 // Beneficio
   double            commission;             // Comisión sobre la transacción
   double            swap;                   // Permuta acumulada al cierre
   double            fee;                    // Pago de la transacción, cargado inmediatamente después de realizarla
   double            sl;                     // Nivel de Stop Loss
   double            tp;                     // Nivel de Take Profit
   ENUM_DEAL_TYPE    type;                   // Tipo
   ENUM_DEAL_ENTRY   entry;                  // Método de cambio de posición
   ENUM_DEAL_REASON  reason;                 // Motivo o fuente para realizar la transacción
   long              magic;                  // ID de experto
   int               digits;                 // Carácter de los dígitos
   ushort            symbol[16];             // Símbolo
   ushort            comment[64];            // Comentario de la transacción
   ushort            external_id[256];       // Identificador de la operación en el sistema de negociación externo (en la Bolsa)
   
//--- Establecer propiedades de cadena
   bool              SetSymbol(const string deal_symbol)          { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length());                }
   bool              SetComment(const string deal_comment)        { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length());             }
   bool              SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); }
                       
//--- Devolver propiedades de cadena
   string            Symbol(void)                                 { return(::ShortArrayToString(symbol));                                                   }
   string            Comment(void)                                { return(::ShortArrayToString(comment));                                                  }
   string            ExternalID(void)                             { return(::ShortArrayToString(external_id));                                              }
  };

//+------------------------------------------------------------------+
//| Guarda las transacciones del historial en un array |||
//+------------------------------------------------------------------+
int SaveDealsToArray(SDeal &array[], bool logs=false)
  {
//--- estructura de la transacción
   SDeal deal={};
   
//--- solicitar el historial de operaciones en el intervalo desde el inicio hasta el momento actual 
   if(!HistorySelect(0, TimeCurrent()))
     {
      Print("HistorySelect() failed. Error ", GetLastError());
      return 0;
     }
   
//--- número total de operaciones en la lista 
   int total=HistoryDealsTotal(); 

//--- procesar cada transacción 
   for(int i=0; i<total; i++) 
     { 
      //--- obtener un ticket de la siguiente operación (la operación se selecciona automáticamente para obtener sus propiedades)
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket==0)
         continue;
      
      //--- guardar sólo el balance y las transacciones comerciales
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);
      if(deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL && deal_type!=DEAL_TYPE_BALANCE)
         continue;
      
      //--- almacenar las propiedades de la transacción en la estructura
      deal.ticket=ticket;
      deal.type=deal_type;
      deal.order=HistoryDealGetInteger(ticket, DEAL_ORDER);
      deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket, DEAL_ENTRY);
      deal.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(ticket, DEAL_REASON);
      deal.time=(datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
      deal.time_msc=HistoryDealGetInteger(ticket, DEAL_TIME_MSC);
      deal.pos_id=HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
      deal.volume=HistoryDealGetDouble(ticket, DEAL_VOLUME);
      deal.price=HistoryDealGetDouble(ticket, DEAL_PRICE);
      deal.profit=HistoryDealGetDouble(ticket, DEAL_PROFIT);
      deal.commission=HistoryDealGetDouble(ticket, DEAL_COMMISSION);
      deal.swap=HistoryDealGetDouble(ticket, DEAL_SWAP);
      deal.fee=HistoryDealGetDouble(ticket, DEAL_FEE);
      deal.sl=HistoryDealGetDouble(ticket, DEAL_SL);
      deal.tp=HistoryDealGetDouble(ticket, DEAL_TP);
      deal.magic=HistoryDealGetInteger(ticket, DEAL_MAGIC);
      deal.SetSymbol(HistoryDealGetString(ticket, DEAL_SYMBOL));
      deal.SetComment(HistoryDealGetString(ticket, DEAL_COMMENT));
      deal.SetExternalID(HistoryDealGetString(ticket, DEAL_EXTERNAL_ID));
      deal.digits=(int)SymbolInfoInteger(deal.Symbol(), SYMBOL_DIGITS);
      
      //--- incrementa el array y
      int size=(int)array.Size();
      ResetLastError();
      if(ArrayResize(array, size+1, total)!=size+1)
        {
         Print("ArrayResize() failed. Error ", GetLastError());
         continue;
        }
      //--- almacenar la transacción en la matriz
      array[size]=deal;
      //--- si está activada, muestra la descripción de la transacción guardada en el diario
      if(logs)
         DealPrint(deal, i);
     }
//--- devuelve el número de tratos almacenados en el array
   return (int)array.Size();
  }

No entiendo por qué se ignora la lógica de la arquitectura.

  • Si el array se rellena elemento a elemento, debería haber una función para rellenar un elemento.
  • Si un elemento es una estructura, debería llenarse por sí mismo.
  • Si asignas un espacio de reserva para el array de una vez, ¿por qué necesitas realizar un "redimensionamiento" en cada llenado?


¿Por qué no escribir, por ejemplo, así?

//+------------------------------------------------------------------+
//|| Estructura de transacciones. Se utiliza para crear un archivo de historial de transacciones.
//+------------------------------------------------------------------+
struct SDeal
  {
   ulong             ticket;                 // Billete de oferta
   long              order;                  // Orden de apertura de la operación
   long              pos_id;                 // Identificador de posición
   long              time_msc;               // Tiempo en milisegundos
   datetime          time;                   // Hora.
   double            volume;                 // Volumen
   double            price;                  // Precio
   double            profit;                 // Beneficio
   double            commission;             // Comisión sobre la transacción
   double            swap;                   // Permuta acumulada al cierre
   double            fee;                    // Pago de la transacción, cargado inmediatamente después de realizarla
   double            sl;                     // Nivel de Stop Loss
   double            tp;                     // Nivel de Take Profit
   ENUM_DEAL_TYPE    type;                   // Tipo
   ENUM_DEAL_ENTRY   entry;                  // Método de cambio de posición
   ENUM_DEAL_REASON  reason;                 // Motivo o fuente para realizar la transacción
   long              magic;                  // ID de experto
   int               digits;                 // Carácter de los dígitos
   ushort            symbol[16];             // Símbolo
   ushort            comment[64];            // Comentario de la transacción
   ushort            external_id[256];       // Identificador de la operación en el sistema de negociación externo (en la Bolsa)
   
//--- Establecer propiedades de cadena
   bool              SetSymbol(const string deal_symbol)          { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length());                }
   bool              SetComment(const string deal_comment)        { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length());             }
   bool              SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); }
                       
//--- Devolver propiedades de cadena
   string            Symbol(void)                                 { return(::ShortArrayToString(symbol));                                                   }
   string            Comment(void)                                { return(::ShortArrayToString(comment));                                                  }
   string            ExternalID(void)                             { return(::ShortArrayToString(external_id));                                              }
   
   bool Set( const ulong _ticket )   
   {
      this.ticket=_ticket;
      this.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(_ticket, DEAL_TYPE);
      this.order=HistoryDealGetInteger(_ticket, DEAL_ORDER);
      this.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(_ticket, DEAL_ENTRY);
      this.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(_ticket, DEAL_REASON);
      this.time=(datetime)HistoryDealGetInteger(_ticket, DEAL_TIME);
      this.time_msc=HistoryDealGetInteger(_ticket, DEAL_TIME_MSC);
      this.pos_id=HistoryDealGetInteger(_ticket, DEAL_POSITION_ID);
      this.volume=HistoryDealGetDouble(_ticket, DEAL_VOLUME);
      this.price=HistoryDealGetDouble(_ticket, DEAL_PRICE);
      this.profit=HistoryDealGetDouble(_ticket, DEAL_PROFIT);
      this.commission=HistoryDealGetDouble(_ticket, DEAL_COMMISSION);
      this.swap=HistoryDealGetDouble(_ticket, DEAL_SWAP);
      this.fee=HistoryDealGetDouble(_ticket, DEAL_FEE);
      this.sl=HistoryDealGetDouble(_ticket, DEAL_SL);
      this.tp=HistoryDealGetDouble(_ticket, DEAL_TP);
      this.magic=HistoryDealGetInteger(_ticket, DEAL_MAGIC);
      this.SetSymbol(HistoryDealGetString(_ticket, DEAL_SYMBOL));
      this.SetComment(HistoryDealGetString(_ticket, DEAL_COMMENT));
      this.SetExternalID(HistoryDealGetString(_ticket, DEAL_EXTERNAL_ID));
      this.digits=(int)SymbolInfoInteger(this.Symbol(), SYMBOL_DIGITS);
      
      return((bool)this.time);
   }
  };

bool SetDeal( SDeal &deal, const ulong ticket )
{
  //--- guardar sólo el balance y las transacciones comerciales
  const ENUM_DEAL_TYPE deal_type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);

  return((deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL && deal_type!=DEAL_TYPE_BALANCE) && deal.Set(ticket));
}

//+------------------------------------------------------------------+
//| Guarda las transacciones del historial en un array |||
//+------------------------------------------------------------------+
int SaveDealsToArray(SDeal &array[], bool logs=false)
  {
   int Amount = 0;
   
//--- solicitar el historial de operaciones en el intervalo desde el inicio hasta el momento actual 
   if(HistorySelect(0, INT_MAX))
   {
  //--- número total de operaciones en la lista 
     const int total=ArrayResize(array, HistoryDealsTotal()); 
  
  //--- procesar cada transacción 
     for(int i=0; i<total; i++) 
       if (SetDeal(array[Amount], HistoryDealGetTicket(i))) 
         Amount++;
   }

//--- devuelve el número de tratos almacenados en el array
   return(ArrayResize(array, Amount));
  }


Yo mismo estoy lejos de escribir código de forma ejemplar. Pero seamos más sensatos de alguna manera.

 
fxsaber #:
  • Si el elemento es una estructura, debe autopoblarse.

Ya estamos otra vez.

//+------------------------------------------------------------------+
//| Devuelve una descripción de la transacción|
//+------------------------------------------------------------------+
string DealDescription(SDeal &deal, const int index)
  {
   string indexs=StringFormat("% 5d", index);
   if(deal.type!=DEAL_TYPE_BALANCE)
      return(StringFormat("%s: deal #%I64u %s, type %s, Position #%I64d %s (magic %I64d), Price %.*f at %s, sl %.*f, tp %.*f",
                          indexs, deal.ticket, DealEntryDescription(deal.entry), DealTypeDescription(deal.type),
                          deal.pos_id, deal.Symbol(), deal.magic, deal.digits, deal.price,
                          TimeToString(deal.time, TIME_DATE|TIME_MINUTES|TIME_SECONDS), deal.digits, deal.sl, deal.digits, deal.tp));
   else
      return(StringFormat("%s: deal #%I64u %s, type %s %.2f %s at %s",
                          indexs, deal.ticket, DealEntryDescription(deal.entry), DealTypeDescription(deal.type),
                          deal.profit, AccountInfoString(ACCOUNT_CURRENCY), TimeToString(deal.time)));
  }

¿Por qué no se autoimprime la estructura?

 
fxsaber #:
Si un elemento es una estructura, debería autorellenarse.

Muestre las estructuras de autorrellenado y autorrellenado estándar, por favor.

Estos códigos están escritos para simplificar la comprensión.

No se trata de envolverlo todo en una monstruosidad ilegible de una sola línea, aunque funcione.

Los artículos se escriben para transmitir la esencia de la forma más clara posible, no el virtuosismo de la escritura de código. Hay hilos especiales para el virtuosismo, y tú participas en ellos. Y son útiles.

Los artículos tienen un propósito diferente.

 
Artyom Trishkin #:

Muestre las estructuras estándar de autoimpresión y autorrelleno, por favor.

Si estamos hablando de estructuras MQ, no están disponibles en MQL5. Los autores no podían saber qué y quién las necesitaría. Por lo tanto, sólo existe la base y la posibilidad de heredar de ellas para dotar a las estructuras estándar de la funcionalidad requerida por el usuario.

Estos códigos están escritos para simplificar la comprensión.

No hay ningún propósito de envolverlo todo en una monstruosidad ilegible empaquetada en una línea, aunque funcione.

No sugiero tal cosa.

Los artículos se escriben para transmitir la esencia de la forma más clara posible, no el virtuosismo de la escritura de código. Hay hilos especiales para el virtuosismo y tú participas en ellos. Y son útiles.

Los artículos tienen otra finalidad.

El virtuosismo es el autor del libro de texto. Estoy a favor de la lógica.


Si un objeto puede hacer algo por sí mismo sin ayuda, debería hacerlo, no por él. Debería haber una jerarquía mínima en la arquitectura de la escritura de código. En tu código, sólo propones sustituir varias matrices separadas de propiedades de transacción por una única matriz de propiedades de transacción utilizando una estructura. Pues bien, no utilizas casi nada de la estructura (lectura/escritura y equiparación). El mismo estilo procedimental de trabajar con propiedades de transacción.

 
fxsaber #:

Si hablamos de estructuras MQ, no están disponibles en MQL5. Los autores no podían saber qué y quién las necesitaría. Por tanto, sólo existe la base y la posibilidad de heredar de ellas para dotar a las estructuras estándar de la funcionalidad requerida por el usuario.

Yo no ofrezco tal cosa.

La virtuosidad es la autora del Tutorial. Yo abogo por la lógica.


Si un objeto puede hacer algo por sí mismo sin ayuda, debe hacerlo él, no por él. Debería haber una jerarquía mínima en la arquitectura de la escritura de código. En tu código, sólo propones sustituir varias matrices separadas de propiedades de transacción por una única matriz de propiedades de transacción utilizando una estructura. Pues bien, no utilizas casi nada de la estructura (lectura/escritura y equiparación). El mismo estilo procedimental de trabajar con propiedades de transacción.

Uso las características que sugieres en los artículos sobre la librería. Aquí no es necesario.

 
//+------------------------------------------------------------------+
//|| Abre el archivo para escribir, devuelve el handle |||
//+------------------------------------------------------------------+
bool FileOpenToWrite(int &handle)
  {
   ResetLastError();
   handle=FileOpen(PATH, FILE_WRITE|FILE_BIN|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: FileOpen() failed. Error %d",__FUNCTION__, GetLastError());
      return false;
     }
//--- con éxito
   return true;
  }
//+------------------------------------------------------------------+
//|| Abre el archivo para lectura, devuelve el handle ||
//+------------------------------------------------------------------+
bool FileOpenToRead(int &handle)
  {
   ResetLastError();
   handle=FileOpen(PATH, FILE_READ|FILE_BIN|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: FileOpen() failed. Error %d",__FUNCTION__, GetLastError());
      return false;
     }
//--- con éxito
   return true;
  }
//+------------------------------------------------------------------+
//| Guarda los datos de la transacción del array || a un fichero
//+------------------------------------------------------------------+
bool FileWriteDealsFromArray(SDeal &array[], ulong &file_size)
  {
//--- si se pasa un array vacío - infórmalo y devuelve false
   if(array.Size()==0)
     {
      PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__);
      return false;
     }
     
//--- abrir el fichero para escribir, obtener su handle
   int handle=INVALID_HANDLE;
   if(!FileOpenToWrite(handle))
      return false;
   
//--- mover el puntero del fichero al final del fichero 
   bool res=true;
   ResetLastError();
   res&=FileSeek(handle, 0, SEEK_END);
   if(!res)
      PrintFormat("%s: FileSeek(SEEK_END) failed. Error %d",__FUNCTION__, GetLastError());
   
//--- escribe los datos del array al final del fichero 
   file_size=0;
   res&=(FileWriteArray(handle, array)==array.Size());
   if(!res)
      PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError());
   else
      file_size=FileSize(handle);

//--- cerrar el archivo 
   FileClose(handle);
   return res;
  }
//+------------------------------------------------------------------+
//| Carga los datos de la transacción desde el fichero a la matriz |||
//+------------------------------------------------------------------+
bool FileReadDealsToArray(SDeal &array[], ulong &file_size)
  {
//--- abrir el archivo para lectura, obtener su handle
   int handle=INVALID_HANDLE;
   if(!FileOpenToRead(handle))
      return false;
   
//--- mover el puntero del fichero al final del fichero 
   bool res=true;
   ResetLastError();
   
//--- leer datos del fichero en un array
   file_size=0;
// res=(FileReadArray(handle, array)>0);
   res=(ArrayResize(array, FileReadArray(handle, array))>0);
   if(!res)
      PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError());
   else
      file_size=FileSize(handle);

//--- cerrar el archivo 
   FileClose(handle);
   return res;
  }

Hay un error frecuente a la hora de resaltarlo y arreglarlo.

Pero aún así, ¿por qué no escribir todo este código de otra manera?

//+------------------------------------------------------------------+
//| Guarda los datos de la transacción del array || a un fichero
//+------------------------------------------------------------------+
bool FileWriteDealsFromArray(SDeal &array[], long &file_size)
{
  return((file_size = FileSave(PATH, array, FILE_COMMON) * sizeof(SDeal)) > 0);
}

//+------------------------------------------------------------------+
//| Carga los datos de la transacción desde el fichero a la matriz |||
//+------------------------------------------------------------------+ 
bool FileReadDealsToArray2(SDeal &array[], long &file_size)
{
  return((file_size = ArrayResize(array, (int)FileLoad(PATH, array, FILE_COMMON)) * sizeof(SDeal)) > 0);
}
 
fxsaber #:
Pero aún así, ¿por qué no escribir todo este código de otra manera?

El artículo está pensado para un nivel de formación más bien bajo. Pretende cubrir una amplia parte de lectores. Los que ya tienen experiencia no necesitan estos artículos. Ellos tienen sus propios bigotes )

 
Artyom Trishkin #:

El artículo está destinado a un nivel de formación bastante bajo. Con el fin de llegar a un amplio sector del público lector.

Foro sobre comercio, sistemas automatizados de comercio y comprobación de estrategias comerciales

Probador de estrategias de MetaTrader 5: errores, fallos, sugerencias para mejorar su trabajo

fxsaber, 2019.09.06 15:45

Una buena característica que estás ignorando

MqlTick tiks[];

if (FileLoad("deribit1.out.bin", ticks))
{
// ....