- Funciones para obtener las propiedades básicas del gráfico actual
- Identificación de gráficos
- Obtener la lista de gráficos
- Obtener el símbolo y el marco temporal de un gráfico arbitrario
- Visión general de funciones para trabajar con el conjunto completo de propiedades
- Propiedades descriptivas de los gráficos
- Comprobar el estado de la ventana principal
- Obtener el número y la visibilidad de las ventanas/subventanas
- Modos de visualización de gráficos
- Gestionar la visibilidad de los elementos del gráfico
- Desplazamientos horizontales
- Escala horizontal (por tiempo)
- Escala vertical (por precio y lecturas del indicador)
- Colores
- Control del ratón y del teclado
- Desacoplar la ventana del gráfico
- Obtener las coordenadas de caída del programa MQL en un gráfico
- Conversión de coordenadas de pantalla a tiempo/precio y viceversa
- Desplazamiento de gráficos por el eje temporal
- Solicitud para volver a dibujar el gráfico
- Cambiar símbolo y marco temporal
- Gestionar indicadores en el gráfico
- Abrir y cerrar gráficos
- Trabajar con plantillas de gráficos tpl
- Guardar la imagen de un gráfico
Trabajar con plantillas de gráficos tpl
La API de MQL5 proporciona dos funciones para trabajar con plantillas. Las plantillas son archivos con la extensión tpl que guardan el contenido de los gráficos, es decir, todos sus ajustes, junto con los objetos trazados, los indicadores y un EA (si lo hay).
bool ChartSaveTemplate(long chartId, const string filename)
La función guarda la configuración actual del gráfico en una plantilla tpl con el nombre especificado.
El gráfico lo establece chartId, 0 significa el gráfico actual.
El nombre del archivo para guardar la plantilla (filename) puede especificarse sin la extensión «.tpl», que se añadirá automáticamente. La plantilla predeterminada se guarda en la carpeta terminal_dir/Profiles/Templates/ y puede utilizarse para aplicación manual en el terminal. No obstante, es posible especificar no sólo un nombre, sino también una ruta relativa al directorio MQL5, en concreto, empezando por «/Archivos/». Así, será posible abrir la plantilla guardada utilizando funciones para trabajar con archivos, analizarlos y, si es necesario, editarlos (véase el ejemplo ChartTemplate.mq5 más adelante).
Si ya existe un archivo con el mismo nombre en la ruta especificada, se sobrescribirá su contenido.
Más adelante veremos un ejemplo combinado para guardar y aplicar una plantilla.
bool ChartApplyTemplate(long chartId, const string filename)
La función aplica una plantilla del archivo especificado al gráfico chartId.
El archivo de plantilla se busca de acuerdo con las siguientes reglas:
- Si filename contiene una ruta (que empiece por una barra invertida «\\» o una barra diagonal «/»), el patrón se compara con una ruta relativa terminal_data_directory/MQL5.
- Si no hay ruta en el nombre, la plantilla se busca en el mismo lugar en el que se encuentra el ejecutable del archivo EX5 en el que se llama a la función.
- Si la plantilla no se encuentra en los dos primeros lugares, se busca en la carpeta de plantillas estándar terminal_dir/Profiles/Templates/.
Tenga en cuenta que terminal_data_directory se refiere a la carpeta donde se almacenan los archivos modificados, y su ubicación puede variar en función del tipo de sistema operativo, el nombre de usuario y la configuración de seguridad del ordenador. Normalmente difiere de la carpeta terminal_dir aunque en algunos casos (por ejemplo, cuando se trabaja con una cuenta del grupo Administradores), pueden ser la misma. La ubicación de las carpetas terminal_data_directory y terminal_directory se puede encontrar utilizando la función TerminalInfoString (véanse las constantes TERMINAL_DATA_PATH y TERMINAL_PATH, respectivamente).
ChartApplyTemplate crea una orden que se añade a la cola de mensajes del gráfico y sólo se ejecuta después de que se hayan procesado todas las órdenes anteriores.
La carga de una plantilla detiene todos los programas MQL que se ejecutan en el gráfico, incluido el que inició la carga. Si la plantilla contiene indicadores y un Asesor Experto, se lanzarán sus nuevas instancias.
Por motivos de seguridad, al aplicar una plantilla con un Asesor Experto a un gráfico, los permisos de trading pueden ser limitados. Si el programa MQL que llama a la función ChartApplyTemplate no tiene permiso para operar, entonces el Asesor Experto cargado utilizando la plantilla no tendrá permiso para operar, independientemente de la configuración de la plantilla. Si al programa MQL que llama a ChartApplyTemplate se le permite operar pero no se permite el trading en la configuración de la plantilla, entonces al Asesor Experto cargado usando la plantilla no se le permitirá operar.
Un ejemplo de script ChartDuplicate.mq5 le permite crear una copia del gráfico actual.
void OnStart()
|
En primer lugar, se crea un archivo tpl temporal mediante ChartSaveTemplate; a continuación, se abre un nuevo gráfico (llamada a ChartOpen) y, por último, la función ChartApplyTemplate aplica esta plantilla al nuevo gráfico.
Sin embargo, en muchos casos, el programador se enfrenta a una tarea más difícil: no limitarse a aplicar la plantilla, sino preeditarla.
Usando las plantillas puede cambiar muchas propiedades de los gráficos que no están disponibles usando otras funciones de la API de MQL5; por ejemplo, la visibilidad de los indicadores en el contexto de los marcos temporales, el orden de las subventanas de los indicadores junto con los objetos aplicados a ellas, etc.
El formato del archivo tpl es idéntico al de los archivos chr utilizados por el terminal para almacenar gráficos entre sesiones (en la carpeta terminal_directory/Profiles/Charts/profile_name).
Un archivo tpl es un archivo de texto con una sintaxis especial. Las propiedades que contiene pueden ser un par clave=valor escrito en una sola línea o algún tipo de grupo que contenga varias propiedades clave=valor. Estos grupos se denominarán más abajo contenedores porque, además de propiedades individuales, también pueden contener otros contenedores anidados.
El contenedor comienza con una línea parecida a «<etiqueta>», donde tag es uno de los tipos de contenedor predefinidos (véase más abajo), y termina con un par de líneas como «</etiqueta>» (los nombres de las etiquetas deben coincidir). En otras palabras: el formato es similar en cierto sentido a XML (sin encabezado), en el que todas las unidades léxicas deben escribirse en líneas separadas y las propiedades de las etiquetas no se indican mediante sus atributos (como en XML dentro de la parte inicial «<tag attribute1=value1...>»), sino en el texto interior de la etiqueta.
Lista de etiquetas admitidas:
- chart - contenedor raíz con las propiedades del gráfico principal y todos los contenedores subordinados;
- expert - contenedor con propiedades generales de un Asesor Experto; por ejemplo, permiso para operar (dentro de un gráfico);
- window - contenedor con propiedades de ventana/subventana y sus contenedores subordinados (dentro del gráfico);
- object - contenedor con propiedades de objeto gráfico (dentro de la ventana);
- indicator - contenedor con las propiedades del indicador (dentro de la ventana);
- graph - contenedor con las propiedades del gráfico del indicador (dentro del indicador);
- level - contenedor con propiedades de nivel de indicador (dentro de indicador);
- period - contenedor con propiedades de visibilidad de un objeto o indicador en un marco temporal específico (dentro de un objeto o indicador);
- inputs - contenedor con configuraciones (variables de entrada) de indicadores personalizados y Asesores Expertos.
La posible lista de propiedades en pares clave=valor es bastante extensa y no tiene documentación oficial. Si es necesario, puede ocuparse usted mismo de estas características de la plataforma.
He aquí fragmentos de un archivo tpl (las sangrías en el formato se hacen para visualizar el anidamiento de contenedores):
<chart>
|
Disponemos del archivo de encabezado TplFile.mqh para trabajar con archivos tpl, con el que podrá analizar y modificar plantillas. Tiene dos clases:
- Container - para leer y almacenar elementos de archivos, teniendo en cuenta la jerarquía (anidamiento), así como para escribir en un archivo tras una posible modificación;
- Selector - para recorrer secuencialmente los elementos de la jerarquía (objetos Contenedor) en busca de una coincidencia con una determinada consulta que se escribe como una cadena similar a un selector xpath («/ruta/elemento[atributo=valor]»).
Los objetos de la clase Container se crean mediante un constructor que toma el descriptor del archivo tpl para leer como primer parámetro y el nombre de la etiqueta como segundo parámetro. Por defecto, el nombre de la etiqueta es NULL, es decir, el contenedor raíz (todo el archivo). Así, el propio contenedor se llena de contenido en el proceso de lectura del archivo (véase el método read).
Se supone que las propiedades del elemento actual, es decir, los pares «clave=valor» situados directamente dentro de este contenedor se añaden al mapa MapArray<string,string> properties. Los contenedores anidados se añaden al array Container *children[].
#include <MQL5Book/MapArray.mqh>
|
En el método read, leemos y analizamos el archivo línea por línea. En caso de etiqueta de apertura de la forma «<etiqueta>», creamos un nuevo objeto contenedor y continuamos leyendo en él. En caso de etiqueta de cierre de la forma «</etiqueta>» con el mismo nombre, devolvemos una bandera de éxito (true) que significa que el contenedor se ha generado. En las líneas restantes, leemos los pares «clave=valor» y los añadimos al array properties.
Hemos preparado Selector para buscar elementos en una plantilla. A su constructor se le pasa una cadena con la jerarquía de las etiquetas buscadas. Por ejemplo, la cadena «/gráfico/ventana/indicador» corresponde a un gráfico que tiene una ventana/subventana que, a su vez, contiene cualquier indicador. El resultado de la búsqueda será la primera coincidencia. Esta consulta, por regla general, encontrará el gráfico de cotizaciones, porque está almacenado en la plantilla como un indicador llamado «Principal» y va al principio del archivo, delante de otras subventanas.
Consultas más prácticas que especifican el nombre y el valor de un atributo concreto. En concreto, la cadena modificada «/gráfico/ventana/indicador[nombre=Momentum]» sólo buscará el indicador Momentum. Esta búsqueda es diferente a llamar a ChartWindowFind, porque aquí el nombre se especifica sin parámetros, mientras que ChartWindowFind utiliza un nombre corto del indicador, lo que normalmente incluye valores de parámetros, pero pueden variar.
Para los indicadores integrados, la propiedad name contiene el nombre en sí, y para los personalizados, dirá «Indicador personalizado». El enlace al indicador personalizado se indica en la propiedad path como una ruta al archivo ejecutable, por ejemplo, "Indicadores\MQL5Book\IndTripleEMA.ex5".
Veamos la estructura interna de la clase Selector.
class Selector
|
En el constructor, descomponemos la consulta selector en componentes independientes y los guardamos en el array path. El componente actual de la ruta que se está comparando en el patrón viene dado por la variable cursor. Al principio de la búsqueda, estamos en el contenedor raíz (estamos considerando todo el archivo tpl), y el cursor es 0. A medida que se encuentren coincidencias, cursor debería aumentar (véase el método accept más abajo).
El operador [], con cuya ayuda se puede obtener el i-ésimo fragmento de la ruta, está sobrecargado en la clase. También tiene en cuenta que en el fragmento, entre corchetes, se puede especificar el par «[clave=valor]».
string operator[](int i) const
|
El método accept comprueba si el nombre del elemento (tag) y sus propiedades (properties) coinciden con los datos especificados en la ruta del selector para la posición actual del cursor. El registro this[cursor] utiliza la sobrecarga anterior del operador [] .
bool accept(const string tag, MapArray<string,string> &properties)
|
El método devolverá false si el nombre de la etiqueta no coincide con el fragmento actual de la ruta, y también si el fragmento contenía el valor de algún parámetro y no es igual o no está en el array properties. En otros casos, obtendremos una coincidencia de las condiciones, como resultado de lo cual el cursor se moverá hacia adelante (cursor++) y el método devolverá true.
El proceso de búsqueda se completará con éxito cuando el cursor alcance el último fragmento de la petición, por lo que necesitamos un método para determinar este momento, que es isComplete.
bool isComplete() const
|
Además, durante el análisis de las plantillas, puede haber situaciones en las que hayamos recorrido la parte de la ruta correspondiente a la jerarquía de contenedores (es decir, hayamos encontrado varias coincidencias), tras lo cual el siguiente fragmento de solicitud no coincida. En este caso, es necesario «volver» a los niveles anteriores de la solicitud, para lo cual se implementa el método unwind.
bool unwind()
|
Ahora todo está listo para organizar la búsqueda en la jerarquía de contenedores (que obtenemos después de leer el archivo tpl) utilizando el objeto Selector. Todas las acciones necesarias serán realizadas por el método find de la clase Container. Toma el objeto Selector como parámetro de entrada y se llama a sí mismo recursivamente mientras haya coincidencias según el método Selector::accept. Alcanzar el final de la solicitud significa éxito, y el método find devolverá el contenedor actual al código de llamada.
Container *find(Selector *selector)
|
Observe que, a medida que nos desplazamos por el árbol de objetos, el método find registra el nombre de etiqueta del objeto actual y el número de objetos anidados, y lo hace con una sangría proporcional al nivel de anidamiento de los objetos. Si el artículo coincide con la solicitud, a la entrada del registro se le añade la palabra «aceptado».
También es importante señalar que esta implementación devuelve el primer elemento coincidente y no continúa buscando otros candidatos, y en teoría, esto puede ser útil para las plantillas porque a menudo tienen varias etiquetas del mismo tipo dentro del mismo contenedor. Por ejemplo, una ventana puede contener muchos objetos, y un programa MQL puede estar interesado en analizar toda la lista de objetos. Se propone estudiar este aspecto de forma opcional.
Para simplificar la llamada de búsqueda, se ha añadido un método del mismo nombre que toma un parámetro de cadena y crea el objeto Selector localmente.
Container *find(const string selector)
|
Dado que vamos a editar la plantilla, deberíamos proporcionar métodos para modificar el contenedor; en concreto, para añadir un par clave=valor y un nuevo contenedor anidado con una etiqueta dada.
void assign(const string key, const string value)
|
Tras la edición, deberá volver a escribir el contenido de los contenedores en un archivo (el mismo o distinto). Un método de ayuda save guarda el objeto en el formato tpl descrito anteriormente: comienza con la etiqueta de apertura «<etiqueta>», continúa descargando todas las propiedades clave=valor y llama a save para los objetos anidados, tras lo cual termina con la etiqueta de cierre «</etiqueta>». El descriptor de archivo se pasa como parámetro para guardar.
bool save(const int h)
|
El método de alto nivel para escribir una plantilla completa en un archivo se denomina write. Su parámetro de entrada (descriptor de archivo) puede ser igual a 0, lo que significa escribir en el mismo archivo del que se ha leído. Sin embargo, el archivo debe abrirse con permiso de escritura.
Es importante tener en cuenta que al sobrescribir un archivo de texto Unicode, MQL5 no escribe la marca UTF inicial (la denominada BOM, Byte Order Mark), y por lo tanto tenemos que hacerlo nosotros. De lo contrario, sin la marca, el terminal no leerá ni aplicará nuestra plantilla.
Si el código de llamada pasa en el parámetro h otro archivo abierto exclusivamente para escribir en formato Unicode, MQL5 escribirá la BOM automáticamente.
bool write(int h = 0)
|
Para demostrar las capacidades de las nuevas clases, considere el problema de ocultar la ventana de un indicador específico. Como sabe, el usuario puede conseguirlo restableciendo las banderas de visibilidad de los marcos temporales en el cuadro de diálogo de propiedades del indicador (pestaña Display). De forma programática, esto no puede hacerse directamente. Aquí es donde entra en juego la capacidad de editar la plantilla.
En la plantilla, la visibilidad del indicador para los marcos temporales se especifica en el contenedor <indicador>, dentro del cual se escribe un contenedor separado para cada marco temporal <período> visible. Por ejemplo, la visibilidad en el marco temporal M15 tiene este aspecto:
<period>
|
Dentro del contenedor <periodo> se utilizan las propiedades period_type y period_size. period_type es una unidad de medida, una de las siguientes:
- 0 para minutos
- 1 para horas
- 2 para semanas
- 3 para meses
period_size es el número de unidades de medida en el marco temporal. Debe tenerse en cuenta que el marco temporal diario es de 24 horas.
Cuando no hay ningún contenedor anidado <período> en el contenedor <indicador>, el indicador se muestra en todos los marcos temporales.
El libro viene con el script ChartTemplate.mq5, que añade el indicador Momentum al gráfico (si no está ya presente) y lo hace visible en un único marco temporal mensual.
void OnStart()
|
A continuación, guardamos la plantilla de gráfico actual en un archivo, que luego abrimos para escribir y leer. Sería posible asignar un archivo separado para la escritura.
const string filename = _Symbol + "-" + PeriodToString(_Period) + "-momentum-rw";
|
Una vez recibido un descriptor de archivo, creamos un contenedor raíz main y leemos todo el archivo en él (los contenedores anidados y todas sus propiedades se leerán automáticamente).
Container main(handle);
|
A continuación, definimos un selector para buscar el indicador Momentum. En teoría, un enfoque más riguroso también requeriría comprobar el periodo especificado (14), pero nuestras clases no admiten la consulta de varias propiedades al mismo tiempo (esta posibilidad se deja para su estudio por separado).
Usando el selector, buscamos e imprimimos el objeto encontrado (sólo como referencia) y añadimos su contenedor anidado <periodo> con la configuración para mostrar el marco temporal mensual.
Container *found = main.find("/chart/window/indicator[name=Momentum]");
|
Por último, escribimos la plantilla modificada en el mismo archivo, la cerramos y la aplicamos en el gráfico.
main.write(); // or main.write(writer);
|
Al ejecutar el script en un gráfico limpio, veremos dichas entradas en el registro:
ChartSaveTemplate(0,/Files/+filename)=true / ok
|
Aquí se puede ver que antes de encontrar el indicador deseado (marcado como «aceptado»), el algoritmo encontró el indicador en la ventana anterior principal, pero no encajaba, porque su nombre no es igual al «Momentum» deseado.
Ahora, si abre la lista de indicadores en el gráfico, habrá momentum, y en su cuadro de diálogo de propiedades, en la pestaña Display, el único marco temporal habilitado es Month.
El libro va acompañado de una versión ampliada del archivo TplFileFull.mqh, que admite distintas operaciones de comparación en las condiciones de selección de etiquetas y su selección múltiple en arrays. Un ejemplo de su uso puede encontrarse en el script ChartUnfix.mq5, que desajusta los tamaños de todas las subventanas del gráfico.