Conversión de los cambios en el libro de órdenes

Si es necesario, un programa MQL puede generar un libro de órdenes para un símbolo personalizado utilizando la función CustomBookAdd. Esto, en concreto, puede ser útil para instrumentos procedentes de bolsas externas, como las criptomonedas.

int CustomBookAdd(const string symbol, const MqlBookInfo &books[], uint count = WHOLE_ARRAY)

La función transmite el estado del libro de órdenes a los programas MQL firmados para el symbol utilizando los datos del array books. El array describe el estado completo del libro de órdenes, es decir, todas las órdenes de compra y venta. El estado traducido sustituye completamente al anterior y pasa a estar disponible a través de la función MarketBookGet.

Utilizando el parámetro count, puede especificar el número de elementos del array books que se pasarán a la función. Por defecto se utiliza todo el array.

La función devuelve un indicador de éxito (true) o de error (false).

Para obtener los libros de órdenes generados por la función CustomBookAdd, un programa MQL que los requiera debe, como es habitual, suscribirse a los eventos utilizando MarketBookAdd.

La actualización de un libro de órdenes no actualiza los precios Bid y Ask del instrumento. Para actualizar los precios requeridos, añada ticks utilizando CustomTicksAdd.

Se comprueba que los datos transmitidos son correctos: los precios y volúmenes deben ser mayores que cero, y para cada elemento se debe especificar su tipo, precio y volumen (campos volume y/o volume_real). Si al menos un elemento del libro de órdenes se describe incorrectamente, la función devolverá un error.

También se comprueba el parámetro Book Depth (SYMBOL_TICKS_BOOKDEPTH) del instrumento personalizado. Si el número de niveles de venta o compra en el libro de órdenes traducido supera este valor, se descartan los niveles sobrantes.

El volumen con mayor precisión volume_real tiene prioridad sobre el volume normal. Si se especifican ambos valores para el elemento del libro de órdenes, se utilizará volume_real.

¡Atención! En la implementación actual, CustomBookAdd bloquea automáticamente el símbolo personalizado como si estuviera suscrito a él por MarketBookAdd, pero al mismo tiempo, los eventos de OnBookEvent no llegan (en teoría, el programa que genera los libros de órdenes puede suscribirse a ellos llamando explícitamente a MarketBookAdd y controlando lo que reciben otros programas). Puede eliminar este bloqueo llamando a MarketBookRelease.
 
Esto puede ser necesario debido al hecho de que los símbolos para los que existen suscripciones al libro de órdenes no pueden ocultarse de Observación de Mercado por ningún medio (hasta que se cancelen todas las suscripciones explícitas o implícitas de los programas, y se cierre la ventana del libro de órdenes). Por consiguiente, estos símbolos no pueden eliminarse.

A modo de ejemplo, vamos a crear un Asesor Experto no de trading PseudoMarketBook.mq5, que generará un pseudoestado del libro de órdenes a partir del historial de ticks más cercano. Esto puede ser útil para los símbolos para los que el libro de órdenes no está traducido, en particular para Forex. Si lo desea, puede utilizar estos símbolos personalizados para depurar formalmente sus propios algoritmos de trading utilizando el libro de órdenes.

Entre los parámetros de entrada, indicamos la profundidad máxima del libro de órdenes.

input uint CustomBookDepth = 20;

El nombre del símbolo personalizado se formará añadiendo el sufijo «.Pseudo» al nombre del símbolo del gráfico actual.

string CustomSymbol = _Symbol + ".Pseudo";

En el manejador OnInit creamos un símbolo personalizado y establecemos su fórmula con el nombre del símbolo original. Así, obtendremos una copia del símbolo original actualizada automáticamente por el terminal, y no tendremos que preocuparnos de copiar cotizaciones o ticks.

int OnInit()
{
   bool custom = false;
   if(!PRTF(SymbolExist(CustomSymbolcustom)))
   {
      if(PRTF(CustomSymbolCreate(CustomSymbolCustomPath_Symbol)))
      {
         CustomSymbolSetString(CustomSymbolSYMBOL_DESCRIPTION"Pseudo book generator");
         CustomSymbolSetString(CustomSymbolSYMBOL_FORMULA"\"" + _Symbol + "\"");
      }
   }
   ...

Si el símbolo personalizado ya existe, el Asesor Experto puede ofrecer al usuario borrarlo y completar el trabajo allí (el usuario debe cerrar primero todos los gráficos con este símbolo).

   else
   {
      if(IDYES == MessageBox(StringFormat("Delete existing custom symbol '%s'?",
         CustomSymbol), "Please, confirm"MB_YESNO))
      {
         PRTF(MarketBookRelease(CustomSymbol));
         PRTF(SymbolSelect(CustomSymbolfalse));
         PRTF(CustomRatesDelete(CustomSymbol0LONG_MAX));
         PRTF(CustomTicksDelete(CustomSymbol0LONG_MAX));
         if(!PRTF(CustomSymbolDelete(CustomSymbol)))
         {
            Alert("Can't delete "CustomSymbol", please, check up and delete manually");
         }
         return INIT_PARAMETERS_INCORRECT;
      }
   }
   ...

Una característica especial de este símbolo es el establecimiento de la propiedad SYMBOL_TICKS_BOOKDEPTH, así como la lectura del tamaño del contrato SYMBOL_TRADE_CONTRACT_SIZE, que será necesaria cuando se generen volúmenes.

   if(SymbolInfoInteger(_SymbolSYMBOL_TICKS_BOOKDEPTH) != CustomBookDepth
   && SymbolInfoInteger(CustomSymbolSYMBOL_TICKS_BOOKDEPTH) != CustomBookDepth)
   {
      Print("Adjusting custom market book depth");
      CustomSymbolSetInteger(CustomSymbolSYMBOL_TICKS_BOOKDEPTHCustomBookDepth);
   }
   
   depth = (int)PRTF(SymbolInfoInteger(CustomSymbolSYMBOL_TICKS_BOOKDEPTH));
   contract = PRTF(SymbolInfoDouble(CustomSymbolSYMBOL_TRADE_CONTRACT_SIZE));
   
   return INIT_SUCCEEDED;
}

El algoritmo se lanza en el manejador OnTick. Aquí llamamos a la función GenerateMarketBook que aún está por escribir. Llenará el array de estructuras MqlBookInfo pasadas por referencia, y la enviaremos a un símbolo personalizado usando CustomBookAdd.

void OnTick()
{
   MqlBookInfo book[];
   if(GenerateMarketBook(2000book))
   {
      ResetLastError();
      if(!CustomBookAdd(CustomSymbolbook))
      {
         Print("Can't add market books, "E2S(_LastError));
         ExpertRemove();
      }
   }
}

La función GenerateMarketBook analiza los últimos ticks de count y, a partir de ellos, emula el posible estado del libro de órdenes, guiándose por las siguientes hipótesis:

  • Lo que se ha comprado es probable que se venda;
  • Lo que se ha vendido es probable que se compre.

La división de ticks en aquellos que corresponden a compras y aquellos que corresponden a ventas, en el caso general (en ausencia de banderas de cambio) puede estimarse por el movimiento del precio en sí:

  • El movimiento del precio Ask al alza se trata como una compra;
  • El movimiento del precio Bid a la baja se trata como una venta.

Como resultado, obtenemos el siguiente algoritmo:

bool GenerateMarketBook(const int countMqlBookInfo &book[])
{
   MqlTick tick// order book centre
   if(!SymbolInfoTick(_Symboltick)) return false;
   
   double buys[];  // buy volumes by price levels
   double sells[]; // sell volumes by price levels
   
   MqlTick ticks[];
   CopyTicks(_SymbolticksCOPY_TICKS_ALL0count); // request tick history
   for(int i = 1i < ArraySize(ticks); ++i)
   {
      // we believe that ask was pushed up by buys
      int k = (int)MathRound((tick.ask - ticks[i].ask) / _Point);
      if(ticks[i].ask > ticks[i - 1].ask)
      {
         // already bought, probably will take profit by selling
         if(k <= 0)
         {
            Place(sells, -kcontract / sqrt(sqrt(ArraySize(ticks) - i)));
         }
      }
      
      // believe that the bid was pushed down by sells
      k = (int)MathRound((tick.bid - ticks[i].bid) / _Point);
      if(ticks[i].bid < ticks[i - 1].bid)
      {
         // already sold, probably will take profit by buying
         if(k >= 0)
         {
            Place(buyskcontract / sqrt(sqrt(ArraySize(ticks) - i)));
         }
      }
   }
   ...

La función de ayuda Place rellena los arrays buys y sells, acumulando volúmenes en ellos por niveles de precios. Lo mostraremos más abajo. Los índices de los arrays se definen como la distancia en puntos desde los mejores precios actuales (Bid o Ask). El tamaño del volumen es inversamente proporcional a la antigüedad del tick, es decir, los ticks más lejanos en el tiempo tienen menos efecto.

Una vez rellenados los arrays, se forma a partir de ellos un array de estructuras MqlBookInfo.

   for(int i = 0k = 0i < ArraySize(sells) && k < depth; ++i// top half of the order book
   {
      if(sells[i] > 0)
      {
         MqlBookInfo info = {};
         info.type = BOOK_TYPE_SELL;
         info.price = tick.ask + i * _Point;
         info.volume = (long)sells[i];
         info.volume_real = (double)(long)sells[i];
         PUSH(bookinfo);
         ++k;
      }
   }
   
   for(int i = 0k = 0i < ArraySize(buys) && k < depth; ++i// bottom half of the order book
   {
      if(buys[i] > 0)
      {
         MqlBookInfo info = {};
         info.type = BOOK_TYPE_BUY;
         info.price = tick.bid - i * _Point;
         info.volume = (long)buys[i];
         info.volume_real = (double)(long)buys[i];
         PUSH(bookinfo);
         ++k;
      }
   }
   
   return ArraySize(book) > 0;
}

La función Place es sencilla.

void Place(double &array[], const int indexconst double value = 1)
{
   const int size = ArraySize(array);
   if(index >= size)
   {
      ArrayResize(arrayindex + 1);
      for(int i = sizei <= index; ++i)
      {
         array[i] = 0;
      }
   }
   array[index] += value;
}

En la siguiente captura de pantalla se muestra un gráfico EURUSD con el Asesor Experto PseudoMarketBook.mq5 ejecutándose en él, y la versión resultante del libro de órdenes.

Libro de órdenes sintético de un símbolo personalizado basado en EURUSD

Libro de órdenes sintético de un símbolo personalizado basado en EURUSD