
Desarrollo de un sistema comercial basado en el libro de órdenes (Parte I): el indicador
Introducción
Recordemos qué es el Depth of Market: se trata de una fila de órdenes pendientes del tipo límite. Estas órdenes representan las intenciones de transacción de los agentes del mercado y, con frecuencia, no se convierten en una transacción propiamente dicha. Esto se debe a que los participantes tienen la posibilidad de cancelar sus órdenes previamente publicadas por diversos motivos. Entre estos motivos se incluyen cambios en las condiciones del mercado y el consiguiente desinterés en ejecutar la orden en la cantidad y al precio especificados anteriormente.
El valor devuelto por la función `SymbolInfoInteger(_Symbol, SYMBOL_TICKS_BOOKDEPTH)` es precisamente la profundidad del libro de órdenes y representa la mitad del array que se llenará con los niveles de precios a trabajar. Una mitad de este array se destina a la cantidad de órdenes límite de venta y la otra mitad, a las órdenes límite de compra que han sido publicadas. Según la documentación, para los símbolos que no tienen fila de solicitudes, el valor de esta propiedad es igual a cero. Tenemos un ejemplo de esto en la siguiente figura, que muestra un libro de órdenes cuya profundidad es 10 y en el que se pueden ver todos los niveles de precios disponibles.
Cabe destacar que la profundidad puede obtenerse del símbolo y no necesariamente del libro de órdenes. El uso de la función SymbolInfoInteger sería suficiente para obtener el valor de esta propiedad, sin necesidad de recurrir al identificador OnBookEvent ni a las funciones relacionadas, como MarketBookAdd. Por supuesto, podemos llegar al mismo resultado contando el número de elementos del array del tipo MqlBookInfo que el identificador OnBookEvent rellena, como veremos con más detalle más adelante.
Puede que te estés preguntando por qué usar este indicador en lugar de utilizar simplemente el libro de órdenes estándar de MetaTrader 5. A continuación, presento algunas de las razones:
- optimización del espacio disponible en el gráfico eligiendo el tamaño del histograma y su posición en la pantalla.
- presentación más limpia de los eventos del libro.
- uso en el simulador de estrategias mediante la futura implementación de un mecanismo de almacenamiento en disco de los eventos BookEvent, teniendo en cuenta que, hasta el momento, no es posible realizar este tipo de pruebas de forma nativa.
Generación personalizada de símbolos
Este proceso nos permitirá realizar pruebas del indicador incluso cuando el mercado esté cerrado o cuando el bróker no transmita los eventos para el símbolo en cuestión. Es decir, en estos casos no habrá una fila de solicitudes en línea ni encontraremos estos eventos almacenados en la caché del ordenador local. Por el momento, no trabajaremos con eventos ocurridos en el pasado en un símbolo real, sino que nos concentraremos en la generación de eventos BookEvent simulados para símbolos ficticios. Esto se debe a que la creación de este símbolo y la simulación de los eventos se justifican por la necesidad de trabajar con la función CustomBookAdd. Se trata de una función diseñada para operar específicamente con símbolos personalizados.
A continuación, encontrarás el script CloneSymbolTicksAndRates, que generará el símbolo personalizado. Ha sido adaptado de la documentación para ajustarse a nuestras necesidades y comienza con la definición de constantes y la inclusión de la biblioteca estándar DateTime.mqh para trabajar con fechas. Observa que el nombre del símbolo personalizado se derivará de la nomenclatura del símbolo real, que se pasará al script mediante la función Symbol(). Por lo tanto, es necesario ejecutar este script sobre el símbolo real que se desea clonar. Aunque también es posible clonar símbolos personalizados, no le veo mucho sentido.
#define CUSTOM_SYMBOL_NAME Symbol()+".C" #define CUSTOM_SYMBOL_PATH "Forex" #define CUSTOM_SYMBOL_ORIGIN Symbol() #define DATATICKS_TO_COPY UINT_MAX #define DAYS_TO_COPY 5 #include <Tools\DateTime.mqh>
En el siguiente fragmento, insertado en la función OnStart() del mismo script, vemos cómo se crea el objeto de fecha «timemaster» que se utiliza para calcular el período de tiempo en el que se recopilarán los ticks y las barras para la clonación. Según la constante DAYS_TO_COPY que definimos, la función Bars copiará los últimos cinco días del símbolo de origen. Este mismo tiempo inicial del rango se convierte a milisegundos y se emplea con la función CopyTicks, completando así la clonación del símbolo.
CDateTime timemaster; datetime now = TimeTradeServer(); timemaster.Date(now); timemaster.DayDec(DAYS_TO_COPY); long DaysAgoMsc = 1000 * timemaster.DateTime(); int bars_origin = Bars(CUSTOM_SYMBOL_ORIGIN, PERIOD_M1, timemaster.DateTime(), now); int create = CreateCustomSymbol(CUSTOM_SYMBOL_NAME, CUSTOM_SYMBOL_PATH, CUSTOM_SYMBOL_ORIGIN); if(create != 0 && create != 5304) return; MqlTick array[] = {}; MqlRates rates[] = {}; int attempts = 0; while(attempts < 3) { int received = CopyTicks(CUSTOM_SYMBOL_ORIGIN, array, COPY_TICKS_ALL, DaysAgoMsc, DATATICKS_TO_COPY); if(received != -1) { if(GetLastError() == 0) break; } attempts++; Sleep(1000); }
Cuando el proceso haya finalizado, el nuevo símbolo debería aparecer en la lista de símbolos de la observación de mercado con el nombre <AtivodeOrigem>.C. A continuación, debemos abrir un nuevo gráfico con este símbolo sintético y pasar a la siguiente etapa.
Si ya existe otro símbolo sintético, se puede aprovechar, por lo que no será necesario crear uno nuevo siguiendo los pasos explicados en esta sección. Al final, bastará con abrir un nuevo gráfico con este símbolo personalizado y ejecutar en él dos aplicaciones MQL5 que desarrollaremos a continuación: el indicador y el script generador de eventos. Detallaremos todo a continuación.
Script generador de eventos del tipo BookEvent para pruebas
Tener un símbolo personalizado no sustituye a la ausencia de una secuencia de ticks del libro de órdenes en línea en el momento en que se desea realizar un backtest del indicador basado en eventos del libro. Por lo tanto, será necesario generar datos simulados. Para ello, se desarrolló el siguiente script.
//+------------------------------------------------------------------+ //| GenerateBookEvent.mq5 | //| Daniel Santos | //+------------------------------------------------------------------+ #property copyright "Daniel Santos" #property version "1.00" #define SYNTH_SYMBOL_MARKET_DEPTH 32 #define SYNTH_SYMBOL_BOOK_ITERATIONS 20 #include <Random.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double BidValue, tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); MqlBookInfo books[]; int marketDepth = SYNTH_SYMBOL_MARKET_DEPTH; CRandom rdn; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { if(!SymbolInfoInteger(_Symbol, SYMBOL_CUSTOM)) // if the symbol exists { Print("Custom symbol ", _Symbol, " does not exist"); return; } else BookGenerationLoop(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void BookGenerationLoop() { MqlRates BarRates_D1[]; CopyRates(_Symbol, PERIOD_D1, 0, 1, BarRates_D1); if(ArraySize(BarRates_D1) == 0) return; BidValue = BarRates_D1[0].close; ArrayResize(books, 2 * marketDepth); for(int j = 0; j < SYNTH_SYMBOL_BOOK_ITERATIONS; j++) { for(int i = 0, j = 0; i < marketDepth; i++) { books[i].type = BOOK_TYPE_SELL; books[i].price = BidValue + ((marketDepth - i) * tickSize); books[i].volume_real = rdn.RandomInteger(10, 500); books[i].volume_real = round((books[i].volume_real + books[j].volume_real) / 2); books[i].volume = (int)books[i].volume_real; //---- books[marketDepth + i].type = BOOK_TYPE_BUY; books[marketDepth + i].price = BidValue - (i * tickSize); books[marketDepth + i].volume_real = rdn.RandomInteger(10, 500); books[marketDepth + i].volume_real = round((books[marketDepth + i].volume_real + books[marketDepth + j].volume_real) / 2); books[marketDepth + i].volume = (int)books[marketDepth + i].volume_real; if(j != i) j++; } CustomBookAdd(_Symbol, books); Sleep(rdn.RandomInteger(400, 1000)); } } //+------------------------------------------------------------------+
En lugar de la función estándar MathRand(), se utilizó una implementación diferente de números aleatorios de 32 bits. Esto se debe a varios motivos, entre ellos la facilidad para generar números enteros dentro de un rango especificado, ventaja que aprovechamos en este script mediante la función RandomInteger(min, max).
Se eligió un valor relativamente alto para la profundidad del libro de órdenes, 32, es decir, se generarán 64 niveles de precios en cada iteración. Si lo prefieres, puedes definir un valor menor.
El algoritmo verifica si el símbolo está personalizado. Si es así, procede a generar cada uno de los elementos del libro de órdenes y lo repite en otro bucle según la cantidad de iteraciones definida. En esta implementación, se realizan 20 repeticiones con pausas entre ellas de duración aleatoria seleccionada dentro de un rango de 400 milisegundos a 1000 milisegundos (equivalente a 1 segundo). Esta dinámica hace que la visualización de los ticks sea más agradable y realista.
Los precios están anclados verticalmente al último precio de cierre del marco temporal diario, según se desprende del símbolo de origen. Por encima de esta referencia se encuentran los 32 niveles de órdenes de venta y, por debajo, los 32 niveles de compra. Según la paleta estándar del indicador, las barras del histograma correspondientes a las órdenes de venta tienen tonos rojizos, mientras que las de compra son de tonos azul claro.
La diferencia de precio entre un nivel y el siguiente se establece según el tamaño del tick del símbolo, obtenido a través de la propiedad SYMBOL_TRADE_TICK_SIZE.
Indicador para mostrar los cambios en el Market Depth
Código fuente de la biblioteca
El indicador se desarrolló empleando programación orientada a objetos. Se creó la clase BookEventHistogram para gestionar el histograma del libro de órdenes, desde su creación, actualización y eliminación de las barras cuando se destruye el objeto de esta clase.
A continuación, se presentan las declaraciones de las variables y funciones de la clase BookEventHistogram:
class BookEventHistogram { protected: color histogramColors[]; //Extreme / Mid-high / Mid-low int bookSize; int currElements; int elementMaxPixelsWidth; bool showMessages; ENUM_ALIGN_MODE corner; string bookEventElementPrefix; public: MqlBookInfo lastBook[]; datetime lastDate; void SetAlignLeft(void); void SetCustomHistogramColors(color &colors[]); void SetBookSize(int value) {bookSize = value;} void SetElementMaxPixelsWidth(int m); int GetBookSize(void) {return bookSize;} void DrawBookElements(MqlBookInfo& book[], datetime now); void CleanBookElements(void); void CreateBookElements(MqlBookInfo& book[], datetime now); void CreateOrRefreshElement(int buttonHeigh, int buttonWidth, int i, color clr, int ydistance); //--- Default constructor BookEventHistogram(void); ~BookEventHistogram(void); };
Observa que no todas las funciones están definidas en este segmento, pero son complementadas en las demás líneas del archivo BookEventHistogram.mqh.
Entre las más importantes, las funciones CreateBookElements y CreateOrRefreshElement trabajan juntas para garantizar la actualización de los elementos existentes, ya sea creando nuevos elementos cuando aún no existen o actualizando los existentes. El resto de funciones se encargan de mantener las propiedades actualizadas o de devolver el valor de algunas de las variables del objeto.
Código fuente del indicador:
El inicio del código contiene la definición del número de plots y de buffers como 3. Un análisis más detallado demostrará que, en realidad, no se emplean buffers de la estructura raíz de un indicador MQL5. Sin embargo, esta declaración permite generar un código que garantiza la interacción del usuario con algunas propiedades durante la inicialización del indicador. En este caso, nos interesa específicamente la propiedad de colores, cuyo esquema de entradas favorece una experiencia intuitiva y amigable para el usuario.
Cada uno de los plots define dos colores: uno para las órdenes de compra y otro para las de venta. Este conjunto de seis colores se utiliza para elegir el color de cada segmento según los criterios establecidos. A simple vista, los segmentos más grandes del histograma se clasifican como «extremos», los de tamaño por encima de la media como «mid-high» y el resto como «mid-low».
Los colores se obtienen mediante la función PlotIndexGetInteger, en la que se especifica el gráfico y la posición dentro del mismo desde la que se desea extraer la información.
#define NUMBER_OF_PLOTS 3 #property indicator_chart_window #property indicator_buffers NUMBER_OF_PLOTS #property indicator_plots NUMBER_OF_PLOTS //--- Invisible plots #property indicator_label1 "Extreme volume elements colors" #property indicator_type1 DRAW_NONE #property indicator_color1 C'212,135,114', C'155,208,226' //--- #property indicator_label2 "Mid-high volume elements colors" #property indicator_type2 DRAW_NONE #property indicator_color2 C'217,111,86', C'124,195,216' //--- #property indicator_label3 "Mid-low volume elements color" #property indicator_type3 DRAW_NONE #property indicator_color3 C'208,101,74', C'114,190,214' #include "BookEventHistogram.mqh" enum HistogramPosition { LEFT, //<<<< Histogram on the left RIGHT, //Histogram on the right >>>> }; enum HistogramProportion { A_QUARTER, // A quarter of the chart A_THIRD, // A third of the chart HALF, // Half of the chart }; input HistogramPosition position = RIGHT; // Indicator position input HistogramProportion proportion = A_QUARTER; // Histogram ratio (compared to chart width) //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ double volumes[]; color histogramColors[]; BookEventHistogram bookEvent;
A continuación, se definen dos enumeradores para ofrecer opciones específicas al usuario durante la carga del indicador. El objetivo es determinar dónde se debe dibujar el histograma, es decir, si a la derecha o a la izquierda del gráfico. También se solicita que el usuario defina la proporción que ocupará el histograma en relación con el ancho del gráfico: un cuarto, un tercio o la mitad. Por ejemplo, si el ancho del gráfico es de 500 píxeles y se define la proporción como la mitad, las barras del histograma podrán tener un tamaño que varíe de 0 a 250 píxeles.
Finalmente, observemos en el código fuente BookEvents.mq5 que las funciones OnBookEvent y OnChartEvent son las que desencadenarán casi todas las solicitudes de actualización del histograma. OnCalculate, por su parte, no participa en el algoritmo, sino que se mantiene únicamente por cuestiones de sintaxis de MQL.
Uso de los scripts e indicador
La secuencia correcta para activar los scripts y el indicador es la siguiente (para que sea coherente con las funcionalidades desarrolladas hasta ahora):
- script CloneSymbolTicksAndRates (en el gráfico del símbolo real que se desea clonar)
- -> indicador BookEvents (gráfico del símbolo personalizado generado)
- -> script GenerateBookEvent (gráfico del símbolo personalizado generado)
El evento BookEvent se propaga a todas las instancias gráficas del símbolo personalizado al que está vinculado. Por lo tanto, da igual ejecutar el indicador y el generador de eventos en gráficos separados o en el mismo gráfico, siempre que ambos se refieran al mismo símbolo personalizado.
La animación a continuación ilustra esta secuencia y también el funcionamiento del indicador. ¡Espero que les guste!
Conclusión
El libro de órdenes —Depth of Market— es, sin duda, un elemento muy relevante para la ejecución de operaciones rápidas, especialmente en algoritmos de alta frecuencia (HFT). Este tipo de evento se puede obtener del mercado a través del bróker en muchos de los símbolos negociados. Con el tiempo, es posible que los brokers se interesen en ampliar la cobertura y la disponibilidad de esta información para otros símbolos.
Adicionalmente, considero que no es adecuado construir un trade system que se base únicamente en el libro de órdenes, aunque este puede resultar útil para identificar zonas de liquidez y encontrar alguna correlación con el movimiento del precio. Por ello, considero prudente combinar el análisis del libro de órdenes con otras herramientas e indicadores para que, en conjunto, puedan ofrecer resultados consistentes.
Queda abierta la posibilidad de realizar futuras mejoras en el indicador, como la implementación de formas de almacenar eventos del tipo BookEvent y posteriormente utilizar estos datos en backtests, ya sea en operaciones manuales o automatizadas.
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/15748
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Este es un artículo muy corto con muy poco código. Vamos a ver en la próxima parte si tiene sentido tener parte 1, 2 y así sucesivamente.
SYMBOL_TICKS_BOOKDEPTH da el número máximo de peticiones mostradas en Profundidad de Mercado. Es incorrecto que esta propiedad da el mismo resultado que contar el número de niveles en el DOM. Da el número máximo, no el número exacto.
Puede hacerlo usando este script:
Este es un artículo muy corto con muy poco código. Veremos en la siguiente parte si tiene sentido tener parte 1, 2 y así sucesivamente.
SYMBOL_TICKS_BOOKDEPTH da el número máximo de peticiones mostradas en la Profundidad de Mercado. Es incorrecto que esta propiedad dé el mismo resultado que contar el número de niveles en el DOM. Da un número máximo, no un número exacto.
Puede hacerlo con este script:
El artículo es super! También quería escribir un TS sobre la pila - más precisamente con el uso de la pila de comillas!