- Tipos de objetos y características de la especificación de sus coordenadas
- Objetos vinculados a tiempo y precio
- Objetos vinculados a coordenadas de pantalla
- Crear objetos
- Borrar objetos
- Encontrar objetos
- Visión general de las funciones de acceso a las propiedades de los objetos
- Propiedades principales de los objetos
- Coordenadas de tiempo y precio
- Anclar la esquina de la ventana y las coordenadas de la pantalla
- Definir el punto de anclaje en el objeto
- Gestión del estado de los objetos
- Prioridad de los objetos (orden Z)
- Ajustes de visualización de objetos: color, estilo y marco
- Ajustes de fuente
- Rotar un texto en un ángulo arbitrario
- Determinar ancho y alto del objeto
- Visibilidad de los objetos en el contexto de marcos temporales
- Asignar un código de carácter a una etiqueta
- Propiedades de los rayos para objetos con líneas rectas
- Gestionar el estado pulsado de los objetos
- Ajustar imágenes en objetos bitmap
- Recortar (dar salida a parte) de una imagen
- Propiedades de los campos de entrada: alineación y sólo lectura
- Anchura del canal de desviación estándar
- Establecer niveles en objetos de nivel
- Propiedades adicionales de los objetos de Gann, Fibonacci y Elliot
- Objeto gráfico
- Mover objetos
- Obtener hora o precio en puntos de línea especificados
Visión general de las funciones de acceso a las propiedades de los objetos
Los objetos tienen varios tipos de propiedades que pueden leerse y establecerse utilizando las funciones ObjectGet y ObjectSet. Como sabemos, este principio ya se ha aplicado al gráfico (véase la sección Visión general de las funciones para trabajar con el conjunto completo de propiedades de gráfico ).
Todas estas funciones toman como primeros tres parámetros un identificador de gráfico, un nombre de objeto y un identificador de propiedad, que debe pertenecer a una de las enumeraciones ENUM_OBJECT_PROPERTY_INTEGER, ENUM_OBJECT_PROPERTY_DOUBLE o ENUM_OBJECT_PROPERTY_STRING. Estudiaremos propiedades específicas gradualmente en las siguientes secciones. Sus tablas dinámicas completas se pueden encontrar en la documentación de MQL5, en la página con Propiedades de los objetos.
Cabe señalar que los identificadores de propiedad de las tres enumeraciones no se intersecan, lo que permite combinar su tratamiento conjunto en un único código unificado. Lo utilizaremos en los ejemplos.
Algunas propiedades son de sólo lectura y se marcarán con «r/o» (read-only).
Como en el caso de la API de trazado, las funciones de lectura de propiedades tienen una forma corta y una forma larga: la forma corta devuelve directamente el valor solicitado, y la forma larga devuelve un booleano de éxito (true) o errores (false), y el valor en sí se coloca en el último parámetro pasado por referencia. La ausencia de error al llamar al formulario corto debe comprobarse utilizando la variable incorporada _LastError.
Al acceder a algunas propiedades, debe especificar un parámetro adicional (modifier), que se utiliza para indicar el número de valor o el nivel si la propiedad es multivalorada. Por ejemplo, si un objeto tiene varios puntos de anclaje, el modificador permite seleccionar uno en concreto.
A continuación se muestran los prototipos de funciones para leer y escribir propiedades de enteros. Tenga en cuenta que el tipo de valores en ellos es long, lo que le permite almacenar propiedades no sólo de los tipos int o long, sino también bool, color, datetime y varias enumeraciones (véase más adelante).
bool ObjectSetInteger(long chartId, const string name, ENUM_OBJECT_PROPERTY_INTEGER property, long value)
bool ObjectSetInteger(long chartId, const string name, ENUM_OBJECT_PROPERTY_INTEGER property, int modifier, long value)
long ObjectGetInteger(long chartId, const string name, ENUM_OBJECT_PROPERTY_INTEGER property, int modifier = 0)
bool ObjectGetInteger(long chartId, const string name, ENUM_OBJECT_PROPERTY_INTEGER property, int modifier, long &value)
Las funciones para propiedades reales se describen de forma similar.
bool ObjectSetDouble(long chartId, const string name, ENUM_OBJECT_PROPERTY_DOUBLE property, double value)
bool ObjectSetDouble(long chartId, const string name, ENUM_OBJECT_PROPERTY_DOUBLE property, int modifier, double value)
double ObjectGetDouble(long chartId, const string name, ENUM_OBJECT_PROPERTY_DOUBLE property, int modifier = 0)
bool ObjectGetDouble(long chartId, const string name, ENUM_OBJECT_PROPERTY_DOUBLE property, int modifier, double &value)
Por último, existen cuatro funciones idénticas para las cadenas.
bool ObjectSetString(long chartId, const string name, ENUM_OBJECT_PROPERTY_STRING property, const string value)
bool ObjectSetString(long chartId, const string name, ENUM_OBJECT_PROPERTY_STRING property, int modifier, const string value)
string ObjectGetString(long chartId, const string name, ENUM_OBJECT_PROPERTY_STRING property, int modifier = 0)
bool ObjectGetString(long chartId, const string name, ENUM_OBJECT_PROPERTY_STRING property, int modifier, string &value)
Para mejorar el rendimiento, todas las funciones para establecer las propiedades de los objetos (ObjectSetInteger, ObjectSetDouble y ObjectSetString) son asíncronas y esencialmente envían comandos al gráfico para modificar el objeto. Una vez ejecutadas con éxito estas funciones, los comandos se colocan en la cola de eventos compartidos del gráfico, indicada por el resultado devuelto de true. Cuando se produce un error, las funciones devolverán false, y el código de error debe comprobarse en la variable _LastError.
Las propiedades de los objetos se modifican con cierto retraso, durante el procesamiento de la cola de eventos del gráfico. Para forzar la actualización del aspecto y las propiedades de los objetos del gráfico, especialmente después de cambiar muchos objetos a la vez, utilice la función ChartRedraw.
Las funciones para obtener las propiedades de los gráficos (ObjectGetInteger, ObjectGetDouble y ObjectGetString) son síncronas, es decir, el código de llamada espera el resultado de su ejecución. En este caso, se ejecutan todos los comandos de la cola de gráficos para obtener el valor real de las propiedades.
Volvamos al ejemplo del script para borrar objetos; más concretamente, a su nueva versión, ObjectCleanup2.mq5. Recordemos que en la función CustomDeleteAllObjects queríamos implementar la posibilidad de seleccionar objetos en función de sus propiedades. Digamos que estas propiedades deben ser el color y el punto de anclaje. Para obtenerlos, utilice la función ObjectGetInteger y un par de elementos de enumeración ENUM_OBJECT_PROPERTY_INTEGER: OBJPROP_COLOR y OBJPROP_ANCHOR. Los analizaremos en detalle más adelante.
Dada esta información, el código se completaría con las siguientes comprobaciones (aquí, por simplicidad, el color y el punto de anclaje vienen dados por las constantes clrRed y ANCHOR_TOP. De hecho, les proporcionaremos variables de entrada).
int CustomDeleteAllObjects(const long chart, const string prefix,
|
Preste atención a las líneas con ObjectGetInteger.
Su entrada es larga y contiene cierta tautología porque las propiedades específicas están vinculadas a funciones ObjectGet de tipos conocidos. Además, a medida que aumenta el número de condiciones, puede parecer redundante repetir el ID del gráfico y el nombre del objeto.
Para simplificar el registro, pasemos a la tecnología que probamos en el archivo ChartModeMonitor.mqh en la sección sobre Modos de visualización de gráficos. Su significado es describir una clase intermediaria con sobrecargas de métodos para leer y escribir propiedades de todos los tipos. Pongamos nombre al nuevo archivo de encabezado ObjectMonitor.mqh.
La clase ObjectProxy reproduce fielmente la estructura de la clase ChartModeMonitorInterface para gráficos. La principal diferencia es la presencia de métodos virtuales para establecer y obtener el ID del gráfico y el nombre del objeto.
class ObjectProxy
|
Vamos a implementar estos métodos en la clase descendiente (más adelante complementaremos la jerarquía de clases con el monitor de propiedades del objeto, similar al monitor de propiedades del gráfico).
class ObjectSelector: public ObjectProxy
|
Hemos separado la interfaz abstracta ObjectProxy y su implementación mínima en ObjectSelector porque más adelante podemos necesitar implementar un array de proxies para múltiples objetos del mismo tipo, por ejemplo. Entonces basta con almacenar un array de nombres o su prefijo común en la nueva clase «multiselector» y asegurarse de que uno de ellos es devuelto desde el método name llamando al operador sobrecargado []:multiSelector[i].get(OBJPROP_XYZ).
Ahora volvamos al script ObjectCleanup2.mq5 y describamos dos variables de entrada para especificar un color y un punto de anclaje como condiciones adicionales para seleccionar los objetos que se van a eliminar.
// ObjectCleanup2.mq5
|
Pasemos estos valores a la función CustomDeleteAllObjects, y las nuevas comprobaciones de condiciones en el bucle sobre objetos podrán formularse de forma más compacta gracias a la clase mediadora.
#include <MQL5Book/ObjectMonitor.mqh>
|
Es importante señalar que especificamos el nombre del objeto (y el identificador implícito del gráfico 0 actual) sólo una vez al crear el objeto ObjectSelector. Además, todas las propiedades se solicitan mediante el método get con un único parámetro que describe la propiedad deseada, y el compilador elegirá automáticamente la función ObjectGet adecuada.
La comprobación adicional del código de error 4203 (OBJECT_WRONG_PROPERTY) permite filtrar los objetos que no tienen la propiedad solicitada, como OBJPROP_ANCHOR. De este modo, en concreto, es posible hacer una selección en la que caerán todos los tipos de flechas (sin necesidad de solicitar por separado distintos tipos de OBJ_ARROW_XYZ), pero las líneas y los «eventos» quedarán excluidos del procesamiento.
Esto es fácil de comprobar ejecutando primero el script ObjectSimpleShowcase.mq5 en el gráfico (creará 14 objetos de diferentes tipos) y luego ObjectCleanup2.mq5. Si activa el modo UseCustomDeleteAll, habrá 5 objetos no borrados en el gráfico: OBJ_VLINE, OBJ_HLINE, OBJ_ARROW_BUY, OBJ_ARROW_SELL, and OBJ_EVENT. Los dos primeros y el último no tienen la propiedad OBJPROP_ANCHOR, y las flechas de compra y venta no pasan por color (se asume que el color de todos los demás objetos creados es rojo por defecto).
No obstante, ObjectSelector se proporciona no sólo por el bien de la sencilla aplicación anterior: es la base para crear un monitor de propiedades para un solo objeto, similar a lo que se implementó para los gráficos. Así, el archivo de encabezado ObjectMonitor.mqh contiene algo más interesante.
class ObjectMonitorInterface: public ObjectSelector
|
Este conjunto de métodos debería recordarle ChartModeMonitorInterface de ChartModeMonitor.mqh. La única innovación es el método applyChanges, que copia las propiedades de un objeto a otro.
Basado en ObjectMonitorInterface, aquí está la descripción de la implementación básica de un monitor de propiedades para un par de tipos de plantilla: un tipo de valor de propiedad (uno de long, double o string) y el tipo de enumeración (uno de ENUM_OBJECT_PROPERTY_ish).
template<typename T,typename E>
|
El constructor ObjectMonitorBase tiene dos parámetros: el nombre del objeto y un array de banderas con identificadores de las propiedades que deben observarse en el objeto especificado. Una parte importante de este código es casi idéntica a ChartModeMonitor. En concreto, como antes, se pasa un array de banderas al método de ayuda detect, cuyo propósito principal es identificar aquellas constantes de enteros que son elementos de la enumeración E, y eliminar el resto. El único añadido que hay que aclarar es la obtención de una propiedad con el número de niveles de un objeto a través de ObjectGetInteger(0, id, OBJPROP_LEVELS). Esto es necesario para admitir la iteración de propiedades con múltiples valores debido a la presencia de niveles (por ejemplo, Fibonacci). Para objetos sin niveles, obtendremos la cantidad 0, y tal propiedad será la escalar habitual.
public:
|
Por supuesto, el método detect es algo diferente de lo que vimos en ChartModeMonitor. Recordemos que, para empezar, contiene un fragmento con una comprobación de si la constante v pertenece a la enumeración E, mediante una llamada a la función EnumToString: si no existe tal elemento en la enumeración, se generará un código de error. Si el elemento existe, añadimos el valor de la propiedad correspondiente al array data.
// ChartModeMonitor.mqh
|
En el monitor de objetos, nos vemos obligados a complicar este esquema, ya que algunas propiedades son multivaloradas debido al parámetro modifier de las funciones ObjectGet y ObjectSet.
Así que introducimos un array estático modifiables con una lista de aquellas propiedades que los modificadores admiten (cada propiedad será discutida en detalle más adelante). La conclusión es que, para este tipo de propiedades multivaloradas, es necesario leerlas y almacenarlas en el array data no una vez, sino varias.
// ObjectMonitor.mqh
|
Aquí también utilizamos el truco con EnumToString para comprobar la existencia de una propiedad con el identificador v. Si tiene éxito, comprobamos si está en la lista de modifiables y establecemos la correspondiente bandera modifiable en true o false.
bool result = false;
|
Por defecto, cualquier propiedad se considera inequívoca y, por lo tanto, el número necesario de lecturas a través de la función ObjectGet o de entradas a través de la función ObjectSet es igual a 1 (la variable k a continuación).
int k = 1;
|
Si un objeto admite niveles, limitamos el número potencial de lecturas/escrituras con el parámetro levels (como recordamos, se obtiene en el código de llamada a partir de la propiedad OBJPROP_LEVELS).
Para la propiedad OBJPROP_BMPFILE, como pronto aprenderemos, sólo se permiten dos estados: activado (botón pulsado, bandera activada) o desactivado (botón liberado, bandera desactivada), por lo que k = 2.
Por último, las coordenadas de objeto (OBJPROP_TIME y OBJPROP_PRICE) son convenientes porque generan un error cuando se intenta leer/escribir un punto de anclaje inexistente. Por lo tanto, asignamos a k algún valor obviamente grande de MOD_MAX, y entonces podemos interrumpir el ciclo de lectura de puntos en un valor distinto de cero _LastError.
// read property value - one or many
|
Dado que una propiedad puede tener varios valores, que se leen en un bucle hasta k, ya no podemos escribir simplemente data.put((E)v, get((E)v)). Necesitamos combinar de alguna manera el identificador de la propiedad v y su número de modificación i. Afortunadamente, el número de propiedades también está limitado en una constante de entero (tipo int): no se ocupan más de dos bytes inferiores. Así que podemos utilizar operadores a nivel de bits para poner i al byte superior. Para ello se ha desarrollado la macro MOD_COMBINE.
#define MOD_COMBINE(V,I) (V | (I << 24)) |
Por supuesto, se proporcionan macros inversas para recuperar el ID de la propiedad y el número de revisión.
#define MOD_GET_NAME(V) (V & 0xFFFFFF)
|
Por ejemplo, aquí podemos ver cómo se utilizan en el método snapshot.
virtual int snapshot() override
|
Este método repite toda la lógica del método del mismo nombre en ChartModeMonitor.mqh; sin embargo, para leer propiedades en todas partes, primero debe extraer el nombre de la propiedad de la clave almacenada usando MOD_GET_NAME y el número usando MOD_GET_INDEX.
Una complicación similar tiene que hacerse en el método restore.
virtual void restore() override
|
La innovación más interesante de ObjectMonitorBase es cómo funciona con los cambios.
MapArray<E,T> * const getChanges()
|
Si pasamos al método applyChanges de estados del monitor de otro objeto, podemos adoptar todos los últimos cambios del mismo.
Para admitir propiedades de los tres tipos básicos (long,double,string) necesitamos implementar la clase ObjectMonitor (análoga a ChartModeMonitor de ChartModeMonitor.mqh).
class ObjectMonitor: public ObjectMonitorInterface
|
Aquí también se conserva la estructura de código anterior, y sólo se han añadido métodos para admitir cambios y nombres (los gráficos, como recordamos, no tienen nombres).
...
|
Basándose en el monitor de objetos creado, es fácil implementar varios trucos que no se admiten en el terminal. En concreto, se trata de la creación de copias de objetos y la edición en grupo de objetos.
Script ObjectCopy
El script ObjectCopy.mq5 demuestra cómo copiar objetos seleccionados. Al principio de su función OnStart, llenamos el array flags con enteros consecutivos que son candidatos a elementos de enumeraciones ENUM_OBJECT_PROPERTY_ de diferentes tipos. La numeración de los elementos de enumeración tiene una marcada agrupación por finalidad, y hay grandes espacios entre los grupos (aparentemente, un margen para futuros elementos), por lo que el array formado es bastante grande: 2048 elementos.
#include <MQL5Book/ObjectMonitor.mqh>
|
A continuación, recopilamos en un array los nombres de los objetos que están seleccionados actualmente en el gráfico. Para ello, utilizamos la propiedad OBJPROP_SELECTED.
string selected[];
|
Por último, en el bucle principal sobre los elementos seleccionados, leemos las propiedades de cada objeto, formamos el nombre de su copia y creamos un objeto bajo él con el mismo conjunto de atributos.
for(int i = 0; i < ArraySize(selected); ++i)
|
Es importante señalar aquí que la propiedad OBJPROP_TYPE es una de las pocas propiedades de sólo lectura, y por lo tanto es vital crear un objeto del tipo requerido para empezar.
La función de ayuda GetFreeName intenta añadir la cadena «/Copy #x» al nombre del objeto, donde x es el número de copia. Así, ejecutando el script varias veces, puede crear la 2ª, 3ª y sucesivas copias.
string GetFreeName(const string name)
|
El terminal recuerda la última configuración de un determinado tipo de objeto, y si se crean uno tras otro, esto equivale a copiar. No obstante, los ajustes suelen cambiar en el proceso de trabajo con diferentes gráficos, y si después de un tiempo hay necesidad de duplicar algún objeto «antiguo», entonces los ajustes para él, por regla general, tienen que hacerse por completo. Esto es especialmente costoso para los tipos de objetos con un gran número de propiedades, por ejemplo, las herramientas de Fibonacci. En tales casos, este script le resultará muy útil.
Algunas de las imágenes de este capítulo, que contienen objetos del mismo tipo, se han creado utilizando este script.
Indicador ObjectGroupEdit
El segundo ejemplo de utilización de ObjectMonitor es el indicador ObjectGroupEdit.mq5, que permite editar a la vez las propiedades de un grupo de objetos seleccionados.
Imaginemos que hemos seleccionado varios objetos en el gráfico (no necesariamente del mismo tipo), para los que es necesario cambiar uniformemente una u otra propiedad. A continuación, abrimos el cuadro de diálogo de propiedades de cualquiera de estos objetos, lo configuramos y, pulsando OK, estos cambios se aplican a todos los objetos seleccionados. Así es como funciona nuestro siguiente programa MQL.
Necesitábamos un indicador como tipo de programa porque implica eventos gráficos. Para este aspecto de la programación MQL5 dispondrá de un capítulo entero dedicado en exclusiva al respecto, pero ahora descubriremos algunos de los aspectos básicos.
Como el indicador no tiene gráficos, las directivas #property contienen ceros y la función OnCalculate está prácticamente vacía.
#property indicator_chart_window
|
Para generar automáticamente un conjunto completo de todas las propiedades de un objeto, utilizaremos de nuevo un array de 2048 elementos con valores enteros consecutivos. También proporcionaremos un array para los nombres de los elementos seleccionados y un array de objetos monitor de la clase ObjectMonitor.
int consts[2048];
|
En el manejador OnInit, inicializamos el array de números y ponemos en marcha el temporizador.
void OnInit()
|
En el manejador del temporizador, guardamos los nombres de los objetos seleccionados en un array. Si la lista de selección ha cambiado, es necesario reconfigurar los objetos monitor, para lo cual se llama a la función auxiliar TrackSelectedObjects.
void OnTimer()
|
La función TrackSelectedObjects en sí es bastante sencilla: borrar los monitores antiguos y crear otros nuevos. Si lo desea, puede hacerlo más inteligente manteniendo la parte no modificada de la selección.
void TrackSelectedObjects()
|
Recordemos que, cuando se crea un objeto monitor, se toma inmediatamente un «molde» de todas las propiedades del objeto gráfico correspondiente.
Ahora llegamos por fin a la parte en la que entran en juego los eventos. Como ya se mencionó en la visión general de las funciones de eventos, el manejador es responsable de los eventos OnChartEvent en el gráfico. En este ejemplo nos interesa un evento CHARTEVENT_OBJECT_CHANGE específico: ocurre cuando el usuario cambia cualquier atributo en el cuadro de diálogo de propiedades del objeto. El nombre del objeto modificado se pasa en el parámetro sparam.
Si este nombre coincide con uno de los objetos monitorizados, pedimos al monitor que haga una nueva instantánea de sus propiedades, es decir, llamamos a objects[i].snapshot().
void OnChartEvent(const int id,
|
Si los cambios se confirman (y es poco probable que no sea así), su número en la variable changes será mayor que 0. A continuación se inicia un bucle sobre todos los objetos seleccionados, y los cambios detectados se aplican a cada uno de ellos, excepto al original.
Dado que potencialmente podemos cambiar muchos objetos, llamamos a la petición de redibujado del gráfico con ChartRedraw.
En el manejador OnDeinit, eliminamos todos los monitores.
void OnDeinit(const int)
|
Eso es todo: la nueva herramienta está lista.
Este indicador permite personalizar el aspecto general de varios grupos de objetos de etiqueta en la sección sobre Definición del punto de anclaje del objeto.
Por cierto: de acuerdo con un principio similar con la ayuda de ObjectMonitor, usted puede hacer otra herramienta popular que no está disponible en el terminal: para deshacer ediciones a las propiedades del objeto, dado que el método restore ya está listo.