English Русский 中文 Deutsch 日本語 Português
Recetas MQL5 - Escribiendo nuestra propia profundidad de mercado

Recetas MQL5 - Escribiendo nuestra propia profundidad de mercado

MetaTrader 5Ejemplos | 29 enero 2016, 14:28
2 174 0
Vasiliy Sokolov
Vasiliy Sokolov

Índice


Introducción

El lenguaje MQL5 está en permanente desarrollo, y con cada año proporciona cada vez más posibilidades para trabajar con la información bursátil. Uno de esos tipos de datos bursátiles es la información sobre la profundidad de mercado en la bolsa. Se trata de un recuadro especial que muestra los niveles de precio y los volúmenes de las órdenes límite. MetaTrader 5 tiene incorporada su propia profundidad de mercado para representar las órdenes límite. En primer lugar, es necesario proporcionar a su asesor experto un acceso sencillo y cómodo a la profundidad de mercado. Por supuesto, en el lenguaje MQL5 existen varias funciones especiales para trabajar con este tipo de información, pero todas estas funciones tiene un nivel bastante bajo y necesitan de cálculos matemáticos adicionales.

No obstante, los cálculos intermedios son algo que se puede evitar. Todo lo necesario es escribir solo una vez una clase especial para trabajar con la profundidad de mercado. Todos esos cálculos complicados se realizarán dentro de ella, y la propia clase proporcionará métodos cómodos para trabajar con los precios y los niveles de la profundidad. Gracias a esta clase, será suficiente con crear simplemente un panel efectivo en forma de indicador, que representará de forma rápida el estado actual de los precios en la profundidad de mercado.


Fig. 1. Profundidad de mercado bursátil en forma de panel

Este artículo enseñará a los lectores a trabajar de forma programática con la profundidad de mercado, también describirá el principio de funcionamiento de la clase CMarketBook, que ampliará de forma orgánica la biblioteca estándar de clases MQL5 y proporcionará métodos cómodos para trabajar con la profundidad del mercado.

La primera parte del artículo mostrará de forma clara que la profundidad de mercado reglamentaria, proporcionada por MetaTrader 5, está dotada de capacidades impresionantes. No vamos a intentar duplicar todas estas numerosas capacidades en nuestro indicador, nuestra misión es otra. Vamos a mostrar de forma práctica, tomando como ejemplo la creación de un cómodo panel comercial, que los principios de la programación orientada a objetos permiten operar con bastante facilidad con estructuras de datos complejas. Comprobaremos que con ayuda de MQL5 no supondrá problemas obtener acceso a la profundidad de mercado directamente desde su propio experto y que, como resultado, podrá visualizar su representación de la forma que le sea más cómoda.

 

Capítulo 1. La profundidad de mercado estándar en MetaTrader 5 y los métodos para trabajar con ella


1.1. La profundidad de mercado estándar en MetaTrader 5

MetaTrader 5 da soporte al trabajo en bolsas centralizadas y proporciona herramientas de trabajo estándar para operar con la profundidad de mercado. En primer lugar, por supuesto, el propio recuadro de las órdenes límite, que desde hace poco tiempo posee un modo de representación ampliado. Para abrir la profundidad de mercado, es necesario antes que nada conectarse a una de las bolsas a las que da soporte MetaTrader 5, y elegir en el menú de contexto "Ver" --> "Profundidad de mercado" --> "Nombre del instrumento". Después aparecerá una ventana aparte, que combina un gráfico de ticks de precio y un recuadro de órdenes límite.

 

Fig. 2. La profundidad de mercado estándar en MetaTrader 5

La profundidad de mercado estándar en MetaTrader 5 dispone de una gran funcionalidad. Concretamente, permite representar:

  • órdenes límite de compra y venta, su nivel de precio y su volumen (aspecto estándar de la profundidad de mercado bursátil clásica);
  • el nivel actual de spread y los niveles de precio ocupados por órdenes límite (modo ampliado);
  • gráfico de ticks y visualización de los volúmenes de oferta, demanda y los últimos volúmenes de operación.
  • el nivel general de órdenes de compra y venta (se representan en forma de dos líneas en la parte baja y en la parte alta del gráfico de ticks, respectivamente).

En la lista adjunta se puede ver que las capacidades de la profundidad de mercado estándar son más que impresionantes. Pero mejor vamos a aclarar cómo trabajar con los datos, obteniendo acceso a ellos programáticamente. Antes que nada, hay que hacerse una idea de cómo está construida la profundidad de mercado bursátil, y de cómo se organizan los datos en ella. Hay información detallada sobre ello en el artículo "Principios de formación de precios en el mercado bursátil tomando de ejemplo la Sección de Derivados de la Bolsa de Moscú" en el apartado "1.3. Relación entre vendedores y compradores, profundidad del Mercado bursátil". No vamos a detenernos con detalle en la descripción del recuadro, porque suponemos que el elector ya tiene una idea bastante clara del mismo.

 

1.2. El modelo de eventos para trabajar con la profundidad de mercado

La profundidad de mercado es un recuadro de datos extremadamente dinámico. En los mercados rápidos, dinámicos, el recuadro de órdenes límite puede cambiar decenas de veces. Por eso es necesario intentar procesar solo la información que es realmente imprescindible para el procesamiento, de otra forma, la cantidad de datos transmitidos y la carga del procesador central durante procesesamiento de estos datos puede superar todos los límites razonables. Por eso precisamente, en MetaTrader 5 es necesario un modelo de eventos especial, que dificulta la recepción y el procesamiento de los datos que no se planea utilizar en realidad. Vamos a profundizar con detalle en este modelo. 

Cada evento en el mercado, al igual que la llegada de cada tick o la ejecución de una operación comercial, puede ser procesado llamando a la función correspondiente que se asocia con él. Por ejemplo, cuando llega un nuevo tick en MQL5, se llama a la función-manejador OnTick(). El cambio de tamaño del gráfico o de su posición llama a la función OnChartEvent(). Este modelo de eventos se hace extensible igualmente al cambio de la profundidad de mercado. Por ejemplo, si alguien emite una orden límite de venta o compra en la profundidad de mercado, entonces su estado cambiará, lo que llamará a la función especial OnBookEvent().

Dado que en el terminal están disponibles decenas e incluso cientos de símbolos diferentes, cada uno de los cuales posee su propia profundidad de mercado, la cantidad de llamadas de la función OnBookEvent puede ser enorme y con un consumo de recursos extremadamente elevado. Para que esto no suceda, se le debe notificar de antemano al terminal el momento de inicio del indicador o experto, y desde qué instrumentos precisamente es necesario recibir información sobre las cotizaciones de segundo nivel (así llaman también a la información mostrada en la profundidad de mercado). Para estos objetivos sirve la función especial de sistema MarketBookAdd. Por ejemplo, si queremos obtener información sobre la profundidad de mercado del instrumento Si-9.15 (contrato de futuros de dólar-rublo que expira en septiembre de 2015), necesitamos escribir en la función OnInit de nuestro experto o indicador el siguiente código:

void OnInit()
{
   MarketBookAdd("Si-9.15");
}

Con la ayuda de esta función, hemos realizado la llamada "suscripción", es decir, hemos notificado al terminal que el experto o indicador debe ser informado en caso de que cambie la profundidad de mercado en lo que respecta al instrumento Si-9.15. Además, los cambios de las profundidades de mercado no estarán a nuestro alcance, lo que reducirá considerablemente los recursos utilizados por el programa.

La función opuesta a MarketBookAdd es la función MarketBookRelease. Esta, al contrario, nos "da de baja" de las notificaciones sobre el cambio de la profundidad de mercado. Se considera recomendable para el programador realizar dicha baja en la sección OnDeinit, cerrando de esta forma la recepción de datos al cerrarse el experto o indicador:

void OnDeinit(const int reason)
{
   MarketBookRelease("Si-9.15");
}

La llamada de la función MarketBookAdd, en esencia, signifca que en el momento del cambio de la profundidad de mercado sobre el instrumento necesario, se llamará a la función-manejador especial OnBookEvent(). De esta forma, el experto o indicador más sencillo que trabaje con la profundidad de mercado, contendrá tres funciones de sistema:

  • OnInit — función de inicialización del experto o indicador en el que se realiza la suscripción para recibir eventos de cambio en la profundidad de mercado del instrumento necesario.
  • OnDeinit — función de desinicialización del experto o indicador en el que se realiza la baja de la suscripción para recibir eventos de cambio en la profundidad de mercado del instrumento necesario.
  • OnBookEvent — función llamada después de los cambios de la profundidad de mercado y que indica que la profundidad ha cambiado.

Nuestro primer experto más sencillo contendrá estas tres funciones. Por ejemplo, en el caso de abajo, el experto escribirá los mensajes correspondientes cada vez que cambie la profundidad de mercado: "La profundidad de mercado de Si-9.15 ha cambiado":

//+------------------------------------------------------------------+
//|                                                       Expert.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()
  {
//---
   MarketBookAdd("Si-9.15");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   MarketBookRelease("Si-9.15");
  }
//+------------------------------------------------------------------+
//| BookEvent function                                               |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
//---
   printf("La profundidad de mercado de " + symbol +  " ha sido modificada"); 
  }
//+------------------------------------------------------------------+

Las instrucciones clave en el código se destacan en color amarillo.

 

1.3. Obteniendo las cotizaciones de segundo nivel con ayuda de la función MarketBookGet y la estructura MqlBookInfo

Ahora que ya hemos aprendido a recibir notificaciones sobre el cambio del estado de la profundidad de mercado, ha llegado el momento de obtener acceso a la información de la profundidad de mercado. Para acceder a esta información, es necesario usar la función MarketBookGet. Vamos a estudiar su prototipo y uso.

Como ya se ha dicho anteriormente, la profundidad de mercado es un recuadro especial de órdenes límite, que consta de dos partes de manera convencional: las órdenes de venta y las órdenes de compra. Como cualquier otro recuadro, lo más fácil es representar la profundidad de mercado en forma de matriz, donde el ínidice de la matriz es el número de la línea del recuadro, y el valor de la matriz es una cierta línea o secuencia de datos, que incluye el volumen, el precio y el tipo de orden. Vamos a imaginar la profundidad de mercado como un recuadro en el que se indica el índice de cada línea en él:

Índice de la línea Tipo de orden Volumen Precio
0 Sell Limit 18 56844
1  Sell Limit  1  56843
2  Sell Limit  21  56842
3  Buy Limit  9  56836
4  Buy Limit  5  56835
5  Buy Limit  15  56834

 Recuadro 1. Representación de la profundidad de mercado en forma de recuadro

Para que sea más fácil orientarse dentro del recuadro, las órdenes de venta están coloreadas en rosa, y las de compra, en verde. El recuadro mostrado de la profundidad de mercado, en esencia, es una matriz bidimensional. En la primera dimensión se indica el número de la línea, y en la segunda, uno de los tres factores del recuadro (tipo de orden — 0, volumen de la orden — 1 y precio de la orden — 2). Sin embargo, para no trabajar con matrices multidimensionales, en MQL5 se usa la estructura especial MqlBookInfo. Incluye todos los valores necesarios. De esta forma, cada índice de la profundidad de mercado contiene la estructura MqlBookInfo, que a su vez contiene información sobre el tipo de orden, su volumen y precio. Vamos a dar una definición de la estructura:

struct MqlBookInfo
  {
   ENUM_BOOK_TYPE   type;       // tipo de orden de la enumeración ENUM_BOOK_TYPE
   double           price;      // precio
   long             volume;     // volumen
  };

Ahora debemos tener claro el método de trabajo con la profundidad de mercado. La función MarketBookGet retorna la matriz de las estructuras MqlBookInfo. El índice de la matriz indica la línea del recuadro de precios, y la propia estructura según el índice contiene la información sobre el volumen, el precio y el tipo de orden. Sabiendo esto, intentamos obtener acceso a la primera orden de la profundidad de mercado, para lo que modificamos un tanto la función OnBookEvent de nuestro experto del ejemplo anterior:

//+------------------------------------------------------------------+
//| BookEvent function                                               |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
//---
   //printf("La profundidad de mercado de " + symbol +  " ha sido modificada"); 
   MqlBookInfo book[];
   MarketBookGet(symbol, book);
   if(ArraySize(book) == 0)
   {
      printf("Failed load market book price. Reason: " + (string)GetLastError());
      return;
   }
   string line = "Price: " + DoubleToString(book[0].price, Digits()) + "; ";
   line += "Volume: " + (string)book[0].volume + "; ";
   line += "Type: " + EnumToString(book[0].type);
   printf(line);
  }

Tras iniciar el experto en cualquiera de los gráficos, recibiremos mensajes sobre el primer precio en la profundidad de mercado y sus parámetros:

2015.06.05 15:54:17.189 Expert (Si-9.15,H1)     Price: 56464; Volume: 56; Type: BOOK_TYPE_SELL
2015.06.05 15:54:17.078 Expert (Si-9.15,H1)     Price: 56464; Volume: 56; Type: BOOK_TYPE_SELL
2015.06.05 15:54:17.061 Expert (Si-9.15,H1)     Price: 56464; Volume: 56; Type: BOOK_TYPE_SELL
...

Mirando nuestro recuadro anterior, no resulta difícil adivinar que el nivel al que se accede con el índice cero de la profundidad corresponde a la peor oferta (BOOK_TYPE_SELL). Al contrario, el nivel de la peor demanda ocupa el último índice en la matriz obtenida. Los mejores precios de venta (Ask) y compra (Bid) se encuentran aproximadamente a mitad de la profundidad de mercado. La primera incomodidad de los precios obtenidos consiste en que en la profundidad de mercado el conteo se suele realizar a partir de los mejores precios, que se encuentran, normalmente, en la parte media del recuadro. Los precios de la peor oferta y demanda tienen un valor secundario. En lo sucesivo, al estudiar la clase CMarketBook, resolveremos este problema proporcionando los indexadores específicos, que son cómodos para el trabajo con la profundidad de mercado.

 

Capítulo 2. Clase CMarketBook para acceder y trabajar con facilidad en la profundidad de mercado


2.1. Diseñando la clase CMarketInfoBook

En el primer capítulo nos familiarizamos con las funciones de sistema, para trabajar con la profundidad de mercado, y también aclaramos las peculiaridades del modelo de eventos, para la organización del acceso a las cotizaciones de segundo nivel. En este capítulo crearemos una clase especial CMarketBook, para trabajar cómodamente con la profundidad de mercado estándar. En base a las tareas surgidas del primer capítulo, podemos discutir sobre qué propiedades debe poseer nuestra clase para trabajar con este tipo de datos.

Bien, lo primero que hay que tener en cuenta en el diseño de esta clase, es el gasto de recursos de los datos obtenidos. La profundidad de mercado puede actualizarse decenas de veces por segundo, y además contiene decenas de elementos del tipo MqlBookInfo. Por consiguiente, nuestra clase debe funcionar desde un principio solo con uno de los instruementos. En el caso de que sea necesario el procesamiento de varias profundidades de mercado simultáneamente, bastará con crear varios ejemplares de nuestra clase, indicando un instrumento en concreto:

CMarketBook("Si-9.15");            // Profundidad de mercado para Si-9.15
CMarketBook("ED-9.15");            // Profundidad de mercado para ED-9.15
CMarketBook("SBRF-9.15");          // Profundidad de mercado para SBRF-9.15
CMarketBook("GAZP-9.15");          // Profundidad de mercado para GAZP-9.15

Lo segundo de lo que deberemos preocuparnos es la organización de un acceso rápido a los datos. Dado que las órdenes límite generan un flujo de niveles de precio de alta frecuencia, no parece posible copiar la profundidad de mercado a un recuadro seguro orientado a objetos. Por eso, nuestra clase proporcionará un acceso directo, aunque no tan seguro, a la matriz MqlBookInfo, gracias a la función de sistema MarketBookGet. Por ejemplo, para acceder al índice cero de la profundidad de mercado con la ayuda de nuetra clase, es necesario escribir lo siguiente:

CMarketBook BookOnSi("Si-9.15");
...
//+------------------------------------------------------------------+
//| BookEvent function                                               |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
//---
   MqlBookInfo info = BookOnSi.MarketBook[0];
  }
//+------------------------------------------------------------------+

MarketBook, además, es una matriz obtenida directamente con la ayuda de la función MarketBookGet. Sin embargo, la comodidad de uso de nuestra clase consistirá, ante todo, en que, aparte del acceso directo a la matriz de las órdenes límite, nuestra clase permitirá dirigirnos concretamente a los precios usados con mayor dfrecuencia en la profundidad de mercado. Por ejemplo, para obtener el mejor precio de oferta, bastará con escribir lo siguiente:

double best_ask = BookOnSi.InfoGetDouble(MBOOK_BEST_ASK_PRICE);

Esto es bastante más cómodo que calcular el índice de la mejor oferta en su espectro, y después obtener los valores de precio por este índice. A partir del código descrito más arriba, podemos colegir que CMarketBook, al igual que otras muchas funciones de sistema de MQL5, del tipo de SymbolInfoDouble o OrderHistoryInteger, usa su propio paquete de modificadores y métodos InfoGetInteger y InfoGetDouble para acceder a los valores de tipo entero y doble, respectivamente. Para obtener la propiedad necesaria, es imprescindible indicar el modificador concreto de esta propiedad. Vamos a escribir detalladamente los modificadores de estas propiedades:

//+------------------------------------------------------------------+
//| Determina los modificadores para obtener las propiedades         |
//| de tipo entero de la profundidad de mercado.                     |
//+------------------------------------------------------------------+
enum ENUM_MBOOK_INFO_INTEGER
{
   MBOOK_BEST_ASK_INDEX,         // Índice del mejor precio de oferta (Ask)
   MBOOK_BEST_BID_INDEX,         // Índice del mejor precio de demanda (Bid) 
   MBOOK_LAST_ASK_INDEX,         // Índice del peor precio de oferta
   MBOOK_LAST_BID_INDEX,         // Índice del peor precio de demanda
   MBOOK_DEPTH_ASK,              // Cantidad de niveles de venta
   MBOOK_DEPTH_BID,              // Cantidad de niveles de compra
   MBOOK_DEPTH_TOTAL             // Cantidad total de niveles de la profundidad de mercado 
};
//+------------------------------------------------------------------+
//| Determina los modificadores para la obtención de las propiedades |
//| de tipo entero de la profundidad de mercado.                     |
//+------------------------------------------------------------------+
enum ENUM_MBOOK_INFO_DOUBLE
{
   MBOOK_BEST_ASK_PRICE,         // Mejor precio de oferta (Ask)
   MBOOK_BEST_BID_PRICE,         // Mejor precio de demanda (Bid)
   MBOOK_LAST_ASK_PRICE,         // Peor precio de oferta 
   MBOOK_LAST_BID_PRICE,         // Peor precio de demanda
   MBOOK_AVERAGE_SPREAD          // Spread medio entre Ask y Bid
};

Por supuesto que, aparte de los métodos InfoGet..., nuestra clase contendrá el método Refresh, que posibilita la actualización de la profundidad de mercado. Gracias a esto, nuestra clase necesita una actualización clara de la información con la llamada del método Refresh(), nosotros implicaremos las actualizaciones de clase con gran gasto de recursos solo cuando sea imprescindible.

 

2.2. Cálculo de los índices de los niveles más usados de la profundidad de mercado

La clase CMarketBook, en esencia, es una cubierta peculiar para la matriz MqlBookInfo. Su trabajo principal es proporcionar de forma rápida y cómoda la información requerida con mayor frecuencia de esta matriz. Por consiguiente, solo habrá dos operaciones con alto consumo de recursos implicadas en la clase:

  • el copiado de la matriz MqlBookInfo con la función de sistema MarketBookGet;
  • el cálculo de los índices de los precios usados con mayor frecuencia.

No podemos acelerar la función de sistema MarketBookGet, pero no es algo que nos haga falta, puesto que todas las funciones de sistema del lenguaje MQL5 están optimizadas al máximo. Lo que sí podemos hacer, es realizar un cálculo lo más rápido posible de los índices necesarios. Recurrimos de nuevo a los modificadores de propiedades ENUM_MBOOK_INFO_INTEGER y ENUM_MBOOK_INFO_DOUBLE. Como se puede ver, casi todas las propiedades disponibles se basan en el cálculo de cuatro índices:

  • índice de la mejor oferta (Ask); 
  • índice de la mejor demanda (Bid);
  • índice de la peor demanda;
  • índice de la peor oferta.

Asimismo, se usan tres propiedades enteras:

  • la cantidad de niveles de precio de venta o la profundidad mercado de venta (profundidad de la oferta);
  • la cantidad de niveles de precio de compra o la profundidad de mercado de compra (profundidad de la demanda);
  • la profundidad total de mercado, igual a la cantidad total de elementos en la profundidad de mercado.

Resulta obvio que el índice de la oferta siempre será igual a cero, puesto que la matriz obtenida con la ayuda de la función MarketBookGet comienza precisamente desde el peor precio de la oferta. Resulta igualmente trivial la determinación del índice de la peor demanda, siempre ocupará el último índice en la matriz MqlInfoBook obtenida (recordemos que el índice del último elemento en la matriz es menor en una unidad a la cantidad total de elementos de esta matriz) :

índice de la peor oferta = 0

índice de la peor demanda = igual a la cantidad total de elementos en la profundidad del mercado - 1

Los índices de las propiedades de tipo entero son igualmente fáciles de calcular. Bien, el número total de elementos en la profundidad de mercado siempre es igual a la cantidad de elementos en la matriz MqlBookInfo. El número de elementos en la profundidad de mercado por parte de la oferta es igual a:

la profundidad de la oferta = índice de la mejor oferta - índice de la mejor oferta + 1

Siempre añadimos una unidad, puesto que la numeración de la matriz empieza desde cero, y para determinar la cantidad de elementos se necesita añadir una unidad al mejor índice. Sin embargo, al añadir una unidad a la mejor oferta, ya obtenemos el índice de la mejor demanda. Por ejemplo, si en el recuadro 1 le añadimos una unidad a la línea con el segundo número, entonces pasaremos de la mejor orden Sell Limit, a un precio de 56 842, a la mejor Buy Limit, con un precio de 56 836. Asimismo, ya hemos aclarado que el índice de la peor oferta siempre es igual a cero. Por consiguiente, podemos reducir la fórmula para determinar la profundidad de la oferta hasta dejarla así:

profundidad de la oferta = índice de la mejor demanda

El cálculo de la profundidad de la demanda es un poco diferente. Resulta obvio que la cantidad de órdenes de compra es igual a la cantidad total de órdenes, menos la cantidad de órdenes de venta o la profundidad de la oferta. Puesto que ya aclaramos con la fórmula anterior que la profundidad de la oferta es igual al índice de la mejor demanda, no resulta difícil calcular la fórmula para determinar la cantidad de órdenes de compra o profundidad de la demanda:

la profundidad de la demanda = es la profundidad total de mercado - índice de la mejor oferta

La profundidad total del mercado siempre es igual a la suma de la profundidad de la demanda y la oferta y, por consiguiente, igual a la cantidad total de elementos en la profundidad de mercado:

la profundidad total de mercado = cantidad total de elementos en la profundidad de mercado

Así, hemos encontrado de forma analítica casi todos los índices usados con mayor frecuencia. Con la ayuda de reducciones matemáticas, hemos sustituido el cálculo de los índices por la indexación directa. En la clase CMarketBook esto es muy importante, dado que se necesita un acceso lo más rápido posible a las propiedades y a los índices de la profundidad de mercado.

Aparte de los propios índices, a veces resulta necesario saber el nivel medio del spread para el instrumento actual. El spread es la diferencia entre los precios de la mejor demanda y oferta. La clase CMarketBook permite obtener el valor medio de este índice con la ayuda del método InfoGetDouble, para lo cual es necesario llamarlo con la ayuda del modificador MBOOK_AVERAGE_SPREAD. CMarketBook realiza el cálculo del spread actual en el método Refresh y calcula su valor medio, recordando la cantidad de llamadas de este método.

Sin embargo, aún no hemos resuelto la tarea de encontrar los índices básicos de la mejor oferta y demanda, por eso vamos a pasar al siguiente apartado.

 

2.3. Predicción de los índices de la mejor oferta y demanda en base a los valores anteriores de estos

La tarea más compleja que nos queda por solucionar es el cálculo de los índices de la mejor oferta y la mejor demanda. Por ejemplo, en el recuadro 1, el índice con la mejor oferta será el índice número 2, y el índice con la mejor demanda, el índice 3. En este recuadro se muestra una profundidad de mercado simplificada, que consta de solo 6 niveles. En realidad, la profundidad de mercado puede tener bastantes más, y contener hasta 64 niveles de compra y venta. Se trata de una magnitud considerable, teniendo en cuenta que la actualización de la profundidad de mercado puede tener lugar varias veces por segundo.

El método más sencillo de búsqueda puede parecer el método de división por dos. En realidad, si tomamos la cantidad total de niveles en el recuadro 1, igual a 6, y la dividimos por dos, entonces la cifra obtenida (3), será el índice de nuestra demanda. Por consiguiente, el índice que lo precede será el índice de la mejor oferta (2). Sin embargo, el método funcionará solo en el caso de que la cantidad de niveles en la profundidad de venta sea igual a la cantidad de niveles de compra. Esto ocurre normalmente en los mercados líquidos, y con frecuencia así es, pero en los mercados no líquidos, la profundidad de mercado puede no estar llena por completo, y en uno de los lados puede que no haya ningún nivel.

Nuestra clase CMarketBook debe funcionar en cualquier mercado y con cualquier profundidad de mercado, por eso, ese sencillo método de la división por dos no nos viene bien. Para ilustrar la situación en la que el método de la división por dos puede no funcionar, recurriremos a la siguiente imagen:

 

Fig. 3. La cantidad de niveles de la demanda no siempre es igual a la cantidad de niveles de la oferta.

En la imagen 3 se muestran dos profundidades de mercado. La primera de ellas es la profundidad de un contrato de futuros de títulos de deuda federal de dos años (OFZ2-9.15). La segunda, es un contrato de futuros euro/dólar (ED-9.15). Resulta evidente que, para OFZ2-9.15, la cantidad de niveles de precio de los compradores es igual a cuatro, mientras que la cantidad de niveles de precio de los vendedores es igual a ocho. En el mercado ED-9.15, que es más líquido, la cantidad de niveles, tanto de los vendedores como de los compradores, es igual a 12 para cada uno de los lados. Se ve que, en el caso de ED-9.15, el método para determinar los índices de división entre dos funcionaría, mientras que en el caso de OFZ2, no.

Sería un método mucho más fiable para determinar el índice la iteración de la profundidad hasta el momento de la primera entrada de la operación con el tipo BOOK_TYPE_BUY. El índice anterior, en este caso, se convertiría en el índice de la mejor oferta. La clase CMarketBook posee precisamente ese método. Recurriremos a él para ilustrar lo dicho más arriba:

void CMarketBook::SetBestAskAndBidIndex(void)
{
   if(!FindBestBid())
   {
      //Find best ask by slow full search
      int bookSize = ArraySize(MarketBook);   
      for(int i = 0; i < bookSize; i++)
      {
         if((MarketBook[i].type == BOOK_TYPE_BUY) || (MarketBook[i].type == BOOK_TYPE_BUY_MARKET))
         {
            m_best_ask_index = i-1;
            FindBestBid();
            break;
         }
      }
   }
}

El trabajo principal de este método consiste en la iteración de la profundidad de mercado por parte del operador for. En cuanto en la profundidad de mercado aparece la primera orden con el tipo BOOK_TYPE_BUY, se establecen los índices de la mejor oferta y demanda, y la iteración se interrumpe. Pero la iteración completa de la profundidad de mercado en cada actualización sería una solución que gastaría demasiados recursos.

¡En lugar de realizar la iteración con cada actualización, se pueden guardar los índices de la mejor oferta y demanda encontrados con anterioridad! En realidad, la profundidad de mercado, normalmente, contiene una cantidad fija de niveles de venta y compra. Por eso no es necesario iterar la profundidad de mercado cada vez, en la busca de los índices que necesitamos. Basta con recurrir a los índices encontrados con anterioridad y comprender si siguen siendo los índices de la mejor oferta y demanda. De esto precisamente se ocupa el método privado FindBestBid. Ha llegado el momento de echar un vistazo a su contenido:

//+------------------------------------------------------------------+
//| Fast find best bid by best ask                                   |
//+------------------------------------------------------------------+
bool CMarketBook::FindBestBid(void)
{
   m_best_bid_index = -1;
   bool isBestAsk = m_best_ask_index >= 0 && m_best_ask_index < m_depth_total &&
                    (MarketBook[m_best_ask_index].type == BOOK_TYPE_SELL ||
                    MarketBook[m_best_ask_index].type == BOOK_TYPE_SELL_MARKET);
   if(!isBestAsk)return false;
   int bestBid = m_best_ask_index+1;
   bool isBestBid = bestBid >= 0 && bestBid < m_depth_total &&
                    (MarketBook[bestBid].type == BOOK_TYPE_BUY ||
                    MarketBook[bestBid].type == BOOK_TYPE_BUY_MARKET);
   if(isBestBid)
   {
      m_best_bid_index = bestBid;
      return true;
   }
   return false;
}

Su funcionamiento es sencillo. Ante todo, el método se asegura de que el índice actual de la mejor oferta se corresponda con el índice de la demanda. A continuación, se activa el índice de la demanda, e intenta encontrarlo de nuevo, recurriendo al elemento que sigue después del índice de la mejor oferta:

int bestBid = m_best_ask_index+1;

Si el elemento hallado realmente es el mejor índice de la demanda, significa que el anterior estado de la profundidad de mercado tenía la misma cantidad de niveles de vendedores y compradores que el actual. Gracias a esta iteración, se consigue evitar la profundidad de mercado, ya que en el método SetBestAskAndBidIndex se llama al método FindBestBid antes de la iteración. De esta forma, la iteración de la profundidad de mercado se realiza solo en el momento de la primera llamada de la función, así como en el caso de que cambie la cantidad de niveles de compra y/o venta.

Aunque el código fuente obtenido ha resultado más grande y complicado que la simple iteración de la profundidad de mercado, en realidad funcionará más rápido. Especialmente se notará la mejora de la productividad en las grandes profundidades de mercado de los mercados líquidos. Las instrucciones sencillas para comprobar la ejecución se realizan muy deprisa, y la propia cantidad de estas comprobaciones es significativamente menor a los ciclos del operador for. Por eso la productividad de este método de búsqueda de índices de los mejores precios será mayor a la iteración normal y corriente.

 

2.4. Determinando el deslizamiento límite con la ayuda del método GetDeviationByVol

Con frecuencia, la profundidad de mercado es utilizada por muchos tráders para determinar la liquidez actual en el mercado, es decir, la profundidad de mercado es usada por ellos como un instrumento adicional para controlar su propio riesgo. Y es que, si la liquidez en el mercado es muy baja, la entrada al mercado con la ayuda de órdenes de mercado puede provocar un gran deslizamiento. El deslizamiento siempre provoca pérdidas adicionales, y en ocasiones puede alcanzar magnitudes sustanciales.

Para que no se produzcan situaciones semejantes, es necesario usar métodos adicionales que controlen la entrada al mercado. Podrá encontrar información adicional en el artículo "Cómo proteger su Asesor Experto cuando tradea en la Bolsa de Moscú". Por eso no vamos a describir estos métodos, sino que solo diremos que al obtener acceso a la profundidad de mercado, podemos valorar la magnitud de su deslizamiento potencial antes de entrar al mercado. La magnitud del deslizamiento depende de dos factores:

  • de la liquidez actual de la parte de la demanda (para la venta) y de la oferta (para la compra);
  • del volumen de la operación realizada.

Teniendo acceso a la profundidad de mercado actual, podemos ver con qué volumen y a qué precios se ejecutará nuestra orden. Conociendo el volumen de nuestra orden, podemos calcular el precio promedio ponderado. La diferencia entre este precio y el precio de la mejor oferta o demanda (dependiendo de la dirección de la entrada) será precisamente nuestro deslizamiento.

Calcular el precio promedio ponderado de entrada no es posible manualmente, puesto que se necesita realizar una gran cantidad de cálculos en un tiempo muy breve (recordemos que el estado de la profundidad de mercado puede cambiar varias veces en un segundo). De esta forma, resulta natural delegar esta tarea en un asesor experto o en un indicador.

La clase CMarketBook contiene un método especial para calcular esta característica: GetDeviationByVol. Dado que la magnitud del deslizamiento influye en las operaciones, es necesario transmitir al método el volumen que se supone que se va a ejecutar en el mercado. Puesto que en el método se usa una aritmética de volúmenes de valor entero, como se adopta en el mercado urgente de la Bolsa de Moscú, el método adopta el volumen en forma de tipo entero long. Aparte de esto, el método necesita saber para qué lado de la liquidez se deben realizar los cálculos, para ello, se usa la enumeración especial ENUM_MBOOK_SIDE:

//+------------------------------------------------------------------+
//| Side of MarketBook.                                              |
//+------------------------------------------------------------------+
enum ENUM_MBOOK_SIDE
{
   MBOOK_ASK,                    // Ask side
   MBOOK_BID                     // Bid side
};

Vamos a mostrar el código fuente del método GetDeviationByVol:

//+------------------------------------------------------------------+
//| Get deviation value by volume. Return -1.0 if deviation is       |
//| infinity (insufficient liquidity)                                |
//+------------------------------------------------------------------+
double CMarketBook::GetDeviationByVol(long vol, ENUM_MBOOK_SIDE side)
{
   int best_ask = InfoGetInteger(MBOOK_BEST_ASK_INDEX);
   int last_ask = InfoGetInteger(MBOOK_LAST_ASK_INDEX); 
   int best_bid = InfoGetInteger(MBOOK_BEST_BID_INDEX);
   int last_bid = InfoGetInteger(MBOOK_LAST_BID_INDEX);
   double avrg_price = 0.0;
   long volume_exe = vol;
   if(side == MBOOK_ASK)
   {
      for(int i = best_ask; i >= last_ask; i--)
      {
         long currVol = MarketBook[i].volume < volume_exe ?
                        MarketBook[i].volume : volume_exe ;   
         avrg_price += currVol * MarketBook[i].price;
         volume_exe -= MarketBook[i].volume;
         if(volume_exe <= 0)break;
      }
   }
   else
   {
      for(int i = best_bid; i <= last_bid; i++)
      {
         long currVol = MarketBook[i].volume < volume_exe ?
                        MarketBook[i].volume : volume_exe ;   
         avrg_price += currVol * MarketBook[i].price;
         volume_exe -= MarketBook[i].volume;
         if(volume_exe <= 0)break;
      }
   }
   if(volume_exe > 0)
      return -1.0;
   avrg_price/= (double)vol;
   double deviation = 0.0;
   if(side == MBOOK_ASK)
      deviation = avrg_price - MarketBook[best_ask].price;
   else
      deviation = MarketBook[best_bid].price - avrg_price;
   return deviation;
}

Como podemos ver, el código ocupa un volumen significativo, pero su principio de cálculo no es complicado en absoluto. En primer lugar, se realiza la iteración de la profundidad de mercado del mejor precio hacia el peor. Para cada dirección relevante se realiza la iteración en su dirección. Durante la iteración, el volumen actual se añade al volumen total. Si el volumen total ha sido llamado y se corresponde con el volumen requerido, tiene lugar la salida del ciclo. Después se calcula el precio medio de entrada para el volumen establecido. Y al final, se calcula la diferencia entre el precio medio de entrada y el mejor precio de la demanda/oferta. La diferencia absoluta será precisamente la desviación estimada.

Este método necesita para el cálculo la iteración directa de la profundidad de mercado. Y aunque se itera solo una de las dos partes de la profundidad de mercado, y normalmente no lo hace por completo, de todas formas este cálculo exige de más tiempo que el cálculo de los índices usados con más frecuencia en la profundidad de mercado. Por eso el cálculo se realiza directamente en un método aparte y se efectúa en caso necesario, es decir, solo en aquellos casos cuando se necesita obtener esta información de una forma clara.

 

2.5. Ejemplos de funcionamiento de la clase CMarketBook

Bien, ya hemos analizado los métodos principales de la clase CMarketBook, y ha llegado el momento de practicar un poco su uso. Nuestro ejemplo de prueba es bastante claro y sencillo, incluso para los programadores principiantes. Escribiremos un asesor de prueba, que realice una sola muestra de la información actual de la profundidad de mercado. Por supuesto, para estos precios iría mejor un script, pero no es posible tener acceso a la profundidad de mercado a través de un script, por lo que será imprescindible usar o bien un asesor, o bien un indicador. El código fuente de nuestro asesor se muestra más abajo:

//+------------------------------------------------------------------+
//|                                               TestMarketBook.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"
#include <Trade\MarketBook.mqh>     // Incluimos la clase CMarketBook
CMarketBook Book(Symbol());         // Inicialiazamos la clase con el instrumento actual

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   PrintMbookInfo();
   return INIT_SUCCEEDED;
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Print MarketBook Info                                            |
//+------------------------------------------------------------------+
void PrintMbookInfo()
  {
   Book.Refresh();                  // Actualizamos el estado de la profundidad de mercado.
   /* Obtenemos la estadística principal */
   int total=Book.InfoGetInteger(MBOOK_DEPTH_TOTAL);
   int total_ask = Book.InfoGetInteger(MBOOK_DEPTH_ASK);
   int total_bid = Book.InfoGetInteger(MBOOK_DEPTH_BID);
   int best_ask = Book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);
   int best_bid = Book.InfoGetInteger(MBOOK_BEST_BID_INDEX);

   printf("PROFUNDIDAD TOTAL DEL MERCADO: "+(string)total);
   printf("CANTIDAD DE NIVELES DE PRECIO DE VENTA: "+(string)total_ask);
   printf("CANTIDAD DE NIVELES DE PRECIO DE COMPRA: "+(string)total_bid);
   printf("ÍNDICE DE LA MEJOR OFERTA: "+(string)best_ask);
   printf("ÍNDICE DE LA MEJOR DEMANDA: "+(string)best_bid);
   
   double best_ask_price = Book.InfoGetDouble(MBOOK_BEST_ASK_PRICE);
   double best_bid_price = Book.InfoGetDouble(MBOOK_BEST_BID_PRICE);
   double last_ask = Book.InfoGetDouble(MBOOK_LAST_ASK_PRICE);
   double last_bid = Book.InfoGetDouble(MBOOK_LAST_BID_PRICE);
   double avrg_spread = Book.InfoGetDouble(MBOOK_AVERAGE_SPREAD);
   
   printf("MEJOR PRECIO DE OFERTA: " + DoubleToString(best_ask_price, Digits()));
   printf("MEJOR PRECIO DE DEMANDA: " + DoubleToString(best_bid_price, Digits()));
   printf("PEOR PRECIO DE OFERTA: " + DoubleToString(last_ask, Digits()));
   printf("PEOR PRECIO DE DEMANDA: " + DoubleToString(last_bid, Digits()));
   printf("SPREAD MEDIO: " + DoubleToString(avrg_spread, Digits()));
  }
//+------------------------------------------------------------------+

Tras iniciar este experto de prueba en el gráfico OFZ2, obtenemos el informe siguiente:

2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   SPREAD MEDIO: 70
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   PEOR PRECIO DE DEMANDA: 9831
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   PEOR PRECIO DE OFERTA: 9999
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   MEJOR PRECIO DE DEMANDA: 9840
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   MEJOR PRECIO DE OFERTA: 9910
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   ÍNDICE DE LA MEJOR DEMANDA: 7
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   ÍNDICE DE LA MEJOR OFERTA: 6
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   CANTIDAD DE NIVELES DE PRECIO DE COMPRA: 2
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   CANTIDAD DE NIVELES DE PRECIO DE VENTA: 7
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   PROFUNDIDAD TOTAL DE MERCADO: 9

Comparemos el informe obtenido con la captura de pantalla de la profundidad de mercado según este instrumento:

 

Fig. 4. Profundidad de mercado de OFZ2 en el momento de inicio del informe de prueba

Como podemos ver, los índices y precios obtenidos se corresponden completamente con la profundidad de mercado actual, que era lo que necesitábamos comprobar. 

 

Capítulo 3. Escribiendo nuestra propia profundidad de mercado en forma de panel indicador


3.1. Principios generales del diseño del panel de la profundidad de mercado. Creando el indicador

Teniendo a su disposición la clase  CMarketBook, bastará con crear un panel especial que represente la profundidad de mercado actual directamente en el gráfico. Crearemos un nuevo panel basándonos en el indicador de usuario. Hemos elegido el uso del indicador como base porque en cada gráfico puede encontrarse solo un asesor experto, mientras que puede haber una cantidad ilimitada de indicadores. Si tomásemos un experto como base para el panel, entonces no podríamos comerciar con la ayuda del experto en este mismo gráfico, lo cual resultaría muy incómodo.

Proporcionaremos a nuestra profundidad de mercado la posibilidad de mostrarse y ocultarse en el gráfico, de forma similar a la implementada en el panel comercial estándar para cada gráfico. Usaremos el mismo botón para mostrarlo y ocultarlo:

 

Fig. 5. Panel comercial estándar en MetaTrader 5

Nuestra profundidad de mercado se ubicará en la esquina superior izquierda del gráfico, en el mismo sitio donde se encuentra el panel comercial. Esto está relacionado con el hecho de que en el gráfico se puede iniciar un asesor experto con el indicador de la profundidad de mercado. Para no tapar su icono, que se encuentra en la esquina superior derecha, ubicaremos nuestro panel a la izquierda.

Al crear el indicador, es necesario usar una de las dos funciones de sistema OnCalculate. Puesto que nuestro panel usa la información recibida en estas funciones, dejaremos estos métodos vacíos. El indicador tampoco usará ni una sola serie gráfica, por eso la propiedad indicator_plots será igual a cero en nuestro caso.

La función de sistema OnBookEvent será la principal a la hora de usar nuestro indicador. Para ello será necesario suscribir nuestro símbolo actual del gráfico para que reciba los eventos sobre el cambio de la profundidad de mercado. La suscripción la realizaremos con la ayuda de la función MarketBookAdd, que ya conocemos.

El panel de la profundidad de mercado se ha implementado en forma de clase especial CBookPanel. Ahora, sin entrar en los detalles de esta clase, mostraremos el código del archivo inicial del indicador:

//+------------------------------------------------------------------+
//|                                                   MarketBook.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"
#property indicator_chart_window
#property indicator_plots 0
#include <Trade\MarketBook.mqh>
#include "MBookPanel.mqh"

CBookPanel Panel;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   MarketBookAdd(Symbol());
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| MarketBook change event                                          |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
   Panel.Refresh();
  }
//+------------------------------------------------------------------+
//| Chart events                                                     |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // identificador de evento  
                  const long& lparam,   // parámetro del evento del tipo long
                  const double& dparam, // parámetro del evento del tipo double
                  const string& sparam) // parámetro del evento del tipo string
  {
   Panel.Event(id,lparam,dparam,sparam);
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Ahora la clase CBookPanel contiene solo los elementos básicos para su funcionamiento: una flecha cuya pulsación hará que en el futuro aparezca nuestra profundidad de mercado, y la inscripción "MarketBook", que suscribirá a nuestra futura profundidad de mercado. Al iniciar nuestro indicador, el gráfico tendrá el aspecto siguiente:

 

Fig. 6. Ubicación del futuro panel MarketBook en el gráfico

Cada elemento de esta clase es igualmente una clase independiente, derivada de la clase básica CNode. Esta clase contiene métodos básicos como Show y Hide, que se pueden definir en las clases heredadas. Asimismo, la clase CNode genera un nombre único para cada ejemplar, lo que hace más cómodo el uso de las funciones estándar para crear objetos gráficos e indicar sus propiedades.

 

3.2. Procesando los eventos de pulsación y creación de la forma de la profundidad de mercado

Ahora nuestro indicador no reacciona a la pulsación de la flecha, por eso continuamos elaborándolo. Lo primero que se debe hacer para nuestro panel es introducir el procesador de eventos OnChartEvent. Llamaremos a este método Event. Aplicará los parámetros recibidos de OnChartEvent. Asimismo, ampliaremos la clase básica CNode, proveyéndola con la matriz CArrayObj, en la que se guardarán otros elementos gráficos del tipo CNode. En el futuro esto ayudará a crear multitud de elementos del mismo tipo: las celdas de la profundidad del mercado.

Ahora vamos a mostrar el código fuente de la clase CBookPanel y su clase progenitora CNode: 

//+------------------------------------------------------------------+
//|                                                   MBookPanel.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#include <Trade\MarketBook.mqh>
#include "Node.mqh"
#include "MBookText.mqh"
#include "MBookFon.mqh"
//+------------------------------------------------------------------+
//| CBookPanel class                                                 |
//+------------------------------------------------------------------+
class CBookPanel : CNode
  {
private:
   CMarketBook       m_book;
   bool              m_showed;
   CBookText         m_text;
public:
   CBookPanel();
   ~CBookPanel();
   void              Refresh();
   virtual void Event(int id, long lparam, double dparam, string sparam);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CBookPanel::CBookPanel()
{
   m_elements.Add(new CBookFon(GetPointer(m_book)));
   ObjectCreate(ChartID(), m_name, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, 70);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, -3);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack);
   ObjectSetString(ChartID(), m_name, OBJPROP_FONT, "Webdings");
   ObjectSetString(ChartID(), m_name, OBJPROP_TEXT, CharToString(0x36));
}
CBookPanel::~CBookPanel(void)
{
   OnHide();
   m_text.Hide();
   ObjectDelete(ChartID(), m_name);
}

CBookPanel::Refresh(void)
{

}


CBookPanel::Event(int id, long lparam, double dparam, string sparam)
{
   switch(id)
   {
      case CHARTEVENT_OBJECT_CLICK:
      {
         if(sparam != m_name)return;
         if(!m_showed)OnShow();        
         else OnHide();
         m_showed = !m_showed;
      }
   }
}
//+------------------------------------------------------------------+

El método Refresh, que actualiza el estado de la profundidad de mercado, aún no está lleno. Lo crearemos un poco más tarde. El funcional actual ya permite mostrarnos el primer prototipo de nuestra profundidad de mercado. Por el momento, al pulsar la flecha solo se muestra el marco gris estándar. Si se pulsa de nuevo, desaparece:

 

Fig. 7. Aspecto exterior de la futura profundidad de mercado

Por el momento, la profundidad de mercado no tiene un aspecto muy convincente, pero vamos a continuar desarrollándola.

 

3.3. Celdas de la profundidad de mercado.

La base de la profundidad de mercado son las celdas. Cada celda es un elemento del recuadro, y contiene información sobre el volumen o el precio. Asimismo, las celdas se distinguen por su color: las órdenes límite de compra adoptan un color azul, las órdenes límite de venta, un color rosa. Para cada profundidad de mercado, la cantidad de celdas puede ser diferente, por consiguiente, todas las celdas deben ser creadas de forma dinámica, según la necesidad, y guardadas en el contenedor especial de datos CArrayObj. Puesto que todas las celdas, independientemente de lo que muestren, tienen el mismo tamaño y tipo, entonces la clase, que implementa diferentes tipos de celdas, será la misma para todos los tipos de celdas.

Tanto para las celdas relacionadas con el volumen, como para las que muestran el precio, se usará la clase especial CBookCeil. El tipo de celda se muestra durante la creación del objeto de esta clase, cada ejemplar de la clase sabrá qué información precisamente de la profundidad de precio deberá representar, y de qué color deberá representar el fondo. CBookCeil usará dos figuras primitivas: la etiqueta de texto OBJ_TEXT_LABEL y la etiqueta rectangular OBJ_RECTANBLE_LABEL. La primera mostrará un texto, la segunda, la propia celda de la profundidad de mercado.

Mostramos aquí el código fuente de la clase CBookCeil:

//+------------------------------------------------------------------+
//|                                                   MBookPanel.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#include "Node.mqh"
#include <Trade\MarketBook.mqh>
#include "Node.mqh"
#include "MBookText.mqh"

#define BOOK_PRICE 0
#define BOOK_VOLUME 1

class CBookCeil : public CNode
{
private:
   long  m_ydist;
   long  m_xdist;
   int   m_index;
   int m_ceil_type;
   CBookText m_text;
   CMarketBook* m_book;
public:
   CBookCeil(int type, long x_dist, long y_dist, int index_mbook, CMarketBook* book);
   virtual void Show();
   virtual void Hide();
   virtual void Refresh();
   
};

CBookCeil::CBookCeil(int type, long x_dist, long y_dist, int index_mbook, CMarketBook* book)
{
   m_ydist = y_dist;
   m_xdist = x_dist;
   m_index = index_mbook;
   m_book = book;
   m_ceil_type = type;
}

void CBookCeil::Show()
{
   ObjectCreate(ChartID(), m_name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, m_xdist);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_FONTSIZE, 9);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   m_text.Show();
   m_text.SetXDist(m_xdist+10);
   m_text.SetYDist(m_ydist+2);
   Refresh();
}

void CBookCeil::Refresh(void)
{
   ENUM_BOOK_TYPE type = m_book.MarketBook[m_index].type;
   if(type == BOOK_TYPE_BUY || type == BOOK_TYPE_BUY_MARKET)
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrCornflowerBlue);
   else if(type == BOOK_TYPE_SELL || type == BOOK_TYPE_SELL_MARKET)
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrPink);
   else
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrWhite);
   MqlBookInfo info = m_book.MarketBook[m_index];
   if(m_ceil_type == BOOK_PRICE)
      m_text.SetText(DoubleToString(info.price, Digits()));
   else if(m_ceil_type == BOOK_VOLUME)
      m_text.SetText((string)info.volume);
}

void CBookCeil::Hide(void)
{
   OnHide();
   m_text.Hide();
   ObjectDelete(ChartID(),m_name);
}

El trabajo principal de esta clase lo realizan los métodos Show y Refresh. El último, dependiendo del tipo de celda transmitido, la colorea de un color u otro y muestra en ella o bien el volumen, o bien el precio. Para crear una celda, es necesario indicar su tipo, la ubicación conforme al eje X, su ubicación con respecto al eje de Y, el índice de la profundidad de mercado que corresponde a esta celda, y la propia profundidad de mercado desde donde la celda recibirá la información.

Las celdas serán creadas por el método especial privado CreateCeils, en la clase que implemente el substrato de la profundidad de mercado. Vamos a mostrar su código fuente:

void CBookFon::CreateCeils()
{
   int total = m_book.InfoGetInteger(MBOOK_DEPTH_TOTAL);
   for(int i = 0; i < total; i++)
   {
      CBookCeil* Ceil = new CBookCeil(0, 12, i*15+20, i, m_book);
      CBookCeil* CeilVol = new CBookCeil(1, 63, i*15+20, i, m_book);
      m_elements.Add(Ceil);
      m_elements.Add(CeilVol);
      Ceil.Show();
      CeilVol.Show();
   }
}

Este se llamará al pulsar la flecha que abre la profundidad de mercado.

Ahora todo está listo para crear nuestra nueva versión de la profundidad de mercado. Después de introducir los cambios y de compilar el proyecto, nuestro indicador tendrá el aspecto siguiente:

 

Fig. 8. Primera versión de la profundidad de mercado, en forma de indicador

3.4. Representando el histograma de los volúmenes en la profundidad de mercado

La profundidad de mercado obtenida ya cumple con la función principal: muestra los niveles comerciales, el volumen y los precios de las órdenes límite de venta y compra. Además, cada cambio de los valores en la profundidad de mercado también cambia los valores en las celdas correspondientes. Sin embargo, no resulta fácil realizar un seguimiento visual de la magnitud de los volúmenes en el recuadro obtenido. Por ejemplo, en la profundidad de mercado estándar de MetaTrader 5, el volumen se muestra con el histograma de fondo, que muestra la magnitud relativa del volumen actual con respecto al volumen máximo en la profundidad de mercado. Asimismo, no estaría de más implementar un funcional similar en nuestra profundidad de mercado.

Podemos resolver esta tarea con métodos diferentes. La solución más simple sería hacer todos los cálculos necesarios directamente en la clase CBookCeil. Para ello, en su método Refresh hay que escribir los siguiente:

void CBookCeil::Refresh(void)
{
   ...
   MqlBookInfo info = m_book.MarketBook[m_index];
   ...
   //Actualizamos el histograma de la profundidad de mercado
   int begin = m_book.InfoGetInteger(MBOOK_LAST_ASK_INDEX);
   int end = m_book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);
   long max_volume = 0;
   if(m_ceil_type != BOOK_VOLUME)return;
   for(int i = begin; i < end; i++)
   {
      if(m_book.MarketBook[i].volume > max_volume)
         max_volume = m_book.MarketBook[i].volume;
   }
   double delta = 1.0;
   if(max_volume > 0)
      delta = (info.volume/(double)max_volume);
   long size = (long)(delta * 50.0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, size);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
}

En el método, mediante la iteración directa de la profundidad de mercado, encontramos el volumen máximo, después se divide el volumen actual entre el máximo. La parte obtenida se multiplica por la anchura máxima de la celda del recuadro de volumen (está representada por una constante de 50 píxeles). La anchura obtenida del substrato será precisamente el histograma buscado:

 

Fig. 9. Profundidad de mercado con histograma de volúmenes

Sin embargo, el problema de este código es que con cada llamada de Refresh se realiza una iteración completa de la profundidad de mercado en cada celda. Para una profundidad de mercado con un tamaño de cuarenta elementos, esto significa 800 iteraciones del ciclo for por cada actualización de la profundidad de mercado. Cada celda realiza la iteración de la profundidad de mercado para su lado, por eso la iteración dentro de cada celda constará de veinte iteraciones (la profundidad de mercado, dividida por dos). Aunque las computadoras actuales se las apañan con esta tarea, se trata de un método de trabajo demasiado inefectivo, especialmente teniendo en cuenta que, a la hora de trabajar con la profundidad de mercado, hay que hacerlo con los algoritmos más rápidos y efectivos.

 

3.5. Cálculo rápido de los volúmenes máximos en la profundidad de mercado, optimización de la iteración

Por desgracia, la iteración completa de la profundidad de mercado es algo de lo que no podemos deshacernos. Después de cada actualización de la profundidad de mercado, el volumen máximo y su nivel de precio puede cambiar de forma cardinal. Sin embargo, podemos minimizar la cantidad de iteraciones. Para ello, hay que aprender a hacer la iteración de la profundidad de mercado no más de una vez entre dos llamadas de Refresh. Lo segundo que debemos hacer es minimizar la propia cantidad de llamadas de la iteración completa. Para ello, es necesario posponer los cálculos o, en otras palabras, ejecutar estos cálculos en el caso de que sean claramente necesarios. Todos los cálculos los trasladaremos directamente a la clase de la profundidad de mercado CMarketBook, para lo que escribiremos la subclase especial de cálculo CBookCalculation, que se encuentra en el mercado CMarketBook. Aquí abajo mostramos su código fuente: 

class CMarketBook;

class CBookCalculation
{
private:
   int m_max_ask_index;         // Índice del volumen máximo de la oferta
   long m_max_ask_volume;       // Volumen de la oferta máxima
   
   int m_max_bid_index;         // Índice del volumen máximo de la demanda
   long m_max_bid_volume;       // Volumen de la demanda máxima
   
   long m_sum_ask_volume;       // Volumen total de la oferta en la profundidad de mercado
   long m_sum_bid_volume;       // Volumen total de la demanda en la profundidad de mercado.
   
   bool m_calculation;          // Etiqueta que indica que todos los cálculos necesarios han sido realizados
   CMarketBook* m_book;         // Índice de la profundidad de mercado
   
   void Calculation(void)
   {
      // FOR ASK SIDE
      int begin = (int)m_book.InfoGetInteger(MBOOK_LAST_ASK_INDEX);
      int end = (int)m_book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);
      for(int i = begin; i < end; i++)
      {
         if(m_book.MarketBook[i].volume > m_max_ask_volume)
         {
            m_max_ask_index = i;
            m_max_ask_volume = m_book.MarketBook[i].volume;
         }
         m_sum_ask_volume += m_book.MarketBook[i].volume;
      }
      // FOR BID SIDE
      begin = (int)m_book.InfoGetInteger(MBOOK_BEST_BID_INDEX);
      end = (int)m_book.InfoGetInteger(MBOOK_LAST_BID_INDEX);
      for(int i = begin; i < end; i++)
      {
         if(m_book.MarketBook[i].volume > m_max_bid_volume)
         {
            m_max_bid_index = i;
            m_max_bid_volume = m_book.MarketBook[i].volume;
         }
         m_sum_bid_volume += m_book.MarketBook[i].volume;
      }
      m_calculation = true;
   }
   
public:
   CBookCalculation(CMarketBook* book)
   {
      Reset();
      m_book = book;
   }
   
   void Reset()
   {
      m_max_ask_volume = 0.0;
      m_max_bid_volume = 0.0;
      m_max_ask_index = -1;
      m_max_bid_index = -1;
      m_sum_ask_volume = 0;
      m_sum_bid_volume = 0;
      m_calculation = false;
   }
   int GetMaxVolAskIndex()
   {
      if(!m_calculation)
         Calculation();
      return m_max_ask_index;
   }
   
   long GetMaxVolAsk()
   {
      if(!m_calculation)
         Calculation();
      return m_max_ask_volume;
   }
   int GetMaxVolBidIndex()
   {
      if(!m_calculation)
         Calculation();
      return m_max_bid_index;
   }
   
   long GetMaxVolBid()
   {
      if(!m_calculation)
         Calculation();
      return m_max_bid_volume;
   }
   long GetAskVolTotal()
   {
      if(!m_calculation)
         Calculation();
      return m_sum_ask_volume;
   }
   long GetBidVolTotal()
   {
      if(!m_calculation)
         Calculation();
      return m_sum_bid_volume;
   }
};

La iteración completa de la profundidad de mercado y los cálculos en los que se invierten más recursos se encuentran ocultos dentro del método privado Calculate. Se llama solo en el caso de que la etiqueta del cálculo m_calculate sea reseteada al estado false. El reseteo de esta etiqueta tiene lugar solo en un sitio: en el método Reset. Puesto que la clase ha sido creada exclusivamente para trabajar dentro de la clase CMarketBook, solo esta clase tiene acceso a ella.

Después de actualizar la profundidad de mercado, el método Refresh de la clase CMarketBook resetea el estado del módulo de cálculo, llamando a su método Reset. Gracias a esto, la iteración completa de la profundidad de mercado sucede no más de una vez entre dos actualizaciones. Asimismo, se usa la ejecución pendiente. En otras palabras, el método Calculate de la clase CBookCalcultae se llama solo en el caso de que suceda una llamada clara de uno de sus seis métodos de acceso común.

Aparte de encontrar el volumen, se han añadido a la clase que realiza la iteración completa de la profundidad de mercado los campos que contienen el volumen total de órdenes límite de venta y compra. Estos parámetros no necesitarán tiempo adicional para el cálculo, ya que son calculados en el ciclo común de la matriz.

Ahora, en lugar de la iteración permanente de la profundidad de mercado, se usa una iteración inteligente, según se necesite. Esto acorta sustancialmente los recursos usados y hace el trabajo con la profundidad de mercado algo más rápido y eficaz.

 

3.6. Últimos flecos: histograma de volúmenes y línea divisoria

Prácticamente hemos finalizado el trabajo de creación de nuestro indicador. La necesidad práctica de encontrar el volumen máximo nos ha ayudado a crear un método efectivo de cálculo economizador de los índices necesarios. Si en lo sucesivo queremos añadir nuevos parámetros de cálculo a nuestra profundidad de mercado, será algo muy sencillo de hacer: bastará con ampliar nuestra clase CBookCalculate con los métodos existentes e introducir los modificadores correspondientes en las enumeraciones ENUM_MBOOK_INFO_INTEGER и ENUM_MBOOK_INFO_DOUBLE.

Ahora solo nos queda poner en práctica el trabajo realizado y reescribir el método Refresh para cada celda:

void CBookCeil::Refresh(void)
{
   ENUM_BOOK_TYPE type = m_book.MarketBook[m_index].type;
   long max_volume = 0;
   if(type == BOOK_TYPE_BUY || type == BOOK_TYPE_BUY_MARKET)
   {
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrCornflowerBlue);
      max_volume = m_book.InfoGetInteger(MBOOK_MAX_BID_VOLUME);
   }
   else if(type == BOOK_TYPE_SELL || type == BOOK_TYPE_SELL_MARKET)
   {
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrPink);
      max_volume = m_book.InfoGetInteger(MBOOK_MAX_ASK_VOLUME); //El volumen ya se ha calculado anteriormente, no tendrá lugar una nueva iteración
   }
   else
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrWhite);
   MqlBookInfo info = m_book.MarketBook[m_index];
   if(m_ceil_type == BOOK_PRICE)
      m_text.SetText(DoubleToString(info.price, Digits()));
   else if(m_ceil_type == BOOK_VOLUME)
      m_text.SetText((string)info.volume);
   if(m_ceil_type != BOOK_VOLUME)return;
   double delta = 1.0;
   if(max_volume > 0)
      delta = (info.volume/(double)max_volume);
   long size = (long)(delta * 50.0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, size);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
}

Desde el punto de vista visual, nuestro indicador funciona igual que la versión anterior, sin embargo, en realidad la velocidad de cálculo del histograma ha aumentado sustancialmente. Precisamente en esto consiste el arte de la programación: en crear usos efectivos y sencillos de los algoritmos, ocultando la complejidad de su implementación dentro de los métodos privados de los módulos (clases) existentes.

Con la aparición del histograma de volúmenes ahora no se entiende muy bien dónde se encuentra la línea que separa los volúmenes de la demanda y de la oferta. Por eso hemos creado una línea que cumpla esa función, completando la clase CBookPanel con la subclase especial CBookLine, que implementa esta línea:

class CBookLine : public CNode
{
private:
   long m_ydist;
public:
   CBookLine(long y){m_ydist = y;}
   virtual void Show()
   {
      ObjectCreate(ChartID(),     m_name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, 13);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_YSIZE, 3);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, 108);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrBlack);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   }
};

Se trata de una clase muy sencilla, que prácticamente solo define su posición. La posición de la línea con respecto al eje Y se debe calcular en el momento de su creación en el método Show. Hacer esto es bastante sencillo, conociendo el índice de la mejor oferta:

long best_bid = m_book.InfoGetInteger(MBOOK_BEST_BID_INDEX);
long y = best_bid*15+19;

En este caso, el índice de la celda best_bid se multiplica por la anchura de cada celda (15 píxeles), y se le añade una constante adicional de 19 píxeles.

Nuestra nueva profundidad de mercado, al fin, ha adquirido el aspecto exterior y el funcional mínimo con el que se convierte en algo agradable de usar. Por supuesto, se podrían hacer otras muchas cosas más. Si así lo deseamos, podemos aproximar la funcionalidad de nuestro indicador a la de la profundidad de mercado estándar de MetaTrader 5.

Pero ese no es el motivo principal de este artículo. El panel de la profundidad de mercado solo se ha creado con el objetivo de mostrar las capacidades de la clase CMarketBook. Ayudará a hacer esta clase más rápida, mejor y más funcional, y por eso ha cumplido su misión con creces. Mostraremos un pequeño vídeo en el que se podrá ver el trabajo que hemos realizado. Más abajo tenemos la dinámica de nuestro panel de la profundidad de mercado:



3.7. Complementando las porpiedades de la profundidad de mercado con información sobre las órdenes límite de un instrumento

Uno de los rasgos distintivos de la Bolsa de Moscú es la retransmisión en tiempo real de la información sobre la cantidad total de órdenes límite. Este artículo está dedicado a la profundidad de mercado como tal, al margen de cualquier mercado. No obstante, esta información, aunque es en verdad específica (propia de una plataforma comercial concreta), está disponible a nivel sistémico en el terminal. Además, amplía los datos proporcionados por la profundidad de mercado. Por eso se decidió ampliar la enumeración de los modificadores de propiedades e incluir el soporte de estas propiedades directamente en la clase de la profundidad de mercado CMarketBook.

Así, la Bolsa de Moscú proporciona la siguiente información en tiempo real:

  • número total de órdenes límite de venta, establecidas según un instrumento en el momento actual;
  • número total de órdenes límite de compra, establecidas según un instrumento en el momento actual;
  • volumen total de todas las órdenes límite de venta, establecidas según un instrumento en el momento actual;
  • volumen total de todas las órdenes límite de compra, establecidas según un instrumento en el momento actual;
  • cantidad de posiciones abiertas o interés abierto (solo para los mercados de futuros).

Aunque el interés abierto está relacionado directamente con la cantidad de órdenes límite en el mercado (es decir, con su liquedez actual), no obstante, esta información se exige a veces de forma conjunta con la información sobre órdenes límite, por eso el acceso a ella a través de la clase CMarketBook parace igualmente conveniente. Para acceder a esta información, es necesario usar las funciones SymbolInfoInteger y SymbolInfoDouble. Sin embargo, para que estos datos estén disponibles en un mismo lugar, ampliaremos nuestra clase de profundidad de mercado con enumeraciones y cambios adicionales en sus funciones InfoGetInteger y InfoGetDouble:

long CMarketBook::InfoGetInteger(ENUM_MBOOK_INFO_INTEGER property)
{
   switch(property)
   {
      ...
      case MBOOK_BUY_ORDERS:
         return SymbolInfoInteger(m_symbol, SYMBOL_SESSION_BUY_ORDERS);
      case MBOOK_SELL_ORDERS:
         return SymbolInfoInteger(m_symbol, SYMBOL_SESSION_SELL_ORDERS);
      ...
   }
   return 0;
}

 

double CMarketBook::InfoGetDouble(ENUM_MBOOK_INFO_DOUBLE property)
{
   switch(property)
   {
      ...
      case MBOOK_BUY_ORDERS_VOLUME:
         return SymbolInfoDouble(m_symbol, SYMBOL_SESSION_BUY_ORDERS_VOLUME);
      case MBOOK_SELL_ORDERS_VOLUME:
         return SymbolInfoDouble(m_symbol, SYMBOL_SESSION_SELL_ORDERS_VOLUME);
      case MBOOK_OPEN_INTEREST:
         return SymbolInfoDouble(m_symbol, SYMBOL_SESSION_INTEREST);
   }
   return 0.0;  
}

Como podemos ver, el código es bastante sencillo. En esencia, duplica el funcional estándar de MQL. Pero el motivo por el que se añade a la clase CMarketBook es precisamente porque proporciona a los usuarios un módulo cómodo y centralizado de acceso a la información de las órdenes límite y de los niveles de precio.


Capítulo 4. Documentación de la clase CMarketBook

Ya hemos finalizado la descripción de la clase para el trabajo con la profundidad de mercado CMarketBook. El capítulo cuarto contiene la documentación de sus métodos abiertos. Gracias a esta documentación, el uso de la clase se convertirá en algo sencillo y comprensible, incluso para los programadores principiantes. Asimismo, este capítuclo se podrá usar cómodamente como guía de trabajo con la clase.


4.1. Métodos de obtención de la información principal de la profundidad de mercado y operaciones con ella

Método Refresh()

Actualiza el estado de la profundidad de mercado. Con cada llamada del evento de sistema OnBookEvent (el estado de la profundidad de mercado ha cambiado), es necesario llamar a este método.

void        Refresh(void);

Uso

Podrá ver un ejemplo de su uso en el apartado correspondiente del capítulo cuatro.

 

Método InfoGetInteger()

Retorna una de las propiedades de la profundidad de mercado, que corresponde al modificador ENUM_MBOOK_INFO_INTEGER. Es posible ver la lista completa de propiedades soportadas en la descripción de la enumeración  ENUM_MBOOK_INFO_INTEGER.

long        InfoGetInteger(ENUM_MBOOK_INFO_INTEGER property);

Valor devuelto

Propiedad de tipo entero de la profundidad de mercado del tipo long. En caso de fallo, retorna -1.

Uso

Podrá ver un ejemplo de su uso en el apartado correspondiente del capítulo cuatro. 

 

Método InfoGetDouble()

Retorna una de las propiedades de la profundidad de mercado, que corresponde al modificador ENUM_MBOOK_INFO_DOUBLE. Es posible ver la lista completa de propiedades soportadas en la descripción de la enumeración  ENUM_MBOOK_INFO_DOUBLE.

double      InfoGetDouble(ENUM_MBOOK_INFO_DOUBLE property);

Valor devuelto

Propiedad de la profundidad de mercado del tipo double. En caso de fallo, retorna -1.0.

Uso

Podrá ver un ejemplo de su uso en el apartado correspondiente del capítulo cuatro.  

 

Método IsAvailable()

Retorna true, si la información de la profundidad de mercado está disponible, y false en caso contrario. Este método debe ser llamado antes de comenzar a trabajar con la clase de la profundidad de mercado, para comprobar las capacidades del robot con este tipo de información.

bool        IsAvailable(void);

Valor devuelto

True, si la profundidad de mercado está disponible para el trabajo posterior, y false en caso contrario.

 

Método SetMarketBookSymbol()

Establece el símbolo con cuya profundidad de mercado de los precios se debe trabajar. Asimismo, el símbolo de la profundidad de mercado se puede establecer mientras se crea el ejemplar de la clase CMarketBook, indicando claramente en el constructor el nombre del símbolo utilizado.

bool        SetMarketBookSymbol(string symbol);

Valor devuelto

True, si el símbolo 'symbol' está disponible para el comercio, false en caso contrario.

 

Método GetMarketBookSymbol()

Retorna el símbolo del instrumento para cuyo trabajo con la profundidad de mercado se ha configurado el ejemplar de clase actual. 

string      GetMarketBookSymbol(void);

Valor devuelto

Nombre del instrumento a cuya profundidad de mercado representa el ejemplar de clase actual. NULL, si el instrumento elegido no está disponible. 

 

Método GetDeviationByVol()

Retorna la magnitud del deslizamiento potencial al entrar en el mercado con una orden de mercado. Esta magnitud tiene un carácter valorativo, y el deslizamiento real puede diferenciarse del calculado por esta función, si el estado de la profundidad de mercado cambia en el momento de entrada. No obstante, esta función da una valoración bastante precisa del deslizamiento que tendrá lugar al entrar al mercado, y puede servir como fuente de información adicional.

El método aplica dos parámetros: el volumen de la operación supuesta y la enumeración que indica el tipo de liquidez utilizada al ejecutar una operación. Por ejemplo, para comprar se usará la liquidez de las órdenes límite establecidas para la venta, y en este caso, como lado 'side' será necesario indicar el tipo MBOOK_ASK. Para la venta, al contrario, se deberá indicar MBOOK_BID. Podrá encontra información más detallada sobre la profundidad de mercado en la descripción de la enumeración ENUM_BOOK_SIDE.

double     GetDeviationByVol(long vol, ENUM_MBOOK_SIDE side);

Parámetros:

  • [in] vol — volumen de la operación supuesta;
  • [in] side — lado de la profundidad de mercado que se usará al realizar la operación.  

Valor devuelto

Magnitud del deslizamiento potencial en puntos del instrumento.

 

4.2. Enumeraciones y modificadores de la clase CMarketBook

Enumeración ENUM_MBOOK_SIDE

La enumeración ENUM_BOOK_SIDE contiene modificadores que indican el tipo de liquidez utilizada. Los campos de la enumeración se indican más abajo:

CampoDescripción
MBOOK_ASK Índice de la liquidez de las órdenes límite de venta proporcionadas.
MBOOK_BID Índice de la liquidez de las órdenes límite de compra proporcionadas.

Observaciones 

Cada orden urgente puede ser ejecutada por órdenes límite. Dependiendo de la dirección de la orden, se usan órdenes límite de compra o de venta. El lado opuesto de la operación de compra serán una o varias órdenes límite de venta. El lado opuesto de la operación de venta serán una o varias órdenes límite de compra. Este modificador, de esta forma, señala a una de las dos partes de la profundidad de mercado: al lado de la compra, o al lado de la venta. El modificador es usado por la función GetDeviationByVol, para cuyo trabajo es necesario saber qué lado de la liquidez se verá implicado por la supuesta operación de mercado.

 

Enumeración ENUM_MBOOK_INFO_INTEGER

La enumeración ENUM_MBOOK_INFO_INTEGER contiene modificadores de las propiedades, que se deben obtener con la ayuda del método InfoGetInteger. Los campos de la enumeración se indican más abajo:

CampoDescripción
MBOOK_BEST_ASK_INDEX Índice del mejor precio de la oferta
MBOOK_BEST_BID_INDEX Índice del mejor precio de la demanda
MBOOK_LAST_ASK_INDEX Índice del peor o último precio de la oferta
MBOOK_LAST_BID_INDEX Índice del peor o último precio de la demanda
MBOOK_DEPTH_ASK La profundidad del mercado por el lado de la oferta o su cantidad total de niveles comerciales
MBOOK_DEPTH_BID La profundidad del mercado por el lado de la demanda o su cantidad total de niveles comerciales
MBOOK_DEPTH_TOTAL Profundidad total del mercado o cantidad de niveles comerciales de compra y venta
MBOOK_MAX_ASK_VOLUME Volumen máximo de la oferta
MBOOK_MAX_ASK_VOLUME_INDEX Índice del nivel del volumen máximo de la oferta
MBOOK_MAX_BID_VOLUME Volumen máximo de la demanda
MBOOK_MAX_BID_VOLUME_INDEX Índice del nivel de la demanda máxima
MBOOK_ASK_VOLUME_TOTAL Volumen total de órdenes límite de venta, accesible en la profundidad de mercado actual
MBOOK_BID_VOLUME_TOTAL  Volumen total de órdenes límite de compra, accesible en la profundidad de mercado actual
MBOOK_BUY_ORDERS Volumen total de órdenes límite de compra, establecidas en el momento actual en la bolsa
MBOOK_SELL_ORDERS Volumen total de órdenes límite de venta, establecidas en el momento actual en la bolsa

 

Enumeración ENUM_MBOOK_INFO_DOUBLE

La enumeración ENUM_MBOOK_INFO_DOUBLE contiene los modificadores de las propiedades que se deben obtener con la ayuda del método InfoGetDouble. Los campos de la enumeración se indican más abajo:

CampoDescripción
MBOOK_BEST_ASK_PRICE Precio de la mejor oferta
MBOOK_BEST_BID_PRICE Precio de la mejor demanda
MBOOK_LAST_ASK_PRICE Precio de la peor o última oferta
MBOOK_LAST_BID_PRICE Precio de la peor o última demanda
MBOOK_AVERAGE_SPREAD Diferencia media entre la mejor demanda y oferta o spread
MBOOK_OPEN_INTEREST  Interés abierto
MBOOK_BUY_ORDERS_VOLUME Cantidad de órdenes de compra
MBOOK_SELL_ORDERS_VOLUME  Cantidad de órdenes de venta

 

4.3. Ejemplo de uso de la clase CMarketBook

Este ejemplo contiene el código fuente en forma de experto, que muestra la información principal de la profundidad de mercado en el momento de su inicio:

//+------------------------------------------------------------------+
//|                                               TestMarketBook.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"
#include <Trade\MarketBook.mqh>     // Incluimos la clase CMarketBook
CMarketBook Book(Symbol());         // Inicialiazamos la clase con el instrumento actual

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   PrintMbookInfo();
   return INIT_SUCCEEDED;
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Print MarketBook Info                                            |
//+------------------------------------------------------------------+
void PrintMbookInfo()
  {
   Book.Refresh();                                                   // Actualizamos el estado de la profundidad de mercado.
//--- Obtenemos la estadística principal de tipo entero
   int total=(int)Book.InfoGetInteger(MBOOK_DEPTH_TOTAL);            // Obtenemos la profundidad total del mercado
   int total_ask = (int)Book.InfoGetInteger(MBOOK_DEPTH_ASK);        // Obtenemos la cantidad de niveles de precio de venta
   int total_bid = (int)Book.InfoGetInteger(MBOOK_DEPTH_BID);        // Obtenemos la cantidad de niveles de precio de compra
   int best_ask = (int)Book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);    // Obtenemos el mejor índice de la oferta
   int best_bid = (int)Book.InfoGetInteger(MBOOK_BEST_BID_INDEX);    // Obtenemos el mejor índice de la demanda

//--- Mostramos la estadística principal
   printf("PROFUNDIDAD TOTAL DEL MERCADO: "+(string)total);
   printf("CANTIDAD DE NIVELES DE PRECIO DE VENTA: "+(string)total_ask);
   printf("CANTIDAD DE NIVELES DE PRECIO DE COMPRA: "+(string)total_bid);
   printf("ÍNDICE DE LA MEJOR OFERTA: "+(string)best_ask);
   printf("ÍNDICE DE LA MEJOR DEMANDA: "+(string)best_bid);
   
//--- Obtenemos la estadística principal de double
   double best_ask_price = Book.InfoGetDouble(MBOOK_BEST_ASK_PRICE); // Obtenemos el mejor precio de la oferta
   double best_bid_price = Book.InfoGetDouble(MBOOK_BEST_BID_PRICE); // Obtenemos el mejor precio de la demanda
   double last_ask = Book.InfoGetDouble(MBOOK_LAST_ASK_PRICE);       // Obtenemos el peor precio de la oferta
   double last_bid = Book.InfoGetDouble(MBOOK_LAST_BID_PRICE);       // Obtenemos el peor precio de la demanda
   double avrg_spread = Book.InfoGetDouble(MBOOK_AVERAGE_SPREAD);    // Obtenemos el spread medio durante el tiempo de funcionamiento de la profundidad de mercado
   
//--- Mostramos el precio y el spread
   printf("MEJOR PRECIO DE OFERTA: " + DoubleToString(best_ask_price, Digits()));
   printf("MEJOR PRECIO DE DEMANDA: " + DoubleToString(best_bid_price, Digits()));
   printf("PEOR PRECIO DE OFERTA: " + DoubleToString(last_ask, Digits()));
   printf("PEOR PRECIO DE DEMANDA: " + DoubleToString(last_bid, Digits()));
   printf("SPREAD MEDIO: " + DoubleToString(avrg_spread, Digits()));
  }
//+------------------------------------------------------------------+

 

Conclusión

El artículo ha resultado bastante dinámico. Hemos analizado la profundidad de mercado desde el punto de vista técnico y hemos propuesto una clase-contenedor de alta productividad para trabajar con ella. Como ejemplo hemos creado, basándonos en esta clase-cotenedor, un indicador de la profundidad de mercado que se puede mostrar de forma compacta en el gráfico de precios del instrumento.

Nuestro indicador de la profundidad de mercado nos ha salido muy sencillo, solo le faltan unos detalles. Sin embargo, hemos logrado el objetivo principal: nos hemos convencido de que la clase CMarketBook que hemos creado, puede generar con relativa velocidad expertos e indicadores complejos, que analizan la liquidez actual según un instrumento. Se ha prestado especial atención durante la creación de la clase CMarketBook a la productividad, y es que la profundidad de mercado es un recuadro muy dinámico que cambia centenares de veces por minuto.

La clase descrita en el artículo será una buena base para su sistema de sclaping o de alta frecuencia. Podrá complementarlo con un funcional específico para su sistema. Para ello, solo tiene que crear su propia clase de profundidad de mercado, derivada de CMarketBook, y escribir los métodos de ampliación que necesite. Sin embargo, esperamos que incluso las propiedades y métodos básicos proporcionados por la clase de la profundidad de mercado hagan su trabajo bastante más sencillo y fiable.

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

Archivos adjuntos |
MQL5.zip (202.33 KB)
Gestionar las órdenes es sencillo Gestionar las órdenes es sencillo
Este artículo explica cómo controlar las posiciones abiertas y las órdenes pendientes de varias maneras diferentes. El objetivo es simplificar la escritura de los asesores expertos.
Cómo evaluar los resultados de los Asesores Expertos Cómo evaluar los resultados de los Asesores Expertos
El presente artículo explica el funcionamiento del Informe de pruebas de MetaTrader 4, mostrando los cálculos realizados.
Almacenamiento y visualización de la información Almacenamiento y visualización de la información
Este artículo expone algunos métodos eficientes de almacenamiento y visualización de la información. Vamos a proponer algunas alternativas tanto para el archivo de log estándar del terminal como para la función Comment().
Eventos en MetaTrader 4 Eventos en MetaTrader 4
En este artículo vamos a tratar el seguimiento programado de eventos en el Terminal Cliente MetaTrader 4, tales como la apertura, el cierre y la modificación de órdenes. Se dirige a los usuarios que tienen unos conocimientos básicos en programación MQL 4 y ya saben manejar el terminal.