496 lines
36 KiB
MQL5
496 lines
36 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| FVG_Detector.mq5 |
|
|
//| Expert Advisor para MT5 |
|
|
//| Detección y Visualización de Fair Value Gaps|
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "FVG Visual Detector"
|
|
#property link ""
|
|
#property version "1.00"
|
|
#property description "Expert Advisor exclusivo para detectar y dibujar Fair Value Gaps"
|
|
#property description "NO ejecuta trades - Solo análisis visual"
|
|
#property strict
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
#include "..\\Utils\\MetricSaver.mqh"
|
|
|
|
|
|
/* Codigo hecho por ia Claude Sonnet 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.
|
|
*/
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| INPUTS - Configuración Personalizable |
|
|
//+------------------------------------------------------------------+
|
|
input group "===== CONFIGURACIÓN GENERAL ====="
|
|
input bool InpEnableBullishFVG = true; // Activar FVG Alcistas
|
|
input bool InpEnableBearishFVG = true; // Activar FVG Bajistas
|
|
input int InpMaxBarsToAnalyze = 500; // Máximo de barras a analizar
|
|
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Timeframe de análisis
|
|
|
|
input group "===== CONFIGURACIÓN VISUAL FVG ALCISTAS ====="
|
|
input color InpBullishColor = clrLimeGreen; // Color FVG Alcistas
|
|
input int InpBullishTransparency = 85; // Transparencia (0-100)
|
|
input ENUM_LINE_STYLE InpBullishBorderStyle = STYLE_SOLID; // Estilo de borde
|
|
input int InpBullishBorderWidth = 1; // Grosor de borde
|
|
|
|
input group "===== CONFIGURACIÓN VISUAL FVG BAJISTAS ====="
|
|
input color InpBearishColor = clrTomato; // Color FVG Bajistas
|
|
input int InpBearishTransparency = 85; // Transparencia (0-100)
|
|
input ENUM_LINE_STYLE InpBearishBorderStyle = STYLE_SOLID; // Estilo de borde
|
|
input int InpBearishBorderWidth = 1; // Grosor de borde
|
|
|
|
input group "===== CONFIGURACIÓN AVANZADA ====="
|
|
input bool InpDeleteFilledFVG = false; // Eliminar FVG cuando precio los llena
|
|
input double InpMinFVGSize = 5.0; // Tamaño mínimo FVG en puntos
|
|
input bool InpExtendToRight = true; // Extender rectángulos a la derecha
|
|
input int InpMaxFVGsToDisplay = 50; // Máximo de FVGs a mostrar
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Estructura para almacenar datos de FVG |
|
|
//+------------------------------------------------------------------+
|
|
struct FVGData
|
|
{
|
|
datetime time; // Tiempo de creación del FVG
|
|
double topPrice; // Precio superior del gap
|
|
double bottomPrice; // Precio inferior del gap
|
|
bool isBullish; // true = Alcista, false = Bajista
|
|
string objectName; // Nombre del objeto gráfico
|
|
bool isFilled; // Si el precio ha llenado el FVG
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Variables Globales |
|
|
//+------------------------------------------------------------------+
|
|
FVGData g_FVGList[]; // Array dinámico para almacenar FVGs
|
|
int g_FVGCount = 0; // Contador de FVGs activos
|
|
string g_prefix = "FVG_"; // Prefijo para objetos gráficos
|
|
int g_lastAnalyzedBar = -1; // Última barra analizada
|
|
double g_point; // Valor del punto del símbolo
|
|
int g_digits; // Dígitos del símbolo
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
CMetricsSave::Start(FILE_CODE4);
|
|
|
|
// Inicializar variables del símbolo
|
|
g_point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
g_digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
|
|
|
// Validar inputs
|
|
if(!ValidateInputs())
|
|
{
|
|
Print("❌ Error: Configuración inválida");
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
}
|
|
|
|
// Limpiar objetos gráficos anteriores
|
|
CleanupOldObjects();
|
|
|
|
// Redimensionar array de FVGs
|
|
ArrayResize(g_FVGList, InpMaxFVGsToDisplay);
|
|
|
|
|
|
// Análisis inicial de barras históricas
|
|
AnalyzeHistoricalBars();
|
|
|
|
Print("✅ FVG Detector iniciado correctamente");
|
|
Print("📊 Símbolo: ", _Symbol);
|
|
Print("⏱️ Timeframe: ", EnumToString(InpTimeframe));
|
|
Print("🔍 Analizando hasta ", InpMaxBarsToAnalyze, " barras");
|
|
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
// Limpiar todos los objetos gráficos creados
|
|
CleanupAllFVGObjects();
|
|
|
|
Print("🔚 FVG Detector detenido. Razón: ", GetDeinitReasonText(reason));
|
|
CMetricsSave::Destroy();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
// Obtener información de la última barra
|
|
int bars = iBars(_Symbol, InpTimeframe);
|
|
|
|
if(bars < 3)
|
|
return; // Necesitamos al menos 3 barras
|
|
|
|
// Verificar si hay una nueva barra
|
|
if(g_lastAnalyzedBar != bars)
|
|
{
|
|
// Analizar nuevos FVGs
|
|
DetectNewFVGs();
|
|
|
|
// Actualizar estado de FVGs existentes (si están llenos)
|
|
if(InpDeleteFilledFVG)
|
|
UpdateFVGStatus();
|
|
|
|
g_lastAnalyzedBar = bars;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Validar parámetros de entrada |
|
|
//+------------------------------------------------------------------+
|
|
bool ValidateInputs()
|
|
{
|
|
if(InpMaxBarsToAnalyze < 10 || InpMaxBarsToAnalyze > 5000)
|
|
{
|
|
Print("⚠️ Advertencia: InpMaxBarsToAnalyze fuera de rango (10-5000)");
|
|
return false;
|
|
}
|
|
|
|
if(InpBullishTransparency < 0 || InpBullishTransparency > 100)
|
|
{
|
|
Print("⚠️ Advertencia: Transparencia alcista fuera de rango (0-100)");
|
|
return false;
|
|
}
|
|
|
|
if(InpBearishTransparency < 0 || InpBearishTransparency > 100)
|
|
{
|
|
Print("⚠️ Advertencia: Transparencia bajista fuera de rango (0-100)");
|
|
return false;
|
|
}
|
|
|
|
if(!InpEnableBullishFVG && !InpEnableBearishFVG)
|
|
{
|
|
Print("⚠️ Advertencia: Debe activar al menos un tipo de FVG");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Analizar barras históricas |
|
|
//+------------------------------------------------------------------+
|
|
void AnalyzeHistoricalBars()
|
|
{
|
|
int barsToAnalyze = MathMin(InpMaxBarsToAnalyze, iBars(_Symbol, InpTimeframe) - 3);
|
|
|
|
Print("🔄 Analizando ", barsToAnalyze, " barras históricas...");
|
|
|
|
for(int i = barsToAnalyze; i >= 2; i--)
|
|
{
|
|
CheckForFVG(i);
|
|
}
|
|
|
|
Print("✅ Análisis histórico completado. FVGs detectados: ", g_FVGCount);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Detectar nuevos FVGs en tiempo real |
|
|
//+------------------------------------------------------------------+
|
|
void DetectNewFVGs()
|
|
{
|
|
// Analizar las últimas 3 barras para nuevos FVGs
|
|
CheckForFVG(2);
|
|
CheckForFVG(1);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Verificar si existe un FVG en la barra especificada |
|
|
//+------------------------------------------------------------------+
|
|
void CheckForFVG(int barIndex)
|
|
{
|
|
// Obtener datos de 3 barras consecutivas
|
|
double high1 = iHigh(_Symbol, InpTimeframe, barIndex + 1);
|
|
double low1 = iLow(_Symbol, InpTimeframe, barIndex + 1);
|
|
|
|
double high2 = iHigh(_Symbol, InpTimeframe, barIndex);
|
|
double low2 = iLow(_Symbol, InpTimeframe, barIndex);
|
|
|
|
double high3 = iHigh(_Symbol, InpTimeframe, barIndex - 1);
|
|
double low3 = iLow(_Symbol, InpTimeframe, barIndex - 1);
|
|
|
|
datetime time2 = iTime(_Symbol, InpTimeframe, barIndex);
|
|
|
|
// Detectar FVG ALCISTA (Bullish FVG)
|
|
// Condición: El low de la barra 3 está por encima del high de la barra 1
|
|
if(InpEnableBullishFVG && low3 > high1)
|
|
{
|
|
double gapSize = (low3 - high1) / g_point;
|
|
|
|
if(gapSize >= InpMinFVGSize)
|
|
{
|
|
// Verificar si ya existe este FVG
|
|
if(!FVGExists(time2, high1, low3))
|
|
{
|
|
CreateFVG(time2, high1, low3, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Detectar FVG BAJISTA (Bearish FVG)
|
|
// Condición: El high de la barra 3 está por debajo del low de la barra 1
|
|
if(InpEnableBearishFVG && high3 < low1)
|
|
{
|
|
double gapSize = (low1 - high3) / g_point;
|
|
|
|
if(gapSize >= InpMinFVGSize)
|
|
{
|
|
// Verificar si ya existe este FVG
|
|
if(!FVGExists(time2, high3, low1))
|
|
{
|
|
CreateFVG(time2, high3, low1, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Verificar si un FVG ya existe |
|
|
//+------------------------------------------------------------------+
|
|
bool FVGExists(datetime time, double bottomPrice, double topPrice)
|
|
{
|
|
for(int i = 0; i < g_FVGCount; i++)
|
|
{
|
|
if(g_FVGList[i].time == time &&
|
|
MathAbs(g_FVGList[i].bottomPrice - bottomPrice) < g_point &&
|
|
MathAbs(g_FVGList[i].topPrice - topPrice) < g_point)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Crear y dibujar un nuevo FVG |
|
|
//+------------------------------------------------------------------+
|
|
void CreateFVG(datetime time, double bottomPrice, double topPrice, bool isBullish)
|
|
{
|
|
// Verificar límite de FVGs
|
|
if(g_FVGCount >= InpMaxFVGsToDisplay)
|
|
{
|
|
// Eliminar el FVG más antiguo
|
|
RemoveOldestFVG();
|
|
}
|
|
|
|
// Crear nombre único para el objeto
|
|
string objName = g_prefix + (isBullish ? "Bull_" : "Bear_") + TimeToString(time, TIME_DATE | TIME_SECONDS);
|
|
|
|
// Crear rectángulo
|
|
if(!ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time, bottomPrice, 0, topPrice))
|
|
{
|
|
Print("❌ Error al crear objeto: ", objName, " - Error: ", GetLastError());
|
|
return;
|
|
}
|
|
|
|
// Configurar propiedades del rectángulo
|
|
datetime endTime = InpExtendToRight ? 0 : TimeCurrent() + PeriodSeconds(InpTimeframe) * 100;
|
|
ObjectSetInteger(0, objName, OBJPROP_TIME, 1, endTime);
|
|
|
|
// Colores y estilos
|
|
color fillColor = isBullish ? InpBullishColor : InpBearishColor;
|
|
int transparency = isBullish ? InpBullishTransparency : InpBearishTransparency;
|
|
ENUM_LINE_STYLE borderStyle = isBullish ? InpBullishBorderStyle : InpBearishBorderStyle;
|
|
int borderWidth = isBullish ? InpBullishBorderWidth : InpBearishBorderWidth;
|
|
|
|
// Aplicar transparencia al color
|
|
color finalColor = ColorWithTransparency(fillColor, transparency);
|
|
|
|
ObjectSetInteger(0, objName, OBJPROP_COLOR, finalColor);
|
|
ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle);
|
|
ObjectSetInteger(0, objName, OBJPROP_WIDTH, borderWidth);
|
|
ObjectSetInteger(0, objName, OBJPROP_FILL, true);
|
|
ObjectSetInteger(0, objName, OBJPROP_BACK, true);
|
|
ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
|
|
ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);
|
|
ObjectSetInteger(0, objName, OBJPROP_HIDDEN, true);
|
|
|
|
// Tooltip
|
|
string tooltip = (isBullish ? "🟢 FVG Alcista" : "🔴 FVG Bajista") +
|
|
"\nTiempo: " + TimeToString(time) +
|
|
"\nRango: " + DoubleToString(bottomPrice, g_digits) + " - " + DoubleToString(topPrice, g_digits);
|
|
ObjectSetString(0, objName, OBJPROP_TOOLTIP, tooltip);
|
|
|
|
// Guardar en el array
|
|
g_FVGList[g_FVGCount].time = time;
|
|
g_FVGList[g_FVGCount].topPrice = topPrice;
|
|
g_FVGList[g_FVGCount].bottomPrice = bottomPrice;
|
|
g_FVGList[g_FVGCount].isBullish = isBullish;
|
|
g_FVGList[g_FVGCount].objectName = objName;
|
|
g_FVGList[g_FVGCount].isFilled = false;
|
|
|
|
g_FVGCount++;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Aplicar transparencia a un color |
|
|
//+------------------------------------------------------------------+
|
|
color ColorWithTransparency(color baseColor, int transparency)
|
|
{
|
|
// Convertir transparencia de 0-100 a alpha 0-255
|
|
int alpha = (int)((100 - transparency) * 255 / 100);
|
|
|
|
// Extraer componentes RGB
|
|
int r = (int)((baseColor) & 0xFF);
|
|
int g = (int)((baseColor >> 8) & 0xFF);
|
|
int b = (int)((baseColor >> 16) & 0xFF);
|
|
|
|
// Aplicar alpha al color
|
|
// En MT5, la transparencia se maneja con la saturación del color
|
|
double factor = alpha / 255.0;
|
|
r = (int)(r * factor + 255 * (1 - factor));
|
|
g = (int)(g * factor + 255 * (1 - factor));
|
|
b = (int)(b * factor + 255 * (1 - factor));
|
|
|
|
return (color)(r | (g << 8) | (b << 16));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Actualizar estado de los FVGs (verificar si están llenos) |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateFVGStatus()
|
|
{
|
|
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
for(int i = g_FVGCount - 1; i >= 0; i--)
|
|
{
|
|
if(g_FVGList[i].isFilled)
|
|
continue;
|
|
|
|
bool isFilled = false;
|
|
|
|
// FVG Alcista se llena cuando el precio baja y toca el nivel inferior
|
|
if(g_FVGList[i].isBullish && currentPrice <= g_FVGList[i].bottomPrice)
|
|
{
|
|
isFilled = true;
|
|
}
|
|
|
|
// FVG Bajista se llena cuando el precio sube y toca el nivel superior
|
|
if(!g_FVGList[i].isBullish && currentPrice >= g_FVGList[i].topPrice)
|
|
{
|
|
isFilled = true;
|
|
}
|
|
|
|
if(isFilled)
|
|
{
|
|
g_FVGList[i].isFilled = true;
|
|
|
|
// Eliminar el objeto gráfico
|
|
ObjectDelete(0, g_FVGList[i].objectName);
|
|
|
|
// Remover del array
|
|
RemoveFVGFromArray(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Remover FVG del array |
|
|
//+------------------------------------------------------------------+
|
|
void RemoveFVGFromArray(int index)
|
|
{
|
|
if(index < 0 || index >= g_FVGCount)
|
|
return;
|
|
|
|
// Mover elementos
|
|
for(int i = index; i < g_FVGCount - 1; i++)
|
|
{
|
|
g_FVGList[i] = g_FVGList[i + 1];
|
|
}
|
|
|
|
g_FVGCount--;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Eliminar el FVG más antiguo |
|
|
//+------------------------------------------------------------------+
|
|
void RemoveOldestFVG()
|
|
{
|
|
if(g_FVGCount == 0)
|
|
return;
|
|
|
|
// El FVG más antiguo está en el índice 0
|
|
ObjectDelete(0, g_FVGList[0].objectName);
|
|
RemoveFVGFromArray(0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Limpiar objetos antiguos al inicio |
|
|
//+------------------------------------------------------------------+
|
|
void CleanupOldObjects()
|
|
{
|
|
int totalObjects = ObjectsTotal(0, 0, OBJ_RECTANGLE);
|
|
|
|
for(int i = totalObjects - 1; i >= 0; i--)
|
|
{
|
|
string objName = ObjectName(0, i, 0, OBJ_RECTANGLE);
|
|
|
|
if(StringFind(objName, g_prefix) == 0)
|
|
{
|
|
ObjectDelete(0, objName);
|
|
}
|
|
}
|
|
|
|
ChartRedraw(0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Limpiar todos los objetos FVG |
|
|
//+------------------------------------------------------------------+
|
|
void CleanupAllFVGObjects()
|
|
{
|
|
for(int i = 0; i < g_FVGCount; i++)
|
|
{
|
|
ObjectDelete(0, g_FVGList[i].objectName);
|
|
}
|
|
|
|
g_FVGCount = 0;
|
|
ChartRedraw(0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Obtener texto de la razón de desinicialización |
|
|
//+------------------------------------------------------------------+
|
|
string GetDeinitReasonText(int reason)
|
|
{
|
|
switch(reason)
|
|
{
|
|
case REASON_PROGRAM:
|
|
return "Programa cerrado";
|
|
case REASON_REMOVE:
|
|
return "EA removido del gráfico";
|
|
case REASON_RECOMPILE:
|
|
return "EA recompilado";
|
|
case REASON_CHARTCHANGE:
|
|
return "Cambio de símbolo o timeframe";
|
|
case REASON_CHARTCLOSE:
|
|
return "Gráfico cerrado";
|
|
case REASON_PARAMETERS:
|
|
return "Parámetros modificados";
|
|
case REASON_ACCOUNT:
|
|
return "Cambio de cuenta";
|
|
default:
|
|
return "Razón desconocida";
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|