Casi un constructor para crear asesores

Vladimir Karputov | 23 diciembre, 2021

Índice


Introducción

Desde el principio, nuestro objetivo ha sido utilizar la Biblioteca Estándar. Recuerdo mi primera tarea, a saber, implementar la funcionalidad más simple: conectar la clase comercial CTrade y ejecutar el método Buy o Sell. Elegí la biblioteca estándar, por la brevedad y concisión del código obtenido. El código corto a continuación, ejecutado en forma de script, abre una posición BUY con un volumen de 1.0 lote:

//+------------------------------------------------------------------+
//|                                                     Open Buy.mq5 |
//|                         Copyright © 2018-2021, Vladimir Karputov |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018-2021, Vladimir Karputov"
#property version   "1.001"
//---
#include <Trade\Trade.mqh>
CTrade         m_trade;                      // trading object
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   m_trade.Buy(1.0); // open Buy position, volume 1.0 lot
  }

Poco a poco, el nivel de exigencia fue aumentando, y me encontré con errores comerciales casi cada vez que escribía un nuevo asesor experto. Así que quería cada vez con más intensidad escribir el código correcto y olvidarme de estos errores para siempre. Y luego salió un artículo muy importante Qué comprobaciones debe superar un robot comercial antes de ser publicado en el Mercado Cuando se publicó este artículo, ya sabía que era necesario disponer de funciones de control fiables para la ejecución de órdenes comerciales. A partir de ese momento, comencé a adquirir gradualmente funciones comprobadas que, con la ayuda del método 'copy->paste', se pueden insertar fácilmente en un asesor y para su posterior uso.

Dado que los indicadores casi siempre se usan en el trabajo de un asesor, comencé a adquirir funciones para crear correctamente el manejador de un indicador

RECUERDE: el estilo de MQL5 implica que el manejador del indicador se cree UNA VEZ y esto se hace, por regla general, en OnInit,

además de las funciones para obtener los datos del indicador. Desde la versión 2.XXX, comencé a mantener dos ramas de desarrollo del constructor: un código de procedimiento regular, y un código en forma de clase (la tarea principal de la clase es la implementación de asesores multidivisa).

Y el constructor seguía desarrollándose, de forma gradual, se le fueron añdiendo las configuraciones más populares:

Cada parámetro de entrada implicaba la creación de bloques de código y nuevas funciones.

Para el uso diario, decidí recopilar las funciones más populares y un conjunto completo de parámetros de entrada en el asesor 'Trading engine 3.mq5': de hecho, este es un asesor listo para usar capaz de ocuparse de mucho trabajo de rutina. Queda ya para cada caso específico añadir o quitar funciones, o cambiar la interacción entre bloques de código.


1. Funcionalidad del asesor después del constructor

El asesor creado por el diseñador dispone de multitud de configuraciones que se pueden combinar para crear estrategias únicas. En la versión 4.XXX, se aplican las siguientes reglas: 

Siempre podrá ver lo que indica 'points' en el gráfico del símbolo arrastrando la herramienta Retícula:

puntos

Fig. 1. Puntos

Bien, aquí tenemos los parámetros de entrada del asesor obtenido después de usar el constructor:


2. Algoritmo general del constructor

A nivel programático general (en el encabezado del asesor), se declara la matriz 'SPosition' ('SPosition' es el nombre de la matriz), que consta de estructuras 'STRUCT_POSITION'. Al principio, esta matriz tiene un tamaño cero; después de que se haya procesado la señal comercial, la matriz también volverá al tamaño cero.

Desde OnTick, se llama la función 'SearchTradingSignals'; si hay una señal (recibimos la señal si no hay posiciones abiertas en el mercado), esta función genera una orden comercial (crea para cada orden comercial una estructura 'STRUCT_POSITION' en la matriz). También en OnTick, se comprueba la presencia de órdenes comerciales, verificando para ello el tamaño de la matriz 'SPosition: (si este es superior a cero, significará que hay una orden comercial y esta orden comercial se reenviará para su ejecución a 'OpenBuy' o a 'OpenSell'). El control de la ejecución de órdenes comerciales se realiza en OnTradeTransaction:

general algorithm (simple)

Fig. 2. Algoritmo general (simple)

Siempre se asume que el asesor trabaja en el símbolo actual, es decir, en el símbolo en cuyo gráfico está instalado el asesor. Ejemplo: el asesor se coloca en el gráfico 'USDPLN', eso significa que el asesor funciona en el símbolo 'USDPLN'.

2.1. Estructura 'STRUCT_POSITION'

Esta estructura es el corazón del asesor, y ejecuta dos papeles al mismo tiempo: en la estructura hay campos en los que se anota la orden comercial (la anotación se realiza en 'SearchTradingSignals'), y campos para controlar la ejecución de la orden comercial (el control se realiza en OnTradeTransaction). 

//+------------------------------------------------------------------+
//| Structure Positions                                              |
//+------------------------------------------------------------------+
struct STRUCT_POSITION
  {
   ENUM_POSITION_TYPE pos_type;              // position type
   double            volume;                 // position volume (if "0.0" -> the lot is "Money management")
   double            lot_coefficient;        // lot coefficient
   bool              waiting_transaction;    // waiting transaction, "true" -> it's forbidden to trade, we expect a transaction
   ulong             waiting_order_ticket;   // waiting order ticket, ticket of the expected order
   bool              transaction_confirmed;  // transaction confirmed, "true" -> transaction confirmed
   //--- Constructor
                     STRUCT_POSITION()
     {
      pos_type                   = WRONG_VALUE;
      volume                     = 0.0;
      lot_coefficient            = 0.0;
      waiting_transaction        = false;
      waiting_order_ticket       = 0;
      transaction_confirmed      = false;
     }
  };

Unos campos son responsables de la orden comercial, mientras que otros lo son del control de la orden comercial. La estructura contiene un constructor, es decir, una función especial 'STRUCT_POSITION()' que se llama al crear el objeto de estructura e inicializa los elementos de la estructura.

Campos de la orden comercial:

Campos de control sobre la ejecución de una orden comercial

Después de levantar la bandera en el campo 'transaction_confirmed', la orden comercial es eliminada de la estructura. Por consiguiente, si el asesor no tiene una orden comercial, la estructura tendrá un tamaño igual a cero. Podrá leer más sobre los campos de estructura y el control en el capítulo "Captando una transacción el código simplificado We catch the transaction".

¿Por qué exactamente este algoritmo?

Podría parecer que basta con comprobar si el método Buy ha retornado 'true' o 'false', y que si es 'true', podemos decidir que la orden comercial ha sido ejecutada. Y en muchas situaciones, este enfoque funcionaría, pero hay momentos en los que el retorno de 'true' no ofrece garantías de obtener un resultado, cosa que, a propósito, se menciona en la documentación sobre los métodos Buy y Sell:

Observación

La finalización satisfactoria del método no siempre significa la finalización satisfactoria de una operación comercial. Debemos verificar el resultado de la ejecución de la solicitud comercial (código de retorno del servidor comercial) llamando al método ResultRetcode(), así como el valor retornado por ResultDeal().

Y la confirmación final y precisa de la ejecución de una transacción comercial puede ser la presencia de una entrada en la historia comercial. Por eso he elegido este algoritmo: si el método se ejecuta con éxito, comprobamos ResultDeal (ticket de la transacción), comprobamos ResultRetcode (código del resultado de la ejecución de la solicitud) y recordamos ResultOrder (ticket de la orden), mientras que el ticket de la orden lo captamos en OnTradeTransaction).


3. Añadimos el indicador estándar — trabajando con el archivo 'Indicators Code.mq5'

Para mayor comodidad, los bloques de código listos para usar (la declaraсión de las variables para almacenar los manejadores, los parámetros de entrada, la creación de identificadores) se recopilan en el asesor 'Indicators Code.mq5'. Los parámetros de entrada de los indicadores y las variables para almacenar los manejadores se encuentran en el encabezado del asesor; la creación de los manejadores, como era de esperar, está escrita en OnInit. Un pequeño matiz: los nombres de las variables para almacenar los manejadores se forman según la siguiente plantilla: 'handle_' + 'indicador', por ejemplo 'handle_iStdDev'. Todo el trabajo con 'Indicators Code.mq5' se reduce a operaciones de 'copiar y pegar'. 

RECUERDE: el estilo MQL5 implica que el manejador del indicador se cree UNA VEZ y esto se hace (como norma general) en OnInit

3.1. Ejemplo de adición del indicador 'iRVI' (Relative Vigor Index, RVI)

Vamos a crear el asesor 'Add indicator.mq5'. En el editor MetaEditor, llamamos al 'MQL Wizard', por ejemplo, clicando en el botón  New, y seleccionamos 'Expert Advisor (template)'

Expert Advisor (template)

Fig. 3. 'MQL Wizard' -> 'Expert Advisor (plantilla)'

En el siguiente paso, recomiendo encarecidamente añadir al menos un parámetro de entrada 

Expert Advisor (template)

Fig. 4. 'Expert Advisor (plantilla)' -> 'Añadir parámetro'

Esta técnica añade automáticamente al código las líneas para el bloque de parámetros de entrada:

//--- input parameters
input int      Input1=9;

'MQL Wizard' ha creado un asesor vacío, ahora vamos a añadirle el indicador 'iRVI' (Relative Vigor Index, RVI). En 'Indicators Code.mq5', realizamos la búsqueda 'handle_iRVI' (la búsqueda se llama con 'ctrl' + 'F'). La búsqueda encuentra la variable en la que se almacena el manejador:

iRVI

Fig. 5. manejador RVI

Copiamos la línea encontrada y la pegamos en el asesor 'Add indicator', en el encabezado:

//--- input parameters
input int      Input1=9;
//---
int      handle_iRVI;                           // variable for storing the handle of the iRVI indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()

Continuamos la búsqueda y encontramos el bloque de creación del manejador:

iRVI

Fig. 6. manejador iRVI

Copiamos las líneas encontradas y las pegamos en el asesor 'Add indicator', en OnInit:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iRVI
   handle_iRVI=iRVI(m_symbol.Name(),Inp_RVI_period,Inp_RVI_ma_period);
//--- if the handle is not created
   if(handle_iRVI==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iRVI indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(Inp_RVI_period),
                  GetLastError());
      //--- the indicator is stopped early
      m_init_error=true;
      return(INIT_SUCCEEDED);
     }
//---
   return(INIT_SUCCEEDED);
  }

Ahora, añadimos los parámetros de entrada del indicador. En 'Indicators Code.mq5', clicamos en la ruleta del ratón, por ejemplo, en 'Inp_RVI_period', y saltaremos directamente al bloque de parámetros de entrada:

iRVI

Fig. 7. manejador iRVI

Copiamos las líneas y las pegamos en los parámetros de entrada:

#property version   "1.00"
//--- input parameters
input group             "RVI"
input ENUM_TIMEFRAMES      Inp_RVI_period                = PERIOD_D1;      // RVI: timeframe
input int                  Inp_RVI_ma_period             = 15;             // RVI: averaging period
//---
int      handle_iRVI;                           // variable for storing the handle of the iRVI indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

Si compilamos, obtendremos un error: el compilador se quejará de 'm_symbol' y de 'm_init_error'. Y esto es correcto, ya que estas variables se encuentran en el código que obtenemos después de que el constructor funcione, y el asesor 'Add indicator' ha sido creado exclusivamente para demostrar cómo trabaja con el archivo 'Indicators Code.mq5'.


4. Añadiendo un indicador personalizado

Añadimos el indicador personalizado MA on DeMarker. En primer lugar, se trata de un indicador personalizado; en segundo lugar, este indicador utiliza group. Por analogía con el apartado anterior, creamos el asesor 'Add custom indicator'. Después de ello, deberemos copiar los parámetros de entrada del indicador personalizado y pegarlos en el asesor:

#property version   "1.00"
//--- input parameters
input group             "DeMarker"
input int                  Inp_DeM_ma_period    = 14;             // DeM: averaging period
input double               Inp_DeM_LevelUP      = 0.7;            // DeM: Level UP
input double               Inp_DeM_LevelDOWN    = 0.3;            // DeM: Level DOWN
input group             "MA"
input int                  Inp_MA_ma_period     = 6;              // MA: averaging period
input ENUM_MA_METHOD       Inp_MA_ma_method     = MODE_EMA;       // MA: smoothing type
//---

Encontramos en el archivo 'Indicators Code.mq5' la variable 'handle_iCustom', la variable para almacenar el manejador del indicador personalizado y la pegamos en el asesor:

//+------------------------------------------------------------------+
//|                                         Add custom indicator.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "1.00"
//--- input parameters
input group             "DeMarker"
input int                  Inp_DeM_ma_period    = 14;             // DeM: averaging period
input double               Inp_DeM_LevelUP      = 0.7;            // DeM: Level UP
input double               Inp_DeM_LevelDOWN    = 0.3;            // DeM: Level DOWN
input group             "MA"
input int                  Inp_MA_ma_period     = 6;              // MA: averaging period
input ENUM_MA_METHOD       Inp_MA_ma_method     = MODE_EMA;       // MA: smoothing type
//---
int      handle_iCustom;                        // variable for storing the handle of the iCustom indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()

Encontramos en el archivo 'Indicators Code.mq5', en OnInit(), el bloque para crear el indicador personalizado y lo pegamos en el asesor:  

int      handle_iCustom;                        // variable for storing the handle of the iCustom indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iCustom
   handle_iCustom=iCustom(m_symbol.Name(),Inp_DEMA_period,"Examples\\DEMA",
                          Inp_DEMA_ma_period,
                          Inp_DEMA_ma_shift,
                          Inp_DEMA_applied_price);
//--- if the handle is not created
   if(handle_iCustom==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iCustom indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(Inp_DEMA_period),
                  GetLastError());
      //--- the indicator is stopped early
      m_init_error=true;
      return(INIT_SUCCEEDED);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |

Aquí tendremos que esforzarnos un poco. Deberemos escribir el marco temporal ('InpWorkingPeriod'), la ruta al indicador (suponemos que el indicador se encuentra en la carpeta raíz 'Indicators') y los parámetros de entrada:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iCustom
   handle_iCustom=iCustom(m_symbol.Name(),InpWorkingPeriod,"MA on DeMarker",
                          "DeMarker",
                          Inp_DeM_ma_period,
                          Inp_DeM_LevelUP,
                          Inp_DeM_LevelDOWN,
                          "MA",
                          Inp_MA_ma_period,
                          Inp_MA_ma_method);
//--- if the handle is not created
   if(handle_iCustom==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iCustom indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(InpWorkingPeriod),
                  GetLastError());
      //--- the indicator is stopped early
      m_init_error=true;
      return(INIT_SUCCEEDED);
     }
//---
   return(INIT_SUCCEEDED);
  }

5. Captando una transacción el código simplificado We catch the transaction

ATENCIÓN: este asesor es una versión simplificada. Muchas funciones tienen un código más corto que en los constructores completos

Si no hay posiciones abiertas por este asesor en el mercado, anotaremos una orden comercial para abrir una posición BUY. La confirmación de que la posición está abierta se imprime desde OnTradeTransaction y desde OnTick. Búsqueda y anotación de una orden comercial en la función 'SearchTradingSignals':

//+------------------------------------------------------------------+
//| Search trading signals                                           |
//+------------------------------------------------------------------+
bool SearchTradingSignals(void)
  {
   if(IsPositionExists())
      return(true);
//---
   int size_need_position=ArraySize(SPosition);
   if(size_need_position>0)
      return(true);
   ArrayResize(SPosition,size_need_position+1);
   SPosition[size_need_position].pos_type=POSITION_TYPE_BUY;
   if(InpPrintLog)
      Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY");
   return(true);
  }

Si no hay posiciones abiertas por el asesor en el mercado y si el tamaño de la matriz 'SPosition' es igual a cero, aumentamos el tamaño de la matriz en uno, creando así un objeto de la estructura 'STRUCT_POSITION', que a su vez llamará al constructor 'STRUCT_POSITION()'. Después de llamar al constructor, los elementos de la estructura se inicializan (por ejemplo, volume  el volumen de la posición está en '0.0' significa que el volumen de la posición se tomará del grupo de parámetros de entrada 'Position size management (lot calculation)'). Queda por escribir en la estructura solo el tipo de orden comercialen este caso, la orden puede leerse como: "Abrir una posición Buy".

Después de escribir la orden comercial, la matriz 'SPosition' constará de una estructura, y los elementos de esta estructura tendrán los siguientes valores:

Elemento Valor Observación 
 pos_type  POSITION_TYPE_BUY  escrito en 'SearchTradingSignals'
 volume  0.0  inicializado en el constructor de la estructura
 lot_coefficient  0.0  inicializado en el constructor de la estructura
 waiting_transaction  false  inicializado en el constructor de la estructura
 waiting_order_ticket  0  inicializado en el constructor de la estructura
 transaction_confirmed  false  inicializado en el constructor de la estructura

5.1. En el nuevo tick, entramos en OnTick

Principio general de acción en OnTick:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int size_need_position=ArraySize(SPosition);
   if(size_need_position>0)
     {
      for(int i=size_need_position-1; i>=0; i--)
        {
         if(SPosition[i].waiting_transaction)
           {
            if(!SPosition[i].transaction_confirmed)
              {
               if(InpPrintLog)
                  Print(__FILE__," ",__FUNCTION__,", OK: ","transaction_confirmed: ",SPosition[i].transaction_confirmed);
               return;
              }
            else
               if(SPosition[i].transaction_confirmed)
                 {
                  ArrayRemove(SPosition,i,1);
                  return;
                 }
           }
         if(SPosition[i].pos_type==POSITION_TYPE_BUY)
           {
            SPosition[i].waiting_transaction=true;
            OpenPosition(i);
            return;
           }
         if(SPosition[i].pos_type==POSITION_TYPE_SELL)
           {
            SPosition[i].waiting_transaction=true;
            OpenPosition(i);
            return;
           }
        }
     }
//--- search for trading signals only at the time of the birth of new bar
   if(!RefreshRates())
      return;
//--- search for trading signals
   if(!SearchTradingSignals())
      return;
//---
  }

Al principio de OnTick, comprobamos el tamaño de la matriz 'SPosition' (es la matriz de estructuras 'STRUCT_POSITION'). Si el tamaño de la matriz es superior a cerocomenzamos el recorrido hacia cero, con dos escenarios disponibles: 

Continuamos monitoreando, recordemos el bloque de código:

         if(SPosition[i].pos_type==POSITION_TYPE_BUY)
           {
            SPosition[i].waiting_transaction=true;
            OpenPosition(i);
            return;
           }

La orden comercial acaba de anotarse en la estructura (en la función 'SearchTradingSignals') y la bandera 'waiting_transaction' está en 'false', esto significa que ponemos la bandera 'waiting_transaction' en 'true' y transmitimos a la función 'OpenPosition' el parámetro 'i'es decir, el número ordinal de la estructura en la matriz. 'OpenPosition'. Como nuestro tipo de orden comercial es 'POSITION_TYPE_BUY', transmitimos el número ordinal de la estructura a la función 'OpenBuy'.

5.2. Función 'OpenBuy'

La tarea de la función es superar las comprobaciones preliminares, enviar una solicitud comercial de apertura de una posición BUY y monitorear su resultado.

La primera comprobación es la verificación de SYMBOL_VOLUME_LIMIT

 SYMBOL_VOLUME_LIMIT  El máximo permitido para un símbolo dado en cuanto al volumen total de la posición abiertas y las órdenes pendientes en una dirección (compra o venta). Por ejemplo, si el límite es de 5 lotes, podemos tener una posición de compra abierta con un volumen de 5 lotes y colocar una orden pendiente Sell Limit con un volumen de 5 lotes. Pero, al mismo tiempo, no podemos colocar una orden pendiente Buy Limit (ya que el volumen total en una dirección excederá el límite) o colocar una orden Sell Limit con un volumen de más de 5 lotes.

Si la verificación falla, eliminamos el elemento de estructura de la matriz 'SPosition'.

La segunda comprobación es el margen

Obtenemos el tamaño del margen libre que quedará depués de la operación comercial (FreeMarginCheck), y el tamaño del margen necesario para una operación comercial (MarginCheck). Después de esto, nos protegemos. Siempre nos cubrimos y dejamos después de la operación una suma como mínimo igual a FreeMarginCheck:

   if(free_margin_check>margin_check)

Si no hemos superado la comprobación, entonces imprimimos la variable 'free_margin_check' y eliminamos el elemento de la estructura desde la matriz 'SPosition'.

La tercera comprobación es el resultado bool de la operación Buy

Si el método Buy ha retornado el resultado 'false', entonces imprimimos el error y anotamos en el campo 'waiting_transaction' el valor 'false' (la causa más extendida es el error 10004, requote), de esta forma, en el nuevo tick se realizará un intento de abrir nuevamente una posición BUY. Si el resultado es 'true' (más abajo, mostramos un bloque de código en el que el método Buy ha retornado el resultado 'true')

         if(m_trade.ResultDeal()==0)
           {
            if(m_trade.ResultRetcode()==10009) // trade order went to the exchange
              {
               SPosition[index].waiting_transaction=true;
               SPosition[index].waiting_order_ticket=m_trade.ResultOrder();
              }
            else
              {
               SPosition[index].waiting_transaction=false;
               if(InpPrintLog)
                  Print(__FILE__," ",__FUNCTION__,", ERROR: ","#1 Buy -> false. Result Retcode: ",m_trade.ResultRetcode(),
                        ", description of result: ",m_trade.ResultRetcodeDescription());
              }
            if(InpPrintLog)
               PrintResultTrade(m_trade,m_symbol);
           }
         else
           {
            if(m_trade.ResultRetcode()==10009)
              {
               SPosition[index].waiting_transaction=true;
               SPosition[index].waiting_order_ticket=m_trade.ResultOrder();
              }
            else
              {
               SPosition[index].waiting_transaction=false;
               if(InpPrintLog)
                  Print(__FILE__," ",__FUNCTION__,", OK: ","#2 Buy -> true. Result Retcode: ",m_trade.ResultRetcode(),
                        ", description of result: ",m_trade.ResultRetcodeDescription());
              }
            if(InpPrintLog)
               PrintResultTrade(m_trade,m_symbol);
           }

entonces comprobamos ResultDeal (ticket de la transacción).

Si el ticket de la transacción es igual a cero entonces comprobamos ResultRetcode (código del resultado de ejecución de la solicitud). Hemos obtenido el código de retorno '10009' (por ejemplo, la orden comercial se ha enviado a un sistema comercial externo, por ejemplo, a la Bolsa, y por eso el ticket de la transacción es igual a cero) entonces, esto significa que en el campo 'waiting_transaction' debemos anotar 'true', mientras que en el campo 'waiting_order_ticket' anotamos ResultOrder (ticket de la orden); de lo contrario, (código de retorno distinto a '10009') deberemos anotar en el campo 'waiting_transaction' 'false' e imprimir un mensaje sobre el error.

Si el ticket de la transacción no es igual a cero (por ejemplo, la ejecución se lleva a cabo en este servidor comercial), deberemos realizar verificaciones similares para el código de retorno y escribir de forma similar los valores en los campos 'waiting_transaction'waiting_order_ticket'.

5.3. OnTradeTransaction

Si la orden comercial se ha enviado con éxito, deberemos esperar la confirmación de que la transacción se ha completado y ha sido registrada en la historia comercial. En OnTradeTransaction, trabajamos con la variable 'trans' (estructura del tipo MqlTradeTransaction). En la estructura, nos interesan solo dos campos: 'deal' y 'type':

struct MqlTradeTransaction
  {
   ulong                         deal;             // Deal ticket
   ulong                         order;            // Order ticket
   string                        symbol;           // Symbol name
   ENUM_TRADE_TRANSACTION_TYPE   type;             // Trading transaction type
   ENUM_ORDER_TYPE               order_type;       // Order type
   ENUM_ORDER_STATE              order_state;      // Order state
   ENUM_DEAL_TYPE                deal_type;        // Deal type
   ENUM_ORDER_TYPE_TIME          time_type;        // Order type by lifetime
   datetime                      time_expiration;  // Order expiration time
   double                        price;            // Price 
   double                        price_trigger;    // Stop Limit order trigger price
   double                        price_sl;         // Stop Loss level
   double                        price_tp;         // Take Profit level
   double                        volume;           // Volume in lots
   ulong                         position;         // Position ticket
   ulong                         position_by;      // Opposite position ticket
  };


En OnTradeTransactionen cuanto captamos la transacción TRADE_TRANSACTION_DEAL_ADD (adición de una transacción a la historia), realizamos la siguiente comprobación: intentamos seleccionar una transacción en la historia a través de HistoryDealSelect y si no lo logramos, imprimimos el error; si la transacción existe en la historia comercial, iniciamos un ciclo hacia la matriz 'SPosition'. En un ciclo, nos fijamos solo en las estructuras cuyo campo 'waiting_transaction' esté en 'true' y el campo 'waiting_order_ticket' sea igual al ticket de la orden de la transacción que hemos seleccionado. Si detectamos una coincidencia, anotaremos en el campo 'transaction_confirmed' el valor 'true', esto significará que la orden comercial se ha ejecutado y confirmado.

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
//--- get transaction type as enumeration value
   ENUM_TRADE_TRANSACTION_TYPE type=trans.type;
//--- if transaction is result of addition of the transaction in history
   if(type==TRADE_TRANSACTION_DEAL_ADD)
     {
      ResetLastError();
      if(HistoryDealSelect(trans.deal))
         m_deal.Ticket(trans.deal);
      else
        {
         Print(__FILE__," ",__FUNCTION__,", ERROR: ","HistoryDealSelect(",trans.deal,") error: ",GetLastError());
         return;
        }
      if(m_deal.Symbol()==m_symbol.Name() && m_deal.Magic()==InpMagic)
        {
         if(m_deal.DealType()==DEAL_TYPE_BUY || m_deal.DealType()==DEAL_TYPE_SELL)
           {
            int size_need_position=ArraySize(SPosition);
            if(size_need_position>0)
              {
               for(int i=0; i<size_need_position; i++)
                 {
                  if(SPosition[i].waiting_transaction)
                     if(SPosition[i].waiting_order_ticket==m_deal.Order())
                       {
                        Print(__FUNCTION__," Transaction confirmed");
                        SPosition[i].transaction_confirmed=true;
                        break;
                       }
                 }
              }
           }
        }
     }
  }

En el nuevo tick, entramos en OnTick. Allí, la estructura que muestra 'true' en el campo 'transaction_confirmed' será eliminada de la matriz 'SPosition'. Por consiguiente, se ha emitido una orden comercial, realizando a continuación un seguimiento de dicha orden hasta que aparezca en la historia comercial.


6. Creando un asesor (señales de apertura de posiciones) con ayuda del constructor

Antes de crear un asesor, deberemos pensar en la estrategia comercial en sí. Vamos a considerar una estrategia simple basada en el indicador DEMA (Double Exponential Moving Average, DEMA). Esta estrategia estará escrita en el constructor por defecto. Buscaremos la señal de apertura de una posición solo en el momento en que surja una nueva barra, mientras que la propia señal comercial será un indicador que aumentará o disminuirá paulatinamente:

DEMA Strategy

Fig. 8. Estrategia DEMA

Recuerde: cualquier estrategia se puede modificar en gran medida ajustando los parámetros. Por ejemplo, podemos dejar el Take Profit y el Stop Loss, pero desactivar el Trailing. O viceversa: desactivar el Take Profit y el Stop Loss, y dejar el Trailing. O bien restringir la dirección en que comerciaremos: permitir solo BUY o solo SELL. Como alternativa, podemos habilitar 'Time control' y restringir el comercio por la noche; o al contrario, configurar el comercio solo por la noche. También podemos cambiar en gran medida el sistema comercial configurando los parámetros del grupo 'Additional features'.

En general, la columna vertebral de una estrategia comercial se construye en la función 'SearchTradingSignals', y todos los demás parámetros se encargan de "sondear" el mercado en busca de enfoques óptimos.

Bien, vamos a crear un nuevo archivo, a saber, una plantilla para un asesor (deberemos seguir los pasos indicados en las figuras 3 y 4). En el paso 4, tendremos que dar un nombre único al asesor, digamos 'iDEMA Full EA.mq5'. Así, obtenemos el siguiente espacio en blanco:

//+------------------------------------------------------------------+
//|                                                iDEMA Full EA.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "1.00"
//--- input parameters
input int      Input1=9;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Ahora, copiamos el código completo del archivo 'Trading engine 3.mq5' y lo pegamos en lugar de las líneas. Debemos editar el encabezado del asesor. Tras pegar el código, obtenemos el siguiente encabezado:

//+------------------------------------------------------------------+
//|                                                iDEMA Full EA.mq5 |
//+------------------------------------------------------------------+
//|                                             Trading engine 3.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "4.003"
#property description "barabashkakvn Trading engine 4.003"
#property description "Take Profit, Stop Loss and Trailing - in Points (1.00055-1.00045=10 points)"
/*
   barabashkakvn Trading engine 4.003
*/
#include <Trade\PositionInfo.mqh>

Le damos al encabezado el siguiente aspecto:

//+------------------------------------------------------------------+
//|                                                iDEMA Full EA.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "1.001"
#property description "iDEMA EA"
#property description "Take Profit, Stop Loss and Trailing - in Points (1.00055-1.00045=10 points)"
/*
   barabashkakvn Trading engine 4.003
*/
#include <Trade\PositionInfo.mqh>

Si realizamos la compilación, no obtendremos un solo error. El asesor obtenido podrá incluso comerciar.

6.1. Función 'SearchTradingSignals'

Esta es la función más importante encargada de verificar la disponibilidad de las órdenes comerciales. Vamos a analizar esta función bloque por bloque.

No más de una posición por barra:

   if(iTime(m_symbol.Name(),InpWorkingPeriod,0)==m_last_deal_in) // on one bar - only one deal
      return(true);

Comprobación del intervalo temporal comercial:

   if(!TimeControlHourMinute())
      return(true);

Recibiendo datos del indicador. Obtenemos los datos del indicador en la matriz 'dema', a la que se le asigna el orden de indexación inverso usando ArraySetAsSeries (el elemento de la matriz [0] se corresponderá con la barra más a la derecha en el gráfico). Los datos los obtendremos con la función personalizada 'iGetArray':

   double dema[];
   ArraySetAsSeries(dema,true);
   int start_pos=0,count=6;
   if(!iGetArray(handle_iCustom,0,start_pos,count,dema))
     {
      return(false);
     }
   int size_need_position=ArraySize(SPosition);
   if(size_need_position>0)
      return(true);

Señal de apertura de una posición BUY. Si es necesario (la variable 'InpReverse' almacena el valor del parámetro de entrada 'Positions: Reverse'), la señal comercial se invertirá. Si hay una restricción en la dirección de comercio (la variable 'InpTradeMode' almacena el valor del parámetro de entrada 'Trade mode:'), se tendrá en cuenta esta limitación:

//--- BUY Signal
   if(dema[m_bar_current]>dema[m_bar_current+1] && dema[m_bar_current+1]>dema[m_bar_current+3])
     {
      if(!InpReverse)
        {
         if(InpTradeMode!=sell)
           {
            ArrayResize(SPosition,size_need_position+1);
            SPosition[size_need_position].pos_type=POSITION_TYPE_BUY;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY");
            return(true);
           }
        }
      else
        {
         if(InpTradeMode!=buy)
           {
            ArrayResize(SPosition,size_need_position+1);
            SPosition[size_need_position].pos_type=POSITION_TYPE_SELL;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL");
            return(true);
           }
        }
     }

El bloque de código de la señal SELL es similar. 


7. Creando un asesor (señales de colocación de una orden pendiente) con ayuda del constructor

El nombre del asesor será 'iDEMA Full EA Pending.mq5', para ello, abra el asesor 'iDEMA Full EA.mq5' y guárdelo con el nuevo nombre.

En primer lugar, siempre se desarrolla una estrategia comercial y solo entonces, para esta estrategia, se implementa el código. Vamos a modificar ligeramente la estrategia que usamos en el capítulo 6. Para ello, crearemos un asesor (señales de apertura de posiciones) usando el constructor; en lugar de una señal para abrir una posición BUY, tendremos una señal para colocar una orden pendiente Buy stop, y en lugar de una señal para abrir una posición SELL, tendremos una señal para colocar una orden pendiente Sell stop. Para las órdenes pendientes, se usarán los siguientes parámetros:

La función 'SearchTradingSignals' adoptará el aspecto siguiente:

//+------------------------------------------------------------------+
//| Search trading signals                                           |
//+------------------------------------------------------------------+
bool SearchTradingSignals(void)
  {
   if(iTime(m_symbol.Name(),InpWorkingPeriod,0)==m_last_deal_in) // on one bar - only one deal
      return(true);
   if(!TimeControlHourMinute())
      return(true);
   double dema[];
   ArraySetAsSeries(dema,true);
   int start_pos=0,count=6;
   if(!iGetArray(handle_iCustom,0,start_pos,count,dema))
     {
      return(false);
     }
   int size_need_pending=ArraySize(SPending);
   if(size_need_pending>0)
      return(true);
//---
   if(InpPendingOnlyOne)
      if(IsPendingOrdersExists())
         return(true);
   if(InpPendingClosePrevious)
      m_need_delete_all=true;
//--- BUY Signal
   if(dema[m_bar_current]>dema[m_bar_current+1] && dema[m_bar_current+1]>dema[m_bar_current+3])
     {
      if(!InpReverse)
        {
         if(InpTradeMode!=sell)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");
            return(true);
           }
        }
      else
        {
         if(InpTradeMode!=buy)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL STOP");
            return(true);
           }
        }
     }
//--- SELL Signal
   if(dema[m_bar_current]<dema[m_bar_current+1] && dema[m_bar_current+1]<dema[m_bar_current+3])
     {
      if(!InpReverse)
        {
         if(InpTradeMode!=buy)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL STOP");
            return(true);
           }
        }
      else
        {
         if(InpTradeMode!=sell)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");
            return(true);
           }
        }
     }
//---
   /*if(InpPendingOnlyOne)
      if(IsPendingOrdersExists())
         return(true);
   if(InpPendingClosePrevious)
      m_need_delete_all=true;
   int size_need_pending=ArraySize(SPending);
   ArrayResize(SPending,size_need_pending+1);
   if(!InpPendingReverse)
      SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP;
   else
      SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP;
   SPending[size_need_pending].indent=m_pending_indent;
   if(InpPendingExpiration>0)
      SPending[size_need_pending].expiration=(long)(InpPendingExpiration*60);
   if(InpPrintLog)
      Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");*/
//---
   return(true);
  }

Tenga en cuenta que no escribimos el precio de una orden pendiente en la estructura SPending, lo cual significa que se usará el precio actual más la distancia de retraso.

Bien, hemos obtenido una señal comercial, pero se ha activado (se ha colocado una orden pendiente) solo cuando el spread se ha vuelto menor al especificado:

iDEMA Full EA Pending

Fig. 9. iDEMA Full EA Pending


Archivos adjuntos al artículo:

Nombre Tipo de archivo Descripción
Código del indicador Asesor Contiene las variables para almacenar los manejadores, los parámetros de entrada de los indicadores y los bloques para crear los mismos
Add indicator.mq5 Asesor Ejemplo de trabajo con el archivo 'Add indicator.mq5' - añadimos un indicador estándar
Add custom indicator.mq5 Asesor
Ejemplo de adición de un indicador personalizado
Trading engine 4.mq5 Asesor Constructor
iDEMA Full EA.mq5
Asesor
Asesor creado con la ayuda del constructor - señales de apertura de posiciones
iDEMA Full EA Pending.mq5
Asesor
Asesor creado con el constructor - señales para colocar órdenes pendientes

Conclusión

Espero que este conjunto de funciones comerciales lo ayude a crear asesores expertos más fiables, preparados para las cambiantes condiciones comerciales del mercado. Y nunca dude en experimentar con los parámetros, ya que activando y desactivando algunos de ellos puede cambiar tremendamente la estrategia.