//+------------------------------------------------------------------+ //| FVG_Detector_EA.mq5 | //| Fair Value Gap Detection Tool | //| Professional Smart Money Concepts Expert | //+------------------------------------------------------------------+ #property copyright "FVG Detector - SMC Analysis Tool" #property link "https://www.smartmoneyconcepts.com" #property version "2.01" #property description "Expert Advisor para detección y visualización de Fair Value Gaps (FVG)" #property description "⚠️ SOLO ANÁLISIS VISUAL - NO EJECUTA OPERACIONES" #property strict //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "..\\Utils\\MetricSaver.mqh" /* Codigo hecho por ia Claude Opus 4.5 Promt: Desarrolla un BOT (Expert Advisor) para MetaTrader 5 (MT5) que EXCLUSIVAMENTE detecte y dibuje Fair Value Gaps (FVG) en el gráfico. ⚠️ Este BOT NO debe ejecutar operaciones, NO debe abrir/cerrar trades y NO debe funcionar como indicador. Su única función es análisis visual mediante dibujo en el gráfico . Requisitos técnicos obligatorios: Implementado como Expert Advisor (EA) en MQL5. Detección y dibujo de FVG alcistas y bajistas. Funcionamiento rápido, eficiente y altamente optimizado. Capaz de trabajar con datos históricos y en tiempo real. Configuración personalizable desde el panel del BOT: Colores de los FVG. Opacidad / transparencia. Grosor y estilo de los rectángulos. Activar o desactivar FVG alcistas o bajistas. Timeframe de análisis. Aspectos visuales: Los FVG deben dibujarse como zonas (rectángulos) claras y limpias. Evitar redibujados innecesarios. El BOT debe actualizar los FVG solo cuando sea necesario. Buenas prácticas de desarrollo: Código limpio, modular y bien comentado. Uso eficiente de OnInit, OnTick y manejo de objetos gráficos. Optimización de loops y cálculos para mínimo consumo de CPU. Usa todo tu conocimiento avanzado en MQL5, optimización y Smart Money Concepts para crear un BOT profesional, estable y rápido, enfocado únicamente en dibujar Fair Value Gaps. */ //+------------------------------------------------------------------+ //| ENUMERACIONES PERSONALIZADAS | //+------------------------------------------------------------------+ // Estilos de borde para los rectángulos FVG enum ENUM_FVG_BORDER_STYLE { FVG_BORDER_SOLID = STYLE_SOLID, // Línea Sólida FVG_BORDER_DASH = STYLE_DASH, // Línea Discontinua FVG_BORDER_DOT = STYLE_DOT, // Línea Punteada FVG_BORDER_DASHDOT = STYLE_DASHDOT, // Guión-Punto FVG_BORDER_DASHDOTDOT = STYLE_DASHDOTDOT // Guión-Punto-Punto }; // Tipo de extensión del FVG enum ENUM_FVG_EXTENSION { FVG_EXTEND_FIXED = 0, // Extensión Fija (N velas) FVG_EXTEND_UNTIL_FILL = 1 // Hasta ser llenado }; //+------------------------------------------------------------------+ //| PARÁMETROS DE ENTRADA - CONFIGURACIÓN DEL USUARIO | //+------------------------------------------------------------------+ // === CONFIGURACIÓN GENERAL === input group "══════════ CONFIGURACIÓN GENERAL ══════════" input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Timeframe de análisis input int InpLookbackBars = 500; // Velas a analizar (historial) input int InpMinFVGSize = 5; // Tamaño mínimo FVG (puntos) input bool InpShowFilledFVG = false; // Mostrar FVG ya llenados input ENUM_FVG_EXTENSION InpExtensionType = FVG_EXTEND_UNTIL_FILL; // Tipo de extensión // === FVG ALCISTAS (BULLISH) === input group "══════════ FVG ALCISTAS (BULLISH) ══════════" input bool InpShowBullishFVG = true; // Activar FVG Alcistas input color InpBullishColor = clrDodgerBlue; // Color FVG Alcista input uchar InpBullishOpacity = 50; // Opacidad (0-255) input int InpBullishWidth = 1; // Grosor del borde input ENUM_FVG_BORDER_STYLE InpBullishStyle = FVG_BORDER_SOLID; // Estilo del borde input bool InpBullishFill = true; // Rellenar rectángulo // === FVG BAJISTAS (BEARISH) === input group "══════════ FVG BAJISTAS (BEARISH) ══════════" input bool InpShowBearishFVG = true; // Activar FVG Bajistas input color InpBearishColor = clrCrimson; // Color FVG Bajista input uchar InpBearishOpacity = 50; // Opacidad (0-255) input int InpBearishWidth = 1; // Grosor del borde input ENUM_FVG_BORDER_STYLE InpBearishStyle = FVG_BORDER_SOLID; // Estilo del borde input bool InpBearishFill = true; // Rellenar rectángulo // === CONFIGURACIÓN AVANZADA === input group "══════════ CONFIGURACIÓN AVANZADA ══════════" input int InpFixedExtension = 50; // Extensión fija (velas) input bool InpDeleteOnFill = true; // Eliminar FVG al llenarse input int InpUpdateFrequency = 1; // Frecuencia actualización (ticks) input string InpObjectPrefix = "FVG_"; // Prefijo objetos gráficos //+------------------------------------------------------------------+ //| ESTRUCTURA PARA ALMACENAR INFORMACIÓN DE FVG | //+------------------------------------------------------------------+ struct FVG_Data { datetime timeStart; // Tiempo de inicio (vela del gap) datetime timeEnd; // Tiempo de fin (extensión) double priceHigh; // Precio superior del gap double priceLow; // Precio inferior del gap bool isBullish; // true = alcista, false = bajista bool isFilled; // true si el gap ha sido llenado int barIndex; // Índice de la vela donde se formó string objectName; // Nombre del objeto gráfico }; //+------------------------------------------------------------------+ //| VARIABLES GLOBALES | //+------------------------------------------------------------------+ FVG_Data g_fvgArray[]; // Array dinámico de FVGs detectados int g_totalFVG = 0; // Contador total de FVGs int g_tickCounter = 0; // Contador de ticks para optimización datetime g_lastBarTime = 0; // Tiempo de la última vela procesada int g_lastBarsCount = 0; // Último conteo de barras string g_chartSymbol; // Símbolo del gráfico ENUM_TIMEFRAMES g_chartTimeframe; // Timeframe activo double g_pointValue; // Valor del punto int g_digits; // Dígitos del símbolo //+------------------------------------------------------------------+ //| FUNCIÓN DE INICIALIZACIÓN DEL EXPERT ADVISOR | //+------------------------------------------------------------------+ int OnInit() { CMetricsSave::Start(FILE_CODE6); // Validar parámetros de entrada if(!ValidateInputParameters()) { Print("❌ Error: Parámetros de entrada inválidos"); return(INIT_PARAMETERS_INCORRECT); } // Inicializar variables globales g_chartSymbol = _Symbol; g_chartTimeframe = (InpTimeframe == PERIOD_CURRENT) ? Period() : InpTimeframe; g_pointValue = SymbolInfoDouble(_Symbol, SYMBOL_POINT); g_digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); g_lastBarTime = 0; g_lastBarsCount = 0; g_tickCounter = 0; // Limpiar objetos gráficos anteriores CleanupGraphicObjects(); // Inicializar array de FVGs ArrayResize(g_fvgArray, 0); g_totalFVG = 0; // Detectar FVGs en el historial DetectHistoricalFVGs(); // Dibujar FVGs detectados DrawAllFVGs(); // Forzar actualización del gráfico ChartRedraw(0); // Mensaje de inicialización exitosa Print("═══════════════════════════════════════════════════════════"); Print("✅ FVG Detector EA iniciado correctamente"); Print("📊 Símbolo: ", g_chartSymbol, " | Timeframe: ", EnumToString(g_chartTimeframe)); Print("📈 FVGs Alcistas: ", InpShowBullishFVG ? "Activados" : "Desactivados"); Print("📉 FVGs Bajistas: ", InpShowBearishFVG ? "Activados" : "Desactivados"); Print("📋 FVGs detectados: ", g_totalFVG); Print("═══════════════════════════════════════════════════════════"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| FUNCIÓN DE DESINICIALIZACIÓN | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Limpiar todos los objetos gráficos creados CleanupGraphicObjects(); // Liberar memoria del array ArrayFree(g_fvgArray); g_totalFVG = 0; // Mensaje de cierre string reasonText = ""; switch(reason) { case REASON_PROGRAM: reasonText = "Programa terminado"; break; case REASON_REMOVE: reasonText = "EA eliminado del gráfico"; break; case REASON_RECOMPILE: reasonText = "EA recompilado"; break; case REASON_CHARTCHANGE: reasonText = "Símbolo o timeframe cambiado"; break; case REASON_CHARTCLOSE: reasonText = "Gráfico cerrado"; break; case REASON_PARAMETERS: reasonText = "Parámetros modificados"; break; case REASON_ACCOUNT: reasonText = "Cuenta cambiada"; break; case REASON_TEMPLATE: reasonText = "Nueva plantilla aplicada"; break; default: reasonText = "Razón desconocida"; break; } Print("══════════════════════════════════════════════════════════"); Print("⏹️ FVG Detector EA detenido - ", reasonText); Print("══════════════════════════════════════════════════════════"); CMetricsSave::Destroy(); } //+------------------------------------------------------------------+ //| FUNCIÓN PRINCIPAL OnTick - PROCESAMIENTO EN TIEMPO REAL | //+------------------------------------------------------------------+ void OnTick() { // Control de frecuencia de actualización para optimización g_tickCounter++; if(g_tickCounter < InpUpdateFrequency) return; g_tickCounter = 0; // Obtener tiempo de la vela actual datetime currentBarTime = iTime(g_chartSymbol, g_chartTimeframe, 0); int currentBarsCount = iBars(g_chartSymbol, g_chartTimeframe); // Verificar si hay una nueva vela bool isNewBar = (currentBarTime != g_lastBarTime); bool barsChanged = (currentBarsCount != g_lastBarsCount); if(isNewBar || barsChanged) { g_lastBarTime = currentBarTime; g_lastBarsCount = currentBarsCount; // Detectar nuevo FVG potencial (solo en la vela más reciente completa) CheckForNewFVG(1); // Actualizar estado de FVGs existentes (verificar si fueron llenados) UpdateFVGStatus(); // Redibujar FVGs si es necesario RedrawModifiedFVGs(); } else { // Solo actualizar extensiones si no hay nueva vela UpdateFVGExtensions(); } } //+------------------------------------------------------------------+ //| VALIDACIÓN DE PARÁMETROS DE ENTRADA | //+------------------------------------------------------------------+ bool ValidateInputParameters() { bool isValid = true; // Validar lookback if(InpLookbackBars < 10 || InpLookbackBars > 10000) { Print("⚠️ Advertencia: LookbackBars debe estar entre 10 y 10000. Usando 500."); // Continuar con valor por defecto } // Validar tamaño mínimo if(InpMinFVGSize < 0) { Print("⚠️ Advertencia: MinFVGSize no puede ser negativo."); } // Validar que al menos un tipo de FVG esté activo if(!InpShowBullishFVG && !InpShowBearishFVG) { Print("⚠️ Advertencia: Ambos tipos de FVG están desactivados. El EA no mostrará nada."); } return isValid; } //+------------------------------------------------------------------+ //| LIMPIEZA DE OBJETOS GRÁFICOS | //+------------------------------------------------------------------+ void CleanupGraphicObjects() { int totalObjects = ObjectsTotal(0, 0, OBJ_RECTANGLE); // Iterar en orden inverso para eliminar objetos con nuestro prefijo for(int i = totalObjects - 1; i >= 0; i--) { string objName = ObjectName(0, i, 0, OBJ_RECTANGLE); if(StringFind(objName, InpObjectPrefix) == 0) { ObjectDelete(0, objName); } } ChartRedraw(0); } //+------------------------------------------------------------------+ //| DETECCIÓN DE FVGs EN DATOS HISTÓRICOS | //+------------------------------------------------------------------+ void DetectHistoricalFVGs() { // Determinar el rango de barras a analizar int totalBars = iBars(g_chartSymbol, g_chartTimeframe); int barsToAnalyze = MathMin(InpLookbackBars, totalBars - 3); if(barsToAnalyze < 3) { Print("⚠️ No hay suficientes datos históricos para análisis"); return; } // Reservar memoria para el array (estimación inicial) ArrayResize(g_fvgArray, 0, barsToAnalyze / 10); // Iterar desde la vela más antigua hacia la más reciente for(int i = barsToAnalyze; i >= 2; i--) { CheckForFVGAtBar(i); } // Ajustar tamaño final del array ArrayResize(g_fvgArray, g_totalFVG); } //+------------------------------------------------------------------+ //| VERIFICAR FVG EN UNA VELA ESPECÍFICA | //+------------------------------------------------------------------+ void CheckForFVGAtBar(int barIndex) { // Obtener datos OHLC de las 3 velas necesarias // Vela 0 (barIndex+1): Primera vela // Vela 1 (barIndex): Vela del impulso (donde se forma el gap) // Vela 2 (barIndex-1): Tercera vela double high0 = iHigh(g_chartSymbol, g_chartTimeframe, barIndex + 1); double low0 = iLow(g_chartSymbol, g_chartTimeframe, barIndex + 1); double high2 = iHigh(g_chartSymbol, g_chartTimeframe, barIndex - 1); double low2 = iLow(g_chartSymbol, g_chartTimeframe, barIndex - 1); // Verificar datos válidos if(high0 <= 0 || low0 <= 0 || high2 <= 0 || low2 <= 0) return; // Tiempos de las velas datetime time1 = iTime(g_chartSymbol, g_chartTimeframe, barIndex); datetime time2 = iTime(g_chartSymbol, g_chartTimeframe, barIndex - 1); // Tamaño mínimo en precio double minSize = InpMinFVGSize * g_pointValue; // === DETECCIÓN FVG ALCISTA (BULLISH) === // Se forma cuando: Low de vela 3 > High de vela 1 if(InpShowBullishFVG && low2 > high0) { double gapHigh = low2; double gapLow = high0; double gapSize = gapHigh - gapLow; if(gapSize >= minSize) { // Crear nuevo FVG FVG_Data newFVG; newFVG.timeStart = time1; newFVG.timeEnd = time2; newFVG.priceHigh = gapHigh; newFVG.priceLow = gapLow; newFVG.isBullish = true; newFVG.isFilled = false; newFVG.barIndex = barIndex; newFVG.objectName = GenerateFVGObjectName(true, barIndex); // Verificar si ya fue llenado en el historial CheckIfFVGFilledHistorical(newFVG); // Solo agregar si no está llenado o si queremos mostrar llenados if(!newFVG.isFilled || InpShowFilledFVG) { AddFVGToArray(newFVG); } } } // === DETECCIÓN FVG BAJISTA (BEARISH) === // Se forma cuando: High de vela 3 < Low de vela 1 if(InpShowBearishFVG && high2 < low0) { double gapHigh = low0; double gapLow = high2; double gapSize = gapHigh - gapLow; if(gapSize >= minSize) { // Crear nuevo FVG FVG_Data newFVG; newFVG.timeStart = time1; newFVG.timeEnd = time2; newFVG.priceHigh = gapHigh; newFVG.priceLow = gapLow; newFVG.isBullish = false; newFVG.isFilled = false; newFVG.barIndex = barIndex; newFVG.objectName = GenerateFVGObjectName(false, barIndex); // Verificar si ya fue llenado en el historial CheckIfFVGFilledHistorical(newFVG); // Solo agregar si no está llenado o si queremos mostrar llenados if(!newFVG.isFilled || InpShowFilledFVG) { AddFVGToArray(newFVG); } } } } //+------------------------------------------------------------------+ //| VERIFICAR SI FVG FUE LLENADO EN HISTORIAL | //+------------------------------------------------------------------+ void CheckIfFVGFilledHistorical(FVG_Data &fvg) { // Obtener el índice de barra actual del FVG int fvgBarShift = iBarShift(g_chartSymbol, g_chartTimeframe, fvg.timeStart); if(fvgBarShift < 0) return; // Verificar todas las velas posteriores al FVG for(int i = fvgBarShift - 1; i >= 0; i--) { double high = iHigh(g_chartSymbol, g_chartTimeframe, i); double low = iLow(g_chartSymbol, g_chartTimeframe, i); if(fvg.isBullish) { // FVG alcista se llena cuando el precio baja hasta el nivel inferior if(low <= fvg.priceLow) { fvg.isFilled = true; fvg.timeEnd = iTime(g_chartSymbol, g_chartTimeframe, i); return; } } else { // FVG bajista se llena cuando el precio sube hasta el nivel superior if(high >= fvg.priceHigh) { fvg.isFilled = true; fvg.timeEnd = iTime(g_chartSymbol, g_chartTimeframe, i); return; } } } } //+------------------------------------------------------------------+ //| AGREGAR FVG AL ARRAY DINÁMICO | //+------------------------------------------------------------------+ void AddFVGToArray(FVG_Data &newFVG) { int newSize = g_totalFVG + 1; ArrayResize(g_fvgArray, newSize, 100); g_fvgArray[g_totalFVG] = newFVG; g_totalFVG = newSize; } //+------------------------------------------------------------------+ //| GENERAR NOMBRE ÚNICO PARA OBJETO FVG | //+------------------------------------------------------------------+ string GenerateFVGObjectName(bool isBullish, int barIndex) { string type = isBullish ? "BULL_" : "BEAR_"; datetime barTime = iTime(g_chartSymbol, g_chartTimeframe, barIndex); return InpObjectPrefix + type + IntegerToString((long)barTime); } //+------------------------------------------------------------------+ //| DIBUJAR TODOS LOS FVGs | //+------------------------------------------------------------------+ void DrawAllFVGs() { for(int i = 0; i < g_totalFVG; i++) { DrawSingleFVG(g_fvgArray[i]); } } //+------------------------------------------------------------------+ //| DIBUJAR UN FVG INDIVIDUAL | //+------------------------------------------------------------------+ void DrawSingleFVG(FVG_Data &fvg) { // Si está llenado y no queremos mostrar llenados, eliminar objeto existente if(fvg.isFilled && !InpShowFilledFVG) { if(ObjectFind(0, fvg.objectName) >= 0) { ObjectDelete(0, fvg.objectName); } return; } // Si ya existe el objeto y está llenado, eliminarlo si corresponde if(fvg.isFilled && InpDeleteOnFill && ObjectFind(0, fvg.objectName) >= 0) { ObjectDelete(0, fvg.objectName); return; } // Calcular tiempo de fin del rectángulo datetime endTime = CalculateFVGEndTime(fvg); // Obtener configuración visual según tipo color fvgColor; uchar opacity; int borderWidth; ENUM_LINE_STYLE borderStyle; bool fillRectangle; if(fvg.isBullish) { fvgColor = InpBullishColor; opacity = InpBullishOpacity; borderWidth = InpBullishWidth; borderStyle = (ENUM_LINE_STYLE)InpBullishStyle; fillRectangle = InpBullishFill; } else { fvgColor = InpBearishColor; opacity = InpBearishOpacity; borderWidth = InpBearishWidth; borderStyle = (ENUM_LINE_STYLE)InpBearishStyle; fillRectangle = InpBearishFill; } // Crear o actualizar el rectángulo if(ObjectFind(0, fvg.objectName) < 0) { // Crear nuevo objeto rectángulo if(!ObjectCreate(0, fvg.objectName, OBJ_RECTANGLE, 0, fvg.timeStart, fvg.priceHigh, endTime, fvg.priceLow)) { Print("❌ Error al crear rectángulo FVG: ", GetLastError()); return; } } else { // Actualizar coordenadas del objeto existente ObjectSetInteger(0, fvg.objectName, OBJPROP_TIME, 0, fvg.timeStart); ObjectSetDouble(0, fvg.objectName, OBJPROP_PRICE, 0, fvg.priceHigh); ObjectSetInteger(0, fvg.objectName, OBJPROP_TIME, 1, endTime); ObjectSetDouble(0, fvg.objectName, OBJPROP_PRICE, 1, fvg.priceLow); } // Configurar propiedades visuales con color ARGB (usando función nativa) uint colorARGB = ColorToARGB(fvgColor, opacity); ObjectSetInteger(0, fvg.objectName, OBJPROP_COLOR, colorARGB); ObjectSetInteger(0, fvg.objectName, OBJPROP_STYLE, borderStyle); ObjectSetInteger(0, fvg.objectName, OBJPROP_WIDTH, borderWidth); ObjectSetInteger(0, fvg.objectName, OBJPROP_FILL, fillRectangle); ObjectSetInteger(0, fvg.objectName, OBJPROP_BACK, true); ObjectSetInteger(0, fvg.objectName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, fvg.objectName, OBJPROP_HIDDEN, true); // Tooltip descriptivo string tooltip = StringFormat("%s FVG\nAlto: %s\nBajo: %s\nTamaño: %.1f pips%s", fvg.isBullish ? "📈 Alcista" : "📉 Bajista", DoubleToString(fvg.priceHigh, g_digits), DoubleToString(fvg.priceLow, g_digits), (fvg.priceHigh - fvg.priceLow) / g_pointValue / 10, fvg.isFilled ? "\n✓ LLENADO" : ""); ObjectSetString(0, fvg.objectName, OBJPROP_TOOLTIP, tooltip); } //+------------------------------------------------------------------+ //| CALCULAR TIEMPO DE FIN DEL FVG | //+------------------------------------------------------------------+ datetime CalculateFVGEndTime(FVG_Data &fvg) { if(fvg.isFilled) { return fvg.timeEnd; } switch(InpExtensionType) { case FVG_EXTEND_FIXED: { // Extensión fija: N velas desde el FVG int barShift = iBarShift(g_chartSymbol, g_chartTimeframe, fvg.timeStart); if(barShift < 0) barShift = 0; int targetBar = MathMax(0, barShift - InpFixedExtension); return iTime(g_chartSymbol, g_chartTimeframe, targetBar); } case FVG_EXTEND_UNTIL_FILL: default: { // Extender hasta el tiempo actual (última vela) return iTime(g_chartSymbol, g_chartTimeframe, 0); } } } //+------------------------------------------------------------------+ //| VERIFICAR NUEVO FVG EN TIEMPO REAL | //+------------------------------------------------------------------+ void CheckForNewFVG(int barIndex) { // Verificar que tenemos suficientes datos if(barIndex < 1) return; // Solo verificar si la vela en barIndex está completa // (barIndex debe ser al menos 1 para que la formación esté completa) CheckForFVGAtBar(barIndex + 1); } //+------------------------------------------------------------------+ //| ACTUALIZAR ESTADO DE FVGs (VERIFICAR SI FUERON LLENADOS) | //+------------------------------------------------------------------+ void UpdateFVGStatus() { for(int i = 0; i < g_totalFVG; i++) { if(g_fvgArray[i].isFilled) continue; // Obtener precio actual double currentHigh = iHigh(g_chartSymbol, g_chartTimeframe, 0); double currentLow = iLow(g_chartSymbol, g_chartTimeframe, 0); if(g_fvgArray[i].isBullish) { // FVG alcista se llena si el precio baja hasta el nivel inferior if(currentLow <= g_fvgArray[i].priceLow) { g_fvgArray[i].isFilled = true; g_fvgArray[i].timeEnd = iTime(g_chartSymbol, g_chartTimeframe, 0); } } else { // FVG bajista se llena si el precio sube hasta el nivel superior if(currentHigh >= g_fvgArray[i].priceHigh) { g_fvgArray[i].isFilled = true; g_fvgArray[i].timeEnd = iTime(g_chartSymbol, g_chartTimeframe, 0); } } } } //+------------------------------------------------------------------+ //| REDIBUJAR FVGs MODIFICADOS | //+------------------------------------------------------------------+ void RedrawModifiedFVGs() { for(int i = 0; i < g_totalFVG; i++) { DrawSingleFVG(g_fvgArray[i]); } ChartRedraw(0); } //+------------------------------------------------------------------+ //| ACTUALIZAR EXTENSIONES DE FVGs | //+------------------------------------------------------------------+ void UpdateFVGExtensions() { bool needsRedraw = false; for(int i = 0; i < g_totalFVG; i++) { if(!g_fvgArray[i].isFilled && InpExtensionType == FVG_EXTEND_UNTIL_FILL) { datetime newEndTime = iTime(g_chartSymbol, g_chartTimeframe, 0); if(ObjectFind(0, g_fvgArray[i].objectName) >= 0) { datetime currentEndTime = (datetime)ObjectGetInteger(0, g_fvgArray[i].objectName, OBJPROP_TIME, 1); if(currentEndTime != newEndTime) { ObjectSetInteger(0, g_fvgArray[i].objectName, OBJPROP_TIME, 1, newEndTime); needsRedraw = true; } } } } if(needsRedraw) { ChartRedraw(0); } } //+------------------------------------------------------------------+ //| FUNCIÓN PARA MANEJAR EVENTOS DEL GRÁFICO | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // Manejar cambio de escala o desplazamiento del gráfico if(id == CHARTEVENT_CHART_CHANGE) { // Redibujar FVGs para asegurar visualización correcta ChartRedraw(0); } } //+------------------------------------------------------------------+ //| FUNCIÓN PARA OBTENER ESTADÍSTICAS | //+------------------------------------------------------------------+ string GetFVGStatistics() { int bullishCount = 0; int bearishCount = 0; int filledCount = 0; int activeCount = 0; for(int i = 0; i < g_totalFVG; i++) { if(g_fvgArray[i].isBullish) bullishCount++; else bearishCount++; if(g_fvgArray[i].isFilled) filledCount++; else activeCount++; } return StringFormat( "═══ ESTADÍSTICAS FVG ═══\n" "Total: %d\n" "Alcistas: %d\n" "Bajistas: %d\n" "Activos: %d\n" "Llenados: %d", g_totalFVG, bullishCount, bearishCount, activeCount, filledCount ); } //+------------------------------------------------------------------+ //| FIN DEL EXPERT ADVISOR | //+------------------------------------------------------------------+