English Русский 中文 Deutsch 日本語
preview
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 37): Sentiment Tilt Meter

Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 37): Sentiment Tilt Meter

MetaTrader 5 — Ejemplos |
28 1
Christian Benjamin
Christian Benjamin

Introducción

Ya estamos en la parte 37 de la serie Desarrollo de un kit de herramientas para el análisis de la acción del precio. Mi objetivo principal siempre ha sido desarrollar herramientas prácticas que ayuden a los operadores a interpretar el comportamiento del mercado y, cuando sea apropiado, automatizar aspectos de ese análisis. Hasta la fecha, hemos introducido utilidades MQL5 independientes, integraciones externas y soluciones híbridas. Hoy ampliamos aún más nuestro conjunto de herramientas con una valiosa y práctica incorporación.

Las visualizaciones de datos a menudo se vuelven confusas debido a la superposición de etiquetas, el exceso de indicadores y las anotaciones improvisadas, que ocultan los cambios de señal subyacentes y su justificación. Esta ambigüedad dificulta la toma de decisiones, socava la confianza en los sistemas basados en reglas y complica la creación de registros de auditoría fiables, esenciales para una automatización rigurosa. 

El Sentiment Tilt Meter (STM) aborda este problema agregando métricas direccionales a nivel de vela en distintos intervalos de tiempo configurables, para obtener una puntuación escalar unificada y suavizada que oscila entre -100 y +100. Esta puntuación se muestra en un panel de control compacto diseñado para evitar solapamientos. Las anotaciones persistentes, como las flechas y las etiquetas de texto, se vinculan con precisión a las marcas de tiempo de cierre de las barras, lo que garantiza que cada alerta pueda verificarse de forma independiente mediante la marca de tiempo y el nivel de precio. 

STM proporciona reglas explícitas y parametrizables, como umbrales de inversión, magnitud y duración del cambio de signo, criterios de momento y ponderación por intervalo de tiempo, lo que permite a los usuarios ajustar la sensibilidad, equilibrando la relación entre la supresión de ruido y la capacidad de respuesta.

Comenzaremos con una descripción general de la estrategia, seguida de la implementación de MQL5, la presentación de las pruebas y los resultados, y concluiremos con una conclusión concisa. Consulte el siguiente índice:


Resumen de la estrategia

Los mercados son ruidosos, y el ruido es el mayor enemigo de la toma de decisiones coherente. Los indicadores se acumulan, las etiquetas se superponen y rápidamente se vuelve imposible determinar qué movió el precio y por qué. El Sentiment Tilt Meter (STM) se creó para despejar esa confusión: ofrecer una lectura única y verificable de la tendencia del mercado a corto plazo, mostrar la evidencia en el gráfico y permitirte tomar decisiones disciplinadas sin tener que recurrir a conjeturas.

El sentimiento, en este contexto, es la tendencia a corto plazo del mercado: el impulso neto hacia la compra o la venta en las últimas velas. El STM mide la tendencia directamente a partir de la evolución del precio, convirtiendo cada vela cerrada en una puntuación pequeña y fácil de interpretar. Las microcaracterísticas son:

  • Candle Body Ratio (CBR)

  • Close Position Percent (CPP)


  • Volatility-Adjusted Distance (VAD)

donde ATR es la línea base del rango verdadero reciente y 

Clamp ⁡ ( x , a , b ) =max ⁡ ( a , min ⁡ ( b , x ) ) \operatorname{clamp}(x,a,b)=\max(a,\min(b,x)) clamp(x,a,b)=max(a,min(b,x)).

La minipuntuación de cada vela es una suma ponderada (por ejemplo)

limitado a [ − 1 , + 1 ] [-1, +1] [−1,+1] y, a continuación, multiplicado por los factores de confianza y de baja volatilidad. Las puntuaciones por intervalo de tiempo se promedian en toda la muestra y se combinan entre los distintos intervalos de tiempo utilizando las ponderaciones de los usuarios,

y, por último, suavizada exponencialmente:

A continuación se muestran los gráficos de precios y puntuaciones:

El diagrama de puntuación muestra el valor suavizado del sentimiento a lo largo del tiempo, señalando cada cruce del signo sin filtrar con pequeños marcadores en forma de «×» y resaltando las señales de cambio aceptadas —tras aplicar filtros de magnitud, persistencia e impulso— con marcadores de puntos más grandes. Esta visualización permite identificar fácilmente las pequeñas fluctuaciones debidas al ruido que podrían provocar señales falsas, así como los cambios de polaridad auténticos que permiten los filtros.

El gráfico de precios muestra la ubicación de estas señales aceptadas dentro de la serie de precios. Las flechas y las etiquetas «COMPRAR» o «VENDER» se sitúan exactamente en el precio de la señal, lo que permite comprobar de inmediato si una señal se ajusta a estructuras clave de precios, como los niveles de soporte y resistencia, las direcciones de las tendencias o las oscilaciones cercanas.

Para que sea aceptada, la EA debe generar señales que sean inmediatamente rastreables (hora y precio), que sean robustas en los backtests y pruebas forward a corto plazo, y que reduzcan los falsos positivos evidentes a un nivel que consideres apto para operar. Si se cumplen esos requisitos, el STM ha cumplido su función: ofrece una lectura clara, ajustable y verificable de la tendencia del mercado a corto plazo en la que puedes confiar como parte de un proceso disciplinado.


Implementación de MQL5

Antes de empezar a programar, dedica dos minutos a configurar correctamente tu entorno; esto te ahorrará mucho tiempo más adelante. Asegúrate de que MetaTrader 5 (MetaTrader 5) y MetaEditor estén instalados. MetaEditor se puede iniciar como una aplicación independiente o se puede acceder a él a través de MetaTrader 5; ambas opciones son válidas.

Para configurar tu Asesor Experto (EA):

  1. Abre MetaEditor.
  2. Ve a Archivo → Nuevo → Asesor experto (plantilla).
  3. Pega el código fuente de tu EA en el archivo que acabas de crear.
  4. Guarda el archivo con un nombre descriptivo y significativo.
  5. Compila el código pulsando F7.

Comprueba que la compilación se complete sin errores ni advertencias. Si se detectan errores, revisa y corrige el código y, a continuación, vuelve a compilar hasta que el compilador indique que la compilación se ha realizado correctamente.

De vuelta a MetaTrader 5:

  • Carga el EA en el Probador de estrategias o adjúntalo directamente a un gráfico.
  • Supervisa el panel de control, los registros y las señales.
  • Consulta las pestañas «Expertos» y «Diario» para ver los mensajes de ejecución y la información de depuración.

Esta configuración garantiza que tu entorno esté correctamente configurado para un desarrollo y unas pruebas eficaces. A partir de aquí, avanzaremos paso a paso para crear el asesor experto Sentiment Tilt Meter (STM), guiándote a lo largo de cada etapa del proceso de desarrollo.

El bloque encabezado y metadatos establece la identidad de EA, los derechos de autor, el enlace al perfil del autor y el modo de compilación (#property strict). Esas líneas son meramente declarativas, pero importantes: incorporan la información de versión y la atribución en el programa compilado y permiten realizar comprobaciones más rigurosas en tiempo de compilación. Inmediatamente después, el EA incluye el ayudante de negociación estándar Trade.mqh, de modo que el código tiene acceso a la clase CTrade y a otros ayudantes relacionados con la negociación; aunque este archivo en concreto utiliza CTrade g_trade de forma limitada (o no lo utiliza en absoluto en el código mostrado), al incluir la biblioteca queda preparado para realizar órdenes más adelante sin necesidad de una refactorización.

//+------------------------------------------------------------------+
//|                                                            STM EA|
//|                                   Copyright 2025, MetaQuotes Ltd.|
//|                           https://www.mql5.com/en/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

#include <Trade\Trade.mqh>

El bloque de entrada define todos los parámetros configurables por el usuario. Los parámetros se agrupan por finalidad: selección del intervalo de tiempo, ajuste del algoritmo de señales, diseño visual y estilo del panel, alertas sonoras o por notificaciones, y comportamiento de las flechas y los marcadores. El marco temporal principal y los marcos temporales adicionales opcionales permiten al EA fusionar señales de varios marcos temporales; los pesos (InpWeightPrimary, InpWeightExtra1, InpWeightExtra2) controlan la proporción de fusión. El suavizado (InpSmoothAlpha) controla el suavizado exponencial de las puntuaciones fusionadas sin procesar; las variables de ajuste de umbrales (por ejemplo, InpFlipThreshold, InpPositiveZone, InpNegativeZone) definen cuándo el EA considera que se ha producido un cambio de tendencia importante o cuándo ha entrado en una zona positiva o negativa.

Los parámetros visuales controlan el panel de control del gráfico: tamaño, esquinas, fuentes, colores, número de barras del histograma y margen interno del panel. Las opciones de flecha/marcador te permiten activar o ajustar las señales colocadas en el gráfico, y varias comprobaciones de seguridad (barras mínimas, magnitud del cambio de signo, requisitos de impulso) reducen el ruido y los cambios de signo erróneos. Todos los parámetros están tipificados explícitamente, por lo que aparecen en el cuadro de diálogo de EA y se pueden ajustar sin necesidad de modificar el código.

// --- User inputs (layout / visuals tuned) ----------------------------
input ENUM_TIMEFRAMES InpTFPrimary = PERIOD_M5;
input bool InpUseMultiTF = true;
input ENUM_TIMEFRAMES InpExtraTF1 = PERIOD_M15;
input ENUM_TIMEFRAMES InpExtraTF2 = PERIOD_H1;
input int InpLookback = 20;
input int InpSSWindow = 5;
input int InpATRPeriod = 14;
input double InpATRMultiplier = 1.0;
input double InpVolQuietThreshold = 0.4;
input int InpHistogramMaxBars = 24;
input int InpPanelPadding = 10;
input int InpPanelCorner = CORNER_LEFT_UPPER;
input int InpPanelX = 5;
input int InpPanelY = 25;
input string InpFont = "Arial";
input int InpTitleFontSize = 12;
input int InpScoreFontSize = 18;
input int InpSmallFontSize = 9;
input color InpPanelColor = clrBlack;
input int InpPanelAlpha = 200;
input bool InpEnableSound = true;
input string InpSoundFile = "alert.wav";
input bool InpEnablePush = false;
input double InpWeightPrimary = 0.6;
input double InpWeightExtra1 = 0.2;
input double InpWeightExtra2 = 0.2;
input double InpSmoothAlpha = 0.28;
input double InpFlipThreshold = 60.0;
input double InpPositiveZone = 30.0;
input double InpNegativeZone = -30.0;
input int InpMinBarsForSignal = 3;

// arrows / marker options
input bool InpShowArrows = true;
input int  InpArrowFontSize = 16;
input int  InpArrowOffsetPoints = 8;
input int  InpMaxSignalsToKeep = 50;
input bool InpSignalOnSignFlip = true;
input double InpMinSignFlipAbs = 6.0;
input int    InpSignFlipHoldBars = 1;
input bool   InpRequireMomentum = true;
input double InpMinMomentum = 0.5;
//+------------------------------------------------------------------+
A continuación, las constantes y las variables globales. El EA crea un g_prefix único por gráfico (símbolo + ID del gráfico) para asignar un espacio de nombres a los objetos del gráfico y evitar conflictos con otros EA u objetos creados manualmente. Hay identificadores para los indicadores ATR de cada marco temporal, búferes para el histograma (g_hist_buffer) e índices para gestionar el almacenamiento circular. Las variables globales también almacenan el estado del valor suavizado (g_smoothed_score, g_prev_smoothed), la última puntuación de alerta utilizada para detectar cambios bruscos y el estado actual de g_zone_state (1, -1 o 0). CTradeg_trade se declara por si se amplía el EA para realizar órdenes. Al almacenar g_last_signal_text y g_last_signal_time, la interfaz de usuario puede mostrar la acción más reciente en el panel.
// globals
long g_chart_id = 0;
string g_prefix = "";
string ui_bg_name = "";
string ui_shadow_name = "";
string ui_title_name = "";
string ui_score_name = "";
string ui_zone_name = "";
string ui_hist_base = "";
string ui_recent_name = "";
string ui_advice_name = "";
string ui_signal_name = "";
int g_atr_handle_primary = INVALID_HANDLE;
int g_atr_handle_extra1 = INVALID_HANDLE;
int g_atr_handle_extra2 = INVALID_HANDLE;
double g_hist_buffer[];
int g_hist_idx = 0;
int g_hist_count = 0;
double g_smoothed_score = 0.0;
double g_prev_smoothed = 0.0;
double g_last_alert_score = 0.0;
int g_zone_state = 0;
CTrade g_trade;
string g_last_signal_text = "None";
datetime g_last_signal_time = 0;
const string BASE_HIST = "STM_HBAR_";
//+------------------------------------------------------------------+
Hay algunas pequeñas rutinas de utilidad que hacen que el código de la interfaz de usuario y de visualización sea más robusto. ARGB_uint compone un entero sin signo ARGB a partir de un valor alfa y un color MQL, lo que permite especificar fondos rectangulares semitransparentes de forma clara. EstimateTextWidth es un estimador sencillo del ancho en píxeles de las cadenas de texto, basado en el tamaño de la fuente y un factor de ancho de carácter ajustado; se utiliza para evitar que las etiquetas se superpongan al colocar la etiqueta de zona y la cadena de texto reciente. Esas aproximaciones son razonables para un panel integrado en el gráfico, en el que la disposición exacta no es fundamental, pero las colisiones resultan molestas.
// ARGB helper - returns a color as unsigned int
uint ARGB_uint(int a, color c)
  {
   uint u = ((uint)c) & 0x00FFFFFF;
   return ((((uint)a) & 0xFF) << 24) | u;
  }

// approximate text width estimator (pixels)
int EstimateTextWidth(string txt,int fontSize)
  {
   if(StringLen(txt) <= 0)
      return 6;
   double factor = 0.58;
   int w = (int)MathRound(StringLen(txt) * fontSize * factor);
   return MathMax(8, w);
  }
//+------------------------------------------------------------------+
DrawText y DrawCell son envoltorios seguros para crear y configurar etiquetas en gráficos y etiquetas rectangulares. Centralizan el patrón de creación de objetos y configuración de propiedades (esquina, distancia x/y, fuente, propiedad de selección, orden z) para que todas las etiquetas de la interfaz de usuario sigan las mismas convenciones. El uso de estas funciones auxiliares evita la repetición de código repetitivo y garantiza un comportamiento coherente cuando se reconstruye o actualiza el panel.
// safe DrawText - sets font explicitly and uses exact x/y
void DrawText(string id,string txt,int x,int y,color clr,int sz)
  {
   if(ObjectFind(g_chart_id,id) < 0)
      ObjectCreate(g_chart_id,id,OBJ_LABEL,0,0,0);
   ObjectSetInteger(g_chart_id,id,OBJPROP_CORNER,InpPanelCorner);
   ObjectSetInteger(g_chart_id,id,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(g_chart_id,id,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(g_chart_id,id,OBJPROP_COLOR,(int)clr);
   ObjectSetInteger(g_chart_id,id,OBJPROP_FONTSIZE,sz);
   ObjectSetString(g_chart_id,id,OBJPROP_FONT,InpFont);
   ObjectSetString(g_chart_id,id,OBJPROP_TEXT,txt);
   ObjectSetInteger(g_chart_id,id,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(g_chart_id,id,OBJPROP_ZORDER,0);
  }

// small helper to draw rectangle label cell
void DrawCell(string id,int x,int y,int w,int h,color bg,color br)
  {
   if(ObjectFind(g_chart_id,id) < 0)
      ObjectCreate(g_chart_id,id,OBJ_RECTANGLE_LABEL,0,0,0);
   ObjectSetInteger(g_chart_id,id,OBJPROP_CORNER,InpPanelCorner);
   ObjectSetInteger(g_chart_id,id,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(g_chart_id,id,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(g_chart_id,id,OBJPROP_XSIZE,w);
   ObjectSetInteger(g_chart_id,id,OBJPROP_YSIZE,h);
   ObjectSetInteger(g_chart_id,id,OBJPROP_BGCOLOR,(int)bg);
   ObjectSetInteger(g_chart_id,id,OBJPROP_BORDER_COLOR,(int)br);
   ObjectSetInteger(g_chart_id,id,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(g_chart_id,id,OBJPROP_ZORDER,1);
  }
CreateUIObjects y DeleteUIObjects gestionan el ciclo de vida de todos los elementos de la interfaz de usuario. CreateUIObjectsprecrea los espacios reservados para el fondo, la sombra, el título, la puntuación, la zona, los elementos recientes y la etiqueta del histograma, y establece las fuentes predeterminadas y el orden Z. DeleteUIObjects los elimina en deinit — una práctica útil que mantiene el gráfico ordenado cuando se elimina el EA. La precreación de un número fijo de objetos de etiquetas de histograma (basado en InpHistogramMaxBars) evita la creación frecuente de objetos en tiempo de ejecución, lo que mejora el rendimiento y la estabilidad.
void CreateUIObjects()
  {
   int default_w = 200;
   int default_h = 80;
   int scol = (int)ARGB_uint(InpShadowAlpha, InpShadowColor);
   DrawCell(ui_shadow_name, InpPanelX + 3, InpPanelY + 3, default_w, default_h, (color)scol, InpGridClr);
   DrawCell(ui_bg_name, InpPanelX, InpPanelY, default_w, default_h, InpPanelBG, InpGridClr);

   DrawText(ui_title_name, "", InpPanelX + InpPanelPadding, InpPanelY + InpPanelPadding, InpTxtClr, InpTitleFontSize);
   DrawText(ui_score_name, "", InpPanelX + InpPanelPadding, InpPanelY + InpPanelPadding + InpTitleFontSize + 4, InpTxtClr, InpScoreFontSize);
   DrawText(ui_zone_name, "", InpPanelX + default_w - InpPanelPadding - 80, InpPanelY + InpPanelPadding + 4, InpTxtClr, InpSmallFontSize);
   DrawText(ui_advice_name, "", InpPanelX + InpPanelPadding, InpPanelY + InpPanelPadding + InpTitleFontSize + InpScoreFontSize + 8, InpTxtClr, InpSmallFontSize);
   DrawText(ui_recent_name, "", InpPanelX + InpPanelPadding, InpPanelY + default_h - InpPanelPadding - 18, InpTxtClr, InpSmallFontSize);
   DrawText(ui_signal_name, "", InpPanelX + InpPanelPadding + 120, InpPanelY + InpPanelPadding + InpTitleFontSize + InpScoreFontSize + 8, InpTxtClr, InpSmallFontSize);

   for(int i = 0; i < InpHistogramMaxBars; i++)
     {
      string name = ui_hist_base + IntegerToString(i);
      if(ObjectFind(g_chart_id, name) < 0)
        {
         ObjectCreate(g_chart_id, name, OBJ_LABEL, 0, 0, 0);
         ObjectSetInteger(g_chart_id, name, OBJPROP_CORNER, InpPanelCorner);
         ObjectSetString(g_chart_id, name, OBJPROP_FONT, InpFont);
         ObjectSetInteger(g_chart_id, name, OBJPROP_FONTSIZE, InpSmallFontSize);
         ObjectSetInteger(g_chart_id, name, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(g_chart_id, name, OBJPROP_ZORDER,0);
        }
     }
  }

void DeleteUIObjects()
  {
   if(g_chart_id == 0) return;
   if(ObjectFind(g_chart_id, ui_shadow_name) >= 0) ObjectDelete(g_chart_id, ui_shadow_name);
   if(ObjectFind(g_chart_id, ui_bg_name) >= 0) ObjectDelete(g_chart_id, ui_bg_name);
   if(ObjectFind(g_chart_id, ui_title_name) >= 0) ObjectDelete(g_chart_id, ui_title_name);
   if(ObjectFind(g_chart_id, ui_score_name) >= 0) ObjectDelete(g_chart_id, ui_score_name);
   if(ObjectFind(g_chart_id, ui_zone_name) >= 0) ObjectDelete(g_chart_id, ui_zone_name);
   if(ObjectFind(g_chart_id, ui_recent_name) >= 0) ObjectDelete(g_chart_id, ui_recent_name);
   if(ObjectFind(g_chart_id, ui_advice_name) >= 0) ObjectDelete(g_chart_id, ui_advice_name);
   if(ObjectFind(g_chart_id, ui_signal_name) >= 0) ObjectDelete(g_chart_id, ui_signal_name);

   for(int i = 0; i < InpHistogramMaxBars; i++)
     {
      string name = ui_hist_base + IntegerToString(i);
      if(ObjectFind(g_chart_id, name) >= 0) ObjectDelete(g_chart_id, name);
     }
  }

RefreshUI es donde se calculan y se dibujan realmente la disposición del panel y el histograma. Mide la altura de las fuentes y reserva espacio para el título, la puntuación, el texto pequeño y una fila del histograma; calcula el ancho del panel de forma dinámica para que el histograma quepa, pero impone un ancho mínimo razonable. Dibuja los rectángulos de la sombra y del fondo, calcula la ubicación de la insignia de la zona evitando que se superponga al título (utilizando EstimateTextWidth) y elige los colores para la puntuación suavizada (scoreCol) en función de unos umbrales. La línea «Reciente:» se genera a partir de las barras más recientes del marco temporal principal (cierre frente a apertura y sus rangos de puntos), y se trunca con puntos suspensivos si se sale del espacio disponible.

Para el histograma, calcula el número de glifos del bloque de texto con el fin de representar visualmente la magnitud, vincula cada glifo a un objeto de etiqueta precreado y elige los colores en función del signo. Esta rutina es defensiva (comprueba el espacio disponible y reduce el espaciado cuando es necesario) y está diseñada para evitar solapamientos entre etiquetas, al tiempo que mantiene el panel compacto y legible.

void RefreshUI(double rawScore, double smoothScore)
  {
   int title_h = InpTitleFontSize + 6;
   int score_h = InpScoreFontSize + 8;
   int small_h = InpSmallFontSize + 4;
   int hist_area_h = 22;
   int gap_between_sections = 6;

   int hist_px_step = 7;
   int hist_px_width = InpHistogramMaxBars * hist_px_step;
   int panel_width = MathMax(340, hist_px_width + InpPanelPadding*2 + 30);
   int extra_recent_space = small_h + 12;
   int panel_height = InpPanelPadding*2 + title_h + score_h + small_h + hist_area_h + (gap_between_sections * 4) + extra_recent_space;

   int scol = (int)ARGB_uint(InpShadowAlpha, InpShadowColor);
   DrawCell(ui_shadow_name, InpPanelX + 3, InpPanelY + 3, panel_width, panel_height, (color)scol, InpGridClr);
   DrawCell(ui_bg_name, InpPanelX, InpPanelY, panel_width, panel_height, InpPanelBG, InpGridClr);

   int x_start = InpPanelX + InpPanelPadding;
   int y_title = InpPanelY + InpPanelPadding;
   string titleText = StringFormat("Sentiment Tilt Meter — %s  [%s]", Symbol(), TFToText(InpTFPrimary));
   DrawText(ui_title_name, titleText, x_start, y_title, InpTxtClr, InpTitleFontSize);

   // ... zone, score, advice, recent and histogram painting (omitted here for brevity)
   // The full EA code contains the rest and iterates label objects to draw histogram blocks.
  }

TFToText es una sencilla herramienta auxiliar que asigna a ENUM_TIMEFRAMES etiquetas de texto breves y comprensibles (M1, M5, M15, H1, etc.).. Se utiliza en el título para que el usuario sepa en todo momento a qué marco temporal principal hace referencia el EA.
string TFToText(ENUM_TIMEFRAMES tf)
  {
   switch(tf)
     {
      case PERIOD_M1:  return "M1";
      case PERIOD_M5:  return "M5";
      case PERIOD_M15: return "M15";
      case PERIOD_M30: return "M30";
      case PERIOD_H1:  return "H1";
      case PERIOD_H4:  return "H4";
      case PERIOD_D1:  return "D1";
      case PERIOD_W1:  return "W1";
      case PERIOD_MN1: return "MN";
      default:         return IntegerToString((int)tf);
     }
  }

OnInit lleva a cabo la inicialización: captura el ID del gráfico y crea el prefijo del objeto, redimensiona e inicializa el búfer del histograma, crea identificadores del indicador ATR (iATR) para los intervalos de tiempo solicitados (solo si el modo multintervalo está activado), calcula un sentimiento fusionado bruto inicial (ComputeFusedSentiment), establece el valor suavizado y el estado de la zona, crea objetos de la interfaz de usuario y dibuja el panel inicial con RefreshUI, e instala un temporizador de 1 segundo (EventSetTimer(1)) para que OnTimer pueda detectar nuevas barras primarias. El diseño inicia el proceso de suavizado con el primer valor sin procesar, de modo que el primer valor suavizado que se muestra sea estable. Fíjate en que el código llama a UpdateZoneState al principio, para que la zona inicial sea correcta. 

OnDeinit realiza la limpieza cuando se elimina el EA: detiene el temporizador, borra los objetos de la interfaz de usuario y elimina cualquier marcador de señal creado por este EA (siguiendo la convención de nomenclatura g_prefix + «SIG_»). Además, libera los identificadores del indicador ATR mediante IndicatorRelease. De este modo, se mantienen limpios los recursos y se evita que queden etiquetas sueltas en el gráfico o que se pierdan los identificadores de los indicadores.

int OnInit()
  {
   g_chart_id = ChartID();
   g_prefix = Symbol() + "_" + IntegerToString((int)g_chart_id) + "_";
   ui_bg_name = g_prefix + "BG";
   ui_shadow_name = g_prefix + "SHDW";
   ui_title_name = g_prefix + "TITLE";
   ui_score_name = g_prefix + "SCORE";
   ui_zone_name = g_prefix + "ZONE";
   ui_hist_base = g_prefix + BASE_HIST;
   ui_recent_name = g_prefix + "RECENT";
   ui_advice_name = g_prefix + "ADVICE";
   ui_signal_name = g_prefix + "LASTSIG";

   ArrayResize(g_hist_buffer, InpHistogramMaxBars);
   ArrayInitialize(g_hist_buffer, 0.0);
   g_hist_idx = 0;
   g_hist_count = 0;

   g_atr_handle_primary = iATR(Symbol(), InpTFPrimary, InpATRPeriod);
   if(InpUseMultiTF)
     {
      g_atr_handle_extra1 = iATR(Symbol(), InpExtraTF1, InpATRPeriod);
      g_atr_handle_extra2 = iATR(Symbol(), InpExtraTF2, InpATRPeriod);
     }

   double raw_init = ComputeFusedSentiment();
   g_smoothed_score = raw_init;
   g_prev_smoothed = g_smoothed_score;
   g_last_alert_score = raw_init;
   UpdateZoneState(raw_init);

   CreateUIObjects();
   RefreshUI(raw_init, g_smoothed_score);

   EventSetTimer(1);
   return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {
   EventKillTimer();
   DeleteUIObjects();

   // delete signal markers created by this EA (prefix-based)
   string name;
   int total = ObjectsTotal(g_chart_id);
   for(int i = total - 1; i >= 0; --i)
     {
      name = ObjectName(g_chart_id, i);
      if(StringFind(name, g_prefix + "SIG_") == 0)
         ObjectDelete(g_chart_id, name);
     }

   if(g_atr_handle_primary != INVALID_HANDLE)
      IndicatorRelease(g_atr_handle_primary);
   if(g_atr_handle_extra1 != INVALID_HANDLE)
      IndicatorRelease(g_atr_handle_extra1);
   if(g_atr_handle_extra2 != INVALID_HANDLE)
      IndicatorRelease(g_atr_handle_extra2);
  }
El EA utiliza OnTimer como su principal mecanismo de sondeo. Comprueba la marca de tiempo de la barra TF primaria actual (iTime(Symbol(), InpTFPrimary, 0)) y, si dicha marca de tiempo difiere del último valor procesado, llama a HandleNewPrimaryBar. Este enfoque evita el procesamiento en cada tick del temporizador y solo se ejecuta una vez por cada nueva barra completada: un buen patrón cuando se desean señales basadas en barras utilizando un temporizador en lugar de OnTick.
datetime g_last_primary_time = 0;
void OnTimer()
  {
   datetime t = iTime(Symbol(), InpTFPrimary, 0);
   if(t == g_last_primary_time) return;
   g_last_primary_time = t;
   HandleNewPrimaryBar();
  }

void HandleNewPrimaryBar()
  {
   double raw = ComputeFusedSentiment();
   double alpha = MathMax(0.0, MathMin(1.0, InpSmoothAlpha));
   g_prev_smoothed = g_smoothed_score;
   g_smoothed_score = alpha * raw + (1.0 - alpha) * g_smoothed_score;
   g_hist_buffer[g_hist_idx] = g_smoothed_score;
   g_hist_idx = (g_hist_idx + 1) % InpHistogramMaxBars;
   if(g_hist_count < InpHistogramMaxBars) g_hist_count++;
   RefreshUI(raw, g_smoothed_score);
   ProcessAlerts(raw, g_smoothed_score);
  }

HandleNewPrimaryBar calcula un nuevo valor bruto de sentimiento fusionado mediante ComputeFusedSentiment, aplica un suavizado exponencial con el valor alfa configurado (g_smoothed_score = alfa * valor bruto + (1-alfa) * prev), almacena el valor suavizado en el búfer del histograma circular, actualiza los índices y el recuento, actualiza la interfaz de usuario y llama a ProcessAlerts para comprobar si este nuevo valor suavizado debe activar mensajes o señales. El búfer circular se implementa con g_hist_idx módulo InpHistogramMaxBars, y g_hist_count lleva la cuenta del número de entradas válidas. De este modo se conserva un historial acumulativo tanto para el histograma visual como para las comprobaciones de retención de signo.

ComputeFusedSentiment es el agregador de puntuaciones de nivel superior. Normaliza los valores absolutos de las tres entradas de ponderación para que su suma sea 1 (con un mecanismo de seguridad que recurre a la ponderación principal únicamente si la suma es cero). A continuación, llama a ComputeTFScore para el intervalo de tiempo principal y, si está habilitado, para los dos intervalos de tiempo adicionales. Por último, calcula una media ponderada y limita el resultado fusionado al intervalo [-100, 100]. De este modo, se mantiene la coherencia de la interfaz de usuario y la lógica de umbrales, independientemente de la escala bruta por TF.

double ComputeFusedSentiment()
  {
   double w1 = MathAbs(InpWeightPrimary);
   double w2 = MathAbs(InpWeightExtra1);
   double w3 = MathAbs(InpWeightExtra2);
   double sum = w1 + w2 + w3;
   if(sum <= 0.0) { w1 = 1.0; w2 = 0.0; w3 = 0.0; sum = 1.0; }
   w1 /= sum; w2 /= sum; w3 /= sum;

   double s1 = ComputeTFScore(Symbol(), InpTFPrimary, InpLookback, InpSSWindow, g_atr_handle_primary);
   if(!InpUseMultiTF) return s1;
   double s2 = ComputeTFScore(Symbol(), InpExtraTF1, InpLookback, InpSSWindow, g_atr_handle_extra1);
   double s3 = ComputeTFScore(Symbol(), InpExtraTF2, InpLookback, InpSSWindow, g_atr_handle_extra2);
   double fused = s1 * w1 + s2 * w2 + s3 * w3;
   return MathMax(-100.0, MathMin(100.0, fused));
  }

double ComputeTFScore(string sym, ENUM_TIMEFRAMES tf, int lookback, int ssWindow, int atrHandle)
  {
   MqlRates rates[];
   int needed = MathMax(lookback, ssWindow) + 6;
   int copied = CopyRates(sym, tf, 0, needed, rates);
   if(copied <= 0) return 0.0;

   double atr = 0.0001;
   if(atrHandle != INVALID_HANDLE)
     {
      double abuf[];
      if(CopyBuffer(atrHandle, 0, 1, 1, abuf) > 0)
         atr = abuf[0] * InpATRMultiplier;
     }
   else
     {
      int rc = MathMin(10, copied - 1);
      double ar = 0.0;
      for(int i = 1; i <= rc; i++) ar += (rates[i].high - rates[i].low);
      if(rc > 0) ar /= rc;
      if(ar > 0) atr = ar * 0.6;
     }

   int limit = MathMin(ssWindow, copied - 1);
   double avgRange = 0.0;
   int vr = MathMin(10, copied - 1);
   for(int k = 1; k <= vr; k++) avgRange += (rates[k].high - rates[k].low);
   if(vr > 0) avgRange /= vr;

   double sumSS = 0.0;
   int count = 0;
   for(int i = 1; i <= limit; i++)
     {
      double open = rates[i].open;
      double close = rates[i].close;
      double high = rates[i].high;
      double low = rates[i].low;
      double range = high - low;
      if(range <= 0.0) continue;
      double dir = (close > open) ? 1.0 : -1.0;
      double cbr = (MathAbs(close - open) / range) * dir;
      double cpp = (((close - low) / range) - 0.5) * 2.0 * dir;
      double vad = 0.0;
      if(atr > 0.0) vad = (range / atr) - 1.0;
      vad = MathMax(-1.0, MathMin(3.0, vad));
      double ss = cbr * 0.4 + cpp * 0.3 + vad * 0.3;
      ss = MathMax(-1.0, MathMin(1.0, ss));

      bool conf = false;
      if((i - 1) >= 0 && (i - 1) <= copied - 1)
        {
         double nOpen = rates[i - 1].open;
         double nClose = rates[i - 1].close;
         if(dir > 0.0 && nClose > nOpen) conf = true;
         if(dir < 0.0 && nClose < nOpen) conf = true;
        }

      double confFactor = conf ? 1.0 : 0.6;
      double volFactor = 1.0;
      if(atr > 0.0 && avgRange / atr < InpVolQuietThreshold) volFactor = 0.6;
      double finalSS = ss * confFactor * volFactor;
      sumSS += finalSS;
      count++;
     }
   if(count == 0) return 0.0;
   double avgSS = sumSS / count;
   return avgSS * 100.0;
  }

ComputeTFScore es donde se calculan las características principales de la señal por marco temporal. Copia un bloque de barras recientes de MqlRates del intervalo de tiempo solicitado y calcula un indicador sustitutivo del ATR: si existe un identificador de ATR, copia el búfer del indicador; en caso contrario, calcula un rango verdadero medio simple como alternativa. El código calcula un avgRange y, a continuación, recorre hasta ssWindow barras anteriores para obtener tres características normalizadas por barra: cbr (cierre frente a apertura en relación con el rango, que refleja la dirección y la fuerza), cpp (posición del cierre dentro de la barra, normalizada y sesgada por la dirección) y vad (distancia ajustada por volumen/volatilidad: rango en relación con el ATR menos uno). Cada característica se pondera (0,4; 0,3 y 0,3, respectivamente), se limita al intervalo [-1, 1] y, a continuación, se multiplica por dos factores de confianza: confFactor, que comprueba si la barra anterior confirmó la dirección, y volFactor, que reduce la contribución si el mercado se muestra inusualmente tranquilo en relación con el ATR.

La muestra final de sentimiento por barra finalSS se acumula y se promedia a lo largo de la ventana y, a continuación, se escala por 100 para obtener un rango similar al de los porcentajes. En resumen: el algoritmo codifica el sesgo direccional, la proximidad dentro de la barra, la magnitud del rango relativo y la confirmación simple entre barras en una única puntuación por TF.

RecentSmoothedSignHold es una función auxiliar que utiliza la lógica de cambio de signo para garantizar que todos los valores suavizados de las barras más recientes compartan el nuevo signo. Recorre hacia atrás el g_hist_buffer circular y devuelve «false» si alguno de los valores suavizados recientes requeridos no supera la prueba de signo. Esto evita que el parpadeo provocado por una sola barra espuria invierta la señal.

ProcessAlerts es el motor de decisión para las notificaciones y las señales gráficas. En primer lugar, comprueba que haya suficiente historial (InpMinBarsForSignal) y, a continuación, comprueba si se ha producido un cambio brusco de gran magnitud (diferencia absoluta respecto a g_last_alert_score que supere InpFlipThreshold); en tal caso, envía una alerta, actualiza el estado de la zona y, opcionalmente, dibuja un marcador de texto de COMPRA/VENTA. Si no se trata de un giro grande, calcula la nueva zona a partir de InpPositiveZone/InpNegativeZone. Si InpSignalOnSignFlip está habilitado, también comprueba los cambios de signo (positivo → negativo o negativo → positivo) y aplica tres pruebas de aceptación: magnitud absoluta mínima del nuevo valor suavizado (InpMinSignFlipAbs), mantenimiento reciente del signo (InpSignFlipHoldBars), y impulso opcional (InpRequireMomentum + InpMinMomentum). Solo cuando se superen todas las pruebas aceptará el cambio de signo como una señal válida, dibujará el marcador en el gráfico, actualizará g_last_alert_score y establecerá g_zone_state.

Si la zona cambia de forma normal (al entrar en zonas positivas o negativas), el código envía alertas de forma similar, registra la última señal y la hora, y traza un marcador. El resultado es una estrategia de señalización por capas y conservadora que da prioridad a los cambios estructurales más claros frente al ruido irregular.

bool RecentSmoothedSignHold(int bars, int desiredSign)
  {
   if(bars <= 1) return true;
   if(g_hist_count < bars) return false;
   int max = InpHistogramMaxBars;
   int idx = (g_hist_idx - 1 + max) % max;
   for(int i = 0; i < bars; i++)
     {
      double v = g_hist_buffer[(idx - i + max) % max];
      if(desiredSign == 1 && v <= 0.0) return false;
      if(desiredSign == -1 && v >= 0.0) return false;
     }
   return true;
  }

void ProcessAlerts(double rawScore, double smoothScore)
  {
   if(g_hist_count < InpMinBarsForSignal)
     {
      PrintFormat("%s: waiting hist_count=%d (need %d)", __FUNCTION__, g_hist_count, InpMinBarsForSignal);
      return;
     }

   // large flip detection
   if(MathAbs(smoothScore - g_last_alert_score) >= InpFlipThreshold)
     {
      string m = StringFormat("STM Flip: %.1f -> %.1f on %s", g_last_alert_score, smoothScore, Symbol());
      SendAlert(m);
      g_last_alert_score = smoothScore;
      UpdateZoneState(smoothScore);

      int newZoneFlip = 0;
      if(smoothScore >= InpPositiveZone) newZoneFlip = 1;
      else if(smoothScore <= InpNegativeZone) newZoneFlip = -1;
      if(newZoneFlip != 0)
        {
         g_last_signal_text = (newZoneFlip == 1) ? "BUY" : "SELL";
         g_last_signal_time = TimeCurrent();
         DrawSignalOnChart(newZoneFlip, smoothScore);
         PruneOldSignals(InpMaxSignalsToKeep);
        }
      RefreshUI(rawScore, g_smoothed_score);
      return;
     }

  }
UpdateZoneState es una función sencilla que establece g_zone_state en 1, -1 o 0 en función de una puntuación suavizada y de los umbrales de zona positivos y negativos configurados. SendAlert centraliza el envío de alertas: reproduce un sonido si está activado, envía una notificación push si está activada, llama a Alert() (cuadro de diálogo del terminal local) y a Print() para el registro. La centralización de las alertas facilita futuras ampliaciones (por ejemplo, correo electrónico o webhooks).
void UpdateZoneState(double smoothScore)
  {
   if(smoothScore >= InpPositiveZone) g_zone_state = 1;
   else if(smoothScore <= InpNegativeZone) g_zone_state = -1;
   else g_zone_state = 0;
  }

void SendAlert(string msg)
  {
   if(InpEnableSound) PlaySound(InpSoundFile);
   if(InpEnablePush) SendNotification(msg);
   Alert(msg);
   Print(__FILE__, ": ", msg);
  }

DrawSignalOnChart se encarga de colocar el texto «COMPRAR/VENDER» y el símbolo de la flecha exactamente en la última barra primaria cerrada (índice 1). Obtiene un nombre de objeto único utilizando la marca de tiempo y g_hist_idx, de modo que las distintas señales se distingan entre sí; calcula un pequeño desplazamiento de precio para situar la etiqueta de texto por encima o por debajo de la flecha, dejando la flecha exactamente en el precio; y crea dos objetos de texto del gráfico: uno para la etiqueta de texto y otro para el símbolo de la flecha. El EA admite una selección sencilla mediante símbolos de flecha y configura las fuentes, los colores, el orden de superposición y la imposibilidad de selección, de modo que estas anotaciones no interfieran en el trabajo manual del operador con los gráficos. Tras crear una señal, llama a PruneOldSignals para garantizar que el gráfico no se llene de demasiados marcadores. 

PruneOldSignals es una tarea de mantenimiento pragmática: enumera todos los objetos del gráfico, selecciona aquellos que comienzan con el prefijo del EA + SIG_, extrae la marca de tiempo codificada en el nombre del objeto, los ordena por marca de tiempo (lo que se consigue eliminando repetidamente el valor mínimo) y elimina los más antiguos hasta que solo quede «keep». Esto garantiza que solo se mantengan los marcadores InpMaxSignalsToKeep más recientes, evita que el gráfico resulte recargado y mantiene el coste de la enumeración de objetos bajo para valores de retención razonables.

void DrawSignalOnChart(int zone, double score)
  {
   datetime tm = iTime(Symbol(), InpTFPrimary, 1);
   double price = iClose(Symbol(), InpTFPrimary, 1);
   if(tm == 0 || price == 0.0) return;

   double point = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   double textOffsetPts = MathMax(1, InpArrowOffsetPoints);
   double textOffset = point * textOffsetPts * 1.5;

   string name = g_prefix + "SIG_" + IntegerToString((int)tm) + "_" + IntegerToString(g_hist_idx);
   string arrName = g_prefix + "SIG_ARR_" + IntegerToString((int)tm) + "_" + IntegerToString(g_hist_idx);

   if(ObjectFind(g_chart_id, name) >= 0) ObjectDelete(g_chart_id, name);
   if(ObjectFind(g_chart_id, arrName) >= 0) ObjectDelete(g_chart_id, arrName);

   double text_price = (zone == 1) ? price + textOffset : price - textOffset;
   if(ObjectCreate(g_chart_id, name, OBJ_TEXT, 0, tm, text_price))
     {
      string txt = (zone == 1) ? "BUY" : ((zone == -1) ? "SELL" : "NEUTRAL");
      color col = (zone == 1) ? clrLime : ((zone == -1) ? clrRed : clrSilver);
      ObjectSetString(g_chart_id, name, OBJPROP_TEXT, txt);
      ObjectSetInteger(g_chart_id, name, OBJPROP_COLOR, (int)col);
      ObjectSetInteger(g_chart_id, name, OBJPROP_FONTSIZE, 12);
      ObjectSetString(g_chart_id, name, OBJPROP_FONT, InpFont);
      ObjectSetInteger(g_chart_id, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(g_chart_id, name, OBJPROP_ZORDER, 2);
     }

   if(InpShowArrows)
     {
      string arrowTxt = (zone == 1) ? "▲" : ((zone == -1) ? "v" : "■");
      if(ObjectCreate(g_chart_id, arrName, OBJ_TEXT, 0, tm, price))
        {
         color acol = (zone == 1) ? clrLime : ((zone == -1) ? clrRed : clrSilver);
         ObjectSetString(g_chart_id, arrName, OBJPROP_TEXT, arrowTxt);
         ObjectSetInteger(g_chart_id, arrName, OBJPROP_COLOR, (int)acol);
         ObjectSetInteger(g_chart_id, arrName, OBJPROP_FONTSIZE, InpArrowFontSize);
         ObjectSetString(g_chart_id, arrName, OBJPROP_FONT, InpFont);
         ObjectSetInteger(g_chart_id, arrName, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(g_chart_id, arrName, OBJPROP_ZORDER, 3);
        }
     }

   PruneOldSignals(InpMaxSignalsToKeep);
  }

void PruneOldSignals(int keep)
  {
   if(keep <= 0) return;
   int total = ObjectsTotal(g_chart_id);
   string names[];
   int times[];
   int n = 0;
   string sigPrefix = g_prefix + "SIG_";
   for(int i = 0; i < total; i++)
     {
      string nm = ObjectName(g_chart_id, i);
      if(StringFind(nm, sigPrefix) == 0)
        {
         string rest = StringSubstr(nm, StringLen(sigPrefix));
         int pos = StringFind(rest, "_");
         if(pos > 0)
           {
            string tsStr = StringSubstr(rest, 0, pos);
            int ts = (int)StringToInteger(tsStr);
            ArrayResize(names, n+1);
            ArrayResize(times, n+1);
            names[n] = nm;
            times[n] = ts;
            n++;
           }
        }
     }
   if(n <= keep) return;

   while(n > keep)
     {
      int minIdx = 0;
      for(int j = 1; j < n; j++) if(times[j] < times[minIdx]) minIdx = j;
      ObjectDelete(g_chart_id, names[minIdx]);
      for(int k = minIdx; k < n-1; k++)
        {
         names[k] = names[k+1];
         times[k] = times[k+1];
        }
      ArrayResize(names, n-1);
      ArrayResize(times, n-1);
      n--;
     }
  }

Por último, el EA incluye múltiples decisiones de diseño orientadas a la defensa y a la experiencia de usuario: prefijos únicos para los nombres de los objetos con el fin de evitar solapamientos, cálculo de ATR de reserva en caso de fallo de los indicadores, limitación de las puntuaciones fusionadas a un rango fijo, suavizado con un alfa configurable para reducir el ruido y aceptación de señales por capas (umbral de inversión, magnitud de inversión de signo/retención/impulso, entrada en zona). El código de la interfaz de usuario incluye un sistema para evitar solapamientos al colocar el icono de zona y el texto de actividades recientes, y el histograma utiliza bloques de texto en lugar de gráficos, por lo que es ligero y compatible con versiones anteriores de MetaTrader 5.


Pruebas y resultados

En esta sección, analizaremos los resultados probados de nuestro EA, incluyendo tanto los resultados del backtesting como las métricas de rendimiento en condiciones reales.

A continuación se muestra un GIF del backtest del índice Crash 300. El panel de control es un resumen compacto y fácil de auditar del estado de STM: en la primera línea se indican el nombre de la herramienta, el instrumento (Crash 300 Index) y el intervalo de tiempo activo (M5). Debajo de eso, una puntuación numérica del sentimiento, codificada por colores, muestra la tendencia del mercado suavizada en una escala de −100 a +100, mientras que una etiqueta que indica la tendencia (por ejemplo, ALZA, BAJA, NEUTRA) ofrece una lectura cualitativa inmediata. Justo debajo de la puntuación, el panel muestra la última señal (por ejemplo, «Sesgo: posición larga (BUY) a las 16:30»), para que puedas comprobar la dirección y la hora exacta en la que se produjo. Una barra de intensidad horizontal muestra la magnitud, y una fila compacta titulada «Recientes» enumera los últimos datos de las velas para situar la puntuación en su contexto. Todos los elementos están vinculados a barras cerradas, por lo que cada alerta y cada etiqueta se pueden relacionar con una hora y un precio concretos.

Durante las pruebas en vivo con el índice Crash 1000, el EA emitió una señal de COMPRA que, tras contrastarla con la marca de tiempo de la barra cerrada, resultó ser correcta tras un breve periodo de tenencia. El panel de control mostraba un claro sesgo positivo y el indicador de COMPRA estaba fijado al cierre exacto de la barra y al precio, por lo que el movimiento y su momento de aparición son totalmente verificables en el gráfico. Este resultado pone de manifiesto la capacidad del STM para detectar una tendencia alcista duradera a corto plazo —los filtros de suavizado y de impulso eliminaron el ruido y ayudaron a evitar una oscilación prematura—, dejando al mismo tiempo la gestión de la operación (tamaño de la posición, stops y objetivos de beneficios) en manos del operador.

  • Pruebas en tiempo real con el índice Crash 300


Conclusión

El Sentiment Tilt Meter (STM) hizo exactamente lo que se había diseñado para hacer: señalar la tendencia a corto plazo de forma clara y fiable durante las series «Crash 300» y «Crash 1000». Las señales son fáciles de verificar —cada flecha y cada etiqueta están vinculadas a una barra cerrada y a un precio—, por lo que puedes comprobar qué ocurrió y cuándo sin tener que hacer conjeturas. Los controles de suavizado y de impulso evitan que el indicador oscile constantemente ante movimientos mínimos, y la ponderación por TF te permite dar prioridad a los horizontes que más te importan según tu estilo.

Esta herramienta es un generador de señales y un registrador, no un sistema de negociación completo. Te indicará cuándo el mercado está desequilibrado, pero no determinará el tamaño de las posiciones, ni fijará los stops, ni gestionará las salidas por ti. Los resultados variarán según el instrumento y la sesión; los índices Crash han dado buenos resultados en estas pruebas, pero es posible que el mercado de divisas, la bolsa o los futuros requieran un ajuste diferente.

Utiliza el STM como filtro o como capa de confirmación. Registra las señales en un archivo CSV, realiza backtests con múltiples símbolos y un breve forward-test, y solo entonces plantéate automatizar las entradas. Cuando operes siguiendo sus señales, incorpóralas a un plan de riesgo sencillo —R fijo, reglas claras para los stop y una verificación en un marco temporal superior— para que una sesión con mucha volatilidad no eche por tierra la ventaja obtenida durante todo un mes.

En resumen: el STM ofrece una visión clara y verificable de la tendencia del mercado a corto plazo. Acelera la toma de decisiones y ayuda a mantener la disciplina. Combínalo con controles de riesgo y pasos de verificación antes de operar con dinero real.







Chart Projector
Analytical Comment
Analytics Master
Analytics Forecaster 
Volatility Navigator
Mean Reversion Signal Reaper
Signal Pulse 
Metrics Board 
External Flow
VWAP
Heikin Ashi   FibVWAP  
RSI DIVERGENCE
Parabolic Stop and Reverse (PSAR) 
Quarters Drawer Script
Intrusion Detector
TrendLoom Tool  Quarters Board 
ZigZag Analyzer  Correlation Pathfinder  Market Structure Flip Detector Tool
Correlation Dashboard   Currency Strength Meter 
PAQ Analysis Tool 
Dual EMA Fractal Breaker
Pin bar, Engulfing and RSI divergence
Liquidity Sweep Opening Range Breakout Tool Boom and Crash Interceptor CCI Zer-Line EA
Candlestick Recognition Candlestick Detection using TA-Lib Candle Range Tool MetaTrader 5 Data Ingestor Model Training and Deployment Use of Python Lib
Sentiment Tilt Meter          

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

Archivos adjuntos |
Mustafa Nail Sertoglu
Mustafa Nail Sertoglu | 25 ago 2025 en 21:40
Gracias por compartir nuevas ideas para gestionar las operaciones bursátiles; que pases unos días estupendos con tus seres queridos.
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Operaciones CRUD en Firebase con MQL Operaciones CRUD en Firebase con MQL
Este artículo ofrece una guía paso a paso para dominar las operaciones CRUD (Create, Read, Update, Delete; crear, leer, actualizar y eliminar) en Firebase, centrándose en su Firebase Realtime Database (base de datos en tiempo real de Firebase) y en Firestore. Descubre cómo usar los métodos del SDK de Firebase para gestionar datos de forma eficiente en aplicaciones web y móviles, desde añadir nuevos registros hasta consultar, modificar y eliminar entradas. Explora ejemplos de código prácticos y las mejores prácticas para estructurar y gestionar datos en tiempo real, lo que permite a los desarrolladores crear aplicaciones dinámicas y escalables con la arquitectura NoSQL flexible de Firebase.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Un motor de riesgo adaptativo en MQL5: Ajustar el tamaño según racha, volatilidad y drawdown Un motor de riesgo adaptativo en MQL5: Ajustar el tamaño según racha, volatilidad y drawdown
Presentamos una clase reutilizable de dimensionamiento adaptativo, CRiskEngine, que calcula el lote por riesgo y stop y lo reduce según tres factores medibles: racha de pérdidas, volatilidad actual frente a su promedio y caída de capital. Se incluye un asesor de prueba con modos conmutables para comparar seis esquemas (lote fijo, riesgo fijo, cada factor y combinados) sobre 842 operaciones de EURUSD, con métricas y curvas. El lector obtiene un motor listo para integrar y una medición clara del efecto de cada factor.