English Русский 中文 Deutsch 日本語
preview
Dominando los registros (Parte 10): Cómo evitar la repetición de registros implementando un sistema de supresión

Dominando los registros (Parte 10): Cómo evitar la repetición de registros implementando un sistema de supresión

MetaTrader 5 — Ejemplos |
103 3
joaopedrodev
joaopedrodev

Introducción

Este artículo surgió a raíz de una solicitud directa de un usuario de la biblioteca Logify. Señaló un problema al que se enfrentan muchas personas en la práctica: cuando el volumen de registros aumenta demasiado, los mensajes repetidos o irrelevantes acaban contaminando el historial, lo que dificulta encontrar lo que realmente importa. Si tienes alguna otra idea, pregunta o reto que te gustaría que abordara, no dudes en dejar un comentario al final. Este es nuestro espacio, y es gracias a vuestras aportaciones que la biblioteca va evolucionando.

Antes de continuar, es importante comprender qué significa "supresión de registros". En resumen, la supresión es el proceso de controlar qué mensajes de registro se graban, con el objetivo de evitar el exceso, la redundancia o la contaminación de la información. En lugar de simplemente volcar todo lo que produce el sistema, se filtra y limita lo que aparece, asegurándose de que el registro contenga solo los mensajes que sean útiles, relevantes y oportunos.

En este artículo, presentaremos una implementación práctica de un sistema de supresión de registros para Logify, diseñado para ser flexible y eficiente. Verás cómo combinar diferentes formas de control, como evitar que se repitan mensajes idénticos en secuencia, limitar la frecuencia con la que aparece el mismo registro, controlar el número máximo de repeticiones e incluso filtrar por la fuente o el archivo de donde proviene el registro. Todo ello gracias a un sistema inteligente basado en modos bit a bit, que permite activar varias reglas simultáneamente sin complicaciones.

Tras leer este artículo, dominarás la creación de una solución robusta para mantener el registro limpio y eficiente, capaz de suprimir automáticamente los excesos. Aprenderás a aplicar reglas claras que faciliten el análisis y reduzcan el ruido, ahorrando recursos y tiempo. Esta mejora resulta especialmente útil en entornos de producción, donde el exceso de registros puede perjudicar el rendimiento y dificultar el mantenimiento. Recuerda que la versión final de la biblioteca se adjunta al final del artículo y está disponible para su descarga.


Organización de archivos

En el proyecto de tu biblioteca Logify, crea una nueva carpeta llamada Suppression dentro de la carpeta principal de Logify. Esto ayuda a mantener el código organizado y deja claro que todo lo relacionado con la "supresión" de registros se concentra allí. Dentro de la carpeta Suppression, cree un nuevo archivo llamado LogifySuppression.mqh. Este archivo será el punto de partida de nuestra nueva clase de supresión, que controlará qué mensajes deben aparecer realmente en el registro, evitando repeticiones y excesos.

Al principio, la clase puede parecer simple, con solo un constructor y un destructor vacíos, como este:

//+------------------------------------------------------------------+
//|                                            LogifySuppression.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CLogifySuppression
  {
private:


public:
                     CLogifySuppression(void);
                    ~CLogifySuppression(void);
   
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CLogifySuppression::CLogifySuppression(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CLogifySuppression::~CLogifySuppression(void)
  {
  }
//+------------------------------------------------------------------+

Es un punto de partida limpio, sin nada implementado todavía, solo para estructurar y probar la inclusión del archivo.


Ampliación: importaciones y definiciones

Para dotar de funcionalidad a esta clase, necesitaremos importar el modelo de datos de registro (LogifyModel.mqh), que define el formato de los mensajes que recibimos para su procesamiento. También definiremos constantes para validar los parámetros de supresión, garantizando límites mínimos y máximos para evitar configuraciones no válidas.

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "../LogifyModel.mqh"
//+------------------------------------------------------------------+
//| Validation constants                                             |
//+------------------------------------------------------------------+
#define MAX_SUPPRESSION_MODE 255   // Maximum valid mode combination
#define MIN_THROTTLE_SECONDS 1     // Minimum interval between messages
#define MIN_REPEAT_COUNT 1         // Minimum number of repetitions


Modos de supresión con enumeración bit a bit

Para comprender cómo gestionar las diferentes formas de evitar la repetición y el exceso de registros, debemos abordar un concepto fundamental: el uso de enumeraciones bit a bit.

Pero, ¿qué es exactamente esto? En programación, una enumeración es una forma estructurada de definir un conjunto de constantes con nombre. Por ejemplo, podrías tener una enumeración para representar niveles de registro como DEBUG, INFO, ERROR, etc. Bit a bit es una operación que actúa directamente sobre los bits que componen un número entero. Estos bits son como interruptores que pueden estar activados (1) o desactivados (0). Cuando combinamos las enumeraciones y las operaciones bit a bit, creamos valores únicos que representan potencias de 2, es decir, 1, 2, 4, 8, 16 y así sucesivamente. Cada uno de estos valores se corresponde con un bit distinto del número binario.

¿Por qué utilizar enumeraciones bit a bit? Imaginemos que queremos aplicar más de un modo de supresión al mismo tiempo. Por ejemplo, limitar los mensajes repetidos consecutivos y también los mensajes que aparecen demasiadas veces seguidas. Si solo creáramos valores simples en la enumeración, solo podríamos elegir un modo a la vez, ¿verdad? Eso limitaría demasiado el sistema. Con las enumeraciones bit a bit, podemos combinar varios modos activando los bits correspondientes. La combinación se realiza mediante el operador OR bit a bit (|), que «une» los bits de los modos deseados en un único número.

Veamos de forma práctica la definición que utilizamos:

enum ENUM_LOG_SUPRESSION_MODE
  {
   LOG_SUPRESSION_MODE_NONE = 0,                   // No suppression
   LOG_SUPRESSION_MODE_CONSECUTIVE = 1 << 0,       // 00001 = 1: Identical consecutive messages
   LOG_SUPRESSION_MODE_THROTTLE_TIME = 1 << 1,     // 00010 = 2: Same message within X seconds
   LOG_SUPRESSION_MODE_BY_REPEAT_COUNT = 1 << 2,   // 00100 = 4: After N repetitions
   LOG_SUPRESSION_MODE_BY_ORIGIN = 1 << 3,         // 01000 = 8: Based on message origin
   LOG_SUPRESSION_MODE_BY_FILENAME = 1 << 4,       // 10000 = 16: Based on source filename
  };

En este contexto, 1 << N significa «1 desplazado N posiciones hacia la izquierda». Cada desplazamiento genera un solo bit:

  • 1 << 0 = 1 (binario 00001)
  • 1 << 1 = 2 (binario 00010)
  • 1 << 2 = 4 (binario 00100)
  • 1 << 3 = 8 (binario 01000)
  • 1 << 4 = 16 (binario 10000)

Si quieres activar más de un modo a la vez, solo tienes que combinar los valores con el operador «O» (|):

int mode = LOG_SUPRESSION_MODE_CONSECUTIVE | LOG_SUPRESSION_MODE_THROTTLE_TIME; // 1 | 2 = 3 (00011)

Ese número 3, en binario 00011, indica que los dos primeros modos están activos simultáneamente. ¿Por qué esto ayuda con la supresión de registros?

  • Flexibilidad: El sistema acepta varias reglas de supresión al mismo tiempo, sin necesidad de crear enumeraciones separadas para cada combinación posible.
  • Eficiencia: Comprobar si un modo está activo es sencillo y rápido; basta con usar el operador bit a bit AND (&) para comprobar si el bit de ese modo está activado.
  • Extensibilidad: Si en el futuro queremos añadir nuevos modos de supresión, simplemente podemos añadir nuevos bits a la enumeración, sin estropear nada de lo que ya funciona.

Por ejemplo, para averiguar si el modo "supresión por origen" está activo, hacemos lo siguiente:

if((mode & LOG_SUPRESSION_MODE_BY_ORIGIN) == LOG_SUPRESSION_MODE_BY_ORIGIN)
  {
   // Apply filter by source
  }

Si el bit correspondiente está activado, la condición será verdadera.


Configuración con estructura

Tras definir los posibles modos de supresión, llega el momento de encapsular toda la lógica de configuración en un único lugar, y ahí es donde entra en juego la estructura MqlLogifySuppressionConfig. Esta struct funciona como un «panel de control» en el que se define cómo y cuándo deben suprimirse los mensajes de registro. La idea es sencilla: almacenar los parámetros que controlan el comportamiento de supresión y permitir una configuración clara y reutilizable, ya sea en EA, indicadores o bibliotecas auxiliares.

Analicemos qué hay ahí:

  • mode: combinación de modos de supresión

    Esta es la parte fundamental de la configuración. Aquí almacenamos una combinación de modos de supresión utilizando un entero (int) con operaciones bit a bit. Esto permite al desarrollador activar varios modos de supresión al mismo tiempo, tales como:

    • Supresión de registros idénticos consecutivos
    • Ignorar los mensajes repetidos en un intervalo de tiempo
    • Detenerse después de X número de repeticiones
  • throttle_seconds: limitación por tiempo

    Este campo define un intervalo (en segundos) que debe existir entre los mensajes repetidos para que se vuelvan a mostrar. Resulta útil en casos donde una función activa el mismo registro varias veces por segundo, lo que rápidamente satura la consola y hace que todo sea ilegible.

  • max_repeat_count: limitar por cantidad

    Aquí definimos el número máximo de veces que puede aparecer el mismo mensaje antes de ser suprimido. Esto resulta útil, por ejemplo, para detectar un error que se ha producido varias veces, pero sin seguir mostrándolo indefinidamente.

  • Lista blanca/lista negra por origen y archivo

    Con frecuencia, el desarrollador desea aplicar la supresión solo a los mensajes que provienen de ciertas partes del código, o excluir por completo orígenes específicos.

    Por eso la estructura incluye cuatro matrices:

    • allowed_origins[]: si se rellena, solo se permitirán estos orígenes.
    • blocked_origins[]: cualquier origen que figure aquí quedará siempre bloqueado.
    • allowed_filenames[]: el mismo concepto, pero aplicado al nombre del archivo fuente.
    • blocked_filenames []: los archivos siempre se bloquean.

    Estos campos permiten un control extremadamente detallado. Por ejemplo, podrías permitir los registros de tu EA principal, pero suprimir todos los registros procedentes de una biblioteca de terceros que genere demasiado ruido.

Definamos un constructor con valores por defecto. En el caso de una clase de supresión de registros en sistemas de negociación, es importante contar con una configuración por defecto equilibrada que evite el exceso de registros, pero que no oculte información importante. Voy a proponer y justificar una configuración por defecto:

  • mode = LOG_SUPRESSION_MODE_THROTTLE_TIME | LOG_SUPRESSION_MODE_CONSECUTIVE | LOG_SUPRESSION_MODE_BY_REPEAT_COUNT: Combina los tres modos básicos de supresión, evita el exceso de registros sin perder información esencial y mantiene un historial trazable
  • throttle_seconds = 5 : 5 segundos es un buen término medio en la mayoría de los casos: lo suficientemente largo como para no pasar por alto cambios importantes, pero lo suficientemente corto como para mantener la trazabilidad.
  • max_repeat_count = 15 : 15 repeticiones te permiten identificar patrones, lo suficiente para depurar el código si es necesario y evitar una sobrecarga en caso de que surjan problemas

//+------------------------------------------------------------------+
//| Struct: MqlLogifySuppressionConfig                               |
//+------------------------------------------------------------------+
struct MqlLogifySuppressionConfig
  {
   // Basic configuration
   int mode;              // Combination of suppression modes
   int throttle_seconds;  // Seconds between messages
   int max_repeat_count;  // Max repetitions before suppression
   
   // Origin whitelist/blacklist
   string allowed_origins[];     // If not empty, only these are allowed
   string blocked_origins[];     // Always blocked
   
   // Filename whitelist/blacklist
   string allowed_filenames[];   // If not empty, only these are allowed
   string blocked_filenames[];   // Always blocked
   
   //--- Default constructor
   MqlLogifySuppressionConfig(void)
     {
      mode = LOG_SUPRESSION_MODE_THROTTLE_TIME | LOG_SUPRESSION_MODE_CONSECUTIVE | LOG_SUPRESSION_MODE_BY_REPEAT_COUNT;
      throttle_seconds = 5;
      max_repeat_count = 15;
      
      ArrayResize(allowed_origins, 0);
      ArrayResize(blocked_origins, 0);
      ArrayResize(allowed_filenames, 0);
      ArrayResize(blocked_filenames, 0);
     }
   
   //--- Destructor
   ~MqlLogifySuppressionConfig(void)
     {
     }
  };
//+------------------------------------------------------------------+

También definiremos dos métodos auxiliares para evitar que el usuario manipule los arrays manualmente mediante ArrayResize() e índices; se han creado métodos prácticos como AddAllowedOrigin(), AddBlockedFilename(), etc. Esto hace que la configuración sea clara, fácil de leer y que haya menos posibilidades de cometer errores:

//+------------------------------------------------------------------+
//| Struct: MqlLogifySuppressionConfig                               |
//+------------------------------------------------------------------+
struct MqlLogifySuppressionConfig
  {
   //--- Helper methods for array configuration
   void AddAllowedOrigin(string origin)
     {
      int size = ArraySize(allowed_origins);
      ArrayResize(allowed_origins, size + 1);
      allowed_origins[size] = origin;
     }
   
   void AddBlockedOrigin(string origin)
     {
      int size = ArraySize(blocked_origins);
      ArrayResize(blocked_origins, size + 1);
      blocked_origins[size] = origin;
     }
   
   void AddAllowedFilename(string filename)
     {
      int size = ArraySize(allowed_filenames);
      ArrayResize(allowed_filenames, size + 1);
      allowed_filenames[size] = filename;
     }
   
   void AddBlockedFilename(string filename)
     {
      int size = ArraySize(blocked_filenames);
      ArrayResize(blocked_filenames, size + 1);
      blocked_filenames[size] = filename;
     }
  };
//+------------------------------------------------------------------+

Por último, hemos incluido el método ValidateConfig(). Esto comprueba que los valores introducidos sean válidos, lo que evita que se produzcan errores. Entre las validaciones:

  • El valor de «throttle_seconds» no puede ser inferior a un umbral mínimo (para evitar valores nulos o negativos).
  • max_repeat_count debe ser mayor o igual que el valor mínimo permitido.
  • El valor del modo debe ser una combinación válida.

Este método devuelve «false» si se produce algún error y rellena la variable «error_message» con una descripción del problema. Esto resulta útil tanto para la depuración como para su uso en aplicaciones que desean mostrar mensajes de error fáciles de entender para el usuario.

//+------------------------------------------------------------------+
//| Struct: MqlLogifySuppressionConfig                               |
//+------------------------------------------------------------------+
struct MqlLogifySuppressionConfig
  {
   //--- Validates configuration parameters
   bool ValidateConfig(string &error_message)
     {
      if(throttle_seconds < MIN_THROTTLE_SECONDS)
        {
         error_message = "throttle_seconds must be greater than or equal to " + (string)MIN_THROTTLE_SECONDS;
         return false;
        }
      
      if(max_repeat_count < MIN_REPEAT_COUNT)
        {
         error_message = "max_repeat_count must be greater than or equal to " + (string)MIN_REPEAT_COUNT;
         return false;
        }
      
      if(mode < LOG_SUPRESSION_MODE_NONE || mode > MAX_SUPPRESSION_MODE)
        {
         error_message = "invalid suppression mode";
         return false;
        }
      
      return true;
     }
  };
//+------------------------------------------------------------------+

Con esta estructura, puedes configurar la supresión de registros con unos pocos comandos, tener un control preciso sobre el comportamiento y, al mismo tiempo, garantizar que todo se mantenga dentro de unos límites aceptables. Todo esto sin depender de una lógica dispersa por todo el código, centralizando todo de una manera limpia y extensible.


Evolución de CLogifySuppression

Ahora que tenemos nuestra estructura de configuración bien definida, vamos a crear la clase responsable de aplicar esta configuración en tiempo real: CLogifySuppression. Será responsable de decidir, con cada registro emitido, si ese mensaje debe aparecer o no en la consola, en función de las reglas activas.

Antes de implementar cualquier lógica de supresión real, es esencial permitir que la clase reciba instrucciones externas sobre cómo debe comportarse. Esto significa que la configuración, con reglas, límites y listas de excepciones, debe provenir de fuera de la clase, desacoplando así la lógica de ejecución de la forma en que se parametrizará. Esta separación entre lógica y configuración es lo que proporciona flexibilidad y reutilización al sistema. Para ello, añadimos dos métodos a la clase de supresión:

class CLogifySuppression
  {
public:
   //--- Configuration management
   void              SetConfig(MqlLogifySuppressionConfig &config);
   MqlLogifySuppressionConfig GetConfig(void) const;
  };
//+------------------------------------------------------------------+
//| Updates suppression configuration                                |
//+------------------------------------------------------------------+
void CLogifySuppression::SetConfig(MqlLogifySuppressionConfig &config)
  {
   m_config = config;
   
   string err_msg = "";
   if(!m_config.ValidateConfig(err_msg))
     {
      Print("[ERROR] ["+TimeToString(TimeCurrent())+"] Log system error: "+err_msg);
     }
  }
//+------------------------------------------------------------------+
//| Returns current configuration                                    |
//+------------------------------------------------------------------+
MqlLogifySuppressionConfig CLogifySuppression::GetConfig(void) const
  {
   return m_config;
  }
//+------------------------------------------------------------------+

El método SetConfig() recibe por referencia un objeto de la estructura MqlLogifySuppressionConfig y lo almacena internamente. A continuación, realiza una validación automática llamando al método ValidateConfig() de la propia estructura. Si alguna configuración está fuera de los límites aceptables, como por ejemplo throttle_seconds inferior al mínimo permitido o un modo no válido, se imprime un error inmediatamente, lo que indica el problema. Esto evita errores silenciosos durante la ejecución, ahorra tiempo de depuración y mantiene el sistema intacto, incluso cuando se configura dinámicamente en tiempo de ejecución.

El método GetConfig() permite consultar el estado actual de la configuración almacenada. Esto puede resultar útil para el diagnóstico, la depuración o la creación de interfaces para mostrar reglas de supresión en sistemas más grandes. De este modo, la configuración de supresión se convierte en algo formal, validado y centralizado.


Declarar las variables privadas

Una vez configurada la configuración, el siguiente paso es almacenar datos que permitan aplicarla en función del historial de llamadas. Dado que la lógica de supresión necesita "recordar" lo que sucedió anteriormente, como cuál fue el último mensaje grabado o cuántas veces se repitió, necesitamos mantener esta información accesible entre llamadas. Hemos añadido los siguientes campos privados a la clase:

class CLogifySuppression
  {
private:

   //--- Configuration
   MqlLogifySuppressionConfig m_config;
   
   //--- State tracking
   string            m_last_message;
   ENUM_LOG_LEVEL    m_last_level;
   int               m_repeat_count;
   datetime          m_last_time;
  };

Entendamos el papel de cada uno:

  • m_config: es la instancia actual de la estructura de configuración. Cada vez que se evalúa un mensaje, se comparará con las reglas definidas aquí, ya sea el intervalo mínimo (throttle_seconds), el número de repeticiones toleradas (max_repeat_count) o las listas de origen y archivo.
  • m_last_message: Almacena el contenido del último mensaje que pasó por el filtro de supresión. Sirve como referencia para determinar si el nuevo mensaje es igual al anterior, uno de los criterios clave para detectar repeticiones consecutivas.
  • m_last_level: Almacena el nivel del último mensaje procesado (información, advertencia, error, etc.). Esto es importante porque una misma cadena de mensaje puede tener diferentes significados en distintos niveles. Por ejemplo, un mensaje del tipo «Info: conexión perdida» no debe tratarse igual que uno del tipo «Error: conexión perdida».
  • m_repeat_count: cuenta cuántas veces seguidas se ha encontrado el mismo mensaje. Se incrementa siempre que el mensaje y el nivel sean idénticos a los de la llamada anterior. Cuando este número supera el límite configurado, se puede activar la supresión.
  • m_last_time: registra la marca de tiempo de la última entrada de registro aceptada. Es la base para calcular el tiempo transcurrido desde el último mensaje y aplicar correctamente el modo de limitación, que suprime los registros emitidos a intervalos muy cortos.

Estas variables, en conjunto, representan el estado interno de supresión. Permiten que el sistema aplique reglas con memoria, es decir, teniendo en cuenta lo que sucedió anteriormente, lo cual es esencial para decidir, de manera confiable y eficiente, si se debe mostrar o no un nuevo mensaje.


Creación del método ShouldSuppress()

Con los bloques anteriores ya establecidos (configuración externa, estado interno y definición de modos), ahora podemos construir el núcleo del sistema de supresión: el método ShouldSuppress(). Este método se llama cada vez que se emite un registro. Recibe como argumento un MqlLogifyModel, que contiene todos los datos sobre el registro en cuestión: mensaje, nivel, origen, fecha y nombre del archivo. La función de ShouldSuppress() es tomar una decisión, en función de la configuración activa, sobre si este registro debe mostrarse o suprimirse.

Comenzamos con la base lógica del método, tratando con los modos más simples:

class CLogifySuppression
  {
private:
   //--- Main suppression logic
   bool              ShouldSuppress(MqlLogifyModel &data);
  };
//+------------------------------------------------------------------+
//| Checks if a message should be suppressed based on active modes   |
//+------------------------------------------------------------------+
bool CLogifySuppression::ShouldSuppress(MqlLogifyModel &data)
  {
   datetime now = data.date_time;
   
   //--- Reset counters if message or level changed
   if(data.msg != m_last_message || data.level != m_last_level)
     {
      m_repeat_count = 0;
      m_last_message = data.msg;
      m_last_level = data.level;
      m_last_time = now;
      return false;
     }
   
   //--- Increment counter once per check
   m_repeat_count++;
   
   //--- Check suppression modes
   if(((m_config.mode & LOG_SUPRESSION_MODE_BY_REPEAT_COUNT) == LOG_SUPRESSION_MODE_BY_REPEAT_COUNT)
   && m_repeat_count >= m_config.max_repeat_count)
     {
      return true;
     }
   
   if(((m_config.mode & LOG_SUPRESSION_MODE_THROTTLE_TIME) == LOG_SUPRESSION_MODE_THROTTLE_TIME)
   && (now - m_last_time) < m_config.throttle_seconds)
     {
      return true;
     }
   
   if((m_config.mode & LOG_SUPRESSION_MODE_CONSECUTIVE) == LOG_SUPRESSION_MODE_CONSECUTIVE)
     {
      return true;
     }
   
   m_last_time = now;
   return false;
  }
//+------------------------------------------------------------------+

Aquí, nos ocupamos de tres modos:

  • Por consecutividad: si el mismo mensaje se registra más de una vez seguidas, solo se mostrará el primero
  • Por número de repeticiones: En lugar de ocultar todos los mensajes iguales consecutivos, te permite establecer una tolerancia, es decir, cuántas veces se puede mostrar el mismo mensaje antes de que empiece a ocultarse.
  • Por intervalo de tiempo: Aunque los mensajes sean idénticos, solo se suprimirán si se envían con un intervalo de tiempo inferior al valor configurado.

Este comportamiento ya es funcional en la mayoría de los casos. Sin embargo, aún falta una función más avanzada: la posibilidad de ignorar los registros en función de su origen (campo «origin») o del archivo (nombre de archivo). Esta función resulta útil cuando el desarrollador desea ocultar mensajes procedentes de un componente específico del sistema, como los registros internos de una biblioteca externa o los mensajes de depuración muy detallados de un único archivo .mq5 o .mqh.


Añadir la supresión por fuente y nombre de archivo (con búsqueda inteligente)

En entornos más complejos, en los que varios componentes o módulos del sistema generan registros simultáneamente, es habitual que el desarrollador desee suprimir únicamente los registros de determinadas partes específicas del código, como los mensajes de un sistema de negociación automática o los registros generados por indicadores secundarios. Con este fin, hemos añadido la posibilidad de suprimir registros en función del campo «origen» (el origen lógico del registro) y del campo «nombre de archivo» (el nombre del archivo MQL que lo generó).

La primera versión de esta lógica puede utilizar comparaciones directas con cadenas exactas. Pero en la práctica, esto resulta limitante. Por ejemplo, imagina que tu origen es "Trade.Signal" y configuras la cadena "signal" como bloqueada. En este enfoque exacto, esto no funcionaría, porque "Trade.Signal" y "signal" no son idénticos. Por eso creamos un método auxiliar llamado StringContainsIgnoreCase(). Este método realiza una búsqueda de subcadenas sin distinguir entre mayúsculas y minúsculas, lo que hace las comparaciones mucho más flexibles y tolerantes.

Aquí está su implementación:

class CLogifySuppression
  {
private:
   //--- Helper methods for string comparison
   bool              StringContainsIgnoreCase(string text, string search_term);
  };
//+------------------------------------------------------------------+
//| Checks if a string contains another string (case insensitive)    |
//+------------------------------------------------------------------+
bool CLogifySuppression::StringContainsIgnoreCase(string text, string search_term)
  {
   string text_lower = text;
   string term_lower = search_term;
   StringToLower(text_lower);
   StringToLower(term_lower);
   return StringFind(term_lower, text_lower) >= 0;
  }
//+------------------------------------------------------------------+

Esta función transforma ambos textos a minúsculas antes de buscar la aparición de la subcadena. Esto significa que "Trade.Signal" puede identificarse como relacionado con "signal" o "trade" sin necesidad de escribir el nombre completo de la fuente. Esto te permite crear una mini jerarquía semántica entre las fuentes. Por ejemplo, al bloquear «signal», se suprimen automáticamente los registros procedentes de «Trade.Signal», «Risk.Signal» o incluso «Execution.Signal». Esta estrategia reduce drásticamente el esfuerzo necesario para configurar filtros útiles, al tiempo que mantiene la lógica clara y eficiente.

Si aplicamos esto a nuestra lógica de supresión:

//+------------------------------------------------------------------+
//| Checks if a message should be suppressed based on active modes   |
//+------------------------------------------------------------------+
bool CLogifySuppression::ShouldSuppress(MqlLogifyModel &data)
  {
   datetime now = data.date_time;
   
   //--- Check origin-based suppression
   if((m_config.mode & LOG_SUPRESSION_MODE_BY_ORIGIN) == LOG_SUPRESSION_MODE_BY_ORIGIN)
     {
      //--- Check blacklist first
      if(ArraySize(m_config.blocked_origins) > 0)
        {
         for(int i = 0; i < ArraySize(m_config.blocked_origins); i++)
           {
            if(StringContainsIgnoreCase(data.origin, m_config.blocked_origins[i]))
              {
               return true;
              }
           }
        }
      
      //--- Then check whitelist
      if(ArraySize(m_config.allowed_origins) > 0)
        {
         bool origin_allowed = false;
         for(int i = 0; i < ArraySize(m_config.allowed_origins); i++)
           {
            if(StringContainsIgnoreCase(data.origin, m_config.allowed_origins[i]))
              {
               origin_allowed = true;
               break;
              }
           }
         
         if(!origin_allowed)
           {
            return true;
           }
        }
     }
   
   //--- Check filename-based suppression
   if((m_config.mode & LOG_SUPRESSION_MODE_BY_FILENAME) == LOG_SUPRESSION_MODE_BY_FILENAME)
     {
      //--- Check blacklist first
      if(ArraySize(m_config.blocked_filenames) > 0)
        {
         for(int i = 0; i < ArraySize(m_config.blocked_filenames); i++)
           {
            if(StringContainsIgnoreCase(data.filename, m_config.blocked_filenames[i]))
              {
               return true;
              }
           }
        }
      
      //--- Then check whitelist
      if(ArraySize(m_config.allowed_filenames) > 0)
        {
         bool filename_allowed = false;
         for(int i = 0; i < ArraySize(m_config.allowed_filenames); i++)
           {
            if(StringContainsIgnoreCase(data.filename, m_config.allowed_filenames[i]))
              {
               filename_allowed = true;
               break;
              }
           }
         
         if(!filename_allowed)
           {
            return true;
           }
        }
     }
   
   //--- Reset counters if message or level changed
   if(data.msg != m_last_message || data.level != m_last_level)
     {
      m_repeat_count = 0;
      m_last_message = data.msg;
      m_last_level = data.level;
      m_last_time = now;
      return false;
     }
   
   //--- Increment counter once per check
   m_repeat_count++;
   
   //--- Check suppression modes
   if(((m_config.mode & LOG_SUPRESSION_MODE_BY_REPEAT_COUNT) == LOG_SUPRESSION_MODE_BY_REPEAT_COUNT)
   && m_repeat_count >= m_config.max_repeat_count)
     {
      return true;
     }
   
   if(((m_config.mode & LOG_SUPRESSION_MODE_THROTTLE_TIME) == LOG_SUPRESSION_MODE_THROTTLE_TIME)
   && (now - m_last_time) < m_config.throttle_seconds)
     {
      return true;
     }
   
   if((m_config.mode & LOG_SUPRESSION_MODE_CONSECUTIVE) == LOG_SUPRESSION_MODE_CONSECUTIVE)
     {
      return true;
     }
   
   m_last_time = now;
   return false;
  }
//+------------------------------------------------------------------+

Hacemos lo mismo con los campos «allowed_origins» y «allowed_filenames», lo que también permite crear una lista blanca, es decir, un filtro que solo deja pasar determinados registros y bloquea todo lo demás, lo cual es lo contrario de la lista negra tradicional.

Esta combinación de filtros por origen, nombre de archivo y patrón textual que no distingue entre mayúsculas y minúsculas da como resultado un sistema de supresión selectiva increíblemente potente. Se puede configurar para que sea permisivo o extremadamente estricto, dependiendo de lo que el desarrollador necesite en ese contexto, ya sea para desarrollar un robot, realizar pruebas retrospectivas de una estrategia o analizar un sistema en tiempo real en producción.


Creación del método Reset()

A medida que se utiliza el sistema de supresión de registros, este acumula información interna, como el último mensaje registrado, cuántas veces se repitió y cuándo apareció por última vez. Este estado interno es esencial para aplicar correctamente las reglas de supresión. Sin embargo, en ciertos momentos tiene sentido "restablecer" este estado.

Un ejemplo clásico se da cuando cambia el contexto de ejecución, como al cambiar de gráficos, símbolos o marcos temporales. En estos casos, conservar el historial anterior puede llevar a decisiones de supresión erróneas, ocultando mensajes relevantes en un nuevo contexto.

Para solucionar esto, creamos el método Reset(). Borra los datos internos que la clase ha almacenado hasta el momento, como si comenzáramos desde cero:

//+------------------------------------------------------------------+
//| Resets all internal state tracking                               |
//+------------------------------------------------------------------+
void CLogifySuppression::Reset(void)
  {
   m_last_message = "";
   m_repeat_count = 0;
   m_last_time = 0;
   m_last_level = LOG_LEVEL_INFO;
  }
//+------------------------------------------------------------------+

Este método es extremadamente sencillo, pero muy importante para garantizar que la lógica de supresión sea precisa. En cuanto se llama al constructor de la clase, también nos aseguramos de invocar internamente el método Reset(). Esto garantiza que cada nueva instancia de la clase comience "limpia", sin ningún historial previo de mensajes, repeticiones o marcas de tiempo.

Más adelante, también puedes optar por llamar a Reset() manualmente si estás implementando un control más avanzado, como reiniciar la supresión entre ejecuciones o después de eventos específicos en tu código.


Creación de los Getters auxiliares

Incluso con toda la automatización de la supresión, en muchas ocasiones el desarrollador necesita saber qué está sucediendo entre bastidores. Si el sistema está ocultando registros que esperabas ver, es útil poder inspeccionar el estado interno de la clase y comprender el motivo.

Para ello, hemos añadido algunos métodos públicos sencillos, denominados getters, que te permiten acceder a las principales variables de control de la supresión. No modifican el estado de la clase, simplemente devuelven valores útiles para herramientas de diagnóstico, registros o depuración:

class CLogifySuppression
  {
public:
   //--- Monitoring getters
   int               GetRepeatCount(void) const { return m_repeat_count; }
   datetime          GetLastMessageTime(void) const { return m_last_time; }
   string            GetLastMessage(void) const { return m_last_message; }
   ENUM_LOG_LEVEL    GetLastLevel(void) const { return m_last_level; }
  };
  • GetRepeatCount() devuelve cuántas veces seguidas apareció el mismo mensaje. Esto puede ayudar a comprender por qué un mensaje fue o no fue suprimido.
  • GetLastMessageTime() te indica cuándo pasó un mensaje por última vez a través del filtro. Esto es fundamental para validar si la supresión por tiempo funciona como se espera.
  • GetLastMessage() muestra el contenido literal del último mensaje que se procesó.
  • La función GetLastLevel() le indica el nivel (información, advertencia, error, etc.) del último mensaje, lo que le permite compararlo con el control de gravedad de su sistema.

Estos métodos son opcionales en el uso general de la clase, pero se vuelven extremadamente valiosos cuando surge algún comportamiento inesperado; después de todo, suprimir mensajes automáticamente es un arma de doble filo: ahorra ruido, pero puede ocultar problemas. Disponer de métodos para inspeccionar la lógica desde dentro reduce drásticamente el tiempo de investigación cuando esto ocurre.


Integración de la supresión de registros en la clase principal de CLogify

Una vez que CLogifySuppression esté en funcionamiento, es hora de integrarlo en el núcleo de nuestra biblioteca: la clase CLogify. Aquí es donde sucede todo, desde el enrutamiento de mensajes hasta la gestión de los handlers. Y ahora, también decidirá cuándo silenciar los registros repetidos o irrelevantes. El primer paso es importar el archivo de supresión y declarar una instancia de la clase como miembro privado de CLogify:

//+------------------------------------------------------------------+
//| Imports                                                          |
//+------------------------------------------------------------------+
#include "LogifyModel.mqh"
#include "Suppression/LogifySuppression.mqh"
#include "Handlers/LogifyHandler.mqh"
#include "Handlers/LogifyHandlerComment.mqh"
#include "Handlers/LogifyHandlerConsole.mqh"
#include "Handlers/LogifyHandlerDatabase.mqh"
#include "Handlers/LogifyHandlerFile.mqh"
#include "Error/LogifyError.mqh"
//+------------------------------------------------------------------+
//| class : CLogify                                                  |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : Logify                                             |
//| Heritage    : No heritage                                        |
//| Description : Core class for log management.                     |
//|                                                                  |
//+------------------------------------------------------------------+
class CLogify
  {
private:
   CLogifySuppression*m_suppression;
  };
//+------------------------------------------------------------------+

Esta instancia se utilizará para decidir, internamente, si un mensaje en particular merece ser registrado o no. En el constructor de CLogify, inicializamos esta instancia:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLogify::CLogify()
  {
   m_suppression = new CLogifySuppression();
  }
//+------------------------------------------------------------------+

Y por supuesto, en el destructor, nos aseguramos de que la memoria quede liberada:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CLogify::~CLogify()
  {
   //--- Delete handlers
   int size_handlers = ArraySize(m_handlers);
   for(int i=0;i<size_handlers;i++)
     {
      if(CheckPointer(m_handlers[i]) != POINTER_INVALID)
        {
         m_handlers[i].Close();
         delete m_handlers[i];
        }
     }
   delete m_suppression;
  }
//+------------------------------------------------------------------+

Ahora, dentro del método Append(), que es el responsable del registro, comprobamos si el mensaje debe suprimirse justo después de que se haya creado la plantilla de registro. Si el mensaje se considera innecesario, se ignora directamente:

//+------------------------------------------------------------------+
//| Generic method for adding logs                                   |
//+------------------------------------------------------------------+
bool CLogify::Append(ENUM_LOG_LEVEL level,string msg, string origin = "", string args = "",string filename="",string function="",int line=0,int code_error=0)
  {
   //--- Ensures that there is at least one handler
   this.EnsureDefaultHandler();
   
   //--- Textual name of the log level
   string levelStr = "";
   switch(level)
     {
      case LOG_LEVEL_DEBUG: levelStr = "DEBUG"; break;
      case LOG_LEVEL_INFO : levelStr = "INFO"; break;
      case LOG_LEVEL_ALERT: levelStr = "ALERT"; break;
      case LOG_LEVEL_ERROR: levelStr = "ERROR"; break;
      case LOG_LEVEL_FATAL: levelStr = "FATAL"; break;
     }
   
   //--- Creating a log template with detailed information
   datetime time_current = TimeCurrent();
   MqlLogifyModel data("",levelStr,msg,args,time_current,time_current,level,origin,filename,function,line,m_error.Error(code_error));
   
   //--- Supression
   if(m_suppression.ShouldSuppress(data))
     {
      return(true);
     }
   
   //--- Call handlers
   int size = this.SizeHandlers();
   for(int i=0;i<size;i++)
     {
      data.formated = m_handlers[i].GetFormatter().Format(data);
      m_handlers[i].Emit(data);
     }
   
   return(true);
  }
//+------------------------------------------------------------------+

Esta sencilla comprobación es la que evita que se procesen mensajes innecesarios. Si el mecanismo detecta que el registro actual es redundante, ya sea porque se ha repetido demasiadas veces seguidas, porque proviene del mismo lugar del código o por cualquier otro criterio configurado, simplemente se detiene ahí. ¡Listo!


Pruebas

Ahora que entendemos cómo funciona internamente el sistema de supresión, es hora de ponerlo a prueba con algunas pruebas prácticas. La idea es validar, en la práctica, si cada uno de los modos de supresión se comporta como se espera, es decir, si suprime los registros duplicados o no deseados según la configuración elegida. Vamos paso a paso.

Prueba 1. Supresión mediante mensajes consecutivos (LOG_SUPRESSION_MODE_CONSECUTIVE)

Este es el modo de supresión más básico. La lógica es sencilla: si el mismo mensaje se registra más de una vez seguidas, solo se mostrará el primero. Esto resulta útil para evitar saturar la consola cuando, por ejemplo, se repite el mismo registro dentro de un bucle. Probemos este comportamiento con un código que envíe exactamente 11 registros idénticos, todos con el mismo contenido y el mismo origen.

//+------------------------------------------------------------------+
//| Import                                                           |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify Logify;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   MqlLogifySuppressionConfig config;
   config.mode = LOG_SUPRESSION_MODE_CONSECUTIVE;
   Logify.Suppression().SetConfig(config);
   
   for(int i=0;i<11;i++)
     {
      Logify.Info("Check signal buy", "Signal");
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Al ejecutar el código anterior, el resultado que aparecerá en la consola será:

2025.07.31 04:34:26 [INFO]: Check signal buy

Y ya está. Sin repeticiones. Aunque el mensaje se registrara 11 veces, como todos eran idénticos y consecutivos, el sistema entendió que bastaba con mostrarlo una sola vez. Esto demuestra que el modo funciona correctamente.

Prueba 2. Supresión por número de repeticiones (LOG_SUPPRESSION_MODE_BY_REPEAT_COUNT)

Este modo ofrece un poco más de flexibilidad que el anterior. En lugar de ocultar todos los mensajes iguales seguidos, te permite establecer una tolerancia; es decir, cuántas veces puede aparecer el mismo mensaje antes de que empiece a ocultarse. Esta opción resulta útil cuando se desea ver un número limitado de repeticiones antes de que el sistema silencie el resto.

Vamos a configurar el sistema para que permita un máximo de 2 repeticiones del mismo mensaje.

//+------------------------------------------------------------------+
//| Import                                                           |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify Logify;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   MqlLogifySuppressionConfig config;
   config.mode = LOG_SUPRESSION_MODE_BY_REPEAT_COUNT;
   config.max_repeat_count = 2;
   Logify.Suppression().SetConfig(config);
   
   for(int i=0;i<11;i++)
     {
      Logify.Info("Check signal buy", "Signal");
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Resultado esperado en la consola:

2025.07.31 04:40:49 [INFO]: Check signal buy
2025.07.31 04:40:49 [INFO]: Check signal buy

Tal y como lo hemos configurado: solo aparecen los dos primeros mensajes. El resto se descartaron automáticamente porque superaban el límite de repeticiones. Este control resulta especialmente útil en entornos que generan registros muy detallados, pero donde aun así queremos capturar las primeras señales de advertencia.

Prueba 3. Supresión por intervalo de tiempo (LOG_SUPPRESSION_MODE_THROTTLE_TIME)

En este caso, la supresión se basa en el tiempo transcurrido entre los mensajes. Aunque los mensajes sean idénticos, solo se omitirán si se envían en un plazo inferior al intervalo de tiempo configurado.

Configuremos el sistema para que permita el mismo mensaje cada 1 segundo. Para simular esto, mostraremos el mismo mensaje 11 veces con un Sleep(200) entre cada uno de ellos (es decir, 200 milisegundos entre cada entrada del registro). Así pues, cada segundo recibiremos 5 mensajes, y el sistema solo debería mostrar uno por segundo, descartando el resto.

//+------------------------------------------------------------------+
//| Import                                                           |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify Logify;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   MqlLogifySuppressionConfig config;
   config.mode = LOG_SUPRESSION_MODE_THROTTLE_TIME;
   config.throttle_seconds = 1;
   Logify.Suppression().SetConfig(config);
   
   for(int i=0;i<11;i++)
     {
      Logify.Info("Check signal buy", "Signal");
      Sleep(200);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Al ejecutarlo, la consola debería mostrar algo parecido a esto:

2025.07.31 04:45:26 [INFO]: Check signal buy
2025.07.31 04:45:27 [INFO]: Check signal buy
2025.07.31 04:45:28 [INFO]: Check signal buy

Aparecen tres registros, uno por segundo, mientras que el resto se descartaron porque se emitieron con un intervalo inferior al permitido. Este modo resulta especialmente interesante para eventos cuya frecuencia puede fluctuar, como las actualizaciones de precios o las comprobaciones de las condiciones del mercado.

Prueba 4. Supresión por origen (LOG_SUPPRESSION_MODE_BY_ORIGIN)

En este modo, el sistema bloquea los mensajes en función del origen introducido en el registro. Si un origen concreto figura en la lista negra, se ignorará cualquier mensaje procedente de él, independientemente de su contenido o del tiempo transcurrido entre ellos. En el ejemplo siguiente, hemos bloqueado el origen «Signal» y solo hemos dejado pasar la fuente «Trade»:

//+------------------------------------------------------------------+
//| Import                                                           |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify Logify;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   MqlLogifySuppressionConfig config;
   config.mode = LOG_SUPRESSION_MODE_BY_ORIGIN;
   config.AddBlockedOrigin("signal");
   Logify.Suppression().SetConfig(config);
   
   for(int i=0;i<11;i++)
     {
      Logify.Info("Check signal buy", "Signal");
     }
   Logify.Info("Purchase order sent successfully", "Trade");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Resultado en la consola:

2025.07.31 04:48:36 [INFO]: Purchase order sent successfully

Solo apareció el mensaje con origen «Trade». El resto se han ocultado porque proceden de una fuente bloqueada explícitamente.

El modo de supresión basado en archivos funciona de forma casi idéntica, pero el criterio de bloqueo es el nombre del archivo desde el que se generó el registro, y no el origen lógico definido en el registro. Por este motivo, las pruebas de supresión por origen y por archivo comparten la misma estructura de código. La diferencia radicaría en la llamada a la función de verificación: una comprueba el origen y la otra el nombre del archivo. Por lo tanto, consideramos que esta prueba también valida el funcionamiento de la supresión por archivo.


Detección y ajuste automático del idioma

Hasta ahora, la clase CLogifyError siempre comenzaba con los mensajes de error en inglés. Al principio funcionó bien, pero tenía un problema importante: el idioma de los errores siempre era el mismo, incluso si el terminal del usuario estaba configurado en otro idioma, como español, francés o portugués. Con el crecimiento del soporte multilingüe en Logify, tenía sentido dar un paso más: adaptar automáticamente el idioma predeterminado de los mensajes de error en función del idioma configurado en el terminal MetaTrader. Para ello, realizamos un pequeño pero significativo cambio en el constructor de la clase. En lugar de iniciar directamente el conjunto de errores en inglés, ahora dejamos que la propia terminal nos diga qué idioma usar:

CLogifyError::CLogifyError()
  {
   SetLanguage(GetLanguageFromTerminal());
  }

El método GetLanguageFromTerminal() utiliza la función nativa TerminalInfoString(TERMINAL_LANGUAGE) para capturar el idioma actualmente configurado en MetaTrader. Este valor es una cadena que contiene el nombre del idioma, como «francés», «coreano» o «portugués (Brasil)». A continuación, lo asignamos a nuestro ENUM_LOG_LANGUAGE, que representa los idiomas compatibles con el sistema de errores de Logify:

ENUM_LOG_LANGUAGE CLogifyError::GetLanguageFromTerminal(void)
  {
   string lang = TerminalInfoString(TERMINAL_LANGUAGE);

   if(lang == "German")
      return LOG_LANGUAGE_DE;
   if(lang == "Spanish")
      return LOG_LANGUAGE_ES;
   if(lang == "French")
      return LOG_LANGUAGE_FR;
   if(lang == "Italian")
      return LOG_LANGUAGE_IT;
   if(lang == "Japanese")
      return LOG_LANGUAGE_JA;
   if(lang == "Korean")
      return LOG_LANGUAGE_KO;
   if(lang == "Portuguese (Brazil)" || lang == "Portuguese (Portugal)")
      return LOG_LANGUAGE_PT;
   if(lang == "Russian")
      return LOG_LANGUAGE_RU;
   if(lang == "Turkish")
      return LOG_LANGUAGE_TR;
   if(lang == "Chinese (Simplified)" || lang == "Chinese (Traditional)")
      return LOG_LANGUAGE_ZH;

   //--- Default language: English
   return LOG_LANGUAGE_EN;
  }

Esta adaptación automática resulta especialmente útil para distribuidores, comerciantes o empresas que trabajan con audiencias internacionales. Ahora la biblioteca "habla el lenguaje de la terminal", sin necesidad de configuración manual. Esto reduce la fricción y evita que los usuarios con menos conocimientos técnicos tengan que averiguar cómo configurar el idioma correcto para los errores. ¿Qué ocurre si, por algún motivo, el idioma de la terminal no se reconoce o no se asigna correctamente? No hay problema, el sistema vuelve automáticamente al inglés, lo que garantiza una experiencia funcional incluso en casos excepcionales.

Este cambio, aunque sencillo de implementar, mejora drásticamente la usabilidad de la biblioteca y alinea el comportamiento de Logify con el principio de conveniencia automática: el sistema se adapta al usuario, y no al revés.


Conclusión

Con todo lo que hemos visto hasta ahora, Logify se ha vuelto aún más inteligente. Ahora es capaz de comprender cuándo los registros son demasiado repetitivos y, además, se comunica automáticamente en el idioma de tu terminal, sin que tengas que configurar nada.

Se han creado varias formas de suprimir los mensajes repetidos, que puedes usar individualmente o en conjunto, según lo que tenga más sentido para tu proyecto:

  • Mensajes repetidos seguidos: reduce ese aluvión de registros idénticos que se suceden uno tras otro.
  • Tiempo mínimo entre registros: evita que el mismo mensaje aparezca varias veces en pocos segundos.
  • Repetición solo a partir de un número determinado: solo permite un número determinado de repeticiones antes de empezar a suprimirlas.
  • El mismo fragmento de código: impide que se registren entradas duplicadas procedentes del mismo lugar.
  • El mismo contenido procedente de distintos archivos: bloquea las repeticiones idénticas, aunque procedan de otro archivo.

Todo esto se puede activar fácilmente, directamente desde la configuración, sin complicar el código. Además, gracias a la nueva detección automática de idioma, la biblioteca elige automáticamente el idioma ideal según la configuración de tu terminal, lo que resulta de gran ayuda al trabajar en entornos internacionales o con otros equipos.

Si tienes alguna idea nueva, quieres sugerir un nuevo modo de supresión o has encontrado algo que se podría mejorar, déjalo en los comentarios. Logify siempre está abierto a cambios y mejoras. A medida que evolucione, les ofreceremos nuevos artículos para mantenerlos al día.

Nombre del archivo Descripción
Experts/Logify/LogiftTest.mq5
Archivo donde probamos las funcionalidades de la biblioteca, que contiene un ejemplo práctico.
Include/Logify/Error/Languages/ErrorMessages.XX.mqh Agrupa los mensajes de error de cada idioma, donde X representa el acrónimo del idioma.
Include/Logify/Error/Error.mqh
Estructura de datos para almacenar errores.
Include/Logify/Error/LogifyError.mqh
Clase para obtener información detallada sobre errores.
Include/Logify/Formatter/LogifyFormatter.mqh
Clase responsable de formatear los registros de log, reemplazando los marcadores de posición con valores específicos.
Include/Logify/Handlers/LogifyHandler.mqh
Clase base para gestionar los manejadores de registros, incluyendo la configuración de niveles y el envío de registros.
Include/Logify/Handlers/LogifyHandlerComment.mqh
Gestor de registros que envía registros formateados directamente al comentario en el gráfico de la terminal en MetaTrader.
Include/Logify/Handlers/LogifyHandlerConsole.mqh
Gestor de registros que envía registros formateados directamente a la consola del terminal en MetaTrader.
Include/Logify/Handlers/LogifyHandlerDatabase.mqh
Gestor de registros que envía registros formateados a una base de datos (actualmente solo contiene una impresión, pero pronto lo guardaremos en una base de datos SQLite real).
Include/Logify/Handlers/LogifyHandlerFile.mqh
Controlador de registros que envía registros formateados a un archivo.
Include/Logify/Suppression/LogifySuppression.mqh  Responsable de aplicar reglas inteligentes de supresión de mensajes de registro, filtrando repeticiones innecesarias.
Include/Logify/Utils/IntervalWatcher.mqh
Comprueba si ha transcurrido un intervalo de tiempo, lo que le permite crear rutinas dentro de la biblioteca.
Include/Logify/Logify.mqh Clase principal para la gestión de registros, integrando niveles, modelos y formato.
Include/Logify/LogifyBuilder.mqh Clase responsable de crear un objeto CLogify, simplificando la configuración.
Include/Logify/LogifyLevel.mqh Archivo que define los niveles de registro de la biblioteca Logify, lo que permite un control detallado.
Include/Logify/LogifyModel.mqh Estructura que modela los registros de log, incluyendo detalles como nivel, mensaje, marca de tiempo y contexto.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19014

Archivos adjuntos |
Logify.zip (157.85 KB)
hini
hini | 12 ago 2025 en 17:20
Un artículo magnífico, gracias. Ahora la biblioteca de registros ya es bastante completa.
joaopedrodev
joaopedrodev | 13 ago 2025 en 13:02
¡Gracias por tus sugerencias para mejorar!
hini
hini | 2 sept 2025 en 16:27
joaopedrodev # :
¡Gracias por tus sugerencias de mejora!

Hola, autor, tengo una sugerencia. Crea algunas macros. Solo tienes que incluir un archivo. No hace falta ninguna configuración. Puedes utilizar la biblioteca con la configuración predeterminada. Cuando el registro está desactivado, la macro no genera ningún código real en el archivo ex5 compilado final.

Red neuronal en la práctica: Nacimiento de C_Neuron Red neuronal en la práctica: Nacimiento de C_Neuron
El artículo muestra cómo encapsular una neurona en MQL5 mediante la clase C_Neuron, con pesos, sesgo y un número de entradas definido por un parámetro. Detallamos el cálculo del costo por mínimos cuadrados y la disposición de los datos de entrenamiento en arrays. Así resulta sencillo cambiar las entradas y repetir experimentos sin modificar la implementación.
Del básico al intermedio: Subventanas (II) Del básico al intermedio: Subventanas (II)
El artículo profundiza en el uso de las subventanas en MetaTrader 5 y muestra cómo la dirección del cálculo en OnCalculate afecta a los búferes y al trazado de las medias. Explica, en la práctica, cómo se encadenan indicadores mediante FIRST INDICATOR’S DATA y PREVIOUS INDICATOR’S DATA, el efecto de la eliminación en cadena de los indicadores dependientes y cómo se comporta indicatorseparatewindow. El lector aprenderá a diagnosticar problemas de visualización y a estructurar indicadores que funcionen correctamente en subventanas.
Del básico al intermedio: Subventanas (III) Del básico al intermedio: Subventanas (III)
Este texto detalla el uso de subventanas en indicadores MQL5: creación básica, detección de instancias y prevención de duplicados. Aborda INDICATOR_SHORTNAME, la consulta de ventanas e indicadores del gráfico y la diferencia entre la visualización en el gráfico principal y en una ventana separada. También muestra cómo definir la altura y los límites de la subventana para estandarizar la interfaz y facilitar la disposición de objetos.
Probador de estrategias para Python-MetaTrader 5 (Parte 01): Simulador de operaciones Probador de estrategias para Python-MetaTrader 5 (Parte 01): Simulador de operaciones
El módulo de MetaTrader 5 disponible en Python ofrece una forma cómoda de abrir operaciones en la aplicación de MetaTrader 5 utilizando Python, pero presenta un gran problema: carece de la función de Probador de estrategias que sí incluye la aplicación de MetaTrader 5. En esta serie de artículos, crearemos un marco de trabajo para realizar pruebas retrospectivas de tus estrategias de trading en entornos de Python.