English Русский 中文 Deutsch 日本語 Português
MQL5 para principiantes, protección antivandálica de los objetos gráficos

MQL5 para principiantes, protección antivandálica de los objetos gráficos

MetaTrader 5Indicadores | 17 febrero 2016, 10:59
1 243 0
Dina Paches
Dina Paches

El vandalismo es una desviación del comportamiento humano
que consiste en destruir o en profanar los objetos de arte, los objetos culturales o
la propiedad pública o privada.

Wikipedia

Contenido:


1. Introducción

Una de las ventajas del lenguaje de programación MQL5 es que las funciones estándar permiten realizar muchas tareas distintas; por otro lado, usted puede alcanzar los objetivos que se plantee con la ayuda del terminal de trading MetaTrader 5.

Este artículo está escrito con un lenguaje sencillo y contiene dos variantes, a modo de ejemplo, que implementan la respuesta del programa al cambio o eliminación de objetos gráficos. Vamos a asegurarnos de que después de eliminar el programa no quedan objetos sin dueño en el gráfico porque si algo o alguien les cambia el nombre, esta situación puede hacer que el programa pierda el control.

Este ejemplo ilustra un panel de control antes y después de cambiar manualmente las propiedades de sus objetos

Fig. 1. Apariencia del panel de control de ejemplo, antes y después de cambiar manualmente las propiedades de sus objetos

Las acciones de respuesta a las interferencias externas del código que describimos en este artículo no son redundantes en aquellos casos donde, por ejemplo, se lanza un programa externo en el gráfico, y, no estando diseñado expresamente para su limpieza, utiliza una función que borra los objetos (ObjectsDeleteAll () o una función que usted mismo puede crear), operando con los parámetros establecidos en el mismo:

  • la eliminación completa de todos los tipos de objetos gráficos en la misma ventana/subventana, creados manualmente o con otros programas;
  • o la eliminación completa del tipo de objeto que también está presente en el panel de control del programa;
  • o la eliminación, por prefijo, que coincide con el prefijo de los objetos del programa.
Estas opciones también son relevantes para el correcto funcionamiento del programa. Proporcionan acciones para la eliminación accidental o intencionada de los objetos del panel de control, así como para los cambios manuales de las propiedades realizados en el código.

Este artículo también servirá de ayuda a los programadores que están aprendiendo a manejar los eventos en la función OnChartEvent().

Me gustaría aclarar desde el principio que no cubriremos las reacciones al código "agresivo" cuyos objetos se modifican o eliminan sin autorización. El propósito principal de los programas del terminal es resolver cuestiones de interés para los traders; por lo tanto, aquí no aceptamos ninguna interferencia derivada de la guerra de robots.

Si usted prefiere las acciones agresivas, permítame hacer la siguiente analogía antes de continuar. Imagine que un limpiador de oficina tira sin querer un ordenador de un escritorio. Si el propietario del ordenador responde furioso, gritando y golpeando los muebles, y estando todo el mundo presente en la sala, dicha actuación se verá desproporcionada e inadecuada. Además, este comportamiento agresivo no ayuda en absoluto a mejorar la situación.

Así pues, antes de continuar con más ejemplos de vandalismo, creo que ha llegado el momento de hablar de la protección de objetos primarios proporcionada en el lenguaje de programación MQL5/MQL4.


2. Protección de objetos primarios en MQL5/MQL4

El acceso a las propiedades de los objetos está más expuesto sin esta protección.

Déjeme explicarle adónde quiero ir a parar. La característica OBJPROP_HIDDEN proporciona protección primaria a los objetos para que estos no se eliminen o modifiquen por medio de sus propiedades: nombre, descripción, colores, etc. Se puede utilizar de manera explícita. Establece o elimina la prohibición que muestra el nombre de un objeto gráfico en la lista de objetos del menú del terminal: Gráficos -> Objetos -> Lista de objetos. La prohibición se establece por defecto en los objetos que muestran eventos de calendario, historial de trading y también en objetos creados con programas MQL5.

Esta es una estrategia de prohibición explícita (no establecida por defecto):

ObjectSetInteger(chart_id,name,OBJPROP_HIDDEN,true);

donde:

  • ObjectSetInteger es la función que establece el valor de la propiedad relevante del objeto;
  • chart_id identifica el gráfico donde se coloca el objeto (cero en la barra actual);
  • name es el nombre del objecto al que se aplica la función;
  • true, en combinación con OBJPROP_HIDDEN, esconde el objeto de la lista de objetos (false cancela la ocultación).

En la Documentación encontrará la implementación práctica de esta estrategia. Haga clic en cualquiera de los enlaces de la lista de tipos de objeto proporcionada, y verá los ejemplos de código con funciones preparadas para crear objetos.

Los objetos que tienen prohibido mostrar el nombre del objeto gráfico en la lista de objetos, pueden verse clicando el botón Todos; esta acción mostrará todos los objetos gráficos. La Lista de objetos actúa como una protección preliminar de objetos gráficos contra la manipulación manual de los parámetros.

Lista de objetos del gráfico antes de hacer clic en el botón "Todos"

Fig. 2. Lista de objetos del gráfico antes de hacer clic en el botón "Todos"

Lista de objetos del gráfico después de hacer clic en el botón "Todos"

Fig. 3. Lista de objetos del gráfico después de hacer clic en el botón "Todos"

Que los objetos gráficos no se puedan esconder completamente, incluso si usted los hace invisibles, es más una ventaja que una desventaja. Observe rápidamente, cambie o copie las propiedades del objeto a través de la lista sin comprobar el gráfico entero, que puede contener varios objetos y barras de años anteriores. Además, los objetos de la lista se pueden ordenar por tipo, por nombre y por otros parámetros.

Otra de las ventajas de los terminales MetaTrader 5 y MetaTrader 4 reside en las facilidades que proporcionan en el momento de escribir ayudantes automáticos, así como para utilizar varios asistentes creados por terceros. ¿Pero existen las personas que nunca se equivocan? Por otro lado el nivel de preparación de los desarrolladores puede variar significativamente; como también lo suele hacer la motivación ética de los mismos. Al igual que las personas los lenguajes de programación evolucionan y cambian con el paso del tiempo.

En consecuencia, si usted dispone de un programa que garantiza en cualquier momento la exactitud de los objetos gráficos, definitivamente es una ventaja. En vez de identificar todos los objetos del gráfico, encuentre los que necesita rápidamente en la lista y vea sus propiedades. Puede estar seguro de que el gráfico no contiene objetos ocultos erróneos, incluyendo los generados por errores de código.

También puede suceder que, al mostrar la lista de objetos, y al seleccionar los que se van a eliminar, se destruyan accidentalmente los objetos del panel de control que, precisamente, deben estar en el gráfico en aquel momento.

Antes de proceder con las acciones de respuesta del programa, podemos aprovechar las capacidades de los lenguajes de programación MQL5 y MQL4. Me refiero a la capacidad de escribir códigos con diferentes métodos para resolver las tareas.


3. Estrategia de creación de variantes

Esto le ayudará a tomar algunas ideas para que usted pueda crear y aplicar sus propias soluciones, lo que le ahorrará tiempo en la búsqueda de soluciones complicadas.

Le voy a explicar esta estrategia de forma natural, tal y como se me ocurrió a mí; e intentaré no parecer demasiado formal o académico. Los snippets de código a los que haré referencia más adelante se adjuntan al final de este artículo. Para su correcto funcionamiento, guarde el archivo ObjectCreateAndSet, disponible en Code Base, en la carpeta Include de su terminal. Contiene funciones para crear objetos y cambiar propiedades, llevando a cabo las comprobaciones necesarias. La operación test_types_of_chart_events del indicador adjunto no necesita este archivo.


3.1. Algunos aspectos a tener en cuenta antes de implementar el código

De acuerdo a la documentación, la función OnChartEvent() obtiene y procesa información de nueve tipos de eventos, necesarios para llevar a cabo algunas tareas, excluyendo los cambios en el gráfico y otros eventos de los usuarios. Algunos de ellos informan sobre la eliminación, el cambio y la creación de objetos gráficos.

En primer lugar, me preguntaba cómo implementar lo siguiente:

  • "autorecuperación" de los objetos si alguien o algo los elimina o los cambia manualmente;
  • "autosalida" del programa del gráfico en tales acciones externas.

Estas acciones se tienen que implementar simultáneamente, eliminando los objetos que se han "perdido" del gráfico porque algún trader o programa les ha cambiado el nombre, y sin bucles extra de manejo de eventos.

La primera idea que se me ocurrió tenía que ver con las propias revisiones del programa, cuando este recibe la información de los eventos de eliminación/modificación de objetos; pero al final no pareció ser la mejor solución. Entonces pensé que el número total de autocomprobaciones implicaría muchas acciones innecesarias, y no siempre justificadas.

Por ejemplo, durante el procesamiento de los eventos de acciones con objetos, el nombre del objeto gráfico se compara con los nombres que el programa monitoriza. Cuando el programa sigue la pista a un evento, la información del mismo se registra tanto en los objetos del programa como en los objetos del gráfico. El nombre del objeto es de tipo string, y los datos de tipo string suelen tardar más en procesarse que otros tipos de datos. Además, el gráfico puede contener varios objetos creados manualmente, o creados con otros programas que procesan un gran número de acciones, formándose, en consecuencia, un gran número de eventos.

A continuación se ilustra la estrategia correspondiente. Se comprueba que los nombres de los objetos coinciden cuando se activa la función de envío de notificaciones de eventos para borrar objetos gráficos. Está implementado sin ninguna gestión preliminar que reduzca la cantidad de comparaciones extra, que se aplican al trabajar con varios objetos gráficos:

if(id==CHARTEVENT_OBJECT_DELETE)
        {
         string deletedChartObject=sparam;
         //---
         for(int i=QUANT_OBJ-1;i>=0;i--)
           {
            if(StringCompare(deletedChartObject,nameObj[i],true)==0)
              {
            /*aquí escribimos las acciones necesarias, si el nombre que llega
            durante el evento coincide por completo con el
            nombre del objeto del programa*/
               return;
              }
           }
        }

Además, la opción de "revisión" (basada en eventos o periódica) no resuelve el problema que se plantea cuando un trader o un programa cambian el nombre de los objetos. De modo que el programa puede perder el control, dejando los objetos "sin dueño".

Por todo lo expuesto aquí pensé en desarrollar un sistema basado en banderas. Dicho sistema se parece a una alarma. Consiste en un semáforo con banderas donde el valor 0 significa que el programa tiene que contemplar los cambios y la eliminación de los objetos como si fueran interferencias externas no autorizadas. El valor de la bandera cambia cuando se registran acciones personales de modificación y eliminación de objetos, de modo que los eventos de eliminación-cambio no se perciben como una "invasión externa".

Sin embargo, se forma una cola de eventos gráficos en la función OnChartEvent(), y el sistema que dispara las banderas durante la creación de los objetos de autorecuperación no quedó lo suficientemente claro en aquel momento. También se evitan los bucles de procesamiento repetidos que se generan al gestionar los eventos de eliminación en este tipo de acciones.

Por ejemplo, hay un panel que solo tiene unos cuantos objetos, y la información de salida se activa con la función Print() en el código que maneja los eventos de eliminación de objetos de dicho panel. Cuando se notifican los eventos, durante la autorecuperación de los objetos, el panel puede llenar fácilmente en cuestión de segundos el archivo de log con varias notas. Entonces el gráfico parpadea debido a esta recreación continua. Esto es independiente de la selección preliminar por nombre, si se aplica el siguiente método (u otro equivalente) en la bandera del semáforo y en la autorecuperación:

else  if(id==CHARTEVENT_OBJECT_DELETE)
     {
     if(flagAntivandal==0)
        {
            string deletedChartObject=sparam;
            //---
            for(int i=QUANT_OBJ-1;i>=0;i--)
              {
               //--- acciones que se llevan a cabo cuando los nombres coinciden:
               if(StringCompare(deletedChartObject,nameObj[i],true)==0)
                 {
                  //--- imprimimos en la pestaña "Expertos" del terminal:
                  TEST_PRINT(deletedChartObject);
                  //--- desactivamos la "alarma" cuando se ejecutan operaciones "amigas" del programa
                  flagAntivandal=-1;
                  //--- eliminamos los objetos restantes:
                  ObjectsDeleteAll(0,prefixObj,0,-1);
                  //--- reconstruimos el panel
                  PanelCreate();
                 //--- redibujamos el gráfico 
                  ChartRedraw();
                 //--- reactivamos la bandera de "protección" de objetos:
                  flagAntivandal=0;
                  return;
                 }
              }
         return;
        }
      return;
     }//else  if(id==CHARTEVENT_OBJECT_DELETE)

La creación/eliminación de objetos se lleva a cabo en un bucle, formándose así un flujo de notificaciones de eventos. Si se elimina accidental o intencionalmente el lienzo que contiene otros objetos (botones, campos de entrada, etc.), y, por lo tanto, se tiene que recrear con el que no se solapa con otros objetos, entonces es posible que haya que eliminar objetos del panel para poder realizar la recuperación.

No le recomiendo utilizar el código anterior si su ordenador no tiene suficiente memoria. En caso contrario, si cuenta con memoria suficiente, haga sus propias pruebas; sustituya los eventos de eliminación y modificación en el código de prueba test_count_click_2.

No he comprobado la capacidad máxima de los archivos de log cuando se utilizan banderas incorrectas. Como mucho, me he visto obligado a cerrar el terminal 2 o 3 segundos después de iniciar los eventos de una "autorecuperación" fallida, mientras observaba el "parpadeo" continuo de los objetos y las notas de la pestaña "Expertos" del terminal. Eliminé rápidamente el archivo de log y vacié la papelera de reciclaje.

Por otro lado, en aquel momento no pude solucionar el problema de los objetos con el nombre cambiado. Se perdió un "ancla" que podría haber ayudado a solucionar la situación.

Por todo esto decidí actualizarme y ponerme al día con los tipos de eventos gráficos. Eché un vistazo a la Documentación. La descripción de la función ObjectSetString() dice claramente que, justo después de renombrar un objeto, se forman dos eventos simultáneamente:

  • eliminación del objeto con el nombre antiguo,
  • creación de un objeto gráfico con el nuevo nombre.

Tras leer la sección Propiedades de objetos decidí que mi ancla fuera la hora de creación del objeto, lo que puede determinarse con la propiedad OBJPROP_CREATETIME.

Por ejemplo, como se observa en el script adjunto test_get_objprop_createtime, la hora de creación del objeto es igual a la hora local del ordenador donde se ejecuta el terminal:

La hora de creación de un objeto es igual a la hora local del ordenador en el momento de crear dicho objeto.

Fig. 4. La hora de creación de un objeto es igual a la hora local del ordenador en el momento de crear dicho objeto.

El script crea un botón en el gráfico, determina la hora de creación y lo imprime en el log de la pestaña Expertos vía Print(). Establece e imprime varias horas en la misma pestaña del terminal: la hora local del ordenador, la hora actual estimada del servidor de trading, la última hora conocida del servidor y la hora de la última cotización, en formato GMT. A continuación se congela durante 10 segundos y borra el botón que creó en el gráfico, terminando así la operación.

El siguiente paso consiste en preparar un plan de acción en el código subsiguiente, con soluciones claras. La solución final tiene que ser lo suficientemente versátil como para que en el futuro no haga falta escribir acciones de recuperación separadas para cada botón. El gráfico tiene que expresar un principio de convivencia pacífico.


El plan de acción consiste en lo siguiente:

3.1.1. Comprobar qué eventos y en qué orden se muestran a otros programas que actúan como observadores independientes, y a programas cuyos objetos se acceden desde el exterior:

  • al cambiar las propiedades del objeto gráfico manualmente con el diálogo de propiedades y desde otro programa;
  • cuando se elimina manualmente el objeto a través de la "Lista de objetos", y desde otro programa;
  • al renombrar objetos manualmente a través del diálogo de propiedades, y desde otro programa.

Al mismo tiempo hay que mostrar las horas de creación de los objetos y, por conveniencia, apuntar los resultados obtenidos en tablas.

De acuerdo pues a todo lo expuesto hasta aquí, este es el primer punto de la implementación del plan:

3.1.2. Un ayudante-indicador que actúa como un observador externo y describe los eventos que suceden en el gráfico.

Elegí un indicador porque permite operar en el gráfico conjuntamente con un Asesor Experto.

No explicaré el funcionamiento del ayudante en profundidad, el código completo se adjunta en el archivo test_types_of_chart_events. Sin embargo, sí que voy a mencionar lo siguiente porque puede resultarle de utilidad en el futuro, cuando se familiarice con los eventos:

  • Este indicador está diseñado para operar con nueve tipos de eventos estándar
  • Muestra el nombre de un evento y el valor que se obtiene con el mismo: id, lparam, dparam, sparam (nombre del objeto en los eventos con objetos gráficos).
  • Se pueden activar y desactivar las notificaciones de algunos eventos. Utilice un gráfico aparte para probar el indicador, que no tenga ningún programa operando en el momento de ejecutar el código de prueba. En caso contrario puede desactivar las notificaciones necesarias para el buen funcionamiento de los programas.

    Propiedades personalizadas externas del indicador-observador de prueba

Fig. 5. Propiedades personalizadas externas del indicador-observador de prueba

El indicador tiene notificaciones de eventos que no están directamente enlazadas a las acciones con objetos, deshabilitados por defecto. Cuenta con notificaciones activadas/desactivadas para cinco de los nueve tipos de eventos estándar con los que opera.

  • Este código de prueba muestra la información en la pestaña Expertos del terminal mediante la función Print(). Así pues, para evitar que los archivos de log del terminal se inunden con un montón de información entrante, no se muestran los eventos CHARTEVENT_MOUSE_MOVE que gestionan el movimiento y los clics de ratón; están desactivados con este indicador. El código del indicador de prueba no incluye la visualización de este tipo de eventos.

3.1.3. Otros ayudantes que participan en las pruebas:

  • script que crea en el gráfico los objetos Button y Edit;
  • script que cambia las propiedades de los objetos por el nombre especificado, y borra los objetos cambiados después de una pausa.


3.2. Resultados de los experimentos realizados

Los resultados se muestran en la siguiente tabla. El programa cuyos objetos están sujetos a acciones "ve" las notificaciones estándar de los eventos del mismo modo que otros programas en el gráfico, por esta razón solo se proporciona una tabla. Se basa en acciones con objetos "Button" y "Edit".

Acción con objeto id lparam dparam sparam Nombre del evento que notifica las acciones realizadas "manualmente" Hora de creación del objeto ** Nombre del evento que notifica las acciones ejecutadas por terceros programas "amigos"
Hora de creación del objeto **
Creación de un objeto nuevo (no por cambio de nombre) 7
0 0 nombre del objeto CHARTEVENT_OBJECT_CREATE hora local en el ordenador donde se ejecuta el terminal CHARTEVENT_OBJECT_CREATE hora local en el ordenador donde se ejecuta el terminal
Cambio de nombre del objeto (el objeto con el nombre anterior se elimina, y al mismo tiempo se crea un objeto nuevo)
6

7

8
0

0

0
0

0

0
nombre del objeto,
nombre del objeto,
nombre del objeto
CHARTEVENT_OBJECT_DELETE

CHARTEVENT_OBJECT_CREATE

CHARTEVENT_OBJECT_CHANGE
la hora de creación del nuevo objeto es igual a la hora de creación del objeto con el nombre anterior sin advertencias
la hora de creación del nuevo objeto es igual a la hora de creación del objeto con el nombre anterior
Cambio del fondo del objeto 8 0 0 nombre del objeto СHARTEVENT_OBJECT_CHANGE sin cambios sin advertencias sin cambios
Cambio de las coordenadas del objeto en el eje X
8 0 0 nombre del objeto СHARTEVENT_OBJECT_CHANGE sin cambios sin advertencias sin cambios
Eliminación del objeto 6 0 0 nombre del objeto CHARTEVENT_OBJECT_DELETE *** CHARTEVENT_OBJECT_DELETE ***

 

Tabla 1. Fracción de lo que puede ver un programa amigo acerca de las notificaciones de los eventos de creación, cambio y eliminación de objetos del gráfico *


Notas:

* El programa maneja las notificaciones de eventos; sin embargo, las notificaciones que aparecen durante la gestión, o después de la gestión, forman una cola.

** He observado que si un gráfico contiene objetos gráficos que no han sido creados por el programa tras reiniciar el terminal, entonces después del reinicio la función OBJPROP_CREATETIME devuelve la hora de creación de dichos objetos que es igual a 1970.01.01 00:00:00. Así pues la hora anterior se "reinicia". Esto también pasa con los objetos del gráfico que se colocan manualmente. Al cambiar los marcos temporales la hora de creación de los objetos no se reinicia.

*** Si intenta determinar la hora de creación del objeto con OBJPROP_CREATETIME durante la notificación del evento de eliminación, entonces, como el objeto ya se ha borrado, se mostrará el mensaje de error 4203 (identificador erróneo de la propiedad del objeto gráfico).

Si los cambios de propiedad se ejecutan manualmente no se muestra la notificación. Naturalmente, hay una justificación detrás de esto. El motivo es que un gráfico del terminal puede operar con muchos programas, incluyendo los que llevan a cabo múltiples acciones con varios objetos gráficos, y estos programas pueden abrir muchos gráficos. Así pues, supongo que las notificaciones de los programas que manejan eventos en todos y cada uno de los cambios programáticos de las propiedades de los objetos, implican un incremento considerable en el consumo de recursos.

Para no restringir a los usuarios con un conjunto estándar de tipos de notificación de eventos, los desarrolladores han facilitado la creación de notificaciones personales en cualquier evento individual del código. La función EventChartCustom() se utiliza con este propósito, y genera notificaciones de eventos personalizados en los gráficos del terminal.

Propongo varias soluciones versátiles a partir de los resultados obtenidos en el experimento.

Ciertamente, algunas de ellas no pueden aplicarse en cualquier escenario. Operan sobre los objetos del panel de control, y seguro que le servirán para tomar algunas ideas.

Estas soluciones se basan en:

  • disposición de las banderas en el código, incluyendo la adaptación, dependiendo de las tareas;
  • funciones diseñadas ad hoc como parte del plan de implementación de las variantes.

Las tres soluciones comparten lo siguiente. El código que implementa las variantes de autorecuperación y autosalida del gráfico debe incluir lo siguiente, independientemente de si hay que proteger los objetos del panel de control:

  • Variables que almacenan las banderas declaradas a nivel global y que son visibles en todas las funciones, incluso en la función OnInit() (no confundir con las variables globales del terminal).
  • Creación de un prefijo para el nombre de los objetos "protegidos".
  • Incluir notificaciones de eliminación de objetos (CHART_EVENT_OBJECT_DELETE) en las propiedades del gráfico, si esta propiedad está desactivada.
  • Establecer la frecuencia del temporizador e incluir en el código la función OnTimer(). Esto sirve para comprobar que las notificaciones de eliminación de objetos se ejecutan tras un tiempo específico predeterminado. El temporizador se detiene en la función OnDeinit() con una llamada a EventKillTimer().
  • El cuerpo de la función OnChartEvent() contiene bloques que manejan las notificaciones de los eventos CHARTEVENT_OBJECT_CHANGE y CHARTEVENT_OBJECT_DELETE. Se especifican acciones para las intervenciones no autorizadas.

La variante que despeja el gráfico de objetos renombrados tiene que proporcionar lo siguiente:

  • Determinar la hora de creación de los objetos "protegidos" y almacenarla en un array si hay muchos objetos, o en variables normales si hay pocos.
  • Funciones específicas de eliminación de objetos, incluyendo el borrado de objetos por hora de creación.

El orden de implementación de las acciones de seguridad es importante.

Si solo se construye una "autosalida" del gráfico, disparada por un cambio o eliminación de objeto no autorizados, entonces, dependiendo de la tarea, una sola variable es suficiente para la bandera. Con valores, por ejemplo:
  • -1: Desactivar la "alarma" antes de ejecutar las acciones que involucran objetos. Para reducir el número de operaciones innecesarias, se establece el operador de retorno return al comienzo de los bloques de eliminación y modificación. Con esto ignoramos las notificaciones entrantes de eventos de eliminación y modificación de objetos:
if(flagAntivandal==-1){return;}
  • 0: Activamos la "alarma" de protección de objetos. Se establece cuando se activa la "alarma" tras la ejecución de acciones "amigas". Al mismo tiempo, como medida de seguridad, las operaciones se ejecutan en este orden:
    • si se recibe una notificación de eliminación o modificación de objeto cuando la bandera vale 0, entonces, primero de todo se comprueba que el evento ocurre con un objeto "protegido";
    • en caso afirmativo, si el evento ocurre con un objeto "protegido", desactivamos la bandera de la alarma cambiando su valor a -1, lo que evitará entrar en un bucle cuando el programa se elimine del gráfico;
    • La función que elimina el programa del gráfico se aplica solo después de seguir estas acciones. Esta función se crea utilizando la operación:
IndicatorSetString(INDICATOR_SHORTNAME,short_name);
  • Si todavía no está familiarizado con la función ExpertRemovе(), por favor eche un vistazo a la Documentación y a los ejemplos que allí se proporcionan.

Para que el objeto se pueda autorecuperar tras una interferencia no autorizada se necesitan por lo menos dos banderas; los objetos se borrarán y se crearán otros nuevos en su lugar. Una de ellas es igual a la bandera previa. La segunda es para evitar que se produzca un bucle que reciba eventos de eliminación durante las acciones de recuperación de objetos. Aquí las acciones son ligeramente más complicadas. La segunda bandera es adaptable, y sirve para ajustar la "alarma", para que funcione bien y se puedan gestionar los eventos de eliminación de objetos. Libera los controles de eventos que no hacen falta. Analizaremos esto más detalladamente en el ejemplo 4.2.

Dicho todo lo anterior, continuemos con más ejemplos.


4. Estrategias de implementación en las modificaciones no autorizadas, o al eliminar los objetos del programa

El capítulo 5 de este artículo proporciona un ejemplo de implementación simultánea de "autorecuperación", en relación a algunos objetos, y de "autosalida" del gráfico, cuando se cambian o se eliminan otros objetos. Describe el plan operacional aplicado, a modo de ayuda.

Incrementamos un poco la dificultad con otro código de ejemplo que crea un panel en el gráfico, para calcular los clics de ratón en los objetos que pertenecen a las áreas "Yes" y "No".

Este es el aspecto que tiene el panel del código de ejemplo adjunto, según el idioma del terminal de trading

Fig. 6. Panel del código de ejemplo adjunto, según el idioma del terminal de trading

Se trata de un código de prueba sencillo que facilita el seguimiento de los planes operativos que se describen a continuación:

El código del indicador de prueba se adjunta en este artículo con el nombre test_count_click_0. Durante la implementación de las siguientes estrategias desarrollé dos variantes del código de prueba. Observe el código completo del funcionamiento de cada estrategia, y pruébelo usted mismo en el gráfico.

El código incluye las líneas adicionales de la salida Print() para que se pueda ver bien el orden de las operaciones en el momento de cambiar o eliminar los objetos. Abajo están las líneas de código sin los comandos auxiliares de salida Print().


4.1. Autosalida del programa del gráfico con eliminación o modificación de objetos

La versión completa y funcional del código de ejemplo se adjunta con el nombre test_count_click_1.

4.1.1. Creación del archivo:

El archivo test_count_click_0 se guarda con el nombre test_count_click_1. Abra con MetaEditor el archivo que tiene el número 0 en el título, y luego seleccione la opción Archivo -> Guardar como... en el menú principal de MetaEditor.

4.1.2. Variables accesibles desde cualquier punto del programa, incluso en el bloque de la función OnInit():

El nombre del indicador en #define se sustituye a nivel interno más adelante:

#define NAME_INDICATOR             "count_click_1: "

Añadimos una variable para la bandera del semáforo:

int flagAntivandal;
Seguida de un array cuyo tamaño es igual a la cantidad de objetos "protegidos" para almacenar las horas de os objetos creados:
datetime timeCreateObj[QUANT_OBJ];

Se añade un array de texto que almacena los mensajes de advertencia de "autosalida" del indicador. El primer mensaje informa de la eliminación correcta del gráfico, y el segundo indica si ocurre algún error durante la operación. Como la salida de estos mensajes está en dos idiomas, inglés y ruso, el array se declara visible en todas las partes del programa.

string textDelInd[2];

Los mensajes almacenados en este array son útiles porque proporcionan información sobre la salida del indicador del gráfico, al recibir el motivo de desinicialización REASON_INITFAILED en OnInit().

4.1.3. La función LanguageTerminal() se encarga de mostrar los mensajes de texto de acuerdo al idioma del terminal de trading:

Los mensajes se añaden a la "autosalida" del programa del gráfico, tanto si esta se ejecuta correctamente como si no.

avoid LanguageTerminal()
  {
   string language_t=NULL;
   language_t=TerminalInfoString(TERMINAL_LANGUAGE);
//---
   if(language_t=="Russian")
     {
      textObj[3]="Да: ";
      textObj[4]="Нет: ";
      textObj[5]="Всего: ";
      //---
      StringConcatenate(textDelInd[0],"Не удалось удалиться индикатору: \"",
                        prefixObj,"\". Код ошибки: ");
      StringConcatenate(textDelInd[1],"Удалился с графика индикатор: \"",
                        prefixObj,"\".");
     }
   else
     {
      textObj[3]="Sí: ";
      textObj[4]="No: ";
      textObj[5]="Todo: ";
      //---
      StringConcatenate(textDelInd[0],"Error al borrar el indicador: \"",
                        prefixObj,"\". Error code: ");
      StringConcatenate(textDelInd[1],
                        "Retirado del indicador del gráfico: \"",
                        prefixObj,"\".");
     }
//---
   return;
  }

4.1.4. En la función PanelCreate() que crea los objetos del panel de control:

Después de crear los objetos se determina su hora de creación y se almacena en el array. La versión sencilla es más o menos así:

void PanelCreate(const long chart_ID=0,const int sub_window=0)
  {
   for(int i=NUMBER_ALL;i>=0;i--)
     {
      //--- campos que muestran el número de clics:
      if(ObjectFind(chart_ID,nameObj[i])<0)
        {
         EditCreate(chart_ID,nameObj[i],sub_window,X_DISTANCE+WIDTH,
                    Y_DISTANCE+(HEIGHT)*(i),WIDTH,HEIGHT,
                    textObj[i],"Arial",FONT_SIZE,"\n",ALIGN_RIGHT,true,
                    CORNER_PANEL,clrText[i],CLR_PANEL,CLR_BORDER);
         //--- determinamos y almacenamos la hora de creación del objeto:
         CreateTimeGet(chart_ID,nameObj[i],timeCreateObj[i]);
        }
     }
//---
   int correct=NUMBER_ALL+1;
//---
   for(int i=QUANT_OBJ-1;i>=correct;i--)
     {
      //--- campos con notas adjuntas:
      if(ObjectFind(chart_ID,nameObj[i])<0)
        {
         EditCreate(chart_ID,nameObj[i],sub_window,X_DISTANCE+(WIDTH*2),
                    Y_DISTANCE+(HEIGHT)*(i-correct),WIDTH,HEIGHT,
                    textObj[i],"Arial",FONT_SIZE,"\n",ALIGN_LEFT,true,
                    CORNER_PANEL,clrText[i-correct],CLR_PANEL,CLR_BORDER);
         //--- determinamos y almacenamos la hora de creación del objeto:
         CreateTimeGet(chart_ID,nameObj[i],timeCreateObj[i]);
        }
     }
   return;
  }

4.1.5. Esta es la variante de la función que determina y almacena la hora de creación del objeto en una variable:

bool CreateTimeGet(const long chart_ID,
                   const string &name,
                   datetime &value
                   )
  {
   datetime res=0;
   if(!ObjectGetInteger(chart_ID,name,OBJPROP_CREATETIME,0,res))
     {
      Print(LINE_NUMBER,__FUNCTION__,", Error = ",
            GetLastError(),", nombre: ",name);
      return(false);
     }
   value=res;
   return(true);
  }

Si el programa está en el gráfico en el reinicio de emergencia del terminal, por ejemplo porque este se congeló, se añade una función que revisa la hora de creación de los objetos:

bool RevisionCreateTime(int quant,datetime &time_create[])
  {
   datetime t=0;
   for(int i=quant-1;i>=0;i--)
     {
      t=time_create[i];
      if(t==0)
        {
         Print(LINE_NUMBER,__FUNCTION__,", Error en la hora de creación: ",
               TimeToString(t,TIME_DATE|TIME_SECONDS));
         return(false);
        }
     }
//---
   return(true);
  }

La llamada a esta función se pone en OnInit() después del método PanelCreate().

Supongo que si después del cierre de emergencia del terminal, y su posterior reinicio, esta función devuelve false, entonces, antes de que ocurra, la función CreateTimeGet() puede imprimir un mensaje de error 4102 en la pestaña "Expertos" del terminal, indicando que el gráfico no responde.

4.1.6. En el bloque de la función OnInit():

4.1.6.1. La bandera del semáforo se inicializa con el valor -1 antes de la función que crea los paneles de control. Esto significa que la alarma se desactiva durante el tiempo de ejecución de las acciones "amigas". En concreto, para evitar que suceda algo inesperado al cambiar el marco temporal del gráfico.

flagAntivandal=-1;

4.1.6.2. Se establece el nombre corto del indicador, si todavía no se ha configurado. Se necesitará para extraer por fuerza el programa del gráfico.

En el ejemplo de código proporcionado se establece el nombre corto del indicador, que es igual al prefijo de los objetos, y contiene el nombre del indicador, el símbolo y el período del gráfico donde se establece:

//--- creación del prefijo de los objetos y del nombre corto del indicador:
   StringConcatenate(prefixObj,NAME_INDICATOR,Symbol(),"_",EnumToString(Period()));
//--- establecemos el nombre corto del indicador:
   IndicatorSetString(INDICATOR_SHORTNAME,prefixObj);

4.1.6.3.Activamos la alarma de "protección" después de la función de creación del panel de control, y tras revisar el array que contiene las horas de creación de los objetos:

//--- creamos un panel:
   PanelCreate();
/*revisamos el array que contiene las horas de creación de los objetos, 
y si algo va mal terminamos el programa:*/
   if(!RevisionCreateTime(QUANT_OBJ,timeCreateObj))
     {return(REASON_INITFAILED);}
/*establecemos la bandera de los eventos
de eliminación y modificación de objetos:*/
   flagAntivandal=0;


4.1.6.4.Activamos la notificación de eventos de eliminación de objetos gráficos, en caso de estar desactivada en las propiedades:

//--- activamos la notificación de eventos de eliminación de objetos gráficos
 bool res=true;
 ChartEventObjectDeleteGet(res);
 if(res!=true)
 {ChartEventObjectDeleteSet(true);}


Nota: las funciones ChartEventObjectDeleteGet() y ChartEventObjectDeleteSet() son ejemplos tomados de la Documentación.

4.1.6.5. Antes de salir de OnInit() establecemos la frecuencia del temporizador en segundos. Esto es para comprobar si las notificaciones de los eventos de eliminación de objetos se desactivarán en las propiedades del gráfico mientras el programa esté operativo:

EventSetTimer(5);

4.1.7. En el bloque de la función OnDeinit():

Especificamos la "alarma" a desactivar:

flagAntivandal=-1;

Paramos la generación de eventos desde el temporizador:

EventKillTimer();

Borramos el indicador si el método OnInit() genera un código REASON_INITFAILED de desinicialización:

//--- borramos el indicador si REASON_INITFAILED viene de OnInit().
   if(reason==REASON_INITFAILED)
     {
      ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
     }

4.1.8. En el bloque de la función OnTimer():

Replicamos las líneas de código a partir del punto 4.1.6.4 con un copypaste. Esto habilita las notificaciones de los eventos de eliminación de objetos con un temporizador, en caso de que otro programa las haya desactivado.

4.1.9. Creación de acciones en la función OnChartEvent():

4.1.9.1. Si el evento de modificación y eliminación de objetos permite la gestión solo por "cuestiones de seguridad", entonces la manipulación de tales eventos se puede combinar en un solo bloque.

4.1.9.2. Lo primero que hay que hacer, si la "alarma" está activada en el momento de recibir una notificación de evento de modificación/eliminación de objeto, es comprobar la bandera. Si la bandera vale -1 (la "alarma" está desactivada), entonces salimos del bloque que maneja el evento.

4.1.9.3. Cuando la bandera vale 0 (la "alarma" está activada), configure la comprobación para emparejar el nombre del objeto con el prefijo de los objetos del programa. La función StringFind() se utiliza con este propósito, así que no hay necesidad de recorrer el array de los nombres de los objetos del programa en cada notificación de cambio/eliminación de objeto. En este caso el prefijo establecido en OnInit() no es corto y en consecuencia el filtro es mejor. La formación del prefijo se proporciona en p.4.1.6.2.

Si no hay ninguna coincidencia por prefijo, salimos del bloque que maneja el evento.

4.1.9.4. Si hay una coincidencia por prefijo, entonces se comprueba si la notificación de evento recibida coincide por nombre con los objetos "protegidos" del programa. Cuando los nombres coinciden completamente, el valor de la bandera de protección cambia a -1 (desactivado) para evitar la creación de bucles extra cuando el programa se elimina del gráfico.

4.1.9.5. Si un objeto se queda sin dueño por haber cambiado de nombre, se realiza una búsqueda de objetos con la misma hora de creación que el de la notificación, y si se encuentran se eliminan.

4.1.9.6. Solo entonces se elimina el programa del gráfico.

Estas son las acciones descritas:

else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)
     {
      if(flagAntivandal==-1)//ignoramos el evento si la "alarma está desactivada"
        {return;}
      else if(flagAntivandal==0) //si la "alarma" está activada,
        {
         //--- buscamos una coincidencia por prefijo:
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)//si hay una coincidencia por prefijo
           {
            string chartObject=sparam;
            findPrefixObject=-1;
            //---
            for(int i=QUANT_OBJ-1;i>=0;i--)
              {
               //--- acciones que se llevan a cabo cuando los nombres coinciden:
               if(StringCompare(chartObject,nameObj[i],true)==0)
                 {
                  //--- desactivamos la alarma para evitar un bucle en las operaciones de eliminación
                  flagAntivandal=-1;
/*eliminamos los objetos cuya hora de creación es igual a la hora de creación
 del objeto eliminado o creado:*/
                  ObDelCreateTimeExcept0(timeCreateObj[i],0,0,OBJ_EDIT);
                  //--- eliminamos el indicador del gráfico:
                  ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
                  //---
                  ChartRedraw();
                  return;
                 }
              }//for(int i=QUANT_OBJ-1;i>=0;i--)
            return;
           }//if(findPrefixObject==0)
         return;
        }//if(flagAntivandal==0)
      return;
     }//if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)  

4.1.10. La función ObDelCreateTimeExcept0() elimina objetos por hora de creación:

La estructura de la función que elimina los objetos por hora de creación tiene este aspecto:

void ObDelCreateTimeExcept0(datetime &create_time,// hora de creación
                            long chart_ID=0,// identificador del gráfico
                            int sub_window=-1,// número de subventana
                            ENUM_OBJECT type=-1)// -1 = todos los tipos de objetos
  {
/*si la hora del objeto = 0(1970.01.01 00:00:00), salimos de la función
 para no tener que borrar manualmente, o programáticamente, los objetos creados 
 cuya hora de creación vale cero después de 
 reiniciar el terminal:*/
   if(create_time==0){return;}
   int quant_obj=0;
   quant_obj=ObjectsTotal(chart_ID,sub_window,type);
//---
   if(quant_obj>0)
     {
      datetime create_time_x=0;
      string obj_name=NULL;
      //---
      for(int i=quant_obj-1;i>=0;i--)
        {
         obj_name=ObjectName(chart_ID,i,sub_window,type);
         create_time_x=(datetime)ObjectGetInteger(chart_ID,obj_name,
                        OBJPROP_CREATETIME);
         //---
         if(create_time_x==create_time)
           {ObDelete(chart_ID,obj_name);}
        }
     }
   return;
  }
//+---------------------------------------------------------------------------+


Hay un filtro, por lo tanto si los datos de creación del objeto "protegido" valen 0, es decir, 1970.01.01 00:00:00, entonces se interrumpe la gestión de los eventos. Esto es necesario para evitar que se eliminen los objetos creados manualmente o mediante programación, cuya hora de creación se establece a cero después de reiniciar el terminal.

4.1.11. Este es el código de la función ObDelete():

Se ha tomado de la librería ObjectCreateAndSet, e imprime un mensaje de error cuando se eliminan objetos:

bool ObDelete(long chart_ID,string name)
  {
   if(ObjectFind(chart_ID,name)>-1)
     {
      ResetLastError();
      if(!ObjectDelete(chart_ID,name))
        {
         Print(LINE_NUMBER,__FUNCTION__,", Código de error = ",
               GetLastError(),", nombre: ",name);
         return(false);
        }
     }
   return(true);
  }

4.1.12. Función que elimina un indicador:

//+---------------------------------------------------------------------------+
//| Borrar un indicador del gráfico                                           |
//+---------------------------------------------------------------------------+
void ChIndicatorDelete(const string short_name,//nombre corto del indicador
                       const string &text_0,//texto que indica si hay un error al eliminar el indicador
                       const string &text_1,//texto que indica si el indicador se ha borrado correctamente
                       const long  chart_id=0,// identificador del gráfico
                       const int   sub_window=-1// número de subventana
                       )
  {
   bool res=ChartIndicatorDelete(chart_id,sub_window,short_name);
//---
   if(!res){Print(LINE_NUMBER,text_0,GetLastError());}
   else Print(LINE_NUMBER,text_1);
  }
//+---------------------------------------------------------------------------+

donde LINE_NUMBER es un texto general que está al comienzo de los mensajes, y que contiene el número de la línea de código establecido en #define:

#define LINE_NUMBER    "Line: ",__LINE__,", "

Llegados a este punto, analicemos la siguiente estrategia.


4.2. Autorecuperación de los objetos del programa

La autorecuperación de objetos exige un grado extra de atención en la gestión de las banderas, para evitar bucles en las operaciones del programa.

La estrategia se basa en la modificación del código test_count_click_1 anterior. El código completo está disponible en el archivo test_count_click_2; échele un vistazo y haga las pruebas que considere oportunas.

4.2.1. Creación de un archivo con esta estrategia:

El archivo test_count_click_1 se guarda con el nombre test_count_click_2.

4.2.2. Las variables tienen un alcance global, se pueden acceder desde cualquier parte del programa y se declaran antes del bloque OnInit():

Ponemos el nombre del indicador en #define para sustituirlo posteriormente a nivel interno:

#define NAME_INDICATOR             "count_click_2: "

Además de la bandera principal descrita arriba, también añadimos otra variable que hace de bandera adaptable auxiliar:

int flagAntivandal, flagResetEventDelete;

Antes de ejecutar las operaciones de recuperación creamos una variable para el mensaje de texto:

string textRestoring;

4.2.3. La función LanguageTerminal() se encarga de mostrar los mensajes de texto según el idioma del terminal de trading:

Si ocurren modificaciones/eliminaciones no autorizadas en los objetos del panel, muestra un mensaje antes de las operaciones de recuperación:

avoid LanguageTerminal()
  {
   string language_t=NULL;
   language_t=TerminalInfoString(TERMINAL_LANGUAGE);
//---
   if(language_t=="Russian")
     {
      textObj[3]="Да: ";
      textObj[4]="Нет: ";
      textObj[5]="Всего: ";
      //---
      textRestoring="Уведомление о страховом случае: объект ";
      //---
      StringConcatenate(textDelInd[0],"Не удалось удалиться индикатору: \"",
                        prefixObj,"\". Код ошибки: ");
      StringConcatenate(textDelInd[1],"Удалился с графика индикатор: \"",
                        prefixObj,"\".");
     }
   else
     {
      textObj[3]="Sí: ";
      textObj[4]="No: ";
      textObj[5]="Todo: ";
      //---
      textRestoring="Notificación del evento asegurado: objeto ";
      //---
      StringConcatenate(textDelInd[0],"Error al borrar el indicador: \"",
                        prefixObj,"\". Error code: ");
      StringConcatenate(textDelInd[1],
                        "Retirado del indicador del gráfico: \"",
                        prefixObj,"\".");
     }
//---
   return;
  }

Utilizo este texto asumiendo que las opciones consideradas actúan como una red de seguridad en caso de ocurrir eventos no deseados.

4.2.4. En el bloque de la función OnInit():

Inicializamos las dos banderas con el valor -1 (desactivamos la alarma completamente) antes de la función de creación del objeto:

flagAntivandal = flagResetEventDelete = -1;

Después del bloque que crea los objetos del panel, y tras revisar el array de las horas de los objetos creados, inicializamos la bandera con el valor 0, activando así la alarma:

//--- creamos un panel:
   PanelCreate();
/*revisamos el array que contiene las horas de creación de los objetos, 
y si algo va mal terminamos el programa:*/
   if(!RevisionCreateTime(QUANT_OBJ,timeCreateObj))
     {return(REASON_INITFAILED);}
/*establecemos las banderas antivandálicas en respuesta a 
los eventos de eliminación y modificación de objetos:*/
   flagAntivandal=flagResetEventDelete=0;

4.2.5. Ahora es el turno de la función OnChartEvent():

Vamos a actualizar el bloque existente que gestiona las notificaciones de los eventos de modificación y eliminación de objetos. En el bloque nuevo las acciones siguen el esquema habitual de gestión de eventos de modificación/eliminación de objetos:

  • En primer lugar, comprobamos que la protección está "activada". En caso negativo, salimos del bloque de gestión de eventos.
  • Luego, si la bandera auxiliar que reinicia los eventos de eliminación es mayor que 0, las notificaciones entrantes de los eventos de modificación/eliminación de objetos se resetean hasta que la bandera es igual a 0.
  • Las acciones de recuperación solo se ejecutan cuando las banderas primarias y auxiliares son iguales a cero, en caso de cambio/eliminación no autorizados.
else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)
     {
      if(flagAntivandal==-1){return;}
      else if(flagResetEventDelete>0)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)
           {
            findPrefixObject=-1;
/*reseteamos los valores del contador de eventos para prevenir 
un bucle de operaciones en la cola de eventos de eliminación de objetos,
que aparece como consecuencia de las acciones de recuperación:*/
            flagResetEventDelete=flagResetEventDelete-1;
            //---
            return;
           }
         return;
        }
      else if(flagAntivandal==0 && flagResetEventDelete==0)
        {
         //--- examinamos una coincidencia por prefijo:
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)//si hay una coincidencia por prefijo
           {
            string chartObject=sparam;//nombre del objeto en el evento
            findPrefixObject=-1;
/*comprobamos si el objeto está "protegido", y si lo está 
            tomamos medidas de recuperación:*/
            RestoringObjArrayAll(chartObject,prefixObj,flagAntivandal,
                                     flagResetEventDelete,QUANT_OBJ,nameObj,
                                     timeCreateObj);
            return;
           }//if(findPrefixObject==0)
         return;
        }//else if(flagAntivandal==0 && flagResetEventDelete==0)
      return;
     }//else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)

La función RestoringObjArrayAll() contiene las acciones de recuperación principales. Algunas de estas funciones se pueden aplicar simultáneamente en varios grupos de objetos. Encontrará un ejemplo relevante en el quinto capítulo de este artículo. El nombre del objeto por evento de eliminación/modificación está en esta función después de comprobar el prefijo, si coincide con los objetos "protegidos".

4.2.6. La función RestoringObjArrayAll().

Contiene estas acciones:

  • Si la comprobación de la coincidencia por nombre indica que el objeto está "protegido":
    • la bandera de la "alarma" obtiene el valor -1 (se desactiva mientras se ejecutan las acciones "amigas");
    • si se trata de un evento de eliminación de objeto, se establece una bandera auxiliar cuyo valor es igual a la cantidad de objetos "protegidos" menos uno, porque si se controla dicho evento significa que en ese momento se borró un objeto por lo menos.
  • Después de ejecutar estas acciones preliminares, se eliminan por prefijo los objetos protegidos restantes mediante la función ObDeletePrefixFlag() que se muestra a continuación, mientras se calculan los objetos eliminados. Este cálculo corrige el valor de la bandera auxiliar flagResetEventDelete, si es necesario.

Durante la eliminación de los objetos aparece el borrado de eventos; en consecuencia estos no se manejan hasta que el valor de la bandera auxiliar no se reinicia a 0, porque se gestionan en esta parte del código:

else if(flagResetEventDelete>0)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)
           {
            findPrefixObject=-1;
/*reiniciamos la bandera contadora de eventos para prevenir 
un bucle de operaciones en la cola de eventos de eliminación de objetos,
que aparece como consecuencia de las acciones de recuperación:*/
            flagResetEventDelete=flagResetEventDelete-1;
            //---
            return;
           }
         return;
        }


    • Cuando los objetos se eliminan por prefijo, y la bandera auxiliar toma un valor igual al número real de eventos "rechazados", colocamos una función que redibuja el gráfico. Solo entonces se ejecutan las acciones.
    • Después de redibujar el gráfico, viene la búsqueda y la eliminación de los objetos cuya hora de creación es igual a la hora de creación del objeto que recibió el evento. Si el evento se forma después de renombrar el objeto, y aparece un objeto "sin dueño" en el gráfico. Se tiene que eliminar con la función ObDelCreateTimeExcept0() que ya hemos presentado en la sección p.4.1.10.
    • Los últimos pasos de la gestión consisten en recrear el panel que se está reponiendo en ese momento.
    • Ya para acabar, redibujamos el gráfico y asignamos el valor 0 a la bandera principal del semáforo, estableciendo la "alarma". Llegados a este punto, el valor de la bandera adaptable se habrá formado automáticamente, de modo que no tenemos que escribir nada más.

//+---------------------------------------------------------------------------------------+
//|Función de "autorecuperación" de objetos en OnChartEvent()                             |
//+---------------------------------------------------------------------------------------+
//|string sparam = nombre del objeto pasado a OnChartEvent()                              |
//|string prefix_obj = prefijo común de los objetos protegidos                            |
//|int &flag_antivandal = bandera de activación/desactivación de la alarma                |
//|int &flag_reset = bandera para reiniciar los eventos                                   |
//|int quant_obj = cantidad total de objetos en el                                        |
//|array "protegido"                                                                      |
//|string &name[] = array de nombres de objetos "protegidos"                              |
//|datetime &time_create[] = array de horas de creación de objetos                        |
//|int quant_obj_all=-1 cantidad total de objetos recuperables                            |
//|del programa, >= quant_obj (si -1, entonces es igual a quant_obj)                      |
//|const long chart_ID = 0 identificador del gráfico                                      |
//|const int sub_window = 0 índice de la ventana                                          |
//|int start_pos=0 posición del nombre del objeto con que se empieza a buscar el prefijo  |
//+---------------------------------------------------------------------------------------+
int RestoringObjArrayAll(const string sparam,
                         const string prefix_obj,
                         int &flag_antivandal,
                         int &flag_reset,
                         const int quant_obj,
                         string &name[],
                         datetime &time_create[],
                         int quant_obj_all=-1,
                         const long chart_ID=0,
                         const int sub_window=0,
                         const int start_pos=0
                         )
  {
//--- aceptamos los resultados aquí:
   int res=-1;
/*Comprobamos que la cantidad de objetos
   es correcta. Solo podemos saber si se han introducido parámetros externos 
   incorrectos durante la creación del código
   después del evento de "alarma".
   Por esto tenemos que asegurarnos de antemano que
   se establecen parámetros externos correctos cuando creamos el código.*/
   if(quant_obj<=0)//si la cantidad especificada de objetos protegidos <= 0
     {
      Print(LINE_NUMBER,__FUNCTION__,
            ", Código de error. Valor incorrecto: quant_obj =",quant_obj);
      return(res);
     }
   else   if(quant_obj>quant_obj_all && quant_obj_all!=-1)
     {
      Print(LINE_NUMBER,__FUNCTION__,
            ", Código de error. Valor incorrecto: quant_obj_all");
      return(res);
     }
   else if(quant_obj_all==-1){quant_obj_all=quant_obj;}
//--- variable para comparar el nombre completo de un objeto:
   int comparison=-1;
/*bucle que compara el nombre del objeto obtenido del evento con los 
 nombres del array de objetos protegidos:*/
   for(int i=quant_obj-1;i>=0;i--)
     {
      comparison=StringCompare(sparam,name[i],true);
      //--- acciones que se llevan a cabo cuando los nombres coinciden:
      if(comparison==0)
        {
         comparison=-1;
         res=1;
         //--- notificación de la ocurrencia del caso "seguro":
         Print(LINE_NUMBER,textRestoring,sparam);
/*mientras el programa restaura los objetos, establecemos la bandera 
para no reaccionar a los eventos de eliminación de objetos:*/
         flag_antivandal=-1;
/*Valor inicial de la bandera contadora de eventos que ya no son necesarios 
para gestionar los eventos posteriores. Esto es para prevenir 
un bucle de operaciones en la cola de eventos de eliminación de objetos
que aparece como consecuencia de las acciones de recuperación:*/
         flag_reset=quant_obj_all-1;
         //--- eliminamos los objetos protegidos restantes del array:
         ObDeletePrefixFlag(flag_reset,prefix_obj,chart_ID,sub_window,start_pos);
         //--- redibujamos el gráfico:
         ChartRedraw();
         //--- eliminamos los objetos renombrados:
         ObDelCreateTimeExcept0(time_create[i],chart_ID,sub_window);
         //--- reconstruimos los objetos:
         PanelCreate();
         //--- finalmente redibujamos el gráfico:
         ChartRedraw();
         //--- activamos la alarma:
         flag_antivandal=0;
         //---
         return(res);
        }
     }//for(int i=QUANT_OBJECTS-1;i>=0;i--)
   return(res);
  }

Nota: Obsérvese que la función no selecciona el objeto por tipo. Intentar reducir la cantidad de gestiones por tipo de objeto durante la "autorecuperación" de objetos puede tener el efecto contrario, si resulta que los objetos eliminados y recreados son de tipos distintos. Por lo tanto, desde mi punto de vista, si en el punto de interferencia no autorizada los objetos del gráfico son de varios tipos, es mejor no aplicar esta estrategia. Esta opción, pues, se elimina de la función anterior, porque supongo que bastantes programadores ignorarán esta advertencia.

El panel del ejemplo que viene en la quinta sección de este artículo tiene objetos de varios tipos. Observará efectos no deseables en el mismo, si selecciona los objetos por tipo durante la "autorecuperación". Basta con utilizar la función Print() en los bloques de código que gestionan los eventos y la recuperación.

Debo añadir una última cosa. Puede ahorrar recursos reduciendo la cantidad de gestiones en el código. Establezca restricciones por tipo de objeto en la variante "autosalida" del gráfico, tal y como se describe arriba.

4.2.7. La función ObDeletePrefixFlag() elimina objetos por prefijo, y si es necesario también corrige el valor de la bandera auxiliar:

Me he inspirado en el esquema del libro de Sergei Kovalev, que tanto Artem Trishkin como el usuario 7777877, a quien no conozco, mencionan en el foro de MQL4. En un primer momento no presté mucha atención a ese texto. Así que agradezco a estos dos usuarios que lo compartieran en el foro, porque la estrategia es bastante universal y me ha servido de ayuda en muchas ocasiones.

Esta es la variante:

//+---------------------------------------------------------------------------+
//| int &flag_reset = bandera que reinicia los eventos de borrado de objetos  |
//| string prefix_obj = prefijo general de los nombres de los objetos         |
//| long  chart_ID = identificador del gráfico                                | 
//| int   sub_window=-1 = índice de la ventana (-1 = todo)                    |
//| int start_pos=-1 = posición de inicio del prefijo general                 |
//| subcadena en el nombre de los objetos                                     |
//+---------------------------------------------------------------------------+
int ObDeletePrefixFlag(int &flag_reset,
                       string prefix_obj,
                       const long chart_ID=0,
                       const int sub_window=0,
                       int start_pos=0)

  {
   int quant_obj=ObjectsTotal(chart_ID,sub_window,-1);
   if(quant_obj>0)
     {
      int count=0;
      int prefix_len=StringLen(prefix_obj);
      string find_substr=NULL,obj_name=NULL;
//---
      for(int i=quant_obj-1;i>=0;i--)
        {
         obj_name=ObjectName(chart_ID,i,sub_window,-1);
         find_substr=StringSubstr(obj_name,start_pos,prefix_len);

         if(StringCompare(find_substr,prefix_obj,true)==0)
           {
            count=count+1;
            ObDelete(chart_ID,obj_name);
           }
        }
      if(count>0 && flag_reset<count)
        {flag_reset=count;}
     }
   return(flag_reset);
  }

Pruebe las operaciones que este código lleva a cabo:
  • ejecute el código de prueba adjunto;
  • haga clic en los objetos que pertenecen a "Yes" y "No", e incremente los valores a partir del cero;
  • intente cambiar los objetos del panel a través del diálogo de propiedades, o bien elimínelos.
Después de ejecutar las acciones de recuperación automáticas, el panel dispondrá de nuevo de los números "clicados" que estaban allí en el momento de cambiar o eliminar los objetos. La cantidad de clics se guarda en un array de datos que se utiliza al recrear el panel.

Sin embargo, se pueden crear varias respuestas.

Nosotros hemos presentado la parte principal de este procedimiento sin tener en cuenta algunos casos especiales, tales como:

  • excluir la gestión de eventos;
  • cambiar las propiedades de los objetos desde otro programa diferente.

Ahora vamos a proceder con una implementación práctica de ejemplo donde el programa responde de dos maneras diferentes a la eliminación y a la modificación de los objetos.


5. Ejemplo de implementación de dos opciones de respuesta en un programa

La selección del indicador, donde se proporcionaría, paso a paso, la implementación de las dos opciones de respuesta a las acciones no autorizadas, no es aleatoria:

  • El lector se puede confundir si proporcionamos un ejemplo de implementación de estas opciones basado en un código complejo.
  • La opción que adjuntamos a continuación es sencilla y proporciona mucha información práctica, incluyendo las condiciones de "batalla".
  • Por otro lado, este indicador también puede ser útil para operar con objetos, incluso fuera del contexto definido en este artículo.

Se puede desarrollar más en el futuro, si hace falta, expandiendo la lista de valores mostrados.

El código completo se adjunta con el nombre id_name_object. En resumen contiene:

  • mapa de arrays y variables para guardar los nombres de los objetos y un mapa de objeto;
  • estrategias de código general;
  • secciones de código que están relacionadas directamente con el tema de este artículo.

El indicador proporcionado hace lo siguiente: al hacer clic en un objeto del gráfico, muestra el nombre del objeto clicado, su hora de creación y su tipo. Copie estos valores de los campos donde se muestran, si es necesario. Al intentar cambiar o eliminar los valores mostrados en el panel, estos se restauran. Esto sucede independientemente de las opciones consideradas, es algo adicional.

Por otro lado, cabe señalar estas características:

  • "minimización" de la parte principal del panel;
  • "autorecuperación" de los objetos, si se cambia o modifica algún objeto de la parte principal del panel;
  • "autosalida" del indicador del gráfico, si el botón del panel minimizado se elimina o se cambia según este principio: "si está minimizado, es porque no debe ser muy necesario en ese momento";
  • las dos últimas medidas se aplican teniendo en cuenta que los objetos se tienen que eliminar después de un posible cambio de nombre;
  • si el terminal está configurado en ruso los títulos del panel se imprimen en ruso, y se muestran en inglés para cualquier otro idioma.

Apariencia del panel de control según el idioma del terminal

Fig. 7. Apariencia del panel de control según el idioma del terminal

Mapa de arrays y variables para guardar los nombres de los objetos del panel

Fig. 8. Mapa de arrays y variables para guardar los nombres de los objetos del panel


Nombre
Tipo de objeto
Descripción
Dónde se almacena el texto
Fuente
Color de texto *
Fondo * Color del borde *
Hora de creación
nameRectLabel
OBJ_RECTANGLE_LABEL
Lienzo
---
--- ---
CLR_PANEL clrNONE
timeCreateRectLabel
nameButton[0]
OBJ_BUTTON
Botón que elimina el identificador del gráfico
textButtUchar[0]**
"Wingdings"
CLR_TEXT
CLR_PANEL clrNONE
timeCreateButton[0]
nameButton[1] OBJ_BUTTON
Botón que minimiza el panel
textButtUchar[1]**
"Wingdings"
CLR_TEXT
CLR_PANEL clrNONE
timeCreateButton[1]
nameButton[2] OBJ_BUTTON
Botón que maximiza el panel, después de haberse minimizado
textButtUchar[2]**
"Wingdings"
CLR_TEXT
CLR_PANEL clrNONE
timeCreateButton[2]
nameEdit0[0]
OBJ_EDIT
"Object's name:" etiqueta
textEdit0[0]
"Arial"
CLR_TEXT_BACK
CLR_PANEL CLR_BORDER
timeCreateEdit0[0]
nameEdit0[1]
OBJ_EDIT
"Creation time:" etiqueta textEdit0[1] "Arial"
CLR_TEXT_BACK
CLR_PANEL CLR_BORDER
timeCreateEdit0[1]
nameEdit0[2]
OBJ_EDIT
"Object's type:" etiqueta textEdit0[2] "Arial"
CLR_TEXT_BACK
CLR_PANEL CLR_BORDER
timeCreateEdit0[2]
nameEdit1[0]
OBJ_EDIT
Campo que muestra el nombre del objeto textEdit1[0]
"Arial"
CLR_TEXT
CLR_PANEL CLR_BORDER
timeCreateEdit1[0]
nameEdit1[1] OBJ_EDIT
Campo que muestra la hora de creación textEdit1[1] "Arial"
CLR_TEXT
CLR_PANEL CLR_BORDER
timeCreateEdit1[1]
nameEdit1[2] OBJ_EDIT
Campo que muestra el tipo de objeto
textEdit1[2] "Arial"
CLR_TEXT
CLR_PANEL CLR_BORDER
timeCreateEdit1[2]

 

Table 2. Mapa de objetos del panel

* Los colores se establecen con la directiva #define:

#define CLR_PANEL                  clrSilver//fondo general del panel
#define CLR_TEXT                   C'39,39,39'//color principal del texto
#define CLR_TEXT_BACK              C'150,150,150' //color del texto de sombra
#define CLR_BORDER                 clrLavender//color del borde

** Símbolos "Wingdings":

uchar textButtUchar[3]={251,225,226};


5.1. Variables externas:

El tamaño del panel se calcula a partir del tamaño de un botón. En consecuencia, tanto la anchura y la altura del botón como el tamaño de la fuente aparecen en las propiedades personalizadas externas. Esto permite aumentar el panel y los textos contenidos en el mismo sin tener que manipular el código.

Propiedades personalizadas externas del indicador

Fig. 9. Propiedades personalizadas externas del indicador


5.2. Las variables de las banderas tienen un alcance global y se pueden acceder desde cualquier parte del programa, hasta en el bloque de la función OnInit():

Se declaran dos banderas:

int flagClick, flagResetEventDelete;

La bandera flagClick hace un seguimiento de las notificaciones de los clics en los botones del panel. Estos son sus valores:

0
Seguimiento de las notificaciones de eventos de clic en los botones de "minimización" del panel, y eliminación del identificador del gráfico; es decir, este valor se da cuando la parte principal del panel está en el gráfico.
-1
Se establece antes de las acciones de "minimización", o cuando se "maximiza" el panel en el gráfico, antes de eliminar el identificador del gráfico, al hacer clic en el botón correspondiente.
1 Cuando se siguen las notificaciones de los eventos de clic en el botón "maximizar".

 

Además, actúa como bandera "antivandálica" principal (en los ejemplos de código anteriores se llamaba flagAntivandal) cuando los objetos se eliminan o se modifican de forma no autorizada. El valor 0 activa la "alarma" de la parte principal del panel, 1 — del botón "maximizar", y -1 desactiva la "alarma" durante las operaciones en curso.

Si desea crear una bandera "antivandálica" adaptable por valor que haga de bandera auxiliar para reiniciar la gestión de los eventos, entonces hace falta una variable más.

La bandera flagResetEventDelete sirve para reiniciar la gestión de eventos innecesarios con valores adaptables, cuando se implementa la opción de "autorecuperación" de los objetos cambiados o eliminados.

En el código adjunto se observan incluso otras variables adicionales.


5.3. En el bloque de la función OnInit():

  • Antes de crear el panel en el gráfico las banderas valen -1.
  • De momento, el array de textos que almacena los campos del panel, se rellena con valores vacíos. Aquí se mostrarán los valores de los objetos del gráfico donde se ha hecho clic con el ratón.
  • Se crea un prefijo de objetos común, y se asigna un nombre corto al indicador de forma arbitraria.
  • Los nombres de los objetos del panel se crean según el prefijo común que se ha formado.
  • Dependiendo del idioma del terminal, se determina el idioma de los títulos del panel, los mensajes de recuperación de los objetos, y la eliminación forzada del indicador.
  • Si el tamaño de los parámetros externos es incorrecto, se establece la restricción de la anchura y altura del botón.
  • En el gráfico se crea la parte principal del panel.
  • Los arrays se revisan con la hora de creación de los objetos.
  • Las banderas flagClick y flagResetEventDelete tienen el valor 0 asignado.
  • Luego se intenta habilitar la notificación de eventos de eliminación de objetos del gráfico, si está deshabilitada en las propiedades.
  • La frecuencia del temporizador se establece en segundos.

int OnInit()
  {
   flagClick=flagResetEventDelete=-1;
/*array de los campos de texto del panel donde 
   se muestran los valores de los objetos gráficos donde se ha hecho clic:*/
   for(int i=QUANT_OBJ_EDIT-1;i>=0;i--){textEdit1[i]=" ";}
//+---------------------------------------------------------------------------+
/*nombre corto del indicador:*/
   StringConcatenate(prefixObj,INDICATOR_NAME,"_",Symbol(),"_",
                     EnumToString(Period()));
   IndicatorSetString(INDICATOR_SHORTNAME,prefixObj);
//--- creación del nombre del objeto
   NameObjectPanel(nameRectLabel,prefixObj,"rl");
   NameObjectPanel(QUANT_OBJ_BUTTON,nameButton,prefixObj,"but");
   NameObjectPanel(QUANT_OBJ_EDIT,nameEdit0,prefixObj,"ed0");
   NameObjectPanel(QUANT_OBJ_EDIT,nameEdit1,prefixObj,"ed1");
//+---------------------------------------------------------------------------+
/*textos del indicador, dependiendo del idioma del terminal de trading:*/
   LanguageTerminal();
//+---------------------------------------------------------------------------+
/*restricciones de la altura y la anchura del botón:*/
   if(objWidthHeight>=20 && objWidthHeight<=300)
     {obj0WidthHeight=objWidthHeight;}
   else if(objWidthHeight>300){obj0WidthHeight=300;}
   else {obj0WidthHeight=20;}
//--- creación del panel de control en el gráfico
   PanelCreate();
/*revisión de los arrays que contienen la hora de creación de los objetos y terminación del programa,
si algo no va bien:
(solo se presentan en la parte principal del panel dos botones de tres,
así que la cantidad durante la revisión es menor)*/
   if(!RevisionCreateTime(QUANT_OBJ_BUTTON-1,timeCreateButton))
     {return(REASON_INITFAILED);}
   if(!RevisionCreateTime(QUANT_OBJ_EDIT,timeCreateEdit0))
     {return(REASON_INITFAILED);}
   if(!RevisionCreateTime(QUANT_OBJ_EDIT,timeCreateEdit1))
     {return(REASON_INITFAILED);}
//---
   flagClick=flagResetEventDelete=0;
//--- incluimos las notificaciones de los eventos de eliminación de objetos gráficos:
   bool res;
   ChartEventObjectDeleteGet(res);
   if(res!=true)
     {ChartEventObjectDeleteSet(true);}
//--- creamos el temporizador
   EventSetTimer(8);
//---
   return(INIT_SUCCEEDED);
  }

5.4. En la función PanelCreate():

Los objetos del panel se dividen en grupos diferentes con el objetivo de simplificar el cambio de su apariencia en el futuro. El panel mismo se sitúa en la esquina superior derecha del gráfico. En consecuencia, las distancias en píxeles a lo largo de los ejes X e Y determinan la posición de los puntos superiores de la parte izquierda de los objetos con respecto a la esquina superior derecha del gráfico.

En los casos de cambio de nombre no autorizado, la hora de creación se establece y se registra en los arrays destinados a este fin. El lienzo del panel tiene una variable separada.

void PanelCreate(const long chart_ID=0,//identificador del gráfico
                 const int sub_window=0)// número de subventana
  {
//--- anchura del lienzo del panel:
   int widthPanel=(obj0WidthHeight*11)+(CONTROLS_GAP_XY*2);
//--- altura del lienzo del panel:
   int heightPanel=(obj0WidthHeight*6)+(CONTROLS_GAP_XY*8);
//--- distancia a lo largo del eje x:
   int x_dist=X_DISTANCE+widthPanel;
//---
   if(ObjectFind(chart_ID,nameRectLabel)<0)//lienzo
     {
      RectLabelCreate(chart_ID,nameRectLabel,sub_window,x_dist,
                      Y_DISTANCE,widthPanel,heightPanel,"\n",CLR_PANEL,
                      BORDER_RAISED,CORNER_PANEL,clrNONE,STYLE_SOLID,1,
                      false,false,true);
      //--- determinamos y registramos la hora de creación del objeto:
      CreateTimeGet(chart_ID,nameRectLabel,timeCreateRectLabel);
     }
//---
   x_dist=X_DISTANCE+CONTROLS_GAP_XY;
   int y_dist=Y_DISTANCE+CONTROLS_GAP_XY;
//---
   for(int i=QUANT_OBJ_BUTTON-2;i>=0;i--)
     {
      //--- botones para eliminar y minimizar el indicador:
      if(ObjectFind(chart_ID,nameButton[i])<0)
        {
         ButtonCreate(chart_ID,nameButton[i],sub_window,
                      x_dist+(obj0WidthHeight*(i+1)),y_dist,obj0WidthHeight,
                      obj0WidthHeight,CORNER_PANEL,
                      CharToString(textButtUchar[i]),"\n","Wingdings",
                      font_sz+1,CLR_TEXT,CLR_PANEL,clrNONE);
         //--- determinamos y registramos la hora de creación del objeto:
         CreateTimeGet(chart_ID,nameButton[i],timeCreateButton[i]);
        }
     }
//---
   x_dist=X_DISTANCE+widthPanel-CONTROLS_GAP_XY;
   int y_dist_plus=(CONTROLS_GAP_XY*2)+(obj0WidthHeight*2);
//---
   for(int i=QUANT_OBJ_EDIT-1;i>=0;i--)
     {
      //--- nombres de las propiedades del objeto:
      if(ObjectFind(chart_ID,nameEdit0[i])<0)
        {
         EditCreate(chart_ID,nameEdit0[i],sub_window,x_dist,
                    y_dist+(y_dist_plus*i),obj0WidthHeight*8,
                    obj0WidthHeight,textEdit0[i],"Arial",font_sz,
                    "\n",ALIGN_CENTER,true,CORNER_RIGHT_UPPER,
                    CLR_TEXT_BACK,CLR_PANEL,CLR_BORDER);
         //--- determinamos y registramos la hora de creación del objeto:
         CreateTimeGet(chart_ID,nameEdit0[i],timeCreateEdit0[i]);
        }
     }
//---
   y_dist=Y_DISTANCE+obj0WidthHeight+(CONTROLS_GAP_XY*2);
//---
   for(int i=QUANT_OBJ_EDIT-1;i>=0;i--)
     {
      //--- mostramos los textos de las propiedades de los objetos:
      if(ObjectFind(chart_ID,nameEdit1[i])<0)
        {
         EditCreate(chart_ID,nameEdit1[i],sub_window,x_dist,
                    y_dist+(y_dist_plus*i),obj0WidthHeight*11,
                    obj0WidthHeight,textEdit1[i],"Arial",font_sz,
                    "\n",ALIGN_LEFT,true,CORNER_RIGHT_UPPER,
                    CLR_TEXT,CLR_PANEL,CLR_BORDER);
         //--- determinamos y registramos la hora de creación del objeto:
         CreateTimeGet(chart_ID,nameEdit1[i],timeCreateEdit1[i]);
        }
     }
   return;
  }

5.5. En OnDeinit():

Si la función OnInit() recibe una señal de desinicialización REASON_INITFAILED, entonces "desactivamos" la bandera principal correspondiente a la hora de eliminación del indicador, eliminamos los objetos por prefijo, paramos la generación de eventos en el temporizador y eliminamos el indicador del gráfico:

void OnDeinit(const int reason)
  {
   flagClick=-1;
   ObjectsDeleteAll(0,prefixObj,0,-1);
   EventKillTimer();
//--- Si OnInit recibe una señal REASON_INITFAILED borramos el indicador.
   if(reason==REASON_INITFAILED)
     {
      ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
     }
//---
   ChartRedraw();
  }

5.6. En OnTimer():

Si la notificación de eventos de eliminación de objetos gráficos está incluida en las propiedades del gráfico, se establecen comprobaciones ocasionales por hora en OnInit():

void OnTimer()
  {
//--- incluimos notificaciones de eventos de eliminación de objetos gráficos
   bool res;
   ChartEventObjectDeleteGet(res);
   if(res!=true)
     {ChartEventObjectDeleteSet(true);}
  }


5.7. La función OnChartEvent():

5.7.1. En el bloque que gestiona las notificaciones de eventos de clic con el ratón sobre objetos:
Si la bandera del semáforo flagClick es igual a 0 entonces:
  • Primero se realizan las comprobaciones correspondientes para averiguar si se trata de un clic en los botones de la parte principal del panel.
  • Si es un clic en el botón de minimización del panel, entonces la bandera flagClick se establece a -1 antes de comenzar a ejecutar las acciones.
  • Si se hizo clic en el botón "minimizar", entonces se minimiza la parte principal del panel, y se crea el botón "maximizar" en la esquina superior derecha del gráfico.
  • Después de lo anterior, la bandera flagClick se establece a 1, lo que implica el seguimiento de los clics en el botón "maximizar" del panel, y su modificación o eliminación no autorizadas.
  • En caso de clic en el botón de eliminación del indicador, el valor de la bandera flagClick se establece a -1 antes de ejecutar cualquier acción. Solo entonces se intenta eliminar el indicador del gráfico.
  • Si no se trata de un clic en los botones del panel de control, sino en cualquier otro objeto del gráfico, a excepción de los objetos del indicador, entonces se determinan el nombre, la hora de creación y el tipo; y se muestran los valores correspondientes.

Si la bandera del semáforo flagClick vale 1, entonces al hacer clic en el botón "maximizar":

  • Antes de emprender cualquier acción asignamos el valor -1 a esta bandera.
  • El botón se elimina y se crea el panel.
  • Después de esto se asigna el valor 0 a la bandera flagClick. Asignamos el valor 1 a la bandera auxiliar flagResetEventDelete, para reiniciar los eventos de eliminación del botón "maximizar".
if(id==CHARTEVENT_OBJECT_CLICK)//id = 1
     {
      if(flagClick==0)//si la parte principal del panel de control está en el gráfico
        {
         string clickedChartObject=sparam;
         findPrefixObject=StringFind(clickedChartObject,prefixObj);
         //---
         if(findPrefixObject==0)
           {
            findPrefixObject=-1;
            //---
            for(int i=1;i>=0;i--)
              {
               if(StringCompare(clickedChartObject,nameButton[i],true)==0)
                 {
                  switch(i)
                    {
                     //--- botón "minimizar":
                     case 1:flagClick=-1;
                     MinimizeTable(nameButton,2,textButtUchar,2,
                                   timeCreateButton,obj0WidthHeight);
                     flagClick=1;return;
                     //--- botón que elimina el indicador del gráfico:
                     case 0:flagClick=-1;
                     ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
                     return;
                     default:return;
                    }
                 }
              }
           }
         else//si el clic no es en botones del panel de control,
           {
            SetValuesTable(clickedChartObject);//establecemos el valor del objeto
            return;
           }
        }
      //--- si se produce un clic en "maximizar" el panel:
      else if(flagClick==1)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)
           {
            string clickedChartObject=sparam;
            findPrefixObject=-1;
            //--- botón para "maximizar" el panel:
            if(StringCompare(clickedChartObject,nameButton[2],true)==0)
              {
               flagClick=-1;
               //--- acciones principales que se llevan a cabo al hacer clic en el botón "maximizar" panel
               ToExpandTheTable(clickedChartObject);
               //---
               flagClick=0;
               //--- reiniciamos el evento de eliminación del botón:
               flagResetEventDelete=1;
               //---
               return;
              }
           }
         return;
        }
      return;
     }//if(id==CHARTEVENT_OBJECT_CLICK)

5.7.2. En el bloque que gestiona las notificaciones de finalización de edición del texto, en el objeto gráfico Edit:
Cuando hay valores de objetos gráficos, entonces se vuelven disponibles para poder copiar el contenido de dichos campos. Por lo tanto, si los valores que muestra el indicador se cambian o se borran cuando se copian accidental o intencionalmente los campos, entonces se restaura el texto anterior:
else  if(id==CHARTEVENT_OBJECT_ENDEDIT)//id = 3
     {
      findPrefixObject=StringFind(sparam,prefixObj);
      if(findPrefixObject==0)
        {
         string endEditChartObject=sparam;
         findPrefixObject=-1;
         int comparison=-1;
         //---
         for(int i=QUANT_OBJ_EDIT-1;i>=0;i--)
           {
            if(StringCompare(endEditChartObject,nameEdit1[i],true)==0)
              {
               string textNew=ObjectGetString(0,endEditChartObject,OBJPROP_TEXT);
               comparison=StringCompare(textEdit1[i],textNew,true);
               //---
               if(comparison!=0)
                 {
                  ObSetString(0,nameEdit1[i],OBJPROP_TEXT,textEdit1[i]);
                 }
               //---
               ChartRedraw();
               return;
              }
           }//for(int i=0;i<QUANT_OBJ_EDIT;i++)
         return;
        }//if(findPrefixObject==0)
      return;
     }//if(id==CHARTEVENT_OBJECT_ENDEDIT)

5.7.3. En el bloque que gestiona las notificaciones de eliminación de objetos:
  • Si la bandera flagClick es igual a -1, entonces salimos del bloque de gestión de eventos porque el valor se asigna a la bandera durante las acciones "amigas".
  • Al completar la primera comprobación, si esta revela que la bandera auxiliar flagResetEventDelete, que reinicia los eventos, es mayor que 0 y el objeto por evento se refiere a los objetos del panel, entonces reseteamos los eventos y salimos del bloque de gestión.
  • Si las banderas flagClick y flagResetEventDelete valen 0, entonces, cuando el nombre del objeto por evento coincide con un objeto "protegido", se ejecutan las acciones de recuperación de la parte principal del panel mediante la función RestoringObjArrayAll() y otras funciones complementarias.
  • Si la bandera principal flagClick es igual a 1 en el evento, eliminamos el indicador del gráfico cuando el nombre del objeto por evento coincide con el botón que "maximiza" el panel.

else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)
     {
      if(flagClick==-1)return;
      else if(flagResetEventDelete>0)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)
           {
            findPrefixObject=-1;
            flagResetEventDelete=flagResetEventDelete-1;
            //---
            return;
           }
         return;
        }
      //--- comprobamos si se elimina el objeto:
      else if(flagClick==0 && flagResetEventDelete==0)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         //---
         if(findPrefixObject==0)
           {
            string chartObject=sparam;
            findPrefixObject=-1;
            int res=-1;
            //--- acciones a tomar si el objeto es el lienzo del panel de control:
            res=RestoringObjOneAll(chartObject,prefixObj,flagClick,
                                   flagResetEventDelete,nameRectLabel,
                                   timeCreateRectLabel,QUANT_OBJ_ALL);
            //---
            if(res==1){return;}
            //--- acciones a tomar si el objeto es el botón del panel de control:
            res=RestoringObjArrayAll(chartObject,prefixObj,flagClick,
                                     flagResetEventDelete,QUANT_OBJ_BUTTON,
                                     nameButton,timeCreateButton,QUANT_OBJ_ALL);
            //---
            if(res==1){return;}
            //--- acciones a tomar si el objeto es una nota (Edit0) del panel de control:
            res=RestoringObjArrayAll(chartObject,prefixObj,flagClick,
                                     flagResetEventDelete,QUANT_OBJ_EDIT,
                                     nameEdit0,timeCreateEdit0,QUANT_OBJ_ALL);
            //---
            if(res==1){return;}
            //--- acciones a tomar si el objeto es una nota (Edit1) del panel de control:
            res=RestoringObjArrayAll(chartObject,prefixObj,flagClick,
                                     flagResetEventDelete,QUANT_OBJ_EDIT,
                                     nameEdit1,timeCreateEdit1,QUANT_OBJ_ALL);
            //---
            return;
           }
        }
      else if(flagClick==1)//si el botón del panel minimizado se cambia o se elimina
        {
         string clickedChartObject=sparam;
         if(StringCompare(clickedChartObject,nameButton[2],true)==0)
           {
            flagClick=-1;
            //--- eliminamos el indicador del gráfico:
            ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
            return;
           }
         return;
        }
      return;
     }//else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)

La característica nueva de este bloque es la función RestoringObjOneAll(), que se aplica cuando el lienzo del panel se cambia o se modifica. Es como la función RestoringObjArrayAll() que hemos presentado anteriormente.
//+-----------------------------------------------------------------------+
//|string sparam = nombre del objeto pasado a OnChartEvent()              |
//|string prefix_obj = prefijo común de los objetos protegidos            |
//|int &flag_antivandal = bandera de activación/desactivación de la alarma|
//|int &flag_reset = bandera de reinicio de eventos                       |
//|string name = nombre del objeto                                        |
//|datetime time_create = hora de creación del objeto                     |
//|int quant_obj_all = cantidad total de objetos recuperables             |
//|del programa, >= quant_obj (si -1, entonces es igual a quant_obj)      |
//|const long chart_ID = 0 identificador del gráfico                      |
//|const int sub_window = 0 índice de la ventana                          |
//|int start_pos=0 posición del nombre del objeto para buscar prefijo     |
//+-----------------------------------------------------------------------+
int RestoringObjOneAll(const string sparam,
                       const string prefix_obj,
                       int &flag_antivandal,
                       int &flag_reset,
                       string name,
                       datetime time_create,
                       const int quant_obj_all,
                       const long chart_ID=0,
                       const int sub_window=0,
                       int start_pos=0
                       )
  {
   int res=-1;
   int comparison=-1;
//---
   comparison=StringCompare(sparam,name,true);
//--- acciones que se llevan a cabo cuando los nombres coinciden:
   if(comparison==0)
     {
      res=1;
      comparison=-1;
      //--- notificación de la ocurrencia del caso "seguro":
      Print(LINE_NUMBER,textRestoring,sparam);
      //---
      flag_antivandal=-1;
      flag_reset=quant_obj_all-1;
      //---
      ObDeletePrefixFlag(flag_reset,prefix_obj,chart_ID,sub_window,start_pos);
      //---
      ChartRedraw();
      //---
      ObDelCreateTimeExcept0(time_create,chart_ID,sub_window,-1);
      //---
      PanelCreate();
      //---
      ChartRedraw();
      //---
      flag_antivandal=0;
      //---
      return(res);
     }
   return(res);
  }

El código completo del indicador se proporciona con el nombre id_name_object.


6. Conclusión

Como hemos demostrado en el presente artículo, las estrategias sencillas que hemos desarrollado, basadas en el sistema de banderas presentado, pueden añadirse al código de uno o varios objetos, así como sus variantes correspondientes. No hay que hacer ningún cambio importante significativo en el código que implementa las operaciones principales del programa.

El código también puede implementar otras acciones relevantes, por ejemplo:

  • cambiar algunos objetos del panel de control mediante otro programa como consecuencia de una operación incorrecta en el código;
  • excluir notificaciones de eventos cuando hay demasiadas.

Si es necesario, implemente comprobaciones adicionales durante las acciones "rutinarias" del panel, así como comprobaciones de los datos importantes mostrados por el temporizador.

En el EA, por ejemplo, manipule la animación durante la modificación/eliminación no autorizada de un objeto gráfico, con o sin visualización de mensajes, si esto no causa ninguna interferencia significativa con el desarrollo de las actividades de trading.

Al mostrar mensajes, ofrezca al usuario la posibilidad de realizar una acción, pulsando la tecla correspondiente: borrar el programa del gráfico o intentar restaurar los objetos dañados. Proporcione un contador, y muestre una advertencia como la siguiente al cambiar o borrar un objeto de forma no autorizada: Esta es la "enésima" interferencia no autorizada con los objetos del programa. Después de la interferencia "N" el programa se borrará a sí mismo del gráfico. Por favor, averigüe el motivo de esta situación".

Ciertamente, puede añadir a sus programas una gran variedad de opciones diferentes. Tan solo tiene que combinar las posibilidades que le ofrece el lenguaje MQL5 con su curiosidad y sus conocimientos de trading y programación.

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

Evaluación y selección de variables en modelos de aprendizaje de máquinas Evaluación y selección de variables en modelos de aprendizaje de máquinas
Este artículo se centra en aspectos específicos relacionados con la elección, los prerrequisitos y la evaluación de las variables de entrada (predictores) de los modelos de aprendizaje de máquinas. Vamos a plantear nuevos enfoques, y también expondremos las oportunidades que ofrece el análisis predictivo profundo, así como la influencia que tiene en el sobreajuste de los modelos. El resultado general de los modelos depende en gran medida del resultado de esta etapa. Analizaremos dos paquetes que ofrecen enfoques nuevos y originales para seleccionar predictores.
Interfaces gráficas I: "Animar" la interfaz gráfica (Capítulo 3) Interfaces gráficas I: "Animar" la interfaz gráfica (Capítulo 3)
En el artículo anterior de esta serie hemos empezado a desarrollar la clase del formulario para los controles. En este artículo continuaremos el desarrollo de la clase llenándola con los métodos para el desplazamiento del formulario dentro del área del gráfico, así como integraremos este elemento de la interfaz en el núcleo de la librería. Además de eso, configuraremos todo de tal manera que, al situar el cursor sobre los controles del formulario, éstos cambien su color.
Gestión de errores y logging en MQL5 Gestión de errores y logging en MQL5
Este artículo se centra en aspectos generales sobre el manejo de los errores de software. Explicaremos qué significa el término logging mediante algunos ejemplos implementados con herramientas de MQL5.
Trading Usando Linux Trading Usando Linux
The article describes how to use indicators to watch the situation on financial markets online.