ICTLibraryEasy/Examples/Benchmark/OtherCode/Code6.mq5
Nique_372 11e9098787
2026-05-25 11:41:23 -05:00

626 lines
No EOL
42 KiB
MQL5

//+------------------------------------------------------------------+
//| FVG_Detector_EA.mq5 |
//| Fair Value Gap Detection Tool |
//| Professional Smart Money Concepts Expert |
//+------------------------------------------------------------------+
#property copyright "FVG Detector"
#property version "1.00"
#property strict
#property description "High-performance FVG detector. Visualization only. No trading."
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
#include "..\\Utils\\MetricSaver.mqh"
/* Codigo hecho por ia Claude Opus 4.7 (best ai today 25/05/2026)
Desarrollo de EA MT5 (MQL5) para detección y dibujo de FVG
Objetivo
Necesito un Expert Advisor (EA) para MetaTrader 5, desarrollado en MQL5, cuya única función sea detectar, dibujar y gestionar automáticamente los Fair Value Gaps (FVG) en el gráfico.
No debe abrir operaciones ni ejecutar lógica de trading. Debe comportarse como un sistema visual de detección, similar a un indicador, pero implementado como EA.
Requerimientos funcionales
1. Detección de FVG
El EA debe detectar automáticamente todos los Fair Value Gaps (FVG) que se vayan formando en el gráfico.
2. Dibujado visual
Cada FVG detectado debe representarse visualmente mediante:
Un rectángulo que cubra toda la zona del FVG.
Una línea horizontal central ubicada exactamente en el midpoint del gap (size / 2).
3. Mitigación
El EA debe detectar cuándo un FVG ha sido mitigado (precio toca o invalida la zona, segun esta logica simple:
para alcitas: top > low (rectangulo)
para bajistas: high > bottom (rectangulo))
Cuando esto ocurra:
El FVG debe marcarse como mitigado.
Debe eliminarse automáticamente del gráfico.
También deben eliminarse sus objetos asociados (línea central, rectángulo, etc.).
4. Multi-timeframe
Debe permitirme seleccionar el timeframe sobre el cual calcular los FVG, independientemente del timeframe del gráfico actual.
Ejemplo:
Calcular FVG en H1 mientras estoy viendo M5.
Parámetros configurables
El EA debe exponer inputs configurables para:
Timeframe
Timeframe de cálculo de FVG.
Apariencia visual
Color del rectángulo FVG bullish.
Color del rectángulo FVG bearish.
Color de la línea central.
Estilo de línea (ENUM_LINE_STYLE).
Grosor de línea.
Transparencia del rectángulo (si aplica).
Si mostrar u ocultar la línea central.
Comportamiento
Activar/desactivar eliminación automática al mitigarse.
Máximo número de FVG activos a mantener en memoria/gráfico.
Requisitos de rendimiento (MUY IMPORTANTE)
La prioridad principal es máxima velocidad y optimización.
Necesito que el EA esté diseñado con enfoque de alto rendimiento.
Reglas de optimización:
Debe ejecutarse solo en nueva vela (new bar detection).
No necesito procesamiento tick-by-tick.
Nada debe recalcularse en cada tick innecesariamente.
Debe minimizar:
uso de memoria,
llamadas innecesarias a funciones,
creación/destrucción excesiva de objetos,
búsquedas repetitivas en arrays o estructuras.
Reutilizar datos/cache siempre que sea posible.
Evitar loops completos sobre histórico innecesario.
Debe estar preparado para manejar muchos FVG sin degradar rendimiento.
Diseño enfocado en bajo overhead gráfico.
Arquitectura y código
Puedes elegir la arquitectura que consideres más eficiente:
Programación procedural
OOP / clases
structs
arrays raw
buffers
cualquier enfoque híbrido
La prioridad no es elegancia, sino MAXIMA velocidad posible y estabilidad.
Restricciones importantes
1. NO trading
El EA:
NO debe abrir órdenes.
NO debe cerrar órdenes.
NO debe modificar posiciones.
NO debe usar lógica de ejecución de mercado.
Solo visualización y gestión de FVG.
2. Logging / Debug
No quiero Print() innecesarios.
Opciones aceptables:
Sin logs.
O logs opcionales mediante #ifdef DEBUG.
La build final debe quedar limpia y SUPER RAPIDA.
3. Archivo único
Necesito recibir todo en un solo archivo .mq5 de tipo Expert Advisor.
No usar:
.mqh externos
includes personalizados
archivos auxiliares
librerías separadas
Todo debe estar autocontenido en un único EA.
Buenas prácticas esperadas
Quiero que uses buenas prácticas de MQL5 orientadas a performance:
manejo correcto de OnInit()
limpieza en OnDeinit()
ejecución optimizada en OnTick()
detección eficiente de nueva vela
naming consistente de objetos
eliminación segura de objetos
evitar memory leaks
control de duplicados de FVG
no redibujar objetos existentes
gestión robusta de arrays/estructuras
Entregable
Genera el código completo de un EA en MQL5 (.mq5) que cumpla todo lo anterior.
Debe ser:
estable,
altamente optimizado,
fácil de mantener,
rápido incluso con muchos FVG,
y centrado exclusivamente en dibujo/gestión de FVG.
Empieza directamente generando el código completo del EA.
*/
//+------------------------------------------------------------------+
//| Inputs |
//+------------------------------------------------------------------+
input group "=== Timeframe ==="
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Timeframe de cálculo
input group "=== Apariencia Visual ==="
input color InpBullColor = clrDodgerBlue; // Color FVG alcista
input color InpBearColor = clrOrangeRed; // Color FVG bajista
input color InpMidLineColor = clrGold; // Color línea central
input ENUM_LINE_STYLE InpLineStyle = STYLE_DOT; // Estilo de línea
input int InpLineWidth = 1; // Grosor de línea
input bool InpFillRectangle = true; // Rellenar rectángulo
input bool InpRectBack = true; // Rectángulo en fondo
input bool InpShowMidLine = true; // Mostrar línea central
input group "=== Comportamiento ==="
input bool InpRemoveOnMitig = true; // Eliminar al mitigarse
input int InpMaxActiveFVG = 200; // Máx. FVG activos
input int InpScanInitialBars = 500; // Barras a escanear al inicio
input group "=== Identificación ==="
input string InpObjPrefix = "FVG_"; // Prefijo objetos
input long InpMagic = 20260101; // Identificador único
//+------------------------------------------------------------------+
//| Estructura compacta para FVG activo |
//+------------------------------------------------------------------+
struct FVG
{
double top; // Borde superior del gap
double bottom; // Borde inferior del gap
double mid; // Punto medio precalculado
datetime time_start; // Tiempo barra inicio del rectángulo
datetime time_end; // Tiempo barra fin (se extiende)
long id; // ID único
bool bullish; // true=alcista, false=bajista
};
//+------------------------------------------------------------------+
//| Variables globales |
//+------------------------------------------------------------------+
FVG g_fvgs[]; // Array dinámico de FVG activos
int g_fvg_count = 0; // Conteo lógico (puede ser <= ArraySize)
long g_next_id = 0; // Generador de IDs
datetime g_last_bar_time = 0; // Para detección de nueva vela
string g_prefix; // Prefijo cacheado
int g_prefix_len = 0; // Longitud cacheada
ENUM_TIMEFRAMES g_tf; // Timeframe efectivo
long g_chart_id = 0; // Chart ID cacheado
// CORRECION por mi parte (LEO) no se puede hacer esto asi que hare el array dinamico no hay de otra...
// Los arrays estaticos no pueden ser set "set as series"
// Buffers reutilizables para evitar reallocs en cada nueva barra
MqlRates g_rates[]; // Solo necesitamos 3 barras para detectar FVG
//+------------------------------------------------------------------+
//| Helpers de naming (inline) |
//+------------------------------------------------------------------+
string MakeRectName(const long id)
{
return g_prefix + "R_" + IntegerToString(id);
}
string MakeLineName(const long id)
{
return g_prefix + "L_" + IntegerToString(id);
}
//+------------------------------------------------------------------+
//| Crear objetos gráficos para un FVG |
//+------------------------------------------------------------------+
void DrawFVG(const FVG &f)
{
const string rname = MakeRectName(f.id);
const color col = f.bullish ? InpBullColor : InpBearColor;
if(ObjectCreate(g_chart_id, rname, OBJ_RECTANGLE, 0, f.time_start, f.top, f.time_end, f.bottom))
{
ObjectSetInteger(g_chart_id, rname, OBJPROP_COLOR, col);
ObjectSetInteger(g_chart_id, rname, OBJPROP_FILL, InpFillRectangle);
ObjectSetInteger(g_chart_id, rname, OBJPROP_BACK, InpRectBack);
ObjectSetInteger(g_chart_id, rname, OBJPROP_SELECTABLE,false);
ObjectSetInteger(g_chart_id, rname, OBJPROP_SELECTED, false);
ObjectSetInteger(g_chart_id, rname, OBJPROP_HIDDEN, true);
ObjectSetInteger(g_chart_id, rname, OBJPROP_WIDTH, 1);
ObjectSetInteger(g_chart_id, rname, OBJPROP_STYLE, STYLE_SOLID);
}
if(InpShowMidLine)
{
const string lname = MakeLineName(f.id);
if(ObjectCreate(g_chart_id, lname, OBJ_TREND, 0, f.time_start, f.mid, f.time_end, f.mid))
{
ObjectSetInteger(g_chart_id, lname, OBJPROP_COLOR, InpMidLineColor);
ObjectSetInteger(g_chart_id, lname, OBJPROP_STYLE, InpLineStyle);
ObjectSetInteger(g_chart_id, lname, OBJPROP_WIDTH, InpLineWidth);
ObjectSetInteger(g_chart_id, lname, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(g_chart_id, lname, OBJPROP_RAY_LEFT, false);
ObjectSetInteger(g_chart_id, lname, OBJPROP_SELECTABLE, false);
ObjectSetInteger(g_chart_id, lname, OBJPROP_SELECTED, false);
ObjectSetInteger(g_chart_id, lname, OBJPROP_HIDDEN, true);
ObjectSetInteger(g_chart_id, lname, OBJPROP_BACK, false);
}
}
}
//+------------------------------------------------------------------+
//| Extender el tiempo final del rectángulo y la línea |
//+------------------------------------------------------------------+
void ExtendFVG(const FVG &f, const datetime new_end)
{
const string rname = MakeRectName(f.id);
ObjectSetInteger(g_chart_id, rname, OBJPROP_TIME, 1, new_end);
if(InpShowMidLine)
{
const string lname = MakeLineName(f.id);
ObjectSetInteger(g_chart_id, lname, OBJPROP_TIME, 1, new_end);
}
}
//+------------------------------------------------------------------+
//| Eliminar objetos gráficos asociados a un FVG |
//+------------------------------------------------------------------+
void DeleteFVGObjects(const long id)
{
ObjectDelete(g_chart_id, MakeRectName(id));
if(InpShowMidLine)
ObjectDelete(g_chart_id, MakeLineName(id));
}
//+------------------------------------------------------------------+
//| Remover FVG del array por índice (swap con último, O(1)) |
//+------------------------------------------------------------------+
void RemoveFVGAt(const int idx)
{
const int last = g_fvg_count - 1;
if(idx != last)
g_fvgs[idx] = g_fvgs[last];
g_fvg_count--;
}
//+------------------------------------------------------------------+
//| Eliminar todos los FVG y limpiar gráfico |
//+------------------------------------------------------------------+
void ClearAllFVG()
{
for(int i = 0; i < g_fvg_count; i++)
DeleteFVGObjects(g_fvgs[i].id);
g_fvg_count = 0;
}
//+------------------------------------------------------------------+
//| Eliminar TODOS los objetos del EA del gráfico (limpieza total) |
//+------------------------------------------------------------------+
void CleanupChartObjects()
{
ObjectsDeleteAll(g_chart_id, g_prefix);
}
//+------------------------------------------------------------------+
//| Agregar un nuevo FVG al array (gestionando capacidad y máximo) |
//+------------------------------------------------------------------+
void AddFVG(const double top, const double bottom, const datetime ts,
const datetime te, const bool bullish)
{
// Si se alcanza el máximo, eliminar el más antiguo (índice 0)
if(g_fvg_count >= InpMaxActiveFVG)
{
DeleteFVGObjects(g_fvgs[0].id);
// Desplazar (shift) — solo cuando llegamos al límite, no es hot path
for(int i = 1; i < g_fvg_count; i++)
g_fvgs[i - 1] = g_fvgs[i];
g_fvg_count--;
}
// Asegurar capacidad sin reallocar en cada inserción
if(ArraySize(g_fvgs) <= g_fvg_count)
{
int new_size = ArraySize(g_fvgs) * 2;
if(new_size < 64) new_size = 64;
if(new_size > InpMaxActiveFVG) new_size = InpMaxActiveFVG;
ArrayResize(g_fvgs, new_size);
}
FVG f;
f.top = top;
f.bottom = bottom;
f.mid = (top + bottom) * 0.5;
f.time_start = ts;
f.time_end = te;
f.id = g_next_id++;
f.bullish = bullish;
g_fvgs[g_fvg_count++] = f;
DrawFVG(f);
}
//+------------------------------------------------------------------+
//| Comprobar mitigación de TODOS los FVG con la barra recién cerrada|
//| Lógica solicitada: |
//| alcista: bar.low <= fvg.top (precio entró desde arriba) |
//| bajista: bar.high >= fvg.bottom (precio entró desde abajo) |
//+------------------------------------------------------------------+
void CheckMitigation(const double bar_high, const double bar_low)
{
if(!InpRemoveOnMitig)
return;
// Recorrido inverso para borrar in-place con swap
for(int i = g_fvg_count - 1; i >= 0; i--)
{
const FVG f = g_fvgs[i];
bool mitigated = false;
if(f.bullish)
{
// FVG alcista: mitigado si el low de la barra entra en el gap
if(bar_low <= f.top)
mitigated = true;
}
else
{
// FVG bajista: mitigado si el high de la barra entra en el gap
if(bar_high >= f.bottom)
mitigated = true;
}
if(mitigated)
{
DeleteFVGObjects(f.id);
RemoveFVGAt(i);
}
}
}
//+------------------------------------------------------------------+
//| Extender el time_end de todos los FVG activos a la barra actual |
//+------------------------------------------------------------------+
void ExtendAllFVG(const datetime new_end)
{
for(int i = 0; i < g_fvg_count; i++)
{
if(g_fvgs[i].time_end != new_end)
{
g_fvgs[i].time_end = new_end;
ExtendFVG(g_fvgs[i], new_end);
}
}
}
//+------------------------------------------------------------------+
//| Escaneo inicial de N barras históricas para sembrar FVG activos |
//+------------------------------------------------------------------+
void InitialScan()
{
if(InpScanInitialBars < 3)
return;
int bars_avail = Bars(_Symbol, g_tf);
if(bars_avail < 3)
return;
int to_scan = InpScanInitialBars;
if(to_scan > bars_avail - 1)
to_scan = bars_avail - 1;
MqlRates rates[];
ArraySetAsSeries(rates, true);
int copied = CopyRates(_Symbol, g_tf, 1, to_scan, rates); // descartamos barra en formación
if(copied < 3)
return;
// rates[0] = barra cerrada más reciente
// Recorremos desde la más antigua a la más reciente para mantener orden cronológico de IDs
// Triple cronológico: oldest=rates[k+2], middle=rates[k+1], newest=rates[k]
const datetime extend_to = rates[0].time;
for(int k = copied - 3; k >= 0; k--)
{
const MqlRates b0 = rates[k]; // newest del triple
const MqlRates b1 = rates[k + 1]; // middle
const MqlRates b2 = rates[k + 2]; // oldest
bool bullish = false, bearish = false;
double top = 0, bottom = 0;
if(b0.low > b2.high)
{
bullish = true;
top = b0.low;
bottom = b2.high;
}
else if(b0.high < b2.low)
{
bearish = true;
top = b2.low;
bottom = b0.high;
}
if(!bullish && !bearish)
continue;
// Verificar si ya está mitigado por alguna barra posterior (entre b0 y la barra actual)
// Si está mitigado, no lo añadimos.
bool already_mitigated = false;
for(int m = k - 1; m >= 0; m--)
{
if(bullish)
{
if(rates[m].low <= top)
{
already_mitigated = true;
break;
}
}
else
{
if(rates[m].high >= bottom)
{
already_mitigated = true;
break;
}
}
}
if(already_mitigated)
continue;
AddFVG(top, bottom, b1.time, extend_to, bullish);
}
}
//+------------------------------------------------------------------+
//| Procesamiento de nueva barra cerrada |
//+------------------------------------------------------------------+
void ProcessNewBar()
{
// Necesitamos las 3 últimas barras CERRADAS: índices 1, 2, 3 (0 = en formación)
// Pero como solo procesamos en nueva vela, también validamos mitigación con la
// recién cerrada (índice 1).
ArraySetAsSeries(g_rates, true);
int copied = CopyRates(_Symbol, g_tf, 1, 3, g_rates);
if(copied < 3)
return;
// g_rates[0] = barra recién cerrada (la más reciente cerrada)
// g_rates[1] = anterior
// g_rates[2] = penúltima anterior
const MqlRates b0 = g_rates[0]; // newest closed
const MqlRates b1 = g_rates[1];
const MqlRates b2 = g_rates[2];
// 1) Comprobar mitigación con la barra recién cerrada
CheckMitigation(b0.high, b0.low);
// 2) Extender visualmente todos los FVG activos hasta el tiempo de la barra recién cerrada
ExtendAllFVG(b0.time);
// 3) Detectar nuevo FVG con el triple (b2, b1, b0) cronológico
bool bullish = false, bearish = false;
double top = 0, bottom = 0;
if(b0.low > b2.high)
{
bullish = true;
top = b0.low;
bottom = b2.high;
}
else if(b0.high < b2.low)
{
bearish = true;
top = b2.low;
bottom = b0.high;
}
if(bullish || bearish)
AddFVG(top, bottom, b1.time, b0.time, bullish);
}
//+------------------------------------------------------------------+
//| Detección eficiente de nueva vela (en el TF de cálculo) |
//+------------------------------------------------------------------+
bool IsNewBar()
{
datetime t[1];
if(CopyTime(_Symbol, g_tf, 0, 1, t) != 1)
return false;
if(t[0] != g_last_bar_time)
{
g_last_bar_time = t[0];
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| OnInit |
//+------------------------------------------------------------------+
int OnInit()
{
CMetricsSave::Start(FILE_CODE6);
g_chart_id = ChartID();
g_tf = (InpTimeframe == PERIOD_CURRENT) ? (ENUM_TIMEFRAMES)Period() : InpTimeframe;
g_prefix = InpObjPrefix + IntegerToString(InpMagic) + "_";
g_prefix_len= StringLen(g_prefix);
// Reservar capacidad inicial razonable para evitar reallocaciones tempranas
int initial_cap = 64;
if(initial_cap > InpMaxActiveFVG) initial_cap = InpMaxActiveFVG;
ArrayResize(g_fvgs, initial_cap);
g_fvg_count = 0;
g_next_id = 0;
// Limpieza de objetos antiguos por seguridad
CleanupChartObjects();
// Inicializar última barra para evitar disparar en el primer tick
datetime t[1];
if(CopyTime(_Symbol, g_tf, 0, 1, t) == 1)
g_last_bar_time = t[0];
// Escaneo histórico inicial (siembra)
InitialScan();
ChartRedraw(g_chart_id);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| OnDeinit |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Liberar gráficamente todos los objetos creados
CleanupChartObjects();
// Liberar memoria del array
ArrayFree(g_fvgs);
g_fvg_count = 0;
ChartRedraw(g_chart_id);
CMetricsSave::Destroy();
}
//+------------------------------------------------------------------+
//| OnTick — solo trabaja en nueva barra |
//+------------------------------------------------------------------+
void OnTick()
{
if(!IsNewBar())
return;
ProcessNewBar();
ChartRedraw(g_chart_id);
}