| //+------------------------------------------------------------------+
|
| //| 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"
|
| #include <TSN\\MQLArticles\\Utils\\TFManager.mqh>
|
|
|
|
|
| /* Codigo hecho por ia Claude Sonnet 4.6
|
| 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 |
|
| //+------------------------------------------------------------------+
|
| //+------------------------------------------------------------------+
|
| //| CFvgFast.mqh — Fair Value Gap · Zero-alloc · Flat arrays |
|
| //| Mismo estilo que CFvgBar: Create / Set / SetGraphic / OnNewBar |
|
| //| Sin clases internas · Sin new/delete · Arrays estáticos |
|
| //+------------------------------------------------------------------+
|
| #property strict
|
|
|
| #ifndef FVG_MAX
|
| #define FVG_MAX 200
|
| #endif
|
|
|
| //──────────────────────────────────────────────────────────────────
|
| // Zona FVG — 40 bytes, sin strings dinámicos
|
| //──────────────────────────────────────────────────────────────────
|
| struct FVGZone
|
| {
|
| double hi;
|
| double lo;
|
| datetime t;
|
| bool active;
|
| bool bullish;
|
| };
|
|
|
| //──────────────────────────────────────────────────────────────────
|
| // CFvgFast
|
| //──────────────────────────────────────────────────────────────────
|
| class CFvgFast
|
| {
|
| private:
|
| //--- arrays planos en el heap del objeto (no BSS, pero un solo alloc en Create)
|
| FVGZone m_bull[FVG_MAX];
|
| FVGZone m_bear[FVG_MAX];
|
|
|
| int m_bull_n;
|
| int m_bear_n;
|
| int m_bull_head; // circular overwrite
|
| int m_bear_head;
|
| long m_bull_total; // total insertados (para saber si circular)
|
| long m_bear_total;
|
|
|
| //--- nearest (actualizado 1x barra, leído N veces por EA)
|
| int m_near_bull;
|
| int m_near_bear;
|
|
|
| //--- config
|
| string m_symbol;
|
| ENUM_TIMEFRAMES m_tf;
|
| string m_prefix;
|
| int m_lookback;
|
| double m_min_gap; // en precio, ya convertido
|
| bool m_draw;
|
|
|
| //--- visual
|
| color m_col_bull;
|
| color m_col_bear;
|
| ENUM_LINE_STYLE m_style;
|
| bool m_fill;
|
| int m_border_w;
|
| bool m_extend;
|
| int m_extend_bars;
|
|
|
| //──────── helpers privados ────────
|
|
|
| string _Name(bool bull, int idx)
|
| {
|
| return m_prefix + (bull ? "B" : "S") + IntegerToString(idx);
|
| }
|
|
|
| void _DrawZone(int idx, bool bull)
|
| {
|
| if(!m_draw) return;
|
| FVGZone z = bull ? m_bull[idx] : m_bear[idx];
|
| if(!z.active) return;
|
|
|
| string nm = _Name(bull, idx);
|
| color c = bull ? m_col_bull : m_col_bear;
|
| datetime t1 = z.t;
|
| datetime t2 = m_extend
|
| ? z.t + (datetime)PeriodSeconds(m_tf) * m_extend_bars
|
| : 0;
|
|
|
| if(ObjectFind(0, nm) < 0)
|
| ObjectCreate(0, nm, OBJ_RECTANGLE, 0, t1, z.hi, t2, z.lo);
|
| else
|
| {
|
| ObjectSetInteger(0, nm, OBJPROP_TIME, 0, t1);
|
| ObjectSetInteger(0, nm, OBJPROP_TIME, 1, t2);
|
| ObjectSetDouble (0, nm, OBJPROP_PRICE, 0, z.hi);
|
| ObjectSetDouble (0, nm, OBJPROP_PRICE, 1, z.lo);
|
| }
|
| ObjectSetInteger(0, nm, OBJPROP_COLOR, c);
|
| ObjectSetInteger(0, nm, OBJPROP_STYLE, m_style);
|
| ObjectSetInteger(0, nm, OBJPROP_WIDTH, m_border_w);
|
| ObjectSetInteger(0, nm, OBJPROP_FILL, m_fill);
|
| ObjectSetInteger(0, nm, OBJPROP_BACK, true);
|
| ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
|
| ObjectSetInteger(0, nm, OBJPROP_HIDDEN, true);
|
| }
|
|
|
| void _DeleteZone(int idx, bool bull)
|
| {
|
| if(!m_draw) return;
|
| ObjectDelete(0, _Name(bull, idx));
|
| }
|
|
|
| void _Insert(const FVGZone &z)
|
| {
|
| if(z.bullish)
|
| {
|
| int i = m_bull_head;
|
| if(m_bull_total >= FVG_MAX) _DeleteZone(i, true);
|
| m_bull[i] = z;
|
| _DrawZone(i, true);
|
| m_bull_head = (m_bull_head + 1) % FVG_MAX;
|
| if(m_bull_n < FVG_MAX) m_bull_n++;
|
| m_bull_total++;
|
| }
|
| else
|
| {
|
| int i = m_bear_head;
|
| if(m_bear_total >= FVG_MAX) _DeleteZone(i, false);
|
| m_bear[i] = z;
|
| _DrawZone(i, false);
|
| m_bear_head = (m_bear_head + 1) % FVG_MAX;
|
| if(m_bear_n < FVG_MAX) m_bear_n++;
|
| m_bear_total++;
|
| }
|
| }
|
|
|
| //--- Detección O(1): lee 3 velas, evalúa gap
|
| void _Detect(int shift)
|
| {
|
| double hi[3], lo[3];
|
| datetime t[1];
|
| if(CopyHigh(m_symbol, m_tf, shift, 3, hi) < 3) return;
|
| if(CopyLow (m_symbol, m_tf, shift, 3, lo) < 3) return;
|
| if(CopyTime(m_symbol, m_tf, shift+1, 1, t) < 1) return;
|
|
|
| //--- Bull FVG: low[0] > high[2]
|
| if(lo[0] > hi[2] && (lo[0] - hi[2]) >= m_min_gap)
|
| {
|
| FVGZone z;
|
| z.hi = lo[0]; z.lo = hi[2]; z.t = t[0];
|
| z.active = true; z.bullish = true;
|
| _Insert(z);
|
| }
|
| //--- Bear FVG: high[0] < low[2]
|
| if(hi[0] < lo[2] && (lo[2] - hi[0]) >= m_min_gap)
|
| {
|
| FVGZone z;
|
| z.hi = lo[2]; z.lo = hi[0]; z.t = t[0];
|
| z.active = true; z.bullish = false;
|
| _Insert(z);
|
| }
|
| }
|
|
|
| //--- Mitigación O(n), solo comparaciones de double
|
| void _Mitigate(double price)
|
| {
|
| int n = (int)MathMin(m_bull_n, FVG_MAX);
|
| for(int i = 0; i < n; i++)
|
| {
|
| if(!m_bull[i].active) continue;
|
| if(price <= m_bull[i].hi && price >= m_bull[i].lo)
|
| {
|
| m_bull[i].active = false;
|
| _DeleteZone(i, true);
|
| }
|
| }
|
| n = (int)MathMin(m_bear_n, FVG_MAX);
|
| for(int i = 0; i < n; i++)
|
| {
|
| if(!m_bear[i].active) continue;
|
| if(price >= m_bear[i].lo && price <= m_bear[i].hi)
|
| {
|
| m_bear[i].active = false;
|
| _DeleteZone(i, false);
|
| }
|
| }
|
| }
|
|
|
| //--- Nearest O(n), 1x barra
|
| void _UpdateNearest(double price)
|
| {
|
| double best_b = DBL_MAX, best_s = DBL_MAX;
|
| m_near_bull = -1;
|
| m_near_bear = -1;
|
|
|
| int n = (int)MathMin(m_bull_n, FVG_MAX);
|
| for(int i = 0; i < n; i++)
|
| {
|
| if(!m_bull[i].active) continue;
|
| double d = MathAbs(price - (m_bull[i].hi + m_bull[i].lo) * 0.5);
|
| if(d < best_b) { best_b = d; m_near_bull = i; }
|
| }
|
| n = (int)MathMin(m_bear_n, FVG_MAX);
|
| for(int i = 0; i < n; i++)
|
| {
|
| if(!m_bear[i].active) continue;
|
| double d = MathAbs(price - (m_bear[i].hi + m_bear[i].lo) * 0.5);
|
| if(d < best_s) { best_s = d; m_near_bear = i; }
|
| }
|
| }
|
|
|
| public:
|
|
|
| CFvgFast() :
|
| m_bull_n(0), m_bear_n(0),
|
| m_bull_head(0), m_bear_head(0),
|
| m_bull_total(0), m_bear_total(0),
|
| m_near_bull(-1), m_near_bear(-1),
|
| m_lookback(500), m_min_gap(0.0),
|
| m_draw(true), m_fill(true),
|
| m_col_bull(clrLimeGreen), m_col_bear(clrCrimson),
|
| m_style(STYLE_SOLID), m_border_w(1),
|
| m_extend(true), m_extend_bars(40),
|
| m_prefix("FVG_") {}
|
|
|
| //──────────────────────────────────────────────────────────────
|
| // API pública — mismo estilo que CFvgBar
|
| //──────────────────────────────────────────────────────────────
|
|
|
| //--- 1. Create: asigna símbolo y timeframe
|
| void Create(string symbol, ENUM_TIMEFRAMES tf)
|
| {
|
| m_symbol = symbol;
|
| m_tf = tf;
|
| }
|
|
|
| //--- 2. SetGraphic: visual (equivalente a fvg.SetGrapich(...))
|
| void SetGraphic(color col_bull = clrLimeGreen,
|
| color col_bear = clrCrimson,
|
| ENUM_LINE_STYLE style = STYLE_SOLID,
|
| bool fill = true,
|
| int border_w = 1,
|
| bool extend = true,
|
| int extend_bars = 40,
|
| string prefix = "FVG_")
|
| {
|
| m_col_bull = col_bull;
|
| m_col_bear = col_bear;
|
| m_style = style;
|
| m_fill = fill;
|
| m_border_w = border_w;
|
| m_extend = extend;
|
| m_extend_bars = extend_bars;
|
| m_prefix = prefix;
|
| }
|
|
|
| //--- 3. Set: config lógica + arranca historial
|
| // min_gap_points: 0 = sin filtro
|
| void Set(int lookback = 500,
|
| double min_gap_pts = 0.0,
|
| bool draw = true)
|
| {
|
| m_draw = draw;
|
| m_lookback = lookback;
|
| m_min_gap = min_gap_pts * SymbolInfoDouble(m_symbol, SYMBOL_POINT);
|
|
|
| // Reset
|
| m_bull_n = m_bear_n = 0;
|
| m_bull_head = m_bear_head = 0;
|
| m_bull_total = m_bear_total = 0;
|
| m_near_bull = m_near_bear = -1;
|
|
|
| // Limpiar objetos anteriores
|
| for(int i = ObjectsTotal(0) - 1; i >= 0; i--)
|
| {
|
| string nm = ObjectName(0, i);
|
| if(StringFind(nm, m_prefix) == 0)
|
| ObjectDelete(0, nm);
|
| }
|
|
|
| // Carga histórica
|
| int bars = MathMin(m_lookback, Bars(m_symbol, m_tf) - 3);
|
| for(int s = bars; s >= 1; s--)
|
| _Detect(s);
|
|
|
| double price = SymbolInfoDouble(m_symbol, SYMBOL_BID);
|
| _Mitigate(price);
|
| _UpdateNearest(price);
|
| }
|
|
|
| //--- 4. OnNewBar: llama en cada cierre de vela
|
| void OnNewBar()
|
| {
|
| _Detect(1);
|
| double price = SymbolInfoDouble(m_symbol, SYMBOL_BID);
|
| _Mitigate(price);
|
| _UpdateNearest(price);
|
| }
|
|
|
| //--- 5. OnTick (opcional): solo mitigación intra-barra
|
| void OnTick()
|
| {
|
| double price = SymbolInfoDouble(m_symbol, SYMBOL_BID);
|
| _Mitigate(price);
|
| }
|
|
|
| //--- 6. Deinit
|
| void Deinit()
|
| {
|
| for(int i = ObjectsTotal(0) - 1; i >= 0; i--)
|
| {
|
| string nm = ObjectName(0, i);
|
| if(StringFind(nm, m_prefix) == 0)
|
| ObjectDelete(0, nm);
|
| }
|
| }
|
|
|
| //--- Acceso al nearest (lectura O(1))
|
| bool GetNearestBull(FVGZone &out)
|
| {
|
| if(m_near_bull < 0) return false;
|
| out = m_bull[m_near_bull];
|
| return out.active;
|
| }
|
|
|
| bool GetNearestBear(FVGZone &out)
|
| {
|
| if(m_near_bear < 0) return false;
|
| out = m_bear[m_near_bear];
|
| return out.active;
|
| }
|
|
|
| bool IsInsideBull(double price)
|
| {
|
| if(m_near_bull < 0) return false;
|
| return m_bull[m_near_bull].active &&
|
| price >= m_bull[m_near_bull].lo &&
|
| price <= m_bull[m_near_bull].hi;
|
| }
|
|
|
| bool IsInsideBear(double price)
|
| {
|
| if(m_near_bear < 0) return false;
|
| return m_bear[m_near_bear].active &&
|
| price >= m_bear[m_near_bear].lo &&
|
| price <= m_bear[m_near_bear].hi;
|
| }
|
|
|
| int CountBull() const { return m_bull_n; }
|
| int CountBear() const { return m_bear_n; }
|
| };
|
|
|
|
|
|
|
| //+------------------------------------------------------------------+
|
| int8_t idx_ctrl_d1 = INVALID_INDEX;
|
| int8_t idx_ctrl_curr = INVALID_INDEX;
|
|
|
| CFvgFast fvg; // ← mismo estilo que tu "CFvgBar fvg;"
|
| TSN::CNewBarManager g_new_bar_manager;
|
| //+------------------------------------------------------------------+
|
| int OnInit()
|
| {
|
| CMetricsSave::Start(FILE_CODE4);
|
|
|
| g_new_bar_manager.GetPosExecute(PERIOD_D1, idx_ctrl_d1);
|
| g_new_bar_manager.GetPosExecute(_Period, idx_ctrl_curr);
|
|
|
| //--- 1. Create
|
| fvg.Create(_Symbol, _Period);
|
|
|
| //--- 2. SetGraphic (colores, estilo, extensión)
|
| fvg.SetGraphic(
|
| clrLimeGreen, // bull
|
| clrCrimson, // bear
|
| STYLE_SOLID, // borde
|
| true, // fill
|
| 1, // border width
|
| true, // extend
|
| 40, // barras extensión
|
| "FVG_" // prefijo
|
| );
|
|
|
| //--- 3. Set (lookback, min gap en puntos, dibujar)
|
| fvg.Set(500, 0.0, true);
|
|
|
| Print("Init OK | Bull: ", fvg.CountBull(), " | Bear: ", fvg.CountBear());
|
| return INIT_SUCCEEDED;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| void OnDeinit(const int reason)
|
| {
|
| fvg.Deinit();
|
| CMetricsSave::Destroy();
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| void OnTick()
|
| {
|
| const datetime curr_time = TimeCurrent();
|
| g_new_bar_manager.Execute(curr_time);
|
|
|
| if(CNewBarManager_IsNewBarM1(g_new_bar_manager) &&
|
| CNewBarManager_IsNewBar(g_new_bar_manager, idx_ctrl_curr))
|
| {
|
| const bool new_day = CNewBarManager_IsNewBar(g_new_bar_manager, idx_ctrl_d1);
|
| fvg.OnNewBar(); // ← idéntico a tu "fvg.OnNewBar();"
|
| }
|
| }
|