Trading bidireccional y cobertura (hedging) de posiciones en MetaTrader 5 usando API HedgeTerminal, Parte 2

Vasiliy Sokolov | 17 julio, 2015

Índice


Introducción

Este artículo es la continuación del artículo “Trading bidireccional y cobertura (hedging) de posiciones en MetaTrader 5 usando el panel HedgeTerminal, Parte 1”. En esta segunda parte vamos a considerar las cuestiones de integración de sus Asesores Expertos (EAs), así como otros programas escritos en MQL5, con la biblioteca HedgeTerminalAPI. Este articulo artículo describe cómo se trabaja con esta biblioteca. Con su ayuda podrá crear los EAs bidireccionales y trabajar en un entorno cómodo y sencillo.

Aparte de la descripción de las funciones de la biblioteca, el artículo explica los principios de las operaciones comerciales asincrónicas y de la programación multihilo. Estas descripciones se encuentran en el capítulo tres y cuarto, respectivamente. Por eso este material va a ser interesante a los traders que no se interesan por el trading bidireccional pero tienen ganas de obtener nuevos conocimientos sobre la programación asincrónica y multihilo.

El material que va a continuación está destinado para los algotraders preparados que conocen el lenguaje de programación MQL5. Si todavía no conoce MQL5, será mejor leer la primera parte del artículo que describe el principio general del trabajo de la biblioteca y del panel HedgeTerminal a través de los esquemas e imágenes sencillos.


Capítulo 1. Interacción de los Asesores Expertos con API HedgeTerminal y su panel

1.1. Instalación de HedgeTerminalAPI. El primer inicio de la biblioteca

La instalación de HedgeTerminalAPI se diferencia de la instalación del panel visual HT en que la primera no puede ser iniciada directamente. Es que la biblioteca no puede iniciarse en MetaTrader 5 por su propia iniciativa. En vez de eso necesitamos escribir un Experto especial que va a llamar la función HedgeTerminalInstall() desde esta biblioteca. Esta función instalará el archivo de cabecera especial Prototypes.mqh que describe las funciones que están disponibles en HedgeTerminalAPI .

Tenemos que hacer tres pasos para instalar la biblioteca en el ordenador:

Paso 1. Descargue la biblioteca HedgeTerminalAPI en su ordenador. La ubicación de la biblioteca respecto a su terminal va a ser la siguiente: \MQL5\Experts\Market\HedgeTerminalApi.ex5.

Paso 2. Crear el nuevo EA en el asistente para MQL usando la plantilla estándar. El asistente para MQL va a generar el siguiente código:

//+------------------------------------------------------------------+
//|                                   InstallHedgeTerminalExpert.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  }

Paso 3. En este EA va a necesitar sólo una función OnInit(), así como la directiva de exportación que describe la función instaladora especial HedgeTerminalInstall() exportada por la biblioteca HedgeTerminalApi. Tiene que iniciar esta función directamente en la función de sistema OnInit(). El código fuente que ejecuta estas operaciones está marcado con el color amarillo.

//+------------------------------------------------------------------+
//|                                   InstallHedgeTerminalExpert.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#import "HedgeTerminalAPI.ex5"
   void HedgeTerminalInstall(void);
#import

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   HedgeTerminalInstall();
   ExpertRemove();   
//---
   return(INIT_SUCCEEDED);
  }

Paso 4. Sus siguientes acciones van a depender si ha comprado esta biblioteca o no. Si la ha comprado, podrá iniciar este EA en tiempo real directamente en el gráfico. En este caso se iniciará el instalador estándar para la gama completa de productos de HedgeTerminal. Podrá pasarlo fácilmente siguiendo las instrucciones descritas en los apartados 2.1 y 2.2 del artículo “Trading bidireccional y cobertura (hedging) de posiciones en MetaTrader 5 usando el panel HedgeTerminal, Parte 1”. El asistente instalará todos los archivos necesarios en su ordenador, incluyendo el archivo de cabecera y el archivo con el EA de prueba.

Si no ha comprado la biblioteca y sólo desea probarla, no podrá iniciarla en tiempo real pero podrá probar API al iniciar el Experto en el Probador de Estrategias. En este caso, el instalador no va a iniciarse. En el modo de prueba, HedgeTermianalAPI trabaja en el modo monousuario (single user mode) por eso no va a necesitar los archivos que se instalan en modo habitual. Con lo cual, en este caso ya no hay que configurar nada más.

Una vez terminada la prueba o el trabajo del EA, en la carpeta común del terminal aparecerá la carpeta \HedgeTerminal. La ruta normal hacia la carpeta común de los terminales MetaTrader es c:\Users\<Nombre de usuario>\AppData\Roaming\MetaQuotes\Terminal\Common\Files\HedgeTerminal\, donde <Nombre de usuario> es el nombre de su cuenta actual en el ordenador. Para este momento la carpeta \HedgeTerminal ya tiene que contener los archivos \MQL5\Include\Prototypes.mqh и \MQL5\Experts\Chaos2.mq5. Copie estos archivos en las carpetas similares de su terminal: el archivo Prototypes.mqh en  \MetaTrader5\MQL5\Include, y el archivo Chaos2.mq5 en \MetaTrader5\MQL5\Experts.

Prototypes.mqh es el archivo de cabecera que incluye la descripción de las funciones exportadas desde la biblioteca HedgeTerminalAPI. Podrá ver su propósito y descripción en los comentarios para ellas.

El archivo Chaos2.mq5 contiene el ejemplo del Asesor Experto que se describe en la sección “Ejemplo de trabajo con la función SendTradeRequest() y la estructura HedgeTradeRequest() tomando de ejemplo el EA "Chaos II" Le ayudará a comprender visualmente cómo funciona HedgeTerminalAPI y qué es lo que necesita hacer para escribir un EA que utiliza las tecnologías de virtualización de HedgeTerminal.

Los archivos copiados ya están disponibles para sus EAs. Y sólo tiene que insertar el archivo de cabecera en el código fuente del EA para empezar a usar la biblioteca. Aquí tiene el ejemplo:

#include <Prototypes.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int transTotal = TransactionsTotal();
   printf((string)transTotal);
  }

Por ejemplo, el código de arriba obtiene el número total de posiciones activas y muestra este número en la pestaña “Asesores Expertos” del terminal MetaTrader 5.

Es importante comprender que HedgeTerminal se inicializa realmente en el momento de la primera llamada a una de sus funciones. Esta inicialización suele llamarse “Carga perezosa” (Lazy loading). Por eso la primera llamada a una de sus funciones puede tardar bastante. Si necesita una reacción rápida durante el primer arranque, hay que realizar la inicialización de HT de antemano, por ejemplo puede llamar a la función TransactionTotal() en el bloque OnInit().

La carga perezosa permite omitir la inicialización explícita desde el EA. Eso facilita bastante el trabajo con HedgeTerminal y hace innecesaria su configuración previa.


1.2. Integración de los EAs con el panel HedgeTerminal

Si tiene el panel visual de HedgeTerminal y la versión completa de la biblioteca que puede ser iniciada en tiempo real, Usted puede integrar sus EAs con este panel de tal manera que todas las acciones comerciales realizadas por ellos se muestren también en el panel. En general, la integración es invisible. Si Usted utiliza las funciones de HedgeTermianalAPI, las acciones realizadas por los robots se visualizan automáticamente en el panel. Sin embargo, Usted puede ampliar la visualización indicando el nombre del EA en cada transacción realizada. Para eso quite el comentario de la etiqueta en el archivo Settings.xml:

<Column ID="Magic" Name="Magic" Width="100"/>

Esta etiqueta se encuentra en las secciones <Active-Position><History-Position>.

Ahora los comentarios están quitados y las etiquetas están incluidas en el procesamiento. Al reiniciar el panel, la nueva columna “Magic” aparecerá en las tablas de posiciones activas e históricas. Va a mostrar el número mágico del EA al que pertenece la posición.

Para mostrar el nombre del EA más habitual para una persona, introduzca el nombre correspondiente en el archivo de los alias ExpertAliases.xml. Digamos, si quiere que en vez del número mágico 123847 aparezca su nombre, por ejemplo "ExPro 1.1", introduzca en el archivo la siguiente etiqueta:

<Expert Magic="123847" Name="ExPro 1.1"></Expert>

Si ha hecho todo correctamente, después de reiniciar el panel, en la columna correspondiente aparecerá el nombre del EA en vez de su magic:

Fig. 1. Visualización de los nombres en vez de sus números mágicos

Fig. 1. Visualización de los nombres en vez de sus números mágicos

Cabe mencionar que el panel y los EAs intercambian los datos entre ellos en tiempo real. Eso significa que si Usted cierra alguna posición del EA directamente desde el panel, el EA se enterará de eso cuando llame la próxima vez a la función TransactionsTotal(). Lo contrario también es cierto: después de que el EA cierre su posición, ésta desaparecerá inmediatamente del panel.


1.3. Principios generales del trabajo con HedgeTerminalAPI

Aparte de las posiciones bidireccionales, HedgeTerminal trabaja también con otros tipos comerciales: por ejemplo, órdenes pendientes, operaciones, operaciones bursátiles en la cuenta. Desde el punto de vista de HedgeTerminal todos estos tipos se reúnen en un solo grupo de transacciones. Una operación, orden pendiente, posición bidireccional- todo eso son transacciones. Sin embargo, la transacción no puede existir por sí sola. En los términos de la programación orientada a objetos, la transacción puede presentarse como una clase base abstracta de la cual se heredan todas las entidades posibles, como operaciones y posiciones bidireccionales. Por eso todas las funciones de HedgeTerminalAPI se puede dividir en varios grupos:

  1. Funciones de búsqueda y selección de transacciones. La signatura común de las funciones y modos de trabajo con ellas coinciden casi completamente con las funciones OrderSend() y OrderSelect() en MetaTrader 4;
  2. Funciones para obtener propiedades de la transacción seleccionada. Cada transacción tiene un conjunto específico de propiedades y sus funciones específicas para seleccionar estas propiedades. La signatura común de las funciones y modos de trabajo con ellas parecen a las funciones de sistema de MetaTrader 5 que sirven para acceder a las propiedades de las posiciones, operaciones y órdenes (como por ejemplo OrderGetDouble() o HistoryDealGetInteger());
  3. En HedgeTerminalAPI se utiliza sólo una función comercial: SendTradeRequest(). Esta función sirve para cerrar una posición bidireccional o una parte de ella. La misma función se utiliza para modificar los niveles Stop Loss, Take Profit o el comentario de salida. El modo de trabajo con ella parece a la función OrderSend() en MetaTrader 5;
  4. La función para obtener errores comunes GetHedgeError(), funciones para el análisis detallado de las acciones comerciales de HedgeTerminal: TotalActionsTask() y GetActionResult(). También sirven para detectar errores. En MetaTrader 4 y MetaTrader 5 no hay funciones análogas a éstas.

El trabajo con casi todas las funciones parece al trabajo con las funciones de sistema de MetaTrader 4 y MetaTrader 5. Por regla general, la función de entrada adquiere algún identificador (valor de enumeración) y devuelve el valor que le corresponde.

Para cada función existen sus enumeraciones. La signatura común de su llamada es la siguiente:

<valor> = Función(<identificador>);

Vamos a ver el ejemplo de obtención del identificador único para la posición. En MetaTrader 5, la obtención de la propiedad correspondiente tendría este aspecto:

ulong id = PositionGetInteger(POSITION_IDENTIFIER);

En HedgeTerminal, la recepción del mismo identificador para una posición bidireccional sería así:

ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID)

Como podemos ver, los principios generales del trabajo con las funciones coinciiden. Se diferencian sólo los tipos de enumeraciones, lo que es natural.


1.4. Selección de transacciones

La selección de transacciones consiste en el repaso de la lista de transacciones. Parece al repaso de las órdenes en MetaTrader 4. No obstante, si en MetaTrader 4 se buscan sólo las órdenes, en HedgeTerminal como una transacción puede encontrarse cualquier cosa, por ejemplo, una orden pendiente o una posición de cobertura (hedging). Por eso, primero hay que seleccionar cada transacción usando la función TransactionSelect(), y luego averiguar su tipo usando la función TransactionType().

Actualmente, sólo hay dos listas de transacciones: lista de transacciones activas y lista transacciones históricas. Usando el modificador ENUM_MODE_TRADES se determina con qué tipo de las listas se trabaja. Es similar al modificador MODE_TRADES en MetaTrader 4.

El algoritmo común de la búsqueda y selección de transacciones es el siguiente:

1: for(int i=TransactionsTotal(MODE_TRADES)-1; i>=0; i--)
2:     {
3:      if(!TransactionSelect(i,SELECT_BY_POS,MODE_TRADES))continue;
4:      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;
5:      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;
6:      if(HedgePositionGetString(HEDGE_POSITION_SYMBOL) != Symbol())continue;
7:      if(HedgePositionGetInteger(HEDGE_POSITION_STATE) == POSITION_STATE_FROZEN)continue;
8:      ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID)
9:     }

El código repasa la lista de transacciones activas en el ciclo for (línea 1). Antes de seguir trabajando con la transacción, hay que seleccionarla usando la función TransactionSelect() (línea 3). De estas transacciones se seleccionan sólo las posiciones bidireccionales (línea 4). Si el número mágico de la posición y su símbolo no corresponde al número mágico del EA actual y al símbolo en el que está iniciado, se pasa a la siguiente posición (líneas 5 y 6). Luego se averigua el identificador único de la posición (línea 8).

Hay que prestar especial atención a la línea 7. Hay que comprobar la posición seleccionada respecto a la posibilidad de su modificación. Si la posición ya se encuentra en el proceso de modificación, no se puede cambiarla en este flujo, aunque se puede obtener una de sus propiedades. Si la posición está bloqueada, es mejor esperar su desbloqueo y conseguir el acceso a sus propiedades o intentar cambiarla de nuevo. Para averiguar la posibilidad de la modificación, se utiliza su propiedad HEDGE_POSITION_STATE.

El modificador POSITION_STATE_FROZEN indica en que la posición está “congelada” para el trabajo y no está disponible para las modificaciones. El modificador POSITION_STATE_ACTIVE indica en que la posición está activa y se puede cambiarla. Estos modificadores se encuentran en la enumeración ENUM_HEDGE_POSITION_STATE que se describe en la sección correspondiente de la documentación.

Si necesitáramos hacer el repaso en la lista de transacciones históricas, tendríamos que sustituir el modificador MODE_TRADES por MODE_HISTORY en las funciones TransactionTotal() y TransactionSelect().

En HedgeTerminal, una transacción puede estar anidada dentro de otra. Eso se diferencia muchísimo del concepto de MetaTrader 5 donde este anidamiento no existe. Por ejemplo, en HedgeTerminal una posición bidireccional histórica se compone de dos órdenes cada una de las cuales incluye un conjunto aleatorio de operaciones. En el esquema este anidamiento podría verse así:

Fig. 2. Anidamiento de transacciones

Fig. 2. Anidamiento de transacciones

El anidamiento de transacciones se ve muy bien en el panel visual de HedgeTerminal.

En la captura de pantalla de abajo se muestra la posición detallada que pertenece al EA MagicEx 1.3 donde eso se ve perfectamente:

Fig. 3. Anidamiento de transacciones en el panel de HedgeTerminal

Fig. 3. Anidamiento de transacciones en el panel de HedgeTerminal

Usted puede acceder a las propiedades de una determinada orden o incluso una transacción que se encuentra dentro de una posición bidireccional.

Para eso necesita hacer lo siguiente:

  1. Seleccionar una transacción histórica y asegurarse de que se trata de una posición bidireccional;
  2. Seleccionar una de las órdenes de esta posición usando la función HedgeOrderSelect();
  3. Obtener una de las propiedades de la orden seleccionada: número de operaciones que contiene;
  4. Seleccionar una de las operaciones que pertenece a la orden haciendo el repaso de todas las operaciones;
  5. Obtener la propiedad necesaria de esta operación.

Preste atención que después de que la transacción haya sido seleccionada, sus determinada propiedades se hacen disponibles para ella. Pues, si la transacción es una orden, después de su selección mediante la función HedgeOrderSelect(), Usted puede conocer el número de sus operaciones (HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL)) o el precio medio ponderado de la entrada (HedgeDealGetDouble(HEDGE_DEAL_PRICE_EXECUTED)).

Por ejemplo, vamos a averiguar el precio de la operación #1197610 marcada con el rojo en la captura de arriba. Esta operación pertenece a la posición bidireccional del EA MagicEx 1.3.

Para que este EA pueda acceder a su posición y a esta operación, hay que escribir el siguiente código:

#include <Prototypes.mqh>

ulong Magic=5760655; // MagicEx 1.3.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+ 
void OnTick()
  {
   for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
    {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;        // Select transaction #i;
      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;                 // If transaction is not position - continue;
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;  // If position is not main - continue;
      ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID);   // Get id for closed order;
      if(id!=5917888)continue;                                             // If id of position != 5917888 - continue;
      printf("1: -> Select position #"+(string)id);                        // Print position id;
      if(!HedgeOrderSelect(ORDER_SELECTED_CLOSED))continue;                // Select closed order or continue;    
      ulong order_id = HedgeOrderGetInteger(HEDGE_ORDER_ID);               // Get id closed order;
      printf("2: ----> Select order #" + (string)order_id);                // Print id closed order;
      int deals_total = (int)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL);// Get deals total in selected order;
      for(int deal_index = deals_total-1; deal_index >= 0; deal_index--)   // Search deal #1197610...
        {
         if(!HedgeDealSelect(deal_index))continue;                         // Select deal by index or continue;
         ulong deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID);               // Get id for current deal;
         if(deal_id != 1197610)continue;                                   // Select deal #1197610;
         double price = HedgeDealGetDouble(HEDGE_DEAL_PRICE_EXECUTED);     // Get price executed;
         printf("3: --------> Select deal #"+(string)deal_id+              // Print price excecuted;
              ". Executed price = "+DoubleToString(price,0));
        }
     }
  }

Después de la ejecución de este código, en la pestaña “Asesores Expertos” del terminal MetaTrader 5 aparecerá el mensaje:

2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      3: --------> Select deal #1197610. Executed price = 4735
2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      2: ----> Select order #6389111
2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      1: -> Select position #5917888

Como se ve, al principio el EA selecciona la posición #5917888, y luego dentro de esta posición selecciona su orden de entrada con el número #6389111. Una vez seleccionada la orden, el EA empieza a repasar las operaciones buscando la que tiene el número 1197610. Después de encontrarla, obtiene el precio de su ejecución y lo muestra en el diario.


1.5. Obtener los códigos de errores usando GetHedgeError()

Durante el trabajo en el entorno de HedgeTerminal pueden surgir errores y situaciones imprevistas. Para estos fines se utilizan las funciones especiales para obtener y analizar los errores.

La manera más simple para que ocurra un error es cuando Usted olvida seleccionar la transacción con la función TransactionSelect(). En este caso, la función TransactionType() devolverá el modificador TRANS_NOT_DEFINED.

Para entender en qué consiste el problema, es necesario obtener el modificador del último error. El modificador nos dirá que ninguna transacción ha sido seleccionada. El código de abajo lo hace:

for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
  {
   //if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;        // forgot to select;
   ENUM_TRANS_TYPE type = TransactionType();
   if(type == TRANS_NOT_DEFINED)
   {
      ENUM_HEDGE_ERR error = GetHedgeError();
      printf("Error, transaction type not defined. Reason: " + EnumToString(error));
   }
  }

Como resultado se muestra el mensaje:

Error, transaction type not defined. Reason: HEDGE_ERR_TRANS_NOTSELECTED

El identificador nos tiene que sugerir que hemos olvidado seleccionar la transacción antes de obtener su tipo.

La estructura ENUM_HEDGE_ERR contiene la lista completa de posibles errores.


1.6. Análisis detallado de acciones comerciales e identificación de errores usando las funciones TotalActionsTask() y GetActionResult()

Aparte de los errores que surgen en el proceso de trabajo con el entorno de HedgeTerminal, pueden surgir errores comerciales como resultado de la llamada a la función SendTradeRequest(). Es más complicado trabajar con este tipo de errores. Una tarea ejecutada por la función SendTradeRequest(), puede contener varias acciones comerciales (subtareas). Por ejemplo, para modificar el comentario saliente en una posición activa que está protegida por el nivel Stop Loss hace falta realizar dos acciones comerciales:

  1. Cancelar la orden stop pendiente que implementa el nivel stop;
  2. Colocar la nueva orden stop pendiente con nuevo comentario en el lugar de la primera.

Si la nueva orden stop se activa, su comentario será mostrado como el comentario que cierra la posición, lo que es correcto.

No obstante, la tarea puede ejecutarse parcialmente. Supongamos que la orden pendiente se quita sin problema pero la orden nueva no se colocará por alguna razón. En este caso, la posición se quedará si el nivel Stop Loss. Y para que el EA pueda procesar este error, tendrá que dirigirse a un log especial de tarea y repasarlo para encontrar la subtarea que ha provocado el fallo.

Para eso existen dos funciones: TotalActionsTask() que devuelve el número total de acciones comerciales (subtareas) que forman parte de la tarea, y GetActionResult() que acepta el índice de la subtarea y devuelve su tipo y el resultado de su ejecución. Puesto que todas las acciones comerciales se ejecutan usando las herramientas estándar de MetaTrader 5, el resultado de su ejecución corresponde al código de devolución del servidor ccomercial.

En caso general, el algoritmo de la búsqueda de la razón del fallo es el siguiente:

  1. Obtener el número total de las subtareas en nuestra tarea usando la función TotalActionsTask();
  2. Realizar el repaso de todas las subtareas en el ciclo for. Determinar el tipo de cada subtarea y el resultado de su ejecución.

Supongamos que la orden con el nuevo comentario no se ha colocado porque el precio de ejecución de la orden estaba muy cerca del nivel actual de precios.

El código de abajo muestra cómo el EA podría encontrar la causa de este fallo:

#include <Prototypes.mqh> 

ulong Magic=5760655; // MagicEx 1.3.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+ 
void OnTick()
  {
//detect active position
   for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
     {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;
      ENUM_TRANS_TYPE type=TransactionType();
      if(type==TRANS_NOT_DEFINED)
        {
         ENUM_HEDGE_ERR error=GetHedgeError();
         printf("Error, transaction not defined. Reason: "+EnumToString(error));
        }
      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;
      if(HedgePositionGetString(HEDGE_POSITION_SYMBOL) != Symbol())continue;
      HedgeTradeRequest request;
      request.action=REQUEST_MODIFY_COMMENT;
      request.exit_comment="My new comment";
      if(!SendTradeRequest(request)) // Is error?
        {
         for(uint action=0; action < TotalActionsTask(); action++)
           {
            ENUM_TARGET_TYPE typeAction;
            int retcode=0;
            GetActionResult(action, typeAction, retcode);
            printf("Action#" + (string)action + ": " + EnumToString(type) +(string)retcode);
           }
        }
     }
  }

Después de la ejecución de este código se mostrará el mensaje:

Action #0 TARGET_DELETE_PENDING_ORDER 10009 (TRADE_RETCODE_PLACED)
Action #1 TARGET_SET_PENDING_ORDER 10015 (TRADE_RETCODE_INVALID_PRICE)

En efecto, si comparamos los números con los modificadores estándar de los códigos de la vuelta del servidor comercial, veremos que la orden pendiente se ha eliminado con éxito pero la colocación de la nueva ha fallido. El servidor comercial ha devuelto el error 10015 (precios incorrectos), lo que puede significar que el precio actual está muy cerca del nivel stop.

Sabiendo eso, el EA puede encargase del control de los niveles Stop. Para eso sólo tendrá que cerrar la posición por sí mismo usando la misma función SendTradeRequest().


1.7. Seguimiento del estatus de ejecución de la tarea comercial

Como ya hemos dicho antes, cada tarea comercial puede componerse de una cantidad aleatoria de subtareas que tienen que ejecutarse de manera consecutiva.

En modo asincrónico, una tarea puede ejecutarse en varios pasos del código ejecutivo. También pueden haber los casos cuando la ejecución de la tarea puede “colgarse”. Todo eso requiere el control de la ejecución de la tarea por parte del EA. Cuando se llama a la función HedgePositionGetInteger() con el modificador HEDGE_POSITION_TASK_STATUS, ella devuelve la enumeración del tipo ENUM_TASK_STATUS que va a contener el estatus de ejecución de la tarea actual para la posición.

Por ejemplo, si después del envío de la orden de cierre de la posición algo ha ido mal y la posición no se ha cerrado, en este caso hay que obtener el estatus de la tarea.

En el ejemplo de abajo se muestra el código que podría ejecutarse por el EA asincrónico para analizar el estatus de la tarea para la posición:

ENUM_TASK_STATUS status=HedgePositionGetInteger(HEDGE_POSITION_TASK_STATUS);
switch(status)
  {
   case TASK_STATUS_COMPLETE:
      printf("Task complete!");
      break;
   case TASK_STATUS_EXECUTING:
      printf("Task executing. Waiting...");
      Sleep(200);
      break;
   case TASK_STATUS_FAILED:
      printf("Filed executing task. Print logs...");
      for(int i=0; i<TotalActionsTask(); i++)
        {
         ENUM_TARGET_TYPE type;
         uint retcode;
         GetActionResult(i,type,retcode);
         printf("#"+i+" "+EnumToString(type)+" "+retcode);
        }
      break;
   case TASK_STATUS_WAITING:
      printf("task will soon start.");
      break;
  }

Es importante comprender que algunas tarea complicadas requieren varias iteraciones para su ejecución.

En el modo asincrónico, la llegada de un evento que avisa sobre el cambio del entorno comercial inicia una iteración nueva. Con lo cual, todas las iteraciones se ejecutan sin retardos, una tras otra, inmediatamente después de la recepción de nuevas respuestas del servidor comercial. La ejecución de la tarea en el modo sincrónico es algo diferente.

Para que los usuarios puedan realizar las tareas compuestas complejas en un paso, en el modo sincrónico se utiliza el emulador del trabajo sincrónico. Este emulador utiliza los retrasos de temporales en su trabajo. Por ejemplo, después de que la subtarea haya sido iniciada, el emulador no devuelve el flujo ejecutivo al EA, sino espera algún tiempo a la expectativa de que el entono comercial se cambie para ese momento. Después de eso relee el entorno comercial otra vez. Si entiende que su subtarea ha sido ejecutada con éxito, inicia otra subtarea.

Este proceso reduce algo el rendimiento general porque se necesita algo de tiempo para la espera. Pero eso convierte la ejecución de una tarea incluso muy compleja en una operación consecutiva bastante simple que se realiza con una sola llamada a la función. Por esa razón, prácticamente no hace falta analizar el log de realización de la tarea durante la ejecución sincrónica.


1.8. Modificación y cierre de posiciones bidireccionales

De la modificación y cierre de posiciones bidireccionales se encarga la función SendTradeRequest(). Para las posiciones activas se puede realizar sólo tres acciones:

  1. Cerrar la posición de forma completa o parcial;
  2. Cambiar los niveles Stop Loss y Take Profit;
  3. Cambiar el comentario saliente de la posición.

Las posiciones históricas no se puede cambiar. Igual que la función OrderSend() en MetaTrader 5, la función SendTradeRequest() utiliza para su trabajo la consulta previamente compuesta en forma de la estructura HedgeTraderRequest. Lea la sección correspondiente de la documentación para recibir la información más detallada sobre la función SendTradeRequest() y la estructura HedgeTraderRequest. El ejemplo que muestra la modificación y el cierre de la posición se encuentra en la sección que describe el EA “Chaos II”.


1.9. Ajuste de propiedades de HedgeTerminal desde el EA

HedgeTerminal tiene un conjunto de propiedades, por ejemplo, la frecuencia de actualizaciones o el número de segundos necesario para la espera de la respuesta del servidor.

Todas estas propiedades están definidas en el archivo Settings.xml. Cuando el EA trabaja en tiempo real, la biblioteca lee estas propiedades desde el archivo y establece los parámetros internos de acuerdo con ellas. Cuando el EA pasa la prueba en el gráfico, el archivo Settings.xml no se usa. Sin embargo, puede surgir la situación cuando va a necesitar la modificación individual de estos parámetros desde el EA independientemente de que si está iniciado en el gráfico o en el Probador de Estrategias.

Para esta ocasión existe un conjunto de funciones especial HedgePropertySet… En la presente versión está disponible sólo un prototipo desde este conjunto:

enum ENUM_HEDGE_PROP_INTEGER
{
   HEDGE_PROP_TIMEOUT,
};

bool HedgePropertySetInteger(ENUM_HEDGE_PROP_INTEGER property, int value)

Por ejemplo, para fijar el tiempo durante el cual la biblioteca va a esperar la respuesta del servidor, sólo hay que poner lo siguiente:

bool res = HedgePropertySetInteger(HEDGE_PROP_TIMEOUT, 30);

Si la respuesta del servidor no llega durante 30 segundos después de que Usted haya enviado la solicitud asincrónica, la posición bloqueada se desbloquea.


1.10. Modos de trabajo sincrónico y asincrónico

HedgeTerminal y su API realizan las acciones comerciales completamente en modo asincrónico.

Sin embargo, este modo requiere una lógica más compleja a los EAs. Para ocultar esta complejidad, HedgeTerminal incluye un emulador especial del trabajo sincrónico que permite a los EAs escritos en el modo sincrónico habitual interactuar con los algoritmos asíncronicos de HedgeTerminalAPI. Esta interacción se revela en el momento de la modificación y el cierre de las posiciones bidireccionales a través de la función SendTradeRequest(). Esta función permite ejecutar una tarea comercial tanto en el modo sincrónico, como en el modo asincrónico. Por defecto, todas las acciones comerciales se realizan sincrónicamente a través del emulador del trabajo sincrónico. Sin embargo, si en la solicitud comercial (estructura HedgeTradeRequest) va a figurar explícitamente la bandera asynch_mode = true, la tarea comercia se ejecutará en modo asincrónico.

En modo asincrónico las tareas se ejecutan independientemente del flujo principal. La implementación de la interacción entre un EA asincrónico y los algoritmos asincrónicos de HedgeTerminal todavía no se ha completado.

En realidad, el emulador sincrónico es muy sencillo. Él inicia las subtareas por turno y luego espera un rato hasta que el entorno comercial en MetaTrader 5 cambie. El emulador analiza los cambios y determina el estatus de la ejecución de la tarea actual. Si se ha ejecutado con éxito, pasa a la siguiente tarea.

El emulador sincrónico causa pequeños retrasos en la ejecución de órdenes comerciales. Eso se debe al hecho de que el entorno comercial de MetaTrader 5 necesita algo de tiempo para visualizar las acciones comerciales realizadas. La necesidad del acceso al entorno está relacionada primeramente con el hecho de que los eventos que llegan al manejador OnTradeTransaction() no están disponibles para HedgeTermianlAPI en el modo de emulación del flujo sincrónico.

El problema de interacción entre los flujos asincrónicos, así como entre el flujo asincrónico y sincrónico a través de la emulación es muy complicado y no tiene soluciones obvias.


1.11. Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script

En el script de abajo se muestra cómo a través de la función TransactionSelect() se realiza el repaso de todas las transacciones disponibles en la lista de transacciones activas.

Cada transacciones se selecciona de la lista. Si la transacción es una posición, se realiza el acceso a algunas de sus propiedades. Estas propiedades se imprimen. Aparte de las propiedades de la posición, también se extraen las propiedades de las órdenes y operaciones que forman parte de esta posición. Para eso, la orden y la operación se selecciona primero mediante las funciones HedgeOrderSelect() y HedgeDealSelect(), respectivamente.

Todas las propiedades de la posición, de sus órdenes y operaciones se combinan y se muestran en una línea usando la función de sistema printf.

//+------------------------------------------------------------------+
//|                                           sample_using_htapi.mq5 |
//|         Copyright 2014, Vasiliy Sokolov, Russia, St.-Petersburg. |
//|                              https://login.mql5.com/ru/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, Vasiliy Sokolov."
#property link      "https://login.mql5.com/ru/users/c-4"
#property version   "1.00"

// Include prototypes function of HedgeTerminalAPI library.
#include <Prototypes.mqh> 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+ 
void OnStart()
  {
   // Search all transaction in list transaction...
   for(int i=TransactionsTotal(); i>=0; i--)
     {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_TRADES))                           // Selecting from active transactions
        {
         ENUM_HEDGE_ERR error=GetHedgeError();                                      // Get reason if selecting has failed
         printf("Error selecting transaction # "+(string)i+". Reason: "+            // Print reason
                EnumToString(error));
         ResetHedgeError();                                                         // Reset error
         continue;                                                                  // Go to next transaction
        }
      // Only for hedge positions
      if(TransactionType()==TRANS_HEDGE_POSITION) 
        {
         // --- Position captions --- //
         ENUM_TRANS_DIRECTION direction=(ENUM_TRANS_DIRECTION)                      // Get direction caption
                              HedgePositionGetInteger(HEDGE_POSITION_DIRECTION);
         double price_entry = HedgeOrderGetDouble(HEDGE_ORDER_PRICE_EXECUTED);      // Get volume of positions
         string symbol = HedgePositionGetString(HEDGE_POSITION_SYMBOL);             // Get symbol of position
         // --- Order captions --- //
         if(!HedgeOrderSelect(ORDER_SELECTED_INIT))continue;                        // Selecting init order in position
         double slippage = HedgeOrderGetDouble(HEDGE_ORDER_SLIPPAGE);               // Get some slippage was
         uint deals_total = (uint)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL);    // Get deals total
         // --- Deals captions --- //
         double commissions=0.0;
         ulong deal_id=0;
         //Search all deals in list deals...
         for(uint d_index=0; d_index<deals_total; d_index++)                        
           {
            if(!HedgeDealSelect(d_index))continue;                                  // Selecting deal by its index
            deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID);                           // Get deal id
            commissions += HedgeDealGetDouble(HEDGE_DEAL_COMMISSION);               // Count commissions
           }
         int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
         printf("Position #" + (string)i + ": DIR " + EnumToString(direction) +     // Print result line
         "; PRICE ENTRY " + DoubleToString(price_entry, digits) + 
         "; INIT SLIPPAGE " + DoubleToString(slippage, 2) + "; LAST DEAL ID " +
         (string)deal_id + "; COMMISSIONS SUM " + DoubleToString(commissions, 2));
        }
     }
  }

1.12. Ejemplo de trabajo con la función SendTradeRequest() y la estructura HedgeTradeRequest tomando de ejemplo el EA "Chaos II"

Como ejemplo, vamos a desarrollar un Asesor Experto basado en la táctica propuesta por Bill Williamas en su libro “Caos de Trading 2” (Trading Chaos. Second Edition).

No vamos a seguir a pie de la letra sus recomendaciones y simplificaremos un poco el esquema excluyendo de la estrategia el indicador Alligator y algunas otras condiciones. La elección de esta estrategia se ha basado en varias consideraciones. La principal de ellas consiste en que esta estrategia incluye las tácticas compuestas complejas de mantenimiento de la posición. A veces es necesario cerrar una parte del volumen de la posición y pasar el nivel Stop Loss en punto muerto (break even).

Cuando la posición ha sido pasada en punto muerto (break even), hay que mover este Stop Loss tras el precio. La segunda consideración consiste en que esta táctica es bastante conocida y los indicadores desarrollados para ella están incluidos en el pack estándar de MetaTrader 5. No obstante, vamos a cambiar y simplificar un poco las reglas para que la complejidad de la lógica del EA no oculte el objetivo prioritario: mostrar en el ejemplo la interacción del EA con la biblioteca HedgeTerminalAPI. La lógica del EA utiliza la mayoría de las funciones comerciales de HedgeTerminalAPI. Será una buena prueba para la biblioteca.

La barra de reversa nos servirá del punto de partida. La barra de reversa alcista es una barra cuyo precio de cierre se encuentra en su tercio superior y su mínimo (Low) es el más bajo en las últimas N barras. La barra de reversa bajista es una barra cuyo precio de cierre se encuentra en su tercio inferior y su máximo (High) es el más alto en las últimas N barras. El parámetro N es aleatorio y se puede fijarlo al iniciar el EA. Eso se diferencia de la estrategia clásica de “Chaos 2”.

Una vez definida la barra de reversa, se colocan dos órdenes pendientes. Para la barra alcista se colocan por encima de su extremo, para la barra bajista -un poco por debajo de su mínimo. Si estas dos órdenes no se disparan durante las barras OldPending, la señal se considera obsoleta y las órdenes se cancelan. El valor del parámetro OldPending, igual que N, se establece por el usuario antes de iniciar el EA en el gráfico.

Después de su disparo, las órdenes se convierten en dos posiciones bidireccionales de la misma dirección. El EA los distingue por los números en los comentarios: "#1" y "#2", respectivamente. No es una solución elegante, pero para la demostración nos valdrá. Inmediatamente de su disparo, para ellas se establece Stop Loss a nivel del extremo (si la barra es bajista) o del mínimo (si la barra es alcista) de la barra de reversa.

La primera posición tiene objetivos cortos. Para ella se establece Take Profit cuyo beneficio en caso del disparo será igual a la pérdida absoluta obtenida del disparo de Stop Loss. Por ejemplo, si la posición larga fue abierta por el precio 1,0000 y Stop Loss se encontraba en el nivel 0,9000, el nivel de Take Profit para ella será 1,0000 + (1,0000 – 0,9000) = 1,1000. La salida de la posición va a realizarse por Stop Loss o Take Pofit.

La segunda posición va a ser a largo plazo. Su Stop Loss va a arrastrarse tras el precio. El Stop va a moverse siguiendo el nuevo fractal de Bill Williams recién formado. Para la posición larga el Stop va a moverse por los fractales inferiores, para la posición corta -por los superiores. La salida de la posición va a realizarse sólo por Stop Loss.

Vamos a ver el gráfico en el que se refleja esta estrategia:

Fig. 4. Representación de las posiciones bidireccionales del EA Chaos 2 en el gráfico de precios

Fig. 4. Representación de las posiciones bidireccionales del EA Chaos 2 en el gráfico de precios

Las barras de reversa están marcadas con el cuadro rojo. En este gráfico el período N corresponde a 2. Ha sido seleccionado el momento más oportuno para esta estrategia. Las posiciones cortas se muestran con la línea punteada azul, las posiciones largas - con la verde. Como podemos ver, incluso en esta estrategia bastante sencilla surgen situaciones cuando pueden existir las posiciones largas y cortas al mismo tiempo. Fíjense en el período de 5 al 8 de enero de 2014.

Es el momento crítico para la tendencia bajista de AUDCAD. El 4 de enero legó la señal de la barra de reversa alcista, y el 5 de enero ya fueron abiertas dos posiciones largas. En este momento todavía existían tres posiciones cortas cuyos Stops se arrastraban tras la tendencia (línea roja punteada). Luego, el día 7 de enero se disparó el Stop Para las posiciones cortas y en el mercado se quedaron sólo las posiciones largas.

Sería muy complicado monitorear estos cambios para una posición neta porque el volumen neto no tomaría en cuenta el número de posiciones que mantenía el EA en realidad. HedgeTerminal permite al EA monitorear sus posiciones personales independientemente de la posición neta actual, los que a su vez hace posible obtener estos gráficos y desarrollar estas estrategias.

Abajo se encuentra el código que implementa esta estrategia.

En él no se usa intencionadamente la programación orientada a objetos y está adaptado para que los novatos en programación puedan comprenderlo mejor:

//+------------------------------------------------------------------+
//|                                                       Chaos2.mq5 |
//|     Copyright 2014, Vasiliy Sokolov specially for HedgeTerminal. |
//|                                          St.-Petersburg, Russia. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, Vasiliy Sokolov."
#property link      "https://login.mql5.com/ru/users/c-4"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Prototypes.mqh>           // Include prototypes function of HedgeTerminalAPI library

//+------------------------------------------------------------------+
//| Input parameters.                                                |
//+------------------------------------------------------------------+
input uint N=2;                     // Period of extermum/minimum
input uint OldPending=3;            // Old pending

//+------------------------------------------------------------------+
//| Private variables of expert advisor.                             |
//+------------------------------------------------------------------+
ulong Magic = 2314;                 // Magic number of expert
datetime lastTime = 0;              // Remembered last time for function DetectNewBar
int hFractals = INVALID_HANDLE;     // Handle of indicator 'Fractals'. See: 'https://www.mql5.com/en/docs/indicators/ifractals'
//+------------------------------------------------------------------+
//| Type of bar by Bill Wiallams strategy.                           |
//+------------------------------------------------------------------+
enum ENUM_BAR_TYPE
  {
   BAR_TYPE_ORDINARY,               // Ordinary bar. 
   BAR_TYPE_BEARISH,                // This bar close in the upper third and it's minimum is lowest at N period
   BAR_TYPE_BULLISH,                // This bar close in the lower third and it's maximum is highest at N period
  };
//+------------------------------------------------------------------+
//| Type of Extremum.                                                |
//+------------------------------------------------------------------+
enum ENUM_TYPE_EXTREMUM
  {
   TYPE_EXTREMUM_HIGHEST,           // Extremum from highest prices
   TYPE_EXTREMUM_LOWEST             // Extremum from lowest prices
  };
//+------------------------------------------------------------------+
//| Type of position.                                                |
//+------------------------------------------------------------------+
enum ENUM_ENTRY_TYPE
  {
   ENTRY_BUY1,                      // Buy position with short target
   ENTRY_BUY2,                      // Buy position with long target
   ENTRY_SELL1,                     // Sell position with short target
   ENTRY_SELL2,                     // Sell position with long target
   ENTRY_BAD_COMMENT                // My position, but wrong comment
  };
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create indicator 'Fractals' ---//
   hFractals=iFractals(Symbol(),NULL);
   if(hFractals==INVALID_HANDLE)
      printf("Warning! Indicator 'Fractals' not does not create. Reason: "+
             (string)GetLastError());
//--- Corection magic by timeframe ---//
   int minPeriod=PeriodSeconds()/60;
   string strMagic=(string)Magic+(string)minPeriod;
   Magic=StringToInteger(strMagic);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete indicator 'Fractals' ---//
   if(hFractals!=INVALID_HANDLE)
      IndicatorRelease(hFractals);
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Run logic only open new bar. ---//
   int totals=SupportPositions();
   if(NewBarDetect()==true)
     {
      MqlRates rates[];
      CopyRates(Symbol(),NULL,1,1,rates);
      MqlRates prevBar=rates[0];
      //--- Set new pendings order ---//
      double closeRate=GetCloseRate(prevBar);
      if(closeRate<=30 && BarIsExtremum(1,N,TYPE_EXTREMUM_HIGHEST))
        {
         DeleteOldPendingOrders(0);
         SetNewPendingOrder(1,BAR_TYPE_BEARISH);
        }
      else if(closeRate>=70 && BarIsExtremum(1,N,TYPE_EXTREMUM_LOWEST))
        {
         DeleteOldPendingOrders(0);
         SetNewPendingOrder(1,BAR_TYPE_BULLISH);
        }
      DeleteOldPendingOrders(OldPending);
     }
//---
  }
//+------------------------------------------------------------------+
//| Analyze open positions and modify it if needed.                  |
//+------------------------------------------------------------------+
int SupportPositions()
  {
//---
   int count=0;
   //--- Analize active positions... ---//
   for(int i=0; i<TransactionsTotal(); i++) // Get total positions.
     {
      //--- Select main active positions ---//
      if(!TransactionSelect(i, SELECT_BY_POS, MODE_TRADES))continue;             // Select active transactions
      if(TransactionType() != TRANS_HEDGE_POSITION)continue;                     // Select hedge positions only
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)                 // Select main positions by magic
      if(HedgePositionGetInteger(HEDGE_POSITION_STATE) == POSITION_STATE_FROZEN) // If position is frozen - continue
         continue;                                                               // Let's try to get access to positions later
      count++;
      //--- What position do we choose?... ---//
      ENUM_ENTRY_TYPE type=IdentifySelectPosition();
      bool modify=false;
      double sl = 0.0;
      double tp = 0.0;
      switch(type)
        {
         case ENTRY_BUY1:
         case ENTRY_SELL1:
           {
            //--- Check sl, tp levels and modify it if need. ---//
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            sl=GetStopLossLevel();
            if(!DoubleEquals(sl,currentStop))
               modify=true;
            tp=GetTakeProfitLevel();
            double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
            double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
            //--- Close by take-profit if price more tp level
            bool isBuyTp=tp<bid && !DoubleEquals(tp,0.0) && type==ENTRY_BUY1;
            bool isSellTp=tp>ask && type==ENTRY_SELL1;
            if(isBuyTp || isSellTp)
              {
               HedgeTradeRequest request;
               request.action=REQUEST_CLOSE_POSITION;
               request.exit_comment="Close by TP from expert";
               request.close_type=CLOSE_AS_TAKE_PROFIT;
               if(!SendTradeRequest(request))
                 {
                  ENUM_HEDGE_ERR error=GetHedgeError();
                  string logs=error==HEDGE_ERR_TASK_FAILED ? ". Print logs..." : "";
                  printf("Close position by tp failed. Reason: "+EnumToString(error)+" "+logs);
                  if(error==HEDGE_ERR_TASK_FAILED)
                     PrintTaskLog();
                  ResetHedgeError();
                 }
               else break;
              }
            double currentTakeProfit=HedgePositionGetDouble(HEDGE_POSITION_TP);
            if(!DoubleEquals(tp,currentTakeProfit))
               modify=true;
            break;
           }
         case ENTRY_BUY2:
           {
            //--- Check sl level and set modify flag. ---//
            sl=GetStopLossLevel();
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            if(sl>currentStop)
               modify=true;
            break;
           }
         case ENTRY_SELL2:
           {
            //--- Check sl level and set modify flag. ---//
            sl=GetStopLossLevel();
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            bool usingSL=HedgePositionGetInteger(HEDGE_POSITION_USING_SL);
            if(sl<currentStop || !usingSL)
               modify=true;
            break;
           }
        }
      //--- if  need modify sl, tp levels - modify it. ---//
      if(modify)
        {
         HedgeTradeRequest request;
         request.action=REQUEST_MODIFY_SLTP;
         request.sl = sl;
         request.tp = tp;
         if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
            request.exit_comment="Exit by T/P level";
         else
            request.exit_comment="Exit by trailing S/L";
         if(!SendTradeRequest(request))
           {
            ENUM_HEDGE_ERR error=GetHedgeError();
            string logs=error==HEDGE_ERR_TASK_FAILED ? ". Print logs..." : "";
            printf("Modify stop-loss or take-profit failed. Reason: "+EnumToString(error)+" "+logs);
            if(error==HEDGE_ERR_TASK_FAILED)
               PrintTaskLog();
            ResetHedgeError();
           }
         else break;
        }
     }
   return count;
//---
  }
//+------------------------------------------------------------------+
//| Return stop-loss level for selected position.                    |
//| RESULT                                                           |
//|   Stop-loss level                                                |
//+------------------------------------------------------------------+
double GetStopLossLevel()
  {
//---
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   double fractals[];
   double sl=0.0;
   MqlRates ReversalBar;

   if(!LoadReversalBar(ReversalBar))
     {
      printf("Reversal bar load failed.");
      return sl;
     }
   //--- What position do we choose?... ---//
   switch(IdentifySelectPosition())
     {
      case ENTRY_SELL2:
        {
         if(HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
           {
            sl=NormalizeDouble(HedgePositionGetDouble(HEDGE_POSITION_SL),Digits());
            CopyBuffer(hFractals,UPPER_LINE,ReversalBar.time,TimeCurrent(),fractals);
            for(int i=ArraySize(fractals)-4; i>=0; i--)
              {
               if(DoubleEquals(fractals[i],DBL_MAX))continue;
               if(DoubleEquals(fractals[i],sl))continue;
               if(fractals[i]<sl)
                 {
                  double price= SymbolInfoDouble(Symbol(),SYMBOL_ASK);
                  int ifreeze =(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_FREEZE_LEVEL);
                  double freeze=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*ifreeze;
                  if(fractals[i]>price+freeze)
                     sl=NormalizeDouble(fractals[i]+point,Digits());
                 }
              }
            break;
           }
        }
      case ENTRY_SELL1:
         sl=ReversalBar.high+point;
         break;
      case ENTRY_BUY2:
         if(HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
           {
            sl=NormalizeDouble(HedgePositionGetDouble(HEDGE_POSITION_SL),Digits());
            CopyBuffer(hFractals,LOWER_LINE,ReversalBar.time,TimeCurrent(),fractals);
            for(int i=ArraySize(fractals)-4; i>=0; i--)
              {
               if(DoubleEquals(fractals[i],DBL_MAX))continue;
               if(DoubleEquals(fractals[i],sl))continue;
               if(fractals[i]>sl)
                 {
                  double price= SymbolInfoDouble(Symbol(),SYMBOL_BID);
                  int ifreeze =(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_FREEZE_LEVEL);
                  double freeze=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*ifreeze;
                  if(fractals[i]<price-freeze)
                     sl=NormalizeDouble(fractals[i]-point,Digits());
                 }
              }
            break;
           }
      case ENTRY_BUY1:
         sl=ReversalBar.low-point;
     }
   sl=NormalizeDouble(sl,Digits());
   return sl;
//---
  }
//+------------------------------------------------------------------+
//| Return Take-Profit level for selected position.                  |
//| RESULT                                                           |
//|   Take-profit level                                              |
//+------------------------------------------------------------------+
double GetTakeProfitLevel()
  {
//---
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   ENUM_ENTRY_TYPE type=IdentifySelectPosition();
   double tp=0.0;
   if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
     {
      if(!HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
         return tp;
      double sl=HedgePositionGetDouble(HEDGE_POSITION_SL);
      double openPrice=HedgePositionGetDouble(HEDGE_POSITION_PRICE_OPEN);
      double deltaStopLoss=MathAbs(NormalizeDouble(openPrice-sl,Digits()));
      if(type==ENTRY_BUY1)
         tp=openPrice+deltaStopLoss;
      if(type==ENTRY_SELL1)
         tp=openPrice-deltaStopLoss;
      return tp;
     }
   else
      return 0.0;
//---
  }
//+------------------------------------------------------------------+
//| Identify what position type is select.                           |
//| RESULT                                                           |
//|   Return type position. See ENUM_ENTRY_TYPE                      |
//+------------------------------------------------------------------+
ENUM_ENTRY_TYPE IdentifySelectPosition()
  {
//---   
   string comment=HedgePositionGetString(HEDGE_POSITION_ENTRY_COMMENT);
   int pos=StringLen(comment)-2;
   string subStr=StringSubstr(comment,pos);
   ENUM_TRANS_DIRECTION posDir=(ENUM_TRANS_DIRECTION)HedgePositionGetInteger(HEDGE_POSITION_DIRECTION);
   if(subStr=="#0")
     {
      if(posDir==TRANS_LONG)
         return ENTRY_BUY1;
      if(posDir==TRANS_SHORT)
         return ENTRY_SELL1;
     }
   else if(subStr=="#1")
     {
      if(posDir==TRANS_LONG)
         return ENTRY_BUY2;
      if(posDir==TRANS_SHORT)
         return ENTRY_SELL2;
     }
   return ENTRY_BAD_COMMENT;
//---
  }
//+------------------------------------------------------------------+
//| Set pending orders under or over bar by index_bar.               |
//| INPUT PARAMETERS                                                 |
//|   index_bar - index of bar.                                      |
//|   barType - type of bar. See enum ENUM_BAR_TYPE.                 |
//| RESULT                                                           |
//|   True if new order successfully set, othewise false.            | 
//+------------------------------------------------------------------+
bool SetNewPendingOrder(int index_bar,ENUM_BAR_TYPE barType)
  {
//---
   MqlRates rates[1];
   CopyRates(Symbol(),NULL,index_bar,1,rates);
   MqlTradeRequest request={0};
   request.volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   double vol=request.volume;
   request.symbol = Symbol();
   request.action = TRADE_ACTION_PENDING;
   request.type_filling=ORDER_FILLING_FOK;
   request.type_time=ORDER_TIME_GTC;
   request.magic=Magic;
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   string comment="";
   if(barType==BAR_TYPE_BEARISH)
     {
      request.price=rates[0].low-point;
      comment="Entry sell by bearish bar";
      request.type=ORDER_TYPE_SELL_STOP;
     }
   else if(barType==BAR_TYPE_BULLISH)
     {
      request.price=rates[0].high+point;
      comment="Entry buy by bullish bar";
      request.type=ORDER_TYPE_BUY_STOP;
     }
   MqlTradeResult result={0};
//--- Send pending order twice...
   for(int i=0; i<2; i++)
     {
      request.comment=comment+" #"+(string)i;       // Detect order by comment;
      if(!OrderSend(request,result))
        {
         printf("Trade error #"+(string)result.retcode+" "+
                result.comment);
         return false;
        }
     }
   return true;
//---
  }
//+------------------------------------------------------------------+
//| Delete old pending orders. If pending order set older that       |
//| n_bars ago pending orders will be removed.                       |
//| INPUT PARAMETERS                                                 |
//|   period - count bar.                                            |
//+------------------------------------------------------------------+
void DeleteOldPendingOrders(int n_bars)
  {
//---
   for(int i=0; i<OrdersTotal(); i++)
     {
      ulong ticket = OrderGetTicket(i);            // Get ticket of order by index.
      if(!OrderSelect(ticket))                     // Continue if not selected.
         continue;
      if(Magic!=OrderGetInteger(ORDER_MAGIC))      // Continue if magic is not main.
         continue;
      if(OrderGetString(ORDER_SYMBOL)!=Symbol())   // Continue if symbol is not main.
         continue;
      //--- Count time elipsed ---//
      datetime timeSetup=(datetime)OrderGetInteger(ORDER_TIME_SETUP);
      int secElapsed=(int)(TimeCurrent()-timeSetup);
      //--- delete old pending order ---//
      if(secElapsed>=PeriodSeconds() *n_bars)
        {
         MqlTradeRequest request={0};
         MqlTradeResult result={0};
         request.action= TRADE_ACTION_REMOVE;
         request.order = ticket;
         if(!OrderSend(request,result))
            printf("Delete pending order failed. Reason #"+(string)result.retcode+" "+result.comment);
        }
     }
//---
  }
//+------------------------------------------------------------------+
//| Detect new bar.                                                  |
//+------------------------------------------------------------------+
bool NewBarDetect(void)
  {
//---
   datetime timeArray[1];
   CopyTime(Symbol(),NULL,0,1,timeArray);
   if(lastTime!=timeArray[0])
     {
      lastTime=timeArray[0];
      return true;
     }
   return false;
//---
  }
//+------------------------------------------------------------------+
//| Get close rate. Type bar defined in trade chaos strategy  |
//| and equal enum 'ENUM_TYPE_BAR'.                                  |
//| INPUT PARAMETERS                                                 |
//|   index - index of bars series. for example:                     |
//|   '0' - is current bar. 1 - previous bar.                        |
//| RESULT                                                           |
//|   Type of ENUM_TYPE_BAR.                                         | 
//+------------------------------------------------------------------+
double GetCloseRate(const MqlRates &bar)
  {
//---
   double highLowDelta = bar.high-bar.low;      // Calculate diaposon bar.
   double lowCloseDelta = bar.close - bar.low;  // Calculate Close - Low delta.
   double percentClose=0.0;
   if(!DoubleEquals(lowCloseDelta, 0.0))                    // Division by zero protected.   
      percentClose = lowCloseDelta/highLowDelta*100.0;      // Calculate percent 'lowCloseDelta' of 'highLowDelta'.
   return percentClose;
//---
  }
//+------------------------------------------------------------------+
//| If bar by index is extremum - return true, otherwise             |
//| return false.                                                    |
//| INPUT PARAMETERS                                                 |
//|   index - index of bar.                                          |
//|   period - Number of bars prior to the extremum.                 |
//|   type - Type of extremum. See ENUM_TYPE_EXTREMUM TYPE enum.     |
//| RESULT                                                           |
//|   True - if bar is extremum, otherwise false.                    | 
//+------------------------------------------------------------------+
bool BarIsExtremum(const int index,const int period,ENUM_TYPE_EXTREMUM type)
  {
//--- Copy rates --- //
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   CopyRates(Symbol(),NULL,index,N+1,rates);
//--- Search extremum --- //
   for(int i=1; i<ArraySize(rates); i++)
     {
      //--- Reset comment if you want include volume analize. ---//
      //if(rates[0].tick_volume<rates[i].tick_volume)
      //   return false;
      if(type==TYPE_EXTREMUM_HIGHEST && 
         rates[0].high<rates[i].high)
         return false;
      if(type==TYPE_EXTREMUM_LOWEST && 
         rates[0].low>rates[i].low)
         return false;
     }
   return true;
//---
  }
//+------------------------------------------------------------------+
//| Print current error and reset it.                                |
//+------------------------------------------------------------------+  
void PrintTaskLog()
  {
//---
   uint totals=(uint)HedgePositionGetInteger(HEDGE_POSITION_ACTIONS_TOTAL);
   for(uint i = 0; i<totals; i++)
     {
      uint retcode=0;
      ENUM_TARGET_TYPE type;
      GetActionResult(i,type,retcode);
      printf("---> Action #"+(string)i+"; "+EnumToString(type)+"; RETCODE: "+(string)retcode);
     }
//---
  }
//+------------------------------------------------------------------+
//| Load reversal bar. The current position must be selected.        |
//| OUTPUT PARAMETERS                                                |
//|   bar - MqlRates bar.
//+------------------------------------------------------------------+  
bool LoadReversalBar(MqlRates &bar)
  {
//---
   datetime time=(datetime)(HedgePositionGetInteger(HEDGE_POSITION_ENTRY_TIME_SETUP_MSC)/1000+1);
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   CopyRates(Symbol(),NULL,time,2,rates);
   int size=ArraySize(rates);
   if(size==0)return false;
   bar=rates[size-1];
   return true;
//---   
  }
//+------------------------------------------------------------------+
//| Compares two double numbers.                                     |
//| RESULT                                                           |
//|   True if two double numbers equal, otherwise false.             |
//+------------------------------------------------------------------+
bool DoubleEquals(const double a,const double b)
  {
//---
   return(fabs(a-b)<=16*DBL_EPSILON*fmax(fabs(a),fabs(b)));
//---
  }

Describiremos brevemente cómo funciona el código. El EA se llama en cada tick. Él analiza la barra anterior usando la función BarIsExtremum(), y si es bajista o alcista, se colocan dos órdenes pendientes (función SetNewPendingOrder()). Después del disparo, las órdenes pendientes se convierten en posiciones. Después de eso, el EA coloca para ellas los niveles Stop Loss y Take Profit.

Lamentablemente, es imposible colocar estos niveles junto con las órdenes pendientes, ya que las posiciones reales todavía no existen. La función SupportPositions() se encarga de colocar estos niveles. Para su trabajo ella necesita saber para qué posición hay que colocar Take Profit y qué posición hay que arrastrar tras nuevos fractales. De eso se encarga la función IdentifySelectPosition(). Ella analiza el comentario iniciador de la posición, y si él contiene la subcadena "#1", se coloca el objetivo corto para él, si la subcadena "#2", se aplica el Trailing Stop.

En caso de la modificación de la posición abierta bidireccional, o en caso de su cierre, se crea la solicitud comercial correspondiente que luego se manda a la función SendTradeRequest() para la ejecución:

...
if(modify)
  {
   HedgeTradeRequest request;
   request.action=REQUEST_MODIFY_SLTP;
   request.sl = sl;
   request.tp = tp;
   if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
      request.exit_comment="Exit by T/P level";
   else
      request.exit_comment="Exit by trailing S/L";
   if(!SendTradeRequest(request))
     {
      ENUM_HEDGE_ERR error=GetHedgeError();
      string logs=error==HEDGE_ERR_TASK_FAILED ? ". Print logs..." : "";
      printf("Modify stop-loss or take-profit failed. Reason: "+EnumToString(error)+" "+logs);
      if(error==HEDGE_ERR_TASK_FAILED)
         PrintTaskLog();
      ResetHedgeError();
     }
   else break;
  }
...

Preste atención en el análisis de los errores con esta función.

Si el envío ha fallado y la función ha devuelto false, es necesario obtener el código del último error usando la función GetHedgeError(). En algunas ocasiones, la orden comercial incluso no empezará a ejecutarse. Pues, si la posición no ha sido elegida previamente, o la orden ha sido hecha incorrectamente, resulta imposible ejecutarla.

Cuando la orden no ha sido ejecutada, no tiene sentido analizar el log de su ejecución, será suficiente obtener el código del error.

Sin embargo, si la solicitud es correcta pero la orden no se ha ejecutado por alguna razón, se devolverá el error HEDGE_ERR_TASK_FAILED. En este caso, es necesario analizar el log de ejecución de la orden realizando la búsqueda en el log. De eso se encarga la función PrintTaskLog():

//+------------------------------------------------------------------+
//| Print current error and reset it.                                |
//+------------------------------------------------------------------+  
void PrintTaskLog()
  {
//---
   uint totals=(uint)HedgePositionGetInteger(HEDGE_POSITION_ACTIONS_TOTAL);
   for(uint i = 0; i<totals; i++)
     {
      uint retcode=0;
      ENUM_TARGET_TYPE type;
      GetActionResult(i,type,retcode);
      printf("---> Action #"+(string)i+"; "+EnumToString(type)+"; RETCODE: "+(string)retcode);
     }
//---
  }

Estos mensajes permiten comprender la causa del fallo y corregirlo.

Para terminar, vamos a ilustrar la visualización del EA Chaos2 y sus posiciones en HedgeTerminal en tiempo real.. El EA está iniciado en el gráfico M1:

Fig. 5. Representación de las posiciones bidireccionales del EA Chaos 2 en el panel de HedgeTerminal

Fig. 5. Representación de las posiciones bidireccionales del EA Chaos 2 en el panel de HedgeTerminal

Como podemos ver, las posiciones bidireccionales incluso del mismo EA se arreglan perfectamente.


1.13. Sobre los “símbolos duplicados” y virtualización por parte del broker

Justo después del lanzamiento de MetaTrader 5, algunos brokers han empezado a proporcionar así llamados “símbolos duplicados”. Sus cotizaciones son idénticas a los símbolos originales, sin embargo suelen tener al final el postfijo "_m" o "_1". Han sido introducidos para que los traders puedan tener las posiciones bidireccionales prácticamente para el mismo símbolo.

No obstante, estos símbolos son prácticamente inútiles para los traders algorítmicos que utilizan los robots. Explico por qué. Supongamos que necesitamos escribir el EA "Chaos II" sin la biblioteca HedgeTerminalAPI. Tenemos unos símbolos duplicados. ¿Cómo podríamos hacerlo? Supongamos que todas las ventas abrimos en un símbolo (por ejemplo, EURUSD), y todas las compras en el otro (por ejemplo, EURUSD_m1).

¿Pero qué pasaría si en el momento de apertura de la posición uno de los símbolos ya se tradeara por otro robot o persona? Incluso si estos símbolos siempre estuvieran libres, eso no solucionaría problemas para este robot. Es que él puede llevar simultáneamente varias posiciones en una dirección.

En la imagen de arriba se muestran tres posiciones de venta pero pueden haber más. Estas posiciones tienen diferentes paradas de protección por eso no se puede unirlas en una posición neta. La solución es abrir cada posición en un símbolo duplicado nuevo. Pero puede que no haya suficientes símbolos porque un robot necesita 6 dobles (tres para cada dirección de la transacción), y para dos robots iniciados en diferentes períodos de tiempo ya son 12.

Ninguno de los brokers ofrecerá esta cantidad de símbolos duplicados, y se trata sólo de dos robots. Pero si incluso hubiera una cantidad ilimitada de estos símbolos y siempre estuvieran libres, sería necesaria una descomposición muy compleja del algoritmo. El robot tendría que repasar todos los símbolos disponibles buscando los dobles y sus posiciones. Se trata de más complicaciones que soluciones. 

Y con eso los problemas de los símbolos duplicados no se acaban. Aquí tiene una lista breve de problemas adicionales que surgen con su uso:

Un símbolo duplicado es en realidad una virtualización en el lado del broker. HedgeTerminal utiliza la virtualización en el lado del cliente.

En ambos casos se utiliza la virtualización como tal. Ella cambia la representación real de las obligaciones de trader. Durante la virtualización, una posición puede convertirse en dos. No hay ningún problema cuando eso ocurre en el lado del cliente porque el cliente puede representar lo que quiera. Pero cuando esta virtualización es dada por el broker, las empresas reguladores y autorizadoras preguntar: de qué manera la información facilitada está relacionada con la real. La segunda dificultad es que prácticamente habrá que tener dos APIs en uno: un conjunto de funciones y modificadores para trabajar en el modo netto y el otro para trabajar en el modo bidireccional.

Muchos traders de algoritmos han encontrados su manera para combinar las transacciones en una sola posición. Muchos de estos modos funcionan e incluso han sido escritos artículos sobre ellos. Sin embargo, la virtualización de las posiciones en realidad es un proceso mucho más complicado que puede parecer. En HedgeTerminal los algoritmos relacionados con la virtualización de las posiciones ocupan cerca de 20 000 líneas del código fuente. Y es que HedgeTerminal implemeta sólo las funciones básicas. Crear este volumen del código en su EA sólo para acompañar las posiciones bidireccionales es consumir los recursos de una manera injustificable.


Capítulo 2. Documentación para API HedgeTerminal

2.1. Funciones para seleccionar transacciones

Función TransactionsTotal()

La función devuelve el número total de transacciones en la lista de transacciones. Es la función básica para organizar el repaso de transacciones disponibles (ver ejemplo en la sección 1,4 y 1,11 del artículo).

int TransactionsTotal(ENUM_MODE_TRADES pool = MODE_TRADES);

Parámetros

Valor devuelto

Devuelve el número total de transacciones en la lista de transacciones.


Función TransactionType()

La función devuelve el tipo de la transacción seleccionada.

ENUM_TRANS_TYPE TransactionType(void);

Valor devuelto

Tipo del valor devuelto. Puede ser uno de los valores de la enumeración ENUM_TRANS_TYPE.

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”


Función TransactionSelect()

La función selecciona la transacción para procesamiento posterior. La función permite seleccionar la transacción por el índice o por el identificador único de la transacción en la lista de transacciones.

bool TransactionSelect(int index,
     ENUM_MODE_SELECT select = SELECT_BY_POS,
     ENUM_MODE_TRADES pool=MODE_TRADES
     );

Parámetros

Valor devuelto

Devuelve true si la transacción ha sido seleccionada con éxito, false en caso del error. Para obtener la información sobre el error, hay que llamar a la función GetHedgeError().

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”

Nota

En caso de seleccionar la transacción por su índice, la complejidad de la operación corresponde a O(1). En caso de seleccionar la transacción por su identificador único, la complejidad de la operación tiende asintóticamente a O(log2(n)).


Función HedgeOrderSelect()

La función selecciona una de las órdenes incluidas en la posición bidireccional. La posición bidireccional que incluye la orden necesaria debe ser seleccionada previamente a través de la función TransactionSelect().

bool HedgeOrderSelect(ENUM_HEDGE_ORDER_SELECTED_TYPE type);

Parámetros

Valor devuelto

Devuelve true si la orden ha sido seleccionada con éxito, false en caso del error. Para obtener la información sobre el error, hay que llamar a la función GetHedgeError().

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”


Función HedgeDealSelect()

La función selecciona una de las transacciones que han ejecutado la orden. La orden que incluye la transacción necesaria debe ser seleccionada previamente a través de la función HedgeOrderSelect().

bool HedgeDealSelect(int index);

Parámetros

Valor devuelto

Devuelve true si la transacción ha sido seleccionada con éxito, false en caso del error. Para obtener la información sobre el error, hay que llamar a la función GetHedgeError().

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”


2.2. Funciones para obtener propiedades de la transacción seleccionada

Función HedgePositionGetInteger()

La función devuelve la propiedad de la posición bidireccional seleccionada. La propiedad puede tener el tipo int, long, datetime o bool, dependiendo del tipo de la propiedad solicitada. La posición bidireccional debe ser seleccionada previamente por la función TransactionSelect().

ulong HedgePositionGetInteger(ENUM_HEDGE_POSITION_PROP_INTEGER property);

Parámetros

Valor devuelto

Valor del tipo ulong. Para el futuro uso del valor, es necesaria su conversión explícita al tipo de la propiedad solicitada.

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”


Función HedgePositionGetDouble()

La función devuelve la propiedad de la posición bidireccional seleccionada. La propiedad devuelta tiene el tipo double. El tipo de la propiedad se indica a través de la enumeración ENUM_HEDGE_POSITION_PROP_DOUBLE. La posición bidireccional debe ser seleccionada previamente por la función TransactionSelect().

ulong HedgePositionGetDouble(ENUM_HEDGE_POSITION_PROP_DOUBLE property);

Parámetros

Valor devuelto

Valor del tipo double.

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”


La función HedgePositionGetString()

La función devuelve la propiedad de la posición bidireccional seleccionada. La propiedad tiene el tipo string. El tipo de la propiedad se indica a través de la enumeración ENUM_HEDGE_POSITION_PROP_STRING. La posición bidireccional debe ser seleccionada previamente por la función TransactionSelect().

ulong HedgePositionGetString(ENUM_HEDGE_POSITION_PROP_STRING property);

Parámetros

Valor devuelto

Valor del tipo string.

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”


La función HedgeOrderGetInteger()

La función devuelve la propiedad de la orden seleccionada que forma parte de la posición bidireccional. La propiedad puede tener el tipo int, long, datetime o bool. El tipo de la propiedad se indica a través de la enumeración ENUM_HEDGE_ORDER_PROP_INTEGER. La orden debe ser seleccionada previamente a través de la función HedgeOrderSelect().

ulong HedgeOrderGetInteger(ENUM_HEDGE_ORDER_PROP_INTEGER property);

Parámetros

Valor devuelto

Valor del tipo ulong. Para el futuro uso del valor, es necesaria su conversión explícita al tipo de la propiedad solicitada.

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”


La función HedgeOrderGetDouble()

La función devuelve la propiedad de la orden seleccionada que forma parte de la posición bidireccional. La propiedad solicitada tiene el tipo double. El tipo de la propiedad se indica a través de la enumeración ENUM_HEDGE_ORDER_PROP_DOUBLE. La orden debe ser seleccionada previamente a través de la función HedgeOrderSelect().

double HedgeOrderGetDouble(ENUM_HEDGE_ORDER_PROP_DOUBLE property);

Parámetros

Valor devuelto

Valor del tipo double.

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”


La función HedgeDealGetInteger()

La función devuelve la propiedad de la transacción seleccionada que forma parte de la orden ejecutada. La propiedad puede tener el tipo int, long, datetime o bool. El tipo de la propiedad se indica a través de la enumeración ENUM_HEDGE_DEAL_PROP_INTEGER. La transacción debe ser seleccionada previamente por la función HedgeDealSelect().

ulong HedgeOrderGetInteger(ENUM_HEDGE_DEAL_PROP_INTEGER property);

Parámetros

Valor devuelto

Valor del tipo ulong. Para el futuro uso del valor, es necesaria su conversión explícita al tipo de la propiedad solicitada.

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”


La función HedgeDealGetDouble()

La función devuelve la propiedad de la transacción seleccionada que forma parte de la orden ejecutada. La propiedad puede tener el tipo double. El tipo de la propiedad se indica a través de la enumeración ENUM_HEDGE_DEAL_PROP_DOUBLE. La transacción debe ser seleccionada previamente por la función HedgeDealSelect().

ulong HedgeOrderGetDouble(ENUM_HEDGE_DEAL_PROP_DOUBLE property);

Parámetros

Valor devuelto

Valor del tipo double.

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”


2.3. Funciones para establecer y obtener las propiedades de HedgeTerminal desde los EAs

La función HedgePropertySetInteger()

La función establece una de las propiedades de HedgeTerminal. La propiedad puede tener el tipo int, long, datetime o bool. El tipo de la propiedad se indica a través de la enumeración ENUM_HEDGE_PROP_INTEGER.

bool HedgePropertySetInteger(ENUM_HEDGE_PROP_INTEGER property, long value);

Parámetros

Valor devuelto

Valor del tipo bool. Si la propiedad ha sido establecida con éxito, la función devuelve true, de lo contrario devuelve false.

Ejemplo de uso

En este ejemplo, la función se utiliza para establecer el tiempo de bloqueo de la posición durante el envío de una solicitud asincrónica. Si la respuesta del servidor no llega durante 30 segundos después de que Usted haya enviado la solicitud asincrónica, la posición bloqueada se desbloquea.

void SetTimeOut()
  {
   bool res=HedgePropertySetInteger(HEDGE_PROP_TIMEOUT,30);
   if(res)
      printf("The property is set successfully");
   else
      printf("Property is not set");
  }

La función HedgePropertyGetInteger()

La función devuelve una de las propiedades de HedgeTerminal. La propiedad puede tener el tipo int, long, datetime o bool. El tipo de la propiedad se indica a través de la enumeración ENUM_HEDGE_PROP_INTEGER.

long HedgePropertyGetInteger(ENUM_HEDGE_PROP_INTEGER property);

Parámetros

Valor devuelto

Valor del tipo long.

Ejemplo de uso

La función obtiene el tiempo de bloqueo de la posición durante el envío de una solicitud asincrónica y lo muestra en el terminal:

void GetTimeOut()
  {
   int seconds=HedgePropertyGetInteger(HEDGE_PROP_TIMEOUT);
   printf("Timeout is "+(string) seconds);
  }

2.4. Funciones para obtener y procesar los códigos de errores

La función GetHedgeError()

La función devuelve el identificador del error que ha surgido con la realización de la última acción. El identificador del error corresponde a la enumeración ENUM_HEDGE_ERR.

ENUM_HEDGE_ERR GetHedgeError(void);

Valor devuelto

Identificador del error. Puede ser uno de los valores de la enumeración ENUM_HEDGE_ERR.

Nota

Después de la llamada, la función GetHedgeError) no resetea el identificador del error. Para resetear forzosamente el identificador del error hay que usar la función ResetHedgeError().

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”


La función ResetHedgeError()

La función resetea el identificador del último error obtenido. Después de su llamada, el identificador ENUM_HEDGE_ERR devuelto por la función GetHedgeError(), será igual a HEDGE_ERR_NOT_ERROR.

void ResetHedgeError(void);

Ejemplo de uso

Véase el ejemplo del uso de la función en la sección 1,11 de este artículo: “Ejemplo de trabajo con las propiedades de las posiciones bidireccionales a base del script”


La función TotalActionsTask()

Después de que la posición ha sido seleccionada por la función HedgePositionSelect(), se puede modificarla usando la función SendTradeRequest(). Por ejemplo, se puede cerrarla o modificar su comentario de salida. Este cambio se realiza usando una tarea comercial especial. Cada tarea puede incluir varias acciones comerciales (subtareas). La tarea puede fallar. En este caso, puede surgir la necesidad de analizar el resultado de ejecución de todas las subtareas que forman parte de la tarea para comprender cuál de las subtareas ha fallado.

La función TotalActionTask() devuelve el número total de subtareas incluidas en la última tarea comercial ejecutada para la posición seleccionada. Sabiendo el número total de subtareas se puede repasar todas las subtareas por el índice y analizar el resultado de su ejecución mediante la función GetActionResult(), y así comprender en qué circunstancias ha ocurrido el fallo.

uint TotalActionsTask(void);

Valor devuelto

Devuelve el número total de subtareas incluidas en la tarea.

Ejemplo de uso

Véase el ejemplo del uso en la sección 1,6 de este artículo: “Análisis detallado de acciones comerciales e identificación de errores usando las funciones TotalActionsTask y GetActionResult.


La función GetActionResult()

La función obtiene el índice de la subtarea incluida en la tarea (véase la función TotalActionTask()). Devuelve el tipo de la subtarea y el resultado de su ejecución a través de los parámetros de referencia. El tipo de la subtarea se define por la enumeración ENUM_TARGET_TYPE. El resultado de la ejecución de la subtarea corresponde a los códigos de devolución del servidor comercial MetaTrader 5

void GetActionResult(uint index, ENUM_TARGET_TYPE& target_type, uint& retcode);

Parámetros

Ejemplo de uso

Véase el ejemplo del uso en la sección 1,6 de este artículo: “Análisis detallado de acciones comerciales e identificación de errores usando las funciones TotalActionsTask y GetActionResult.


2.5. Ejecución de las acciones comerciales

La función SendTradeRequest()

La función envía la solicitud para modificar la posición bidireccional seleccionada en HedgeTerminal. El resultado de ejecución de la función será una de tres acciones:

  1. Cierre de la posición o de una parte de su volumen;
  2. Modificación de los niveles Stop Loss y Take Profit;
  3. Modificación del comentario saliente.

El tipo de la acción y sus parámetros se indican en la estructura HedgeTradeRequest que se pasa a la función por referencia como parámetro. Antes de llamara la función, la posición bidireccional debe ser seleccionada previamente por la función TransactionSelect().

bool SendTradeRequest(HedgeTradeRequest& request);

Parámetros

[in] request – Estructura de la solicitud para modificar la posición bidireccional. La descripción de la estructura y los valores de los campos se encuentran en la descripción de la estructura de HedgeTradeRequest.

Valor devuelto

Devuelve true si la solicitud para la modificación de la posición ha sido ejecutada con éxito. De lo contrario, devuelve false. En caso del fallo en la ejecución de la solicitud, utilice las funciones TotalActionsTask() y GetActionResult() para encontrar las razones de este error.

Nota

En el modo asincrónico del envío de la orden, la bandera de devolución va a contener true si la tarea se ha colocado y se ha iniciado con éxito. No obstante, es necesario comprender que incluso después del inicio exitoso de la tarea, su ejecución no se garantiza. Por eso no se puede controlar el éxito de ejecución de la tarea a través de esta bandera en el modo asincrónico. En el modo sincrónico el inicio y la ejecución de la tarea se realiza en un solo flujo, por eso en este modo se puede controlar la ejecución de la solicitud comercial mediante esta bandera.


Estructura de la solicitud comercial HedgeTradeRequest

Las posiciones bidireccionales en HedgeTerminal se cierran y se modifican a través de la llamada a la función SendTradeRequest() en la cual la solicitud comercial se utiliza como argumento. La solicitud está representada por una estructura especial predefinida HedgeTradeRequest que contiene todos los campos necesarios para cerrar o modificar la posición seleccionada:

struct HedgeTradeRequest
  {
   ENUM_REQUEST_TYPE action;             // type of action
   double            volume;             // volume of position
   ENUM_CLOSE_TYPE   close_type;         // Marker of closing order
   double            sl;                 // stop-loss level
   double            tp;                 // take-profit level
   string            exit_comment;       // outgoing comment
   uint              retcode;            // last retcode in executed operation
   bool              asynch_mode;        // true if the closure is performed asynchronously, otherwise false
   ulong             deviation;          // deviation in step price
                     HedgeTradeRequest() // default params
     {
      action=REQUEST_CLOSE_POSITION;
      asynch_mode=false;
      volume=0.0;
      sl = 0.0;
      tp = 0.0;
      retcode=0;
      deviation=3;
     }
  };

Descripción de campos

CampoDescripción
 action Tipo de acciones a realizar con la posición. Puede ser uno de los valores de la enumeración ENUM_REQUEST_TYPE.
 volume El volumen a cerrar. Puede ser menos que el volumen de la posición actual activa. Si el volumen es igual a cero, la posición activa se cierra por completo.
 sl El nivel Stop Loss a establecer para la posición activa.
 tp El nivel Take Profit a establecer para la posición activa.
 exit_comment  El comentario saliente para la posición activa.
 retcode El código del resultado de la última operación ejecutada.
 asynch_mode true, si se utiliza el modo asincrónico del envío de la solicitud, de lo contrario, false.
 deviation La desviación máxima del precio que se utiliza.


2.6. Enumeraciones para trabajar con las funciones de selección de transacciones

ENUM_TRANS_TYPE

Todas las transacciones disponibles para el análisis, sean las órdenes pendientes o posiciones bidireccionales, se encuentran en las listas de transacciones activas o históricas.

La enumeración ENUM_TRANS_TYPE contiene el tipo de cada transacción seleccionada. Esta enumeración es devuelta por la función TransactionType(). A continuación, se muestran los campos de la enumeración y sus descripciones:

CampoDescripción
 TRANS_NOT_DEFINED La transacción no está seleccionada por la función TransactionSelect() o su tipo no está definido.
 TRANS_HEDGE_POSITION La transacción es una posición bidireccional.
 TRANS_BROKERAGE_DEAL  La transacción es una operación de broker (operación en la cuenta). Por ejemplo, depósito de dinero en la cuenta o corrección.
 TRANS_PENDING_ORDER La transacción es una orden pendiente.
 TRANS_SWAP_POS La transacción es un swap cargado en la posición neta.


ENUM_MODE_SELECT

La enumeración define el tipo del parámetro index, establecido en la función TransactionSelect().

CampoDescripción
 SELECT_BY_POS En el parámetro index se pasa el número de la transacción en la lista.
 SELECT_BY_TICKET En el parámetro index se pasa el número del ticket.


ENUM_MODE_TRADES

La enumeración define la fuente de datos de la cual se selecciona la transacción usando la función TransactionSelect().

CampoDescripción
 MODE_TRADES La transacción se selecciona de las transacciones activas.
 MODE_HISTORY La transacción se selecciona de las transacciones históricas.

2.7. Enumeraciones para trabajar con las funciones de obtención de las propiedades de transacciones

Enumeración ENUM_TRANS_DIRECTION

Cada transacción comercial, sea una operación o una posición bidireccional, tiene una dirección de mercado.

Esta dirección de mercado se define por la enumeración ENUM_TRANS_DIRECTION. Abajo se muestran sus campos y sus descripciones:

CampoDescripción
 TRANS_NDEF La dirección de la transacción no está definida. Por ejemplo, las operaciones de broker en la cuenta no tienen la dirección de mercado y se marcan con este modificador.
 TRANS_LONG Se indica que la transacción comercial (orden o posición bidireccional) es una transacción de Compra.
 TRANS_SHORT  Se indica que la transacción comercial (orden o posición bidireccional) es una transacción de Venta.


Enumeración ENUM_HEDGE_POSITION_STATUS

La enumeración contiene el estatus de una posición bidireccional.

CampoDescripción
 HEDGE_POSITION_ACTIVE  Posición activa. Las posiciones activas se muestran en la pestaña Active del panel de HedgeTerminal.
 HEDGE_POSITION_HISTORY  Posición histórica. Las posiciones históricas se muestran en la pestaña History del panel de HedgeTerminal.


Enumeración ENUM_HEDGE_POSITION_STATE

La enumeración contiene el estado de una posición bidireccional.

CampoDescripción
 POSITION_STATE_ACTIVE La posición seleccionada es activa y se puede cambiarla sólo usando la solicitud comercial HedgeTradeRequest.
 POSITION_STATE_FROZEN  La posición seleccionada está bloqueada y no se puede modificarla. Cuando se recibe este modificador, hay que esperar hasta que se desbloquee.


Enumeración ENUM_HEDGE_POSITION_PROP_INTEGER

La enumeración define el tipo de la propiedad que devuelve la función HedgePositionGetInteger().

CampoDescripción
 HEDGE_POSITION_ENTRY_TIME_SETUP_MSC Tiempo de colocación de la orden (en milisegundos desde 01.01.1970) que inicializa la posición bidireccional.
 HEDGE_POSITION_ENTRY_TIME_EXECUTED_MSC  Tiempo de ejecución de la orden (en milisegundos desde 01.01.1970) que inicializala posición bidireccional (hora de apertura de la posición).
 HEDGE_POSITION_EXIT_TIME_SETUP_MSC Tiempo de colocación de la orden (en milisegundos desde 01.01.1970) que cierra la posición bidireccional.
 HEDGE_POSITION_EXIT_TIME_EXECUTED_MSC Tiempo de ejecución de la orden (en milisegundos desde 01.01.1970) que cierra la posición bidireccional (hora del cierre de la posición).
 HEDGE_POSITION_TYPE Tipo de la posición bidireccional. Es igual al tipo de la orden iniciadora. Contiene uno de los valores de la enumeración de sistema ENUM_ORDER_TYPE.
 HEDGE_POSITION_DIRECTION Dirección de la posición. Se define por la enumeración ENUM_TRANS_DIRECTION.
 HEDGE_POSITION_MAGIC El número mágico del EA al que pertenece la posición seleccionada. .El valor cero indica que la posición ha sido abierta manualmente.
 HEDGE_POSITION_CLOSE_TYPE El marcador de la orden que cierra la posición. Se define por la enumeración ENUM_CLOSE_TYPE.
 HEDGE_POSITION_ID Identificador de la posición. Es igual al identificador de la orden iniciadora.
 HEDGE_POSITION_ENTRY_ORDER_ID Identificador de la orden iniciadora.
 HEDGE_POSITION_EXIT_ORDER_ID Identificador de la orden de cierre para la posición histórica.
 HEDGE_POSITION_STATUS Estatus de la posición. Se define por la enumeración  ENUM_HEDGE_POSITION_STATUS.
 HEDGE_POSITION_STATE Estado de la posición. Se define por la enumeración ENUM_HEDGE_POSITION_STATE
 HEDGE_POSITION_USING_SL Bandera del uso del nivel Stop Loss. Si Stop Loss se utiliza, la función  HedgePositionGetInteger() devuelve true, de lo contrario - false.
 HEDGE_POSITION_USING_TP Bandera del uso del nivel Take Profit. Si Take Profit se utiliza, la función HedgePositionGetInteger() devuelve true, de lo contrario - false.
 HEDGE_POSITION_TASK_STATUS El estatus de la tarea que se ejecuta para la posición seleccionada. La posición puede estar en el proceso de modificación. Este modificador se utiliza para monitorear los cambios de esta posición. El estatus de la posición se define por la enumeración ENUM_TASK_STATUS.
 HEDGE_POSITION_ACTIONS_TOTAL Devuelve el número total de subtareas iniciadas para modificar esta posición.

 

Enumeración ENUM_HEDGE_POSITION_PROP_DOUBLE

La enumeración define el tipo de la propiedad que devuelve la función HedgePositionGetDouble().

CampoDescripción
 HEDGE_POSITION_VOLUME Volumen de la posición bidireccional.
 HEDGE_POSITION_PRICE_OPEN Precio medio ponderado de apertura de la posición.
 HEDGE_POSITION_PRICE_CLOSED Precio medio ponderado del cierre de la posición.
 HEDGE_POSITION_PRICE_CURRENT Precio actual de la posición activa. Para una posición histórica este modificador devolverá el precio del cierre de la posición.
 HEDGE_POSITION_SL Nivel Stop Loss. Si Stop Loss no se usa, es igual a cero.
 HEDGE_POSITION_TP Nivel Take Profit. Si Take Profit no se usa, es igual a cero.
 HEDGE_POSITION_COMMISSION Cuantía de la comisión pagada para la posición.
 HEDGE_POSITION_SLIPPAGE Deslizamiento en puntos.
 HEDGE_POSITION_PROFIT_CURRENCY  Volumen de ganancias o pérdidas de esta posición. El importe de ganancias se indica en la moneda del depósito.
 HEDGE_POSITION_PROFIT_POINTS Volumen de ganancias o pérdidas de esta posición. Las ganancias se indican en puntos del instrumento financiero de la posición abierta.

Nota

El deslizamiento HEDGE_POSITION_SLIPPAGE se calcula como la diferencia en puntos entre la mejor operación de entrada en la posición y el precio medio ponderado de entrada.

 

Enumeración ENUM_HEDGE_POSITION_PROP_STRING

La enumeración define el tipo de la propiedad que devuelve la función HedgePositionGetString().

CampoDescripción
 HEDGE_POSITION_SYMBOL Símbolo de la posición actual.
 HEDGE_POSITION_ENTRY_COMMENT Comentario de entrada de la posición.
 HEDGE_POSITION_EXIT_COMMENT Comentario de salida de la posición.

 

Enumeración ENUM_HEDGE_ORDER_STATUS

La enumeración contiene el tipo de la orden.

CampoDescripción
 HEDGE_ORDER_PENDING  La orden es pendiente y está disponible en la pestaña “Trading” en MetaTrader 5.
 HEDGE_ORDER_HISTORY La orden es histórica y está disponible en el historial de las órdenes de MetaTrader 5.

Enumeración ENUM_HEDGE_ORDER_SELECTED_TYPE

La enumeración define el tipo de la orden seleccionada por la función HedgeOrderSelect().

CampoValor
 ORDER_SELECTED_INIT La orden que inicia la posición bidireccional.
 ORDER_SELECTED_CLOSED  La orden que cierra la posición bidireccional.
 ORDER_SELECTED_SL La orden que actúa como el nivel Stop Loss.


Enumeración ENUM_HEDGE_ORDER_PROP_INTEGER

La enumeración define el tipo de la propiedad que devuelve la función HedgeOrderGetInteger().

CampoDescripción
 HEDGE_ORDER_ID Identificador único de la orden.
 HEDGE_ORDER_STATUS Estatus de la orden. Puede ser uno de los valores de la enumeración ENUM_HEDGE_ORDER_STATUS.
 HEDGE_ORDER_DEALS_TOTAL El número total de operaciones que han ejecutado la orden. Para las órdenes pendientes este valor es igual a cero.
 HEDGE_ORDER_TIME_SETUP_MSC Tiempo de colocación de la orden pendiente (en millisegundos transcurridos desde 01.01.1970).
 HEDGE_ORDER_TIME_EXECUTED_MSC Tiempo de ejecución de la orden ejecutada (en millisegundos transcurridos desde 01.01.1970).
 HEDGE_ORDER_TIME_CANCELED_MSC Tiempo de cancelación de la orden ejecutada (en millisegundos transcurridos desde 01.01.1970).

Nota

La hora de ejecución de la orden HEDGE_ORDER_TIME_EXECUTED_MSC corresponde a la hora de su última operación. 


Enumeración ENUM_HEDGE_ORDER_PROP_DOUBLE

La enumeración define el tipo de la propiedad que devuelve la función HedgeOrderGetDouble().

CampoDescripción
 HEDGE_ORDER_VOLUME_SETUP Volumen de la orden que ha sido establecido en el momento de su colocación.
 HEDGE_ORDER_VOLUME_EXECUTED Volumen ejecutado. Si la orden es pendiente, el volumen ejecutado será igual a cero.
 HEDGE_ORDER_VOLUME_REJECTED El volumen de la orden que no se ha podido ejecutar. Es la diferencia entre el volumen inicial y el volumen ejecutado.
 HEDGE_ORDER_PRICE_SETUP Precio de colocación de la orden.
 HEDGE_ORDER_PRICE_EXECUTED Precio medio ponderado de la ejecución de la orden.
 HEDGE_ORDER_COMMISSION Cuantía de comisiones pagadas al broker por ejecutar la orden. Se indica en la moneda del depósito.
 HEDGE_ORDER_SLIPPAGE Deslizamiento de la orden.

Nota

El deslizamiento HEDGE_ORDER_SLIPPAGE se calcula como la diferencia en puntos entre la mejor operación ejecutada y el precio medio ponderado de la entrada de la orden.


Enumeración ENUM_HEDGE_DEAL_PROP_INTEGER

La enumeración define el tipo de la propiedad que devuelve la función HedgeDealGetInteger().

CampoDescripción
 HEDGE_DEAL_ID Identificador único de la operación.
 HEDGE_DEAL_TIME_EXECUTED_MSC Tiempo de ejecución de la operación (en millisegundos transcurridos desde 01.01.1970).


Enumeración ENUM_HEDGE_DEAL_PROP_DOUBLE

La enumeración define el tipo de la propiedad que devuelve la función HedgeDealGetDouble().

CampoDescripción
 HEDGE_DEAL_VOLUME_EXECUTED Volumen de la operación.
 HEDGE_DEAL_PRICE_EXECUTED Precio de ejecución de la operación.
 HEDGE_DEAL_COMMISSION Cuantía de comisiones pagadas al broker por ejecutar la operación.Se indica en la moneda del depósito.

 

2.8. Enumeraciones para establecer y obtener las propiedades de HedgeTerminal

Enumeración ENUM_HEDGE_PROP_INTEGER

La enumeración establece el tipo de la propiedad que hay que obtener o establecer en HedgeTerminal.

CampoDescripción
 HEDGE_PROP_TIMEOUT El tiempo en segundos durante el cual HedgeTerminal va a esperar la respuesta del servidor antes de que cancele el bloqueo de la posición que se está modificando.


2.9. Enumeraciones para trabajar con las funciones de procesamiento de los códigos de errores

Enumeración ENUM_TASK_STATUS

Cada posición bidireccional puede estar en el proceso de modificación. La posición se modifica por la tarea comercial.

Cada tarea comercial inicializada tiene su estatus de ejecución definido en el enumeración ENUM_TASK_STATUS. Abajo se muestran sus campos y sus descripciones:

CampoDescripción
 TASK_STATUS_WAITING No hay tarea actual o la tarea está en espera.
 TASK_STATUS_EXECUTING La tarea comercial ejecutada para la posición se encuentra en el proceso de ejecución.
 TASK_STATUS_COMPLETE La tarea comercial para la posición se ha completado con éxito.
 TASK_STATUS_FAILED La tarea comercial para la posición se ha completado con fallo.


Enumeración ENUM_HEDGE_ERR

La enumeración contiene el identificador del error que puede ser devuelto por la función GetHedgeError().

CampoDescripción
 HEDGE_ERR_NOT_ERROR No hay error.
 HEDGE_ERR_TASK_FAILED La tarea para la posición seleccionada ha fallado.
 HEDGE_ERR_TRANS_NOTFIND Transacción no encontrada.
 HEDGE_ERR_WRONG_INDEX Índice incorrecto.
 HEDGE_ERR_WRONG_VOLUME Volumen incorrecto.
 HEDGE_ERR_TRANS_NOTSELECTED  La transacción no ha sido seleccionada previamente por la función TransactionSelect().
 HEDGE_ERR_WRONG_PARAMETER Uno de los parámetros pasados es incorrecto.
 HEDGE_ERR_POS_FROZEN La posición bidireccional se encuentra en el proceso de modificación y no está disponible para nuevas modificaciones. Espere el desbloqueo de la posición.
 HEDGE_ERR_POS_NO_CHANGES La solicitud comercial no contiene cambios.

 

Enumeración ENUM_TARGET_TYPE

La enumeración define el tipo de la subtarea seleccionada por la función GetActionResult().

CampoDescripción
 TARGET_NDEF Subtarea no definida.
 TARGET_CREATE_TASK La subtarea está en espera. Este tipo se utiliza en la lógica interna del trabajo de HedgeTerminalAPI.
 TARGET_DELETE_PENDING_ORDER Eliminación de la orden pendiente.
 TARGET_SET_PENDING_ORDER Colocación de la orden pendiente.
 TARGET_MODIFY_PENDING_ORDER  Modificación del precio de la orden pendiente.
 TARGET_TRADE_BY_MARKET Ejecución de acciones comerciales.


2.10. Enumeraciones para trabajar con las funciones de procesamiento de los códigos de errores

Enumeración ENUM_REQUEST_TYPE

La enumeración describe la acción que HedgeTerminal aplica a la posición bidireccional.

CampoDescripción
 REQUEST_CLOSE_POSITION Cierra la posición. Si en el campo volume de la estructura HedgeTradeRequest se indica el volumen menor que el actual, se cerrará sólo una parte de la posición. En este caso, la parte de la posición cerrada va a corresponder al valor del campo volume.
 REQUEST_MODIFY_SLTP Establece o modifica los niveles Stop Loss y Take Profit actuales.
 REQUEST_MODIFY_COMMENT Modifica el comentario saliente para la posición activa.


Enumeración ENUM_CLOSE_TYPE.

La enumeración establece un marcador especial para la orden que cierra la posición bidireccional. Este marcador especifica la razón del cierre de la posición. La razón del cierre puede ser una de las siguientes:

CampoDescripción
 CLOSE_AS_MARKET Indica que la posición ha sido cerrada por mercado. Los niveles Stop Loss y Take Profit no han sido colocados o alcanzados.
 CLOSE_AS_STOP_LOSS Indica que la posición ha sido cerrada por alcanzar el nivel Stop Loss.
 CLOSE_AS_TAKE_PROFIT  Indica que la posición ha sido cerrada por alcanzar el nivel Take Profit.

 

Capítulo 3. Principios de las operaciones comerciales asincrónicas

El tema de las operaciones asincrónicas es complicado y requiere un artículo separado y extenso. No obstante, puesto que HedgeTerminal utiliza activamente las operaciones asincrónicas, será apropiado describir brevemente los principios de organización de los EAs que utilizan este tipo del envío de solicitudes comerciales. Además, hoy en día prácticamente no hay materiales sobre este tema.

3.1. Organización y esquema del envío de la orden comercial sincrónica

En MetaTrader 5 hay dos funciones para enviar órdenes comerciales al servidor:

La función OrderSend() acepta la solicitud como una estructura rellenada MqlTradeRequest y realiza la comprobación básica respecto a la corrección de su rellenado. Si la comprobación básica es exitosa, envía la solicitud al servidor, espera el resultado de su ejecución, luego devuelve este resultado al flujo del cliente a través de la estructura MqlTradeResult y la bandera de devolución. Si la comprobación básica ha fallado, la función devolverá el valor negativo.

La razón por la que la solicitud no ha pasado la verificación será indicada también en MqlTradeResult.

El esquema de ejecución del flujo de un programa MQL personalizado con el uso de la función OrderSend() se muestra a continuación:

Fig. 6. Esquema de organización y esquema del envío de la orden comercial sincrónica

Fig. 6. Esquema de organización y esquema del envío de la orden comercial sincrónica

En el esquema se ve que el flujo del programa MQL es inseparable del flujo de sistema general del envío de la solicitud al servidor y ejecución de operaciones comerciales en la bolsa.

Por eso, después de que la función OrderSend() termine su trabajo, se puede analizar el resultado de ejecución de la solicitud comercial. El flujo del cliente se muestra con flechas rojas. Se ejecuta prácticamente al instante. La mayor parte de tiempo es necesaria para la ejecución de las operaciones comerciales en la bolsa. Puesto que los dos flujos están vinculados, entre el inicio y la finalización del trabajo de la función OrderSend() pasa un rato importante. Debido a que la ejecución de las operaciones se realiza en el único flujo, la lógica de los programas MQL5 puede ser secuencial.


3.2. Organización y esquema del envío de la orden comercial asincrónica

La función OrderSendAsync() es diferente. Igual que OrderSend(), ella acepta la solicitud comercial MqlTradeRequest y también devuelve la bandera que indica el resultado de su trabajo.

Sin embargo, a diferencia del primer ejemplo ella no espera a que el servidor ejecute la solicitud, y devuelva el valor obtenido del módulo de la comprobación básica de los valores de la solicitud comercial (Basic verification inside the terminal). El esquema de ejecución del flujo del cliente con el uso de la función OrderSendAsync() se muestra a continuación:

Fig. 7. Esquema de organización y esquema del envío de la orden comercial asincrónica.

Fig. 7. Esquema de organización y esquema del envío de la orden comercial asincrónica

Después de que la solicitud comercial haya pasado la comprobación básica con éxito, se envía al servidor comercial en paralelo al flujo principal. Igual como en el primer caso, también se requiere un tiempo para el paso de la solicitud comercial por la red y su ejecución en la bolsa. Pero el flujo del cliente recibirá el resultado de la función OrderSendAsync() prácticamente al instante.

En el esquema de arriba se ve que OrderSendAsync() forma prácticamente un nuevo flujo independiente paralelo que se ejecuta por el servidor comercial, y el resultado de su ejecución va a la función OnTradeTransaction() o OnTrade(). Estas funciones, a su vez, empiezan un flujo del cliente nuevo. Precisamente en este flujo nuevo es necesario procesar el resultado del envío de la solicitud comercial. Esto complica considerablemente la lógica del EA porque con el envío asincrónico de la orden, es imposible organizar el envío de la solicitud y su comprobación en un solo flujo. Por ejemplo, no se puede colocar secuencialmente el código para el envío y comprobación de la solicitud en la función OnTick().

Vamos a escribir un EA de prueba sencillo que muestra lo antes dicho:

//+------------------------------------------------------------------+
//|                                                    AsynchExp.mq5 |
//|                           Copyright 2014, Vasiliy Sokolov (C-4). |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
input bool UsingAsynchMode=true;
bool sendFlag=false;
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(sendFlag)return;
   printf("Formation of order and send to the server...");
   MqlTradeRequest request={0};
   request.magic=12345;
   request.symbol = Symbol();
   request.volume = 0.1;
   request.type=ORDER_TYPE_BUY;
   request.comment= "asynch test";
   request.action = TRADE_ACTION_DEAL;
   request.type_filling=ORDER_FILLING_FOK;
   MqlTradeResult result;
   uint tiks= GetTickCount();
   bool res = false;
   if(UsingAsynchMode)
      res=OrderSendAsync(request,result);
   else
      res=OrderSend(request,result);
   uint delta=GetTickCount()-tiks;
   if(OrderSendAsync(request,result))
     {
      printf("The order has been successfully"+
             "sent to the server.");
     }
  else
     {
     printf("The order is not shipped."+
             " Reason: "+(string)result.retcode);
     }
   printf("Time to send a trade request: "+(string)delta);
   sendFlag=true;
//---
  }

Asegurémonos de que el EA funciona. Vamos a iniciarlo con el parámetro UsingAsynchMode = false.

En este caso, el EA abrirá una posición larga con el volumen de 0,1 lote. La solicitud comercial se realizará sincrónicamente usando la función OrderSend(). Aquí tenemos el ejemplo de su log:

2014.11.06 17:49:28.442 AsynchExp (AUDCAD,H1)   Time to send a trade request: 94
2014.11.06 17:49:28.442 AsynchExp (AUDCAD,H1)   The order has been successfullysent to the server.
2014.11.06 17:49:28.345 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

Se ve que la solicitud comercial se ha completado en 94 millisegundos. Este tiempo nos dice que la solicitud ha pasado la comprobación básica, ha sido enviado al servidor, y después de eso ha sido ejecutado.

Ahora vamos a modificar el código del EA cambiando el volumen de la transacción por el valor máximo posible DBL_MAX:

request.volume = DBL_MAX;

Es obvio que este valor se encuentra fuera de lo límites de los rangos reales. Vamos a probar ejecutar esta solicitud en el modo sincrónico:

2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   Time to send a trade request: 0
2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   The order is not shipped. Reason: 10014
2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

El envío de la solicitud ha fallado. La razón del fallo - error 10014 (volumen incorrecto en la solicitud). La solicitud no ha superado la comprobación básica e incluso no ha sido enviado al servidor, en lo que indica la tiempo de ejecución de la solicitud: 0 millisegundos.

Volvemos a cambiar la solicitud. Ahora pondremos un volumen bastante elevado pero no extremo - 15 lotes. Para la cuenta de 1000$, en la que se prueba este EA, es un volumen muy grande. En ella no se puede abrir una posición como ésta.

Veremos qué nos devolverá la función OrderSend():

2014.11.06 17:59:22.643 AsynchExp (AUDCAD,H1)   Time to send a trade request: 78
2014.11.06 17:59:22.643 AsynchExp (AUDCAD,H1)   The order is not shipped. Reason: 10019
2014.11.06 17:59:22.550 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

En este caso, el error es diferente: 10019 (No hay fondos suficientes para ejecutar la solicitud, lo que es cierto). Preste atención en el tiempo de la solicitud: 79 millisegundos. Eso indica que la solicitud ha sido enviada al servidor y ha sido él quien ha devuelto el error que señala en la falta de fondos monetarios.

Ahora enviaremos la misma solicitud con el volumen de 15 lotes usando la función OrderSendAsync(). Igual que con OrderSend() la posición no se abrirá, pero vamos a analizar e log:

2014.11.06 18:03:58.106 AsynchExp (AUDCAD,H1)   Time to send a trade request: 0
2014.11.06 18:03:58.106 AsynchExp (AUDCAD,H1)   The order has been successfully sent to the server.
2014.11.06 18:03:58.104 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

¡El log nos dice que no hay error! Ya que el error 10019 es detectado por el servidor comercial, no está disponible para el flujo actual con el envío asincrónico de la orden. El valor devuelto sólo dice que la solicitud ha superado la comprobación básica. Para obtener el error actual 10019, necesitamos analizar el resultado en un nuevo flujo del cliente, en la función de sistema OnTradeTransaction() que necesitamos añadir a nuestro EA:

void  OnTradeTransaction(const MqlTradeTransaction    &trans,
                         const MqlTradeRequest        &request,
                         const MqlTradeResult         &result)
  {
   uint delta = GetTickCount() - tiks;
   printf("Server answer: " + (string)result.retcode + "; Time: " + (string)delta);
  }

Iniciamos el EA de nuevo y mostramos los logs:

2014.11.06 18:17:00.943 AsynchExp (AUDCAD,H1)   Server answer: 10019; Time: 94
2014.11.06 18:17:00.854 AsynchExp (AUDCAD,H1)   Time to send a trade request: 0
2014.11.06 18:17:00.854 AsynchExp (AUDCAD,H1)   The order has been successfully sent to the server.
2014.11.06 18:17:00.851 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

El error 10019 ha sido recibido, pero no inmediatamente después del envío sino en el nuevo flujo del cliente iniciado en OnTradeTransaction().


3.3. Velocidad de ejecución de órdenes asincrónicas

Los traders suelen tener la opinión equivocada de que la velocidad de ejecución de una orden asincrónica se acerca a cero.

Se basa en la observación de que la función OrderSendAsync() finaliza su trabajo generalmente en menos de 1 milisegundo desde el momento de su inicio. En realidad, como lo hemos mostrado más arriba, el tiempo real para ejecutar una transacción comercial hay que medirlo cuando la respuesta del servidor se recibe dentro de la función OnTradeTransaction() o OnTrade(). Esta medición nos da la velocidad real que será igual a la velocidad de la ejecución sincrónica para una orden solitaria. Las ventajas reales en el tiempo de ejecución se hacen más evidentes cuando se envía un grupo de transacciones. Hay por lo menos tres grupos cuando es necesario enviar varias solicitudes simultáneamente:

Recuerde que es necesario tomar en cuenta el límite para el envío de estas solicitudes a la hora de colocar varias solicitudes a la vez.

En MetaTrader 5 build 1010 y superiores este límite es igual a 64 transacciones, 4 de las cuales están reservadas para el usuario y el resto están disponibles para los EAs. Este límite ha sido introducido para proteger a los traders principiantes de los errores serios en sus programas, así como para reducir la carga spam a los servidores comerciales.

Eso significa que al mismo tiempo, por ejemplo en el ciclo for, se puede enviar de hasta 60 órdenes comerciales llamando a la función SendOrderAsync() con la solicitud comercial correspondiente. Después de que todas 60 transacciones hayan sido enviadas, el búfer de transacciones estará completamente lleno. Tendremos que esperar la confirmación del servidor que una de las transacciones ha sido procesada por el servidor.

Después de este procesamiento su sitio en el búfer de transacciones queda libre y podemos ocuparlo con una nueva orden comercial. Después de que el búfer quede completamente lleno, los sitios para nuevas transacciones se liberan más lentamente. Pues, el servidor comercial necesita más tiempo para procesar cada transacción, y el evento TradeTransaction() que avisa sobre el inicio del procesamiento pasa a través de la red, lo que provoca inevitablemente retrasos adicionales.

De esta manera, el tiempo necesario para el envío de las solicitudes va a crecer de manera no lineal respecto a su cantidad. La tabla de abajo contiene la velocidad estimada de colocación de las solicitudes para el modo asíncrónico del envío de las órdenes. Las pruebas se han realizado varias veces y la velocidad mostrada es un valor medio:

Número de solicitudesTiempo, milisegundos
5050
100180
2002100
5009000
100023000

Cuando el número de solicitudes es menos de 60, el script no espera la respuesta del servidor, por eso el tiempo es tan insignificante. Aproximadamente, es igual al tiempo requerido para el envío de una solicitud. En realidad, para obtener el tiempo real aproximado de ejecución, es necesario al tiempo de colocación de las solicitudes indicado en la tabla sumar el tiempo medio de ejecución de una solicitud.

 

Capítulo 4. Principios de la programación multihilo (multithreading) en el entorno MetaTrader.

Los programadores que utilizan el lenguaje MQL saben que no se puede controlar los flujos (hilos) directamente desde el programa MQL. Esta restricción es para el bien de los programadores principiantes porque el uso de los flujos complica considerablemente el algoritmo del programa. No obstante, en algunas ocasiones, dos o más EAs tienen que comunicarse entre sí, por ejemplo, deben crear y leer los datos comunes.

HedgeTerminal pertenece precisamente a estos EAs. Para informar a cada EA que utiliza la biblioteca HedgeTerminalAPI sobre las acciones de otros EAs, HT organiza el intercambio de datos a través de la lectura multihilo y la escritura del archivo ActivePositions.xml. Es una solución no trivial y raramente usada entre los programadores de MQL. Por eso vamos a crear un EA multihilo con algoritmo de trabajo similar a los algoritmos de HedgeTerminal. Eso nos ayudará a comprender mejor la programación multihilo, y entender mejor cómo funciona HedgeTerminal.


4.1. Programación multihilo en el ejemplo del recolector de cotizaciones UnitedExchangeQuotes

Vamos a aprender los principios de la programación multihilo a base de un determinado ejemplo: escribiremos el recolector de cotizaciones de varios proveedores (brokers).

La idea es la siguiente: supongamos que tenemos 6-7 brokers que nos facilitan cotizaciones para el mismo símbolo. Naturalmente, sus cotizaciones pueden variarse ligeramente. Sin embargo, el análisis de estas diferencias nos abre el camino hacia las estrategias de arbitraje. Además, las comparación de las cotizaciones en dinámica no ayudará a identificar al mejor y al peor proveedor. Si el broker ofrece constantemente los mejores precios que los demás brokers, probablemente es mejor acudir a él para tradear. No vamos a perseguir el valor práctico de los resultados, simplemente describiremos el mecanismo que nos permite lograr estos resultados.

Aquí tenemos la captura de pantalla del EA que tenemos que escribir al final del capítulo:

Fig. 8. La apariencia del recolector de cotizaciones UnitedExhangesQuotes.

Fig. 8. La apariencia del recolector de cotizaciones UnitedExhangesQuotes

El EA muestra los resultados en forma de la tabla que contiene cuatro columnas y un número ilimitado de filas.

Cada fila es el broker que cotiza el símbolo (en este caso es EURUSD). Ask y Bid representan la mejor oferta y la mejor demanda de este broker, respectivamente. En la captura se ve que las cotizaciones se diferencian ligeramente. La diferencia entre la oferta del broker actual y el otro se muestra en la columna D-ASK (Delta Ask). De la misma manera, la diferencia entre la demanda se muestra en la columna D-BID (Delta Bid). Por ejemplo, en el momento de la captura se ve que el proveedor “Alpari Limited” tiene el mejor Ask, y “Banco VTB 24” tiene el más caro.

Los programas MQL no tienen acceso al entorno de otros terminales MetaTrader. En otras palabras, si el programa ha sido iniciado en un terminal, no puede obtener los datos del otro. No obstante, todos los programas MQL pueden intercambiar datos a través de los archivos que se ubican en la carpeta compartida de todos los terminales MetaTrader. Si un programa escribe la información (por ejemplo, la cotización actual) en el archivo, el programa MQL de otro terminal podrá leerla. En MQL no hay otros medios sin que tengamos que usar DLLs externas. Por eso vamos a usar este método.

La mayor dificultad supone la organización de este acceso. Por un lado, el EA necesitará leer las cotizaciones de otros proveedores, y por otro, escribir la cotización de su proveedor en el mismo archivo. Hay otro problema: en el momento de la lectura de la cotización otro EA puede escribir la misma cotización. El resultado de este trabajo en paralelo puede ser impredecible. En el mejor de los casos, se producirá un fallo y la ejecución del programa se detendrá. En el peor de los casos, van a aparecer periódicamente unos errores raros relacionados con la visualización de las cotizaciones.

Para evitar estos errores, o por lo menos minimizar la posibilidad de su aparición, hemos desarrollado un plan concreto.

Primero, vamos a guardar toda la información en el formato XML. Este formato a reemplazado los archivos ini más antiguos y burdos. Además de eso, XML permite desplegar de una manera flexible las estructuras complejas de datos (por ejemplo, clases) desde sus nodos. Segundo, vamos a determinar el algoritmo general de lectura y escritura. Es evidente que van a haber dos operaciones principales: lectura de datos y escritura de datos. Cuando un programa MQL lee o escribe, otros programas no pueden acceder a este archivo. De esta manera, eliminaremos la situación cuando un programa lee los datos y el segundo los cambia. Sin embargo, hay que tener en cuenta que el acceso a los datos no siempre será posible.

Vamos a crear la clase especial CQuoteList que va a contener los algoritmos de acceso a XML, así como los datos sobre todas las cotizaciones desde este archivo.

Una de las funciones de esta clase es TryGetHandle(). Va a intentar acceder al archivo y devolver su manejador (handle) en caso del éxito. Aquí tenemos su implementación:

int CQuoteList::TryGetHandle(void)
{
   int attempts = 10;
   int handle = INVALID_HANDLE;
   // We try to open 'attemps' times
   for(att = 0; att < attempts; att++)
   {
      handle = FileOpen("Quotes.xml", FILE_WRITE|FILE_READ|FILE_BIN|FILE_COMMON);
      if(handle == INVALID_HANDLE)
      {
         Sleep(15);
         continue;
      }
      break;
   }
   return handle;
}

Como vemos, ella hace varios intentos de abrir el archivo en el modo combinado de lectura/escritura. Por defecto, el numero de intentos es igual a diez.

Si el intento no ha tenido éxito, la función espera 15 milisegundos e intenta abrir el archivo otra vez hasta agotar todos los intentos.

Una vez abierto el archivo, su handle se pasa a la función LoadQuotes(). El listado completo de esta función y la clase CQuoteList están disponibles en el archivo adjunto a este artículo. No vamos a mostrar el contenido completo de la función, sólo especificaremos la secuencia general de sus acciones:

  1. La función TryGetHandle() abre el archivo para la lectura y escritura;
  2. El documento XML se carga en la memoria del EA usando la biblioteca XML Parser;
  3. A base del documento XML cargado, se forma un nuevo array de cotizaciones que guarda la información necesaria;
  4. El array creado contiene la cotización que pertenece al EA actual. Sus valores se actualizan;
  5. El array de cotizaciones se convierte de nuevo en el documento XML. El contenido del archivo XML abierto se reemplaza por este documento XML;
  6. El archivo XML de cotizaciones se cierra.

Se ve que la función LoadQuotes() hace enorme trabajo, pero en la mayoría de los casos eso toma menos de 1 millisegundo.

La lectura de los datos y su actualización con su posterior guardado están reunidos en un bloque. Eso ha sido hecho a propósito, para no perder el control sobre el archivo entre las operaciones de su lectura y su escritura.

Cuando las cotizaciones ya han sido cargadas y se encuentran dentro de la clase, se puede procesarlas igual que otros datos en MetaTrader 5. Para eso en la clase CQuotesList ha sido implementada una interfaz especial escrita en el estilo de MetaTrader 5.

La llamada a las funciones y la visualización de los datos se realiza dentro del bloque OnTick(). Aquí tenemos su contenido:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
      return;
   if(!QuotesList.LoadQuotes())    
      return;   
   PrintQuote quote = {0};
   Panel.DrawAccess(QuotesList.CountAccess());
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   string brokerName = AccountInfoString(ACCOUNT_COMPANY);
   for(int i = 0; i < QuotesList.BrokersTotal(); i++)
   {
      if(!QuotesList.BrokerSelect(i))
         continue;
      if(!QuotesList.SymbolSelect(Symbol()))
         continue;
      quote.ask = QuotesList.QuoteInfoDouble(QUOTE_ASK);
      quote.bid = QuotesList.QuoteInfoDouble(QUOTE_BID);
      quote.delta_ask = ask - quote.ask;
      quote.delta_bid = quote.bid - bid;
      quote.broker_name = QuotesList.BrokerName();
      quote.index = i;
      Panel.DrawBroker(quote);
   }
  }

¡Cabe mencionar que el mismo código fuente del ejemplo trabaja tanto en MetaTrader 4, como en MetaTrader 5, sin ninguna modificación!

Existen sólo insignificantes diferencias de visualización de los paneles en diferentes versiones del terminal. Sin duda alguna, es un hecho formidable que facilita la portación de los códigos entre las plataformas.

Es mejor y más eficaz observar el trabajo de este EA en dinámica. Para eso, abajo se encuentra el vídeo especial que muestra su trabajo en diferentes cuentas:

 

La lectura y la escritura en el archivo tiene ventajas significativas, pero también tiene sus desventajas.

Las ventajas principales son las siguientes:

  1. Flexibilidad. Se puede guardar y cargar los datos de cualquier tipo, incluso las clases enteras;
  2. Velocidad relativa. El ciclo entero de la lectura y reescritura casi siempre no toma más de 1 milisegundo. Es más que suficiente en comparación con las operaciones comerciales relativamente lentas que tardan 80-150 milisegundos, a veces más.
  3. Se basa en las herramientas estándar del lenguaje MQL5 sin tener que llamar a las DLL ajenas.

El mayor inconveniente de esta solución es la carga importante sobre el sistema de almacenamiento de datos. Cuando la cotización es una y los brokers son dos, el número de operaciones de reescritura es relativamente pequeño. Pero en caso de un flujo de cotizaciones más cargado y una gran cantidad de los brokers/símbolos, el número de reescrituras se hace muy elevado. El ejemplo de demostración ha generado más de 90 000 reescrituras del archivo Quotes.xml en menos de una hora. Esta estadística está disponible en la parte superior del panel del EA: “I/O Rewrite” muestra el número total de las reescrituras del archivo, “fps” muestra la velocidad entre dos últimas reescrituras, “Avrg” muestra la velocidad media de reescrituras en un segundo.

Si utiliza para el almacenamiento de sus archivos SSD o HDD clásico, eso va a producir un impacto negativo en su duración de vida. Por eso es mejor utilizar el disco virtual RAM para esta organización del intercambio de datos.

A diferencia del ejemplo demostrado, HedgeTerminal utiliza el archivo ActivePositions.xml con moderación, escribiendo en él sólo los cambios más importantes de la posición que no están disponibles a través del contexto global. De esta manera, produce mucho menos operaciones de lectura/escritura que en el ejemplo. Por eso cuando lo utiliza, los medios especiales tipo discos RAM no son necesarios.


4.2. Aplicación de la interacción multihilo entre los EAs

La interacción entre los programas MQL independientes en tiempo real es un tema complicado pero interesante. Lo hemos descrito concisamente, aunque merece un artículo separado. En la mayoría de los casos, la interacción multihilo entre los EAs no es necesaria. Sin embargo, aquí hay una lista de tareas y variedades de programas para los cuales se necesita la organización de esta interacción:


Descripción de los archivos adjuntos

Vamos a describir brevemente los archivos adjuntos al artículo, así como el procedimiento de su compilación.

Prototypes.mqh - archivo de descripción de las funciones de la bibliotecas HedgeTerminalAPI. Este archivo contiene la descripción y los prototipos de las funciones que se encuentran en la biblioteca HedgeTerminalAPI. Gracias a él, su EA “sabe” qué funciones y modificadores están disponibles en la biblioteca, cómo llamar a estas funciones correctamente y qué valores devuelven.

Hay que colocar este archivo en la carpetaa C:\Program Files\MetaTrader 5\MQL5\Include, donde "C:\Program Files\MetaTrader 5\" - nombre de la carpeta que contiene su terminal MetaTrader 5. Después de copiar el archivo a la carpeta necesaria, Usted puede referirse a él en su programa MQL. Tendrá que hacerlo cada vez que necesite utilizar la biblioteca HedgeTerminalAPI en su trabajo. Para referirse al archivo Prototypes.mqh, tendrá que escribir en el código una directriz especial de inclusión de este archivo en su programa:

#include <Prototypes.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //...
   // Aquí se encuentra el contenido de su programa.
   //...
  }

En el ejemplo de arriba, esta directriz está marcada con el color amarillo y se llama "#include <Ptototypes.mqh>". Ahora el script de arriba puede referirse a las funciones de la biblioteca y utilizar la capacidad funcional en su trabajo.

Es importante comprender que en el proceso del desarrollo de la biblioteca HedgeTerminalAPI, el archivo de los prototipos puede sufrir algunos pequeños cambios. A menudo, con el lanzamiento de versiones actualizadas de la biblioteca, va a ser necesario actualizar también el archivo de los prototipos que va a describir los cambios realizados. Por favor, acoja este aspecto con comprensión. En cualquier caso, Usted siempre podrá instalar la versión reciente del archivo de prototipos desde la biblioteca manualmente (el procedimiento de la instalación se describe en el apartado 1.1), o descargar este archivo desde los adjuntos a este artículo (se supone la actualización periódica de los archivos adjuntos).

Chaos2.mqh - código fuente del Asesor Experto Chaos2 que ha sido descrito detalladamente en el aparatado 1.12: Ejemplo de trabajo con la función SendTradeRequest y la estructura HedgeTradeRequest tomando de ejemplo el EA "Chaos II". Para compilar este código correctamente, es necesario que el archivo de los prototipos de las funciones Prototypes.mqh se encuentre en la carpeta correspondiente \Include, y la biblioteca HedgeTerminalAPI se encuentre en la ruta: C:\Program Files\MetaTrader 5\MQL5\Market\hedgeterminalapi.ex5. Donde "C:\Program Files\MetaTrader 5\" - nombre de la carpeta (carpeta de datos del terminal) en la que está instalado su terminal MetaTrader 5.

El código fuente UnitedExchangeQuotes - un archivo zip especial (unitedexchangequotes.zip), que contiene el proyecto con el mismo nombre descrito detalladamente en el Capítulo 4: “Principios de la programación multihilo (multithreading) en el entorno MetaTrader”. Este archivo zip incluye los siguientes archivos:

Todos los archivos en este archivo contienen las rutas relativas. Por ejemplo, el archivo UnitedExchangeQuotes.mq5 se ubica en la carpeta \MQL5\Experts. Eso significa que hay que ubicarlo en la subcarpeta homónima de la carpeta de datos del terminal MetaTrader 5, por ejemplo en C:\Program Files\MetaTrader 5\MQL5\Experts\UnitedExchangeQuotes.mq5.


Conclusión

Hemos considerado detalladamente las cuestiones de trabajo con la interfaz de HedgeTerminal.

Ha sido mostrado que los principios de trabajo con esta biblioteca parecen mucho a los principios de trabajo con la API de MetaTrader 4. Igual que en éste, antes de empezar a trabajar con una transacción (análogo del concepto “orden” en MetaTrader 4), hay que seleccionarla usando la función TransactionSelect(). Normalmente, en Hedge Terminal la transacción es una posición bidireccional. Después de seleccionar una posición, se puede obtener sus propiedades o realizar una acción comercial con ella (por ejemplo, fijar Stop Loss o simplemente cerrarla). Este algoritmo de acciones es prácticamente idéntico al algoritmo de trabajo con las órdenes en el terminal MetaTrader 4.

Aparte de la información principal sobre el número de posiciones bidireccionales y sus propiedades, HedgeTerminal permite acceder a los valores que no están disponibles directamente en MetaTrader 5 y que requieren unos cálculos analíticos complejos. Por ejemplo, Usted puede ver el valos del deslizamiento de cada posición bidireccional sólo al solicitar una de sus propiedades. Se puede ver el número de operaciones que pertenecen a la posición seleccionada. Todos los cálculos y la combinación necesaria de las operaciones se realizan “entre bastidores” en el momento del inicio de HedgeTerminal. Es cómodo porque el EA no tiene que calcular nada. Toda la información necesaria ya ha sido calculada y está disponible a través de la API sencilla e intuitiva.

Debido a que la API y el panel HedgeTerminal utilizan los algoritmos comunes, es posible la representación unificada de los datos. Por consecuencia, se puede gestionar los EAs desde el panel de HedgeTerminal, y los cambios realizados por el EA se visualizarán en el panel.