- Crear y eliminar símbolos personalizados
- Propiedades de símbolos personalizados
- Fijación de coeficientes de margen
- Configurar sesiones de cotización y trading
- Añadir, sustituir y suprimir cotizaciones
- Añadir, sustituir y eliminar ticks
- Conversión de los cambios en el libro de órdenes
- Particularidades del trading con símbolos personalizados
Añadir, sustituir y eliminar ticks
La API de MQL5 permite generar el historial de un símbolo personalizado no sólo a nivel de barra, sino también a nivel de tick. De este modo, es posible conseguir un mayor realismo a la hora de probar y optimizar los Asesores Expertos, así como emular la actualización en tiempo real de los gráficos de símbolos personalizados, transmitiéndoles sus ticks. El conjunto de ticks transferidos al sistema se tiene en cuenta automáticamente al formar las barras. En otras palabras: no es necesario llamar a las funciones de la sección anterior que operan sobre las estructuras MqlRates si se proporciona información más detallada sobre los cambios de precios para el mismo período en forma de ticks, a saber, los arrays de estructuras MqlTick. La única ventaja de las cotizaciones por barra MqlRates es el rendimiento y la eficiencia de memoria.
Hay dos funciones para añadir ticks: CustomTicksAdd y CustomTicksReplace. La primera añade ticks interactivos que llegan a la ventana Observación de Mercado (desde donde son transferidos automáticamente por el terminal a la base de datos de ticks) y que generan los correspondientes eventos en los programas MQL. La segunda escribe los ticks directamente en la base de datos de ticks.
int CustomTicksAdd(const string symbol, const MqlTick &ticks[], uint count = WHOLE_ARRAY)
La función CustomTicksAdd añade datos del array ticks al historial de precios de un símbolo personalizado especificado en symbol. De manera predeterminada, si el ajuste count es igual a WHOLE_ARRAY, se añade el array completo. Si es necesario, puede especificar un número menor y descargar sólo una parte de los ticks.
Tenga en cuenta que el símbolo personalizado debe estar seleccionado en la ventana Market Watch en el momento de la llamada a la función. Para los símbolos no seleccionados en Observación de Mercado, debe utilizar la función CustomTicksReplace (véase más adelante).
El array de datos de ticks debe estar ordenado por tiempo en orden ascendente, es decir, es necesario que se cumplan las siguientes condiciones: ticks[i].time_msc <= ticks[j].time_msc para todos los i < j.
La función devuelve el número de bytes añadidos, o -1 en caso de error.
La función CustomTicksAdd transmite los ticks al gráfico del mismo modo que si procedieran del servidor del bróker. Normalmente, la función se aplica durante uno o varios ticks. En este caso, se «reproducen» en la ventana Observación de Mercado, a partir de la cual se guardan en la base de datos de ticks.
No obstante, cuando se transfiere una gran cantidad de datos en una llamada, la función cambia su comportamiento para ahorrar recursos. Si se transmiten más de 256 ticks, se dividen en dos partes. La primera parte (grande) se escribe inmediatamente en la base de datos de ticks (al igual que CustomTicksReplace). La segunda parte, consistente en los últimos 128 ticks (los más recientes), se pasa a la ventana Observación de Mercado, y después es guardada por el terminal en la base de datos.
La estructura MqlTick tiene dos campos con valores temporales: time (tiempo de tick en segundos) y time_msc (tiempo de tick en milisegundos). Ambos valores están fechados a partir del 01/01/1970. El campo time_msc (no nulo) rellenado tiene prioridad sobre time. Observe que time se rellena en segundos como resultado del recálculo basado en la fórmula time_msc / 1000. Si el campo time_msc es cero, se utiliza el valor del campo time, y el campo time_msc obtiene a su vez el valor en milisegundos de la fórmula time * 1000. Si ambos campos son iguales a cero, la hora actual del servidor (con precisión de milisegundos) se pone en un tick.
De los dos campos que describen el volumen, volume_real tiene mayor prioridad que volume.
Dependiendo de qué otros campos se rellenen en un elemento de array concreto (estructura MqlTick), el sistema establece indicadores para el tick guardado en el campo flags:
- ticks[i].bid - TICK_FLAG_BID (el tick ha cambiado el precio de compra (Bid))
- ticks[i].ask - TICK_FLAG_ASK (el tick ha cambiado el precio de venta (Ask))
- ticks[i].last - TICK_FLAG_LAST (el tick ha cambiado el precio de la última operación)
- ticks[i].volume o ticks[i].volume_real - TICK_FLAG_VOLUME (el tick ha cambiado de volumen)
Si el valor de algún campo es menor o igual que cero, la bandera correspondiente no se escribe en el campo flags.
Las banderas TICK_FLAG_BUY y TICK_FLAG_SELL no se añaden al historial de un símbolo personalizado.
La función CustomTicksReplace sustituye completamente el historial de precios del símbolo personalizado en el intervalo de tiempo especificado por los datos del array pasado.
int CustomTicksReplace(const string symbol, long from_msc, long to_msc,
const MqlTick &ticks[], uint count = WHOLE_ARRAY)
El intervalo se establece mediante los parámetros from_msc y to_msc, en milisegundos desde el 01/01/1970. Ambos valores se incluyen en el intervalo.
El array ticks debe estar ordenado por orden cronológico de llegada de los ticks, lo que corresponde a un tiempo creciente, o mejor dicho, no decreciente, ya que los ticks con la misma hora suelen aparecer seguidos en un flujo con una precisión de milisegundos.
El parámetro count puede utilizarse para procesar una parte del array.
Los ticks se sustituyen secuencialmente día a día antes de la hora especificada en to_msc, o hasta que se produzca un error en el orden de los ticks. Primero se procesa el primer día del intervalo especificado, luego va el día siguiente, y así sucesivamente. En cuanto se detecta una discrepancia entre la hora del tick y el orden ascendente (no descendente), el proceso de sustitución del tick se detiene en el día actual. En este caso, los ticks de los días anteriores se sustituirán correctamente, mientras que el día actual (en el momento del tick erróneo) y todos los días restantes del intervalo especificado permanecerán sin cambios. La función devolverá -1, y el código de error en _LastError será 0 («sin error»).
Si el array ticks no tiene datos para algún periodo dentro del intervalo general entre from_msc y to_msc (ambos inclusive), después de ejecutar la función, el historial del símbolo personalizado tendrá un hueco correspondiente a los datos que faltan.
Si no hay datos en la base de datos de ticks en el intervalo de tiempo especificado, CustomTicksReplace le añadirá ticks desde el array ticks.
La función CustomTicksDelete puede utilizarse para borrar todos los ticks del intervalo de tiempo especificado.
int CustomTicksDelete(const string symbol, long from_msc, long to_msc)
El nombre del símbolo personalizado que se está editando se establece en el parámetro symbol, y el intervalo que debe borrarse se establece mediante los parámetros from_msc y to_msc (ambos inclusive), en milisegundos.
La función devuelve el número de bytes escritos, o -1 en caso de error.
¡Atención! La supresión de los ticks con CustomTicksDelete conlleva la supresión automática de las barras correspondientes. Sin embargo, llamar a CustomRatesDelete, es decir, eliminar barras, ¡no elimina ticks!
Para dominar el material en la práctica, resolveremos varios problemas aplicados utilizando las funciones recién consideradas.
Para empezar, abordemos una tarea tan interesante como la creación de un símbolo personalizado basado en un símbolo real pero con una densidad de ticks reducida. Esto acelerará la simulación y la optimización, además de reducir el consumo de recursos (principalmente RAM) en comparación con el modo basado en ticks reales, manteniendo al mismo tiempo una calidad aceptable, cercana a la ideal, del proceso.
Acelerar la simulación y la optimización
Los operadores a menudo buscan formas de acelerar los procesos de optimización y simulación de Asesores Expertos. Entre las posibles soluciones, las hay obvias, para las que basta con cambiar la configuración (cuando está permitido), y las hay que llevan más tiempo y requieren la adaptación de un Asesor Experto o un entorno de prueba.
Entre el primer tipo de soluciones se encuentran:
· Reducir el espacio de optimización eliminando algunos parámetros o reduciendo su paso;
· Reducir el periodo de optimización;
· Cambiar al modo de simulación de ticks de menor calidad (por ejemplo, de los reales a OHLC M1);
· Permitir el cálculo de beneficios en puntos en lugar de dinero;
· Actualizar el ordenador;
· Utilizar MQL Cloud u ordenadores adicionales de la red local.
Entre el segundo tipo de soluciones relacionadas con el desarrollo se encuentran:
· Crear perfiles de código, sobre la base de los cuales se pueden eliminar los «cuellos de botella» del código;
· Si es posible, utilizar el cálculo de indicadores con eficiencia de recursos, es decir, sin la directiva #property tester_everytick_calculate ;
· Transferir los algoritmos de los indicadores (si se utilizan) directamente al código del Asesor Experto: las llamadas a los indicadores imponen ciertos costes generales;
· Eliminar gráficos y objetos;
· Almacenar en caché los cálculos, si es posible;
· Reducir el número de posiciones abiertas simultáneamente y de órdenes colocadas (su cálculo en cada tick puede llegar a ser notable con un número elevado);
· Virtualizar por completo liquidaciones, órdenes, transacciones y posiciones: el mecanismo de contabilidad integrado, debido a su versatilidad, compatibilidad multidivisa y otras características, tiene sus propios gastos generales, que pueden eliminarse realizando acciones similares en el código MQL5 (aunque esta opción es la que más tiempo consume).
La reducción de la densidad de ticks pertenece a un tipo intermedio de solución: requiere la creación programática de un símbolo personalizado, pero no afecta al código fuente del Asesor Experto.
El script CustomSymbolFilterTicks.mq5 generará un símbolo personalizado con ticks reducidos. El instrumento inicial será el símbolo de trabajo del gráfico en el que se lanza el script. En los parámetros de entrada, puede especificar la carpeta para el símbolo personalizado y la fecha de inicio del tratamiento del historial. Por defecto, si no se indica ninguna fecha, el cálculo se realiza para los últimos 120 días.
input string CustomPath = "MQL5Book\\Part7"; // Custom Symbol Folder
|
El nombre del símbolo se forma a partir del nombre del instrumento fuente y el sufijo «.TckFltr». Más adelante añadiremos la designación del método de reducción de ticks.
string CustomSymbol = _Symbol + ".TckFltr";
|
Por comodidad, en el manejador OnStart es posible borrar una copia anterior de un símbolo si ya existe.
void OnStart()
|
A continuación, con el consentimiento del usuario, se crea un símbolo. El historial se rellena con datos de tick en la función auxiliar GenerateTickData. Si tiene éxito, el script añade un nuevo símbolo a Observación de Mercado y abre el gráfico.
if(IDYES == MessageBox(StringFormat("Create new custom symbol '%s'?", CustomSymbol),
|
La función GenerateTickData procesa los ticks en un bucle en porciones, por día. Los ticks por día se solicitan llamando a CopyTicksRange. A continuación, necesitan ser reducidos de una manera u otra, lo cual es implementado por la clase TickFilter, que mostraremos a continuación. Por último, el array de ticks se añade al historial de símbolos personalizado mediante CustomTicksReplace.
bool GenerateTickData()
|
El control de errores y el recuento de ticks procesados se aplican en todas las fases. Al final, enviamos al registro el número de ticks iniciales y restantes, así como el factor de «compresión».
Ahora pasemos directamente a la técnica de reducción de ticks. Obviamente, puede haber muchos enfoques, y cada uno de ellos se adapta mejor o peor a una estrategia de trading concreta. Ofreceremos 3 versiones básicas combinadas en la clase TickFilter (TickFilter.mqh). Además, para completar el cuadro, también se admite el modo de copia de ticks sin reducción.
Así, en la clase se implementan los siguientes modos:
- Sin reducción
- Omitiendo secuencias de ticks con un cambio de precio monótono sin inversión (al modo «zig-zag»).
- Omitiendo fluctuaciones de precios dentro del diferencial
- Registrando sólo los ticks con una configuración fractal cuando el precio Bid o Ask representa un extremo entre dos ticks adyacentes.
Estos modos se describen como elementos de la enumeración FILTER_MODE.
class TickFilter
|
Cada uno de los modos se implementa mediante un método estático independiente que acepta como entrada un array de ticks que necesita ser diluido. La edición de un array se realiza in situ (sin asignar un nuevo array de salida).
static int filterBySequences(MqlTick &data[]);
|
Todos los métodos devuelven el número de ticks restantes (tamaño de array reducido).
Para unificar la ejecución del procedimiento en diferentes modos, se proporciona el método filter. Para el modo NONE, el array data permanece igual.
static int filter(FILTER_MODE mode, MqlTick &data[])
|
Por ejemplo, así es como se implementa el filtrado por secuencias monótonas de ticks en el método filterBySequences.
static int filterBySequences(MqlTick &data[])
|
Y así es como se ve el adelgazamiento fractal.
static int filterByFractals(MqlTick &data[])
|
Vamos a crear secuencialmente un símbolo personalizado para EURUSD en varios modos de reducción de densidad de ticks, y a comparar su rendimiento, es decir, el grado de «compresión», la rapidez de la simulación y cómo cambiará el rendimiento de trading del Asesor Experto.
Por ejemplo, al diluir las secuencias de ticks se obtienen los siguientes resultados (para un historial de un año y medio en MQ Demo).
Create new custom symbol 'EURUSD.TckFltr-SE'?
|
Para los modos de suavizar las fluctuaciones y para los fractales, los indicadores son diferentes:
EURUSD.TckFltr-FL will be updated
|
Para realizar experimentos prácticos de trading basados en ticks comprimidos necesitamos un Asesor Experto. Tomemos la versión adaptada de BandOsMATicks.mq5, en la que, frente a la original, se habilita el trading en cada tick (en el método SimpleStrategy::trade, la líneaif(lastBar == iTime(_Symbol, _Period, 0)) return false; está deshabilitada), y los valores de los indicadores de señal se toman de las barras 0 y 1 (anteriormente solo se completaban las barras 1 y 2).
Vamos a ejecutar el Asesor Experto utilizando el rango de fechas desde principios de 2021 hasta el 1 de junio de 2022. Los ajustes se adjuntan en el archivo MQL5/Presets/MQL5Book/BandOsMAticks.set. El comportamiento general de la curva de balance en todos los modos es bastante similar.
Gráficos combinados de balances de prueba en diferentes modos por ticks
El desplazamiento horizontal de los extremos equivalentes de las diferentes curvas se debe al hecho de que el gráfico de informes estándar no utiliza el tiempo, sino el número de operaciones para la coordenada horizontal, que, por supuesto, difiere debido a la precisión de la activación de las señales de trading para diferentes bases de ticks.
Las diferencias en las métricas de rendimiento se muestran en la siguiente tabla (N: número de operaciones; $: beneficio; PF: factor de beneficio; RF: factor de recuperación; DD: reducción):
Modo |
Ticks |
Hora |
Memoria |
N |
$ |
PF |
RF |
DD |
---|---|---|---|---|---|---|---|---|
Real |
31002919 |
02:45.251 |
835 Mb |
962 |
166.24 |
1.32 |
2.88 |
54.99 |
Emulación |
25808139 |
01:58.131 |
687 Mb |
928 |
171.94 |
1.34 |
3.44 |
47.64 |
OHLC M1 |
2084820 |
00:11.094 |
224 Mb |
856 |
193.52 |
1.39 |
3.97 |
46.55 |
Secuencia |
16310236 |
01:24.784 |
559 Mb |
860 |
168.95 |
1.34 |
2.92 |
55.16 |
Flutter |
21362616 |
01:52.172 |
623 Mb |
920 |
179.75 |
1.37 |
3.60 |
47.28 |
Fractal |
12270854 |
01:04.756 |
430 Mb |
866 |
142.19 |
1.27 |
2.47 |
54.80 |
Consideraremos que la prueba basada en ticks reales es la más fiable y evaluaremos el resto por lo cerca que está de esta prueba. Evidentemente, la modalidad OHLC M1 mostró la mayor velocidad y un menor coste de recursos debido a una pérdida significativa de precisión (no se tuvo en cuenta la modalidad a precios de apertura). Presenta unos resultados financieros excesivamente optimistas.
Entre los tres modos con ticks comprimidos artificialmente, «Secuencia» es el más parecido al real en cuanto a conjunto de indicadores. Es 2 veces más rápido que el real en términos de tiempo y es 1,5 veces más eficiente en términos de consumo de memoria. El modo «Flutter» parece conservar mejor el número original de operaciones. El modo fractal, más rápido y menos exigente en memoria, por supuesto, requiere más tiempo y recursos que OHLC M1, pero no sobreestima las puntuaciones de trading.
Tenga en cuenta que los algoritmos de reducción de ticks pueden funcionar de forma diferente o, por el contrario, dar malos resultados con diferentes estrategias de trading, instrumentos financieros e incluso el historial de ticks de un determinado bróker. Investigue con sus Asesores Expertos y en su entorno de trabajo.
Como parte del segundo ejemplo de trabajo con símbolos personalizados, consideremos una característica interesante proporcionada por la traducción de ticks mediante CustomTicksAdd.
Muchos operadores utilizan paneles de trading, es decir, programas con controles interactivos para realizar manualmente acciones de trading arbitrarias. Tiene que practicar a trabajar con ellos sobre todo en línea, ya que el probador impone algunas restricciones. En primer lugar, el probador no admite eventos y objetos en el gráfico. Esto hace que los controles dejen de funcionar. Además, en el probador no se pueden aplicar objetos arbitrarios para el marcado de gráficos.
Intentemos resolver estos problemas.
Podemos generar un símbolo personalizado basado en ticks históricos a cámara lenta. Entonces, el gráfico de dicho símbolo se convertirá en un análogo de un probador visual.
Este planteamiento tiene varias ventajas:
- Comportamiento estándar de todos los eventos del gráfico
- Aplicación interactiva y establecimiento de indicadores
- Aplicación interactiva y ajuste de objetos
- Conmutación de marcos temporales sobre la marcha
- Pruebe el historial hasta el momento actual, incluido el día de hoy (el probador estándar no permite la simulación del día de hoy).
En cuanto al último punto, observamos que los desarrolladores de MetaTrader 5 prohibieron deliberadamente la comprobación de trading en el último día (actual), aunque a veces es necesario para encontrar rápidamente errores (en el código o en la estrategia de trading).
También es potencialmente interesante modificar los precios sobre la marcha (aumentando el diferencial, por ejemplo).
Basándonos en el gráfico de dicho símbolo personalizado, más adelante podemos implementar un emulador de trading manual sobre datos históricos.
El generador de símbolos será el Asesor Experto no de trading CustomTester.mq5. En sus parámetros de entrada, proporcionaremos una indicación de la colocación de un nuevo símbolo personalizado en la jerarquía de símbolos, la fecha de inicio en el pasado para la traducción de ticks (y la construcción de cotizaciones de símbolos personalizados), así como un marco temporal para el gráfico, que se abrirá automáticamente para la simulación visual.
input string CustomPath = "MQL5Book\\Part7"; // Custom Symbol Folder
|
El nombre del nuevo símbolo se construye a partir del nombre del símbolo del gráfico actual y el sufijo «.Tester».
string CustomSymbol = _Symbol + ".Tester"; |
Si no se especifica la fecha de inicio en los parámetros, el Asesor Experto retrocederá 120 días desde la fecha actual.
const uint DailySeconds = 60 * 60 * 24;
|
Los ticks se leerán del historial de ticks reales del símbolo de trabajo por lotes para todo el día al mismo tiempo. El puntero al día que se está leyendo se almacena en la variable Cursor.
bool FirstCopy = true;
|
Los ticks de un día que se van a reproducir se solicitarán en el array Ticks, desde donde se trasladarán en pequeños lotes de tamaño step al gráfico de un símbolo personalizado.
MqlTick Ticks[]; // ticks for the "current" day in the past
|
Para reproducir los ticks a un ritmo constante, iniciemos el temporizador en OnInit.
void OnInit()
|
Los ticks serán generados por la función GenerateData. Inmediatamente después del lanzamiento, cuando se reinicia la bandera InitDone, intentaremos crear un nuevo símbolo o borrar las cotizaciones y ticks antiguos si el símbolo personalizado ya existe.
bool GenerateData()
|
En este punto, omitiremos algo en (A) y volveremos a este punto más adelante.
Una vez creado el símbolo, lo seleccionamos en Observación de Mercado y abrimos un gráfico para él.
SymbolSelect(CustomSymbol, true);
|
Aquí también faltan un par de líneas (B); están relacionadas con futuras mejoras, pero aún no son necesarias.
Si el símbolo ya ha sido creado, empezamos a emitir ticks en lotes de Step ticks, pero no más de 256. Esta limitación está relacionada con las particularidades de la función CustomTicksAdd.
else
|
La función de ayuda GenerateTicks emite ticks en lotes de Step ticks (pero no más de 256), leyéndolos del array diario Ticks por Index de desplazamiento. Cuando el array está vacío o lo hemos leído hasta el final, solicitamos los ticks del día siguiente llamando a FillTickBuffer.
bool GenerateTicks()
|
La función FillTickBuffer utiliza CopyTicksRange para el funcionamiento.
bool FillTickBuffer()
|
Cuando el Asesor Experto se detenga, cerraremos también el gráfico dependiente (para que no se duplique en el siguiente inicio).
void OnDeinit(const int)
|
En este punto, el Asesor Experto podría considerarse completo, pero hay un problema. El caso es que, por una razón u otra, las propiedades de un símbolo personalizado no se copian «tal cual» del símbolo de trabajo original, al menos en la implementación actual de la API de MQL5. Esto se aplica incluso a propiedades muy importantes, como SYMBOL_TRADE_TICK_VALUE, SYMBOL_TRADE_TICK_SIZE. Si imprimimos los valores de estas propiedades inmediatamente después de llamar a CustomSymbolCreate(CustomSymbol, CustomPath, _Symbol), veremos ceros allí.
Para organizar la comprobación de las propiedades, su comparación y, en caso necesario, su corrección, hemos escrito una clase especial CustomSymbolMonitor (CustomSymbolMonitor.mqh) derivada de SymbolMonitor. Puede estudiar su estructura interna por su cuenta, mientras que aquí sólo presentaremos la interfaz pública.
Los constructores permiten crear un monitor de símbolos personalizado, especificando un símbolo de trabajo ejemplar (por nombre en una cadena, o a partir del objeto SymbolMonitor) que sirve como fuente de ajustes.
class CustomSymbolMonitor: public SymbolMonitor
|
Dado que los símbolos personalizados, a diferencia de los símbolos estándar, permiten establecer propiedades propias, se ha añadido a la clase un triple de métodos set. En concreto, se utilizan para transferir por lotes las propiedades de una muestra y comprobar el éxito de estas acciones en otros métodos de la clase.
Ahora podemos volver al generador de símbolos personalizado y a su fragmento de código fuente, tal y como indicaba antes el comentario (A).
// (A) check important properties and set them in "manual" mode
|
Ahora puede ejecutar el Asesor Experto CustomTester.mq5 y observar cómo se forman dinámicamente las cotizaciones en el gráfico que se abre automáticamente, y también cómo se reenvían los ticks desde el historial en la ventana Observación de Mercado.
No obstante, esto se hace a un ritmo constante de 32 ticks por 0.1 segundo. Es conveniente cambiar la velocidad de reproducción sobre la marcha a petición del usuario, tanto hacia arriba como hacia abajo. Este control puede organizarse, por ejemplo, desde el teclado.
Por lo tanto, es necesario añadir el manejador OnChartEvent. Como sabemos, para el evento CHARTEVENT_KEYDOWN, el programa recibe el código de la tecla pulsada en el parámetro lparam, y lo pasamos a la función CheckKeys (ver más abajo). Un fragmento (C), estrechamente relacionado con (B), ha tenido que ser pospuesto por el momento y volveremos a él en breve.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
En la función CheckKeys estamos procesando las teclas «flecha arriba» y «flecha abajo» para aumentar y disminuir la velocidad de reproducción. Además, la tecla «pausa» permite suspender completamente el proceso de «simulación» (transmisión de ticks). Al pulsar de nuevo «pausa» se reanuda el trabajo a la misma velocidad.
void CheckKeys(const long key)
|
El nuevo código puede probarse en acción después de asegurarse primero de que el gráfico en el que trabaja el Asesor Experto está activo. Recuerde que los eventos de teclado sólo van a la ventana activa. Este es otro problema de nuestro probador.
Dado que el usuario debe realizar acciones de trading en el gráfico de símbolos personalizado, la ventana del generador estará casi siempre en segundo plano. Pasar a la ventana del generador para detener temporalmente el flujo de ticks y reanudarlo después no resulta práctico. Por lo tanto, se requiere de alguna manera para organizar el control interactivo desde el teclado directamente desde la ventana de símbolos personalizados.
Para ello, es adecuado un indicador especial, que podemos añadir automáticamente a la ventana de símbolos personalizados que se abre. El indicador interceptará los eventos de teclado en su propia ventana (ventana con un símbolo personalizado) y los enviará a la ventana del generador.
El código fuente del indicador se adjunta en el archivo KeyboardSpy.mq5. Por supuesto, el indicador no tiene gráficos. Un par de parámetros de entrada están dedicados a obtener el ID del gráfico HostID, donde deben enviarse los mensajes y el código de evento personalizado EventID, en el que se empaquetarán los eventos interactivos.
#property indicator_chart_window
|
El trabajo principal se realiza en el manejador OnChartEvent.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
Tenga en cuenta que todas las «teclas de acceso rápido» que hemos elegido son sencillas, es decir, no utilizan atajos con teclas de estado del teclado, como Ctrl o Shift. Esto se hizo a la fuerza porque dentro de los indicadores creados programáticamente (en particular, a través de iCustom) no se lee el estado del teclado. En otras palabras: llamar a TerminalInfoInteger(TERMINAL_KEYSTATE_XYZ) siempre devuelve 0. En el manejador anterior, lo hemos añadido sólo a efectos de demostración, para que pueda comprobar esta limitación si lo desea, mostrando los parámetros entrantes en el «lado receptor».
Sin embargo, los clics de flecha y pausa se transferirán al gráfico principal normalmente, y eso es suficiente para nosotros. Lo único que queda por hacer es integrar el indicador con el Asesor Experto.
En el fragmento omitido anteriormente (B), durante la inicialización del generador crearemos un indicador y lo añadiremos al gráfico de símbolos personalizado.
#define EVENT_KEY 0xDED // custom event
|
Más adelante, en el fragmento (C), garantizaremos la recepción de mensajes de usuario del indicador y su transferencia a la función ya conocida CheckKeys.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
Así, ahora se puede controlar la velocidad de reproducción tanto en el gráfico con el Asesor Experto como en el gráfico del símbolo personalizado generado por él.
Con el nuevo conjunto de herramientas, puede intentar trabajar de forma interactiva con un gráfico que «vive en el pasado». En el gráfico aparece un comentario con la velocidad de reproducción actual o una marca de pausa.
En el gráfico con el Asesor Experto, el tiempo de los ticks de emisión «actual» se muestra en el comentario.
Asesor Experto que reproduce el historial de ticks (y cotizaciones) de un símbolo real
Básicamente, el usuario no puede hacer nada en esta ventana (si solo se elimina el Asesor Experto y se detiene la generación de símbolos personalizados). Aquí no es visible el proceso de traducción de ticks en sí. Además, como el Asesor Experto abre automáticamente un gráfico de símbolos personalizado (donde se actualizan las cotizaciones históricas), es éste el que se activa. Para obtener la captura de pantalla anterior tuvimos que cambiar brevemente al gráfico original.
Por lo tanto, volvamos al gráfico del símbolo personalizado. La forma en que se actualiza de manera suave y progresiva en el pasado ya es estupenda, pero no se pueden realizar experimentos de trading con ella. Por ejemplo, si ejecuta en él su panel de trading habitual, sus controles, aunque formalmente funcionarán, no ejecutarán las transacciones, ya que el símbolo personalizado no existe en el servidor, y por tanto obtendrá errores. Esta característica se observa en cualquier programa que no esté especialmente adaptado para símbolos personalizados. Veamos un ejemplo de cómo puede virtualizarse el trading con un símbolo personalizado.
En lugar de un panel de trading (para simplificar el ejemplo, pero sin pérdida de generalidad), tomaremos como base el Asesor Experto más sencillo, CustomOrderSend.mq5, que puede realizar varias acciones de trading a golpe de tecla:
- 'B' - compra en el mercado
- 'S' - venta en el mercado
- 'U' - colocación de una orden de compra limitada
- 'L' - colocación de una orden de venta limitada
- 'C' - cerrar todas las posiciones
- 'D' - borrar todas las órdenes
- 'R' - enviar un informe de trading al diario
En los parámetros de entrada del Asesor Experto estableceremos el volumen de una operación (por defecto, el lote mínimo) y la distancia a los niveles de Stop Loss y Take Profit en puntos.
input double Volume; // Volume (0 = minimal lot)
|
Si Distance2SLTP se deja igual a cero, no se colocan niveles de protección en las órdenes de mercado y no se forman órdenes pendientes. Cuando Distance2SLTP tiene un valor distinto de cero, se utiliza como la distancia desde el precio actual al colocar una orden pendiente (ya sea al alza o a la baja, dependiendo del comando).
Teniendo en cuenta las clases presentadas anteriormente de MqlTradeSync.mqh, la lógica anterior se convierte en el siguiente código fuente.
#include <MQL5Book/MqlTradeSync.mqh>
|
Como podemos ver, aquí se utilizan tanto funciones estándar de la API de trading como métodos de MqlTradeRequestSync. Este último, indirectamente, también acaba llamando a un montón de funciones integradas. Necesitamos hacer que este Asesor Experto opere con un símbolo personalizado.
La idea más sencilla, aunque requiera tiempo, es sustituir todas las funciones estándar por sus propios análogos que cuenten órdenes, transacciones, posiciones y estadísticas financieras en algunas estructuras. Por supuesto, esto es posible sólo en los casos en que tenemos el código fuente del Asesor Experto, que debe adaptarse.
En el archivo adjunto CustomTrade.mqh se muestra una aplicación experimental de este enfoque. Puede familiarizarse con el código completo por su cuenta, ya que en el marco del libro sólo enumeraremos los puntos principales.
En primer lugar, observamos que muchos cálculos se hacen de forma simplificada, muchos modos no son compatibles y no se realiza una comprobación completa de la corrección de los datos. Utilice el código fuente como punto de partida para sus propios desarrollos.
Todo el código está envuelto en el espacio de nombres CustomTrade para evitar conflictos.
Las entidades de orden, transacción y posición se formalizan como las clases correspondientes CustomOrder, CustomDeal y CustomPosition. Todos ellos son herederos de la clase MonitorInterface<I,D,S>::TradeState. Recordemos que esta clase ya admite automáticamente la formación de arrays de propiedades de enteros, reales y cadenas para cada tipo de objeto y sus triples específicos de enumeraciones. Por ejemplo, CustomOrder tiene este aspecto:
class CustomOrder: public MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,
|
Tenga en cuenta que en el entorno virtual de la antigua hora «actual» no puede utilizar la función TimeCurrent y en su lugar se toma la última hora conocida del símbolo personalizado SymbolInfoInteger(symbol, SYMBOL_TIME).
Durante el trading virtual, los objetos actuales y su historial se acumulan en arrays de las clases correspondientes.
AutoPtr<CustomOrder> orders[];
|
La metáfora para seleccionar órdenes, transacciones y posiciones era necesaria para simular un enfoque similar en las funciones integradas. Para ellas existen duplicados en el espacio de nombres CustomTrade que sustituyen a los originales mediante directivas de sustitución de macros.
#define HistorySelect CustomTrade::MT5HistorySelect
|
Por ejemplo, así es como se implementa la función MT5HistorySelectByPosition:
bool MT5HistorySelectByPosition(long id)
|
Como puede ver, todas las funciones de este grupo tienen el prefijo MT5, por lo que su doble propósito queda claro de inmediato y es fácil distinguirlas de las funciones del segundo grupo.
El segundo grupo de funciones del espacio de nombres CustomTrade realiza acciones utilitarias: comprueba y actualiza los estados de las órdenes, transacciones y posiciones, crea nuevos objetos y elimina los antiguos en función de la situación. En concreto, incluyen las funciones CheckPositions y CheckOrders, que pueden llamarse con un temporizador o en respuesta a acciones del usuario. Pero no puede hacer esto si utiliza un par de otras funciones diseñadas para mostrar el estado actual e histórico de la cuenta de trading virtual:
- string ReportTradeState() devuelve un texto multilínea con una lista de posiciones abiertas y órdenes colocadas.
- void PrintTradeHistory() muestra el historial de órdenes y operaciones en el registro.
Estas funciones llaman de forma independiente a CheckPositions y CheckOrders para proporcionarle información actualizada.
Además, existe una función para visualizar posiciones y órdenes activas en el gráfico en forma de objetos: DisplayTrades.
El archivo de encabezado CustomTrade.mqh debe incluirse en el Asesor Experto antes que otros encabezados para que la sustitución de macros tenga efecto en todas las líneas posteriores de los códigos fuente.
#include <MQL5Book/CustomTrade.mqh>
|
Ahora, el algoritmo anterior CustomOrderSend.mq5 puede empezar a «operar» en el entorno virtual basado en el símbolo personalizado actual (que no requiere un servidor o un probador estándar) sin ningún cambio adicional.
Para mostrar rápidamente el estado, iniciaremos un segundo temporizador y cambiaremos periódicamente el comentario, además de mostrar objetos gráficos.
int OnInit()
|
Para construir un informe pulsando 'R', añadimos el manejador OnChartEvent.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
Por último, todo está listo para probar el nuevo paquete de software en acción.
Ejecute el generador de símbolos personalizados CustomTester.mq5 en EURUSD. En el gráfico «EURUSD.Tester» que se abre, ejecute CustomOrderSend.mq5 y comience a operar. A continuación se muestra una imagen del proceso de simulación:
Trading virtual en un gráfico de símbolos personalizado
Aquí puede ver dos posiciones largas abiertas (con niveles de protección) y una orden pendiente límite de venta.
Transcurrido un tiempo, una de las posiciones (indicada a continuación por una línea azul discontinua con una flecha) se cierra y se activa una orden de venta pendiente (línea roja con una flecha), lo que da como resultado la siguiente imagen:
Trading virtual en un gráfico de símbolos personalizado
Tras cerrar todas las posiciones (algunas por Take Profit y el resto por orden del usuario), se ordenó un informe pulsando 'R'.
History Orders: (1) #1 ORDER_TYPE_BUY 2022.02.15 01:20:50 -> 2022.02.15 01:20:50 L=0.01 @ 1.1306 (4) #2 ORDER_TYPE_SELL_LIMIT 2022.02.15 02:34:29 -> 2022.02.15 18:10:17 L=0.01 @ 1.13626 [sell limit] (2) #3 ORDER_TYPE_BUY 2022.02.15 10:08:20 -> 2022.02.15 10:08:20 L=0.01 @ 1.13189 (3) #4 ORDER_TYPE_BUY 2022.02.15 15:01:26 -> 2022.02.15 15:01:26 L=0.01 @ 1.13442 (1) #5 ORDER_TYPE_SELL 2022.02.15 15:35:43 -> 2022.02.15 15:35:43 L=0.01 @ 1.13568 (2) #6 ORDER_TYPE_SELL 2022.02.16 09:39:17 -> 2022.02.16 09:39:17 L=0.01 @ 1.13724 (4) #7 ORDER_TYPE_BUY 2022.02.16 23:31:15 -> 2022.02.16 23:31:15 L=0.01 @ 1.13748 (3) #8 ORDER_TYPE_SELL 2022.02.16 23:31:15 -> 2022.02.16 23:31:15 L=0.01 @ 1.13742 Deals: (1) #1 [#1] DEAL_TYPE_BUY DEAL_ENTRY_IN 2022.02.15 01:20:50 L=0.01 @ 1.1306 = 0.00 (2) #2 [#3] DEAL_TYPE_BUY DEAL_ENTRY_IN 2022.02.15 10:08:20 L=0.01 @ 1.13189 = 0.00 (3) #3 [#4] DEAL_TYPE_BUY DEAL_ENTRY_IN 2022.02.15 15:01:26 L=0.01 @ 1.13442 = 0.00 (1) #4 [#5] DEAL_TYPE_SELL DEAL_ENTRY_OUT 2022.02.15 15:35:43 L=0.01 @ 1.13568 = 5.08 [tp] (4) #5 [#2] DEAL_TYPE_SELL DEAL_ENTRY_IN 2022.02.15 18:10:17 L=0.01 @ 1.13626 = 0.00 (2) #6 [#6] DEAL_TYPE_SELL DEAL_ENTRY_OUT 2022.02.16 09:39:17 L=0.01 @ 1.13724 = 5.35 [tp] (4) #7 [#7] DEAL_TYPE_BUY DEAL_ENTRY_OUT 2022.02.16 23:31:15 L=0.01 @ 1.13748 = -1.22 (3) #8 [#8] DEAL_TYPE_SELL DEAL_ENTRY_OUT 2022.02.16 23:31:15 L=0.01 @ 1.13742 = 3.00 Total: 12.21, Trades: 4 |
Los paréntesis indican los identificadores de posición y los corchetes, los tickets de las órdenes para las transacciones correspondientes (los tickets de ambos tipos van precedidos de una «almohadilla», '#').
Aquí no se tienen en cuenta swaps ni comisiones. Su cálculo puede sumarse.
Consideraremos otro ejemplo de trabajo con ticks de símbolos personalizados en la sección sobre particularidades del trading con símbolos personalizados. Hablaremos de la creación de gráficos de equivolumen.