English Русский Deutsch 日本語
preview
Potencie su operativa con Smart Money Concepts (SMC): OB, BOS y FVG

Potencie su operativa con Smart Money Concepts (SMC): OB, BOS y FVG

MetaTrader 5 — Ejemplos |
31 12
Hlomohang John Borotho
Hlomohang John Borotho

Introducción

A veces, los operadores se enfrentan al reto de la falta de coherencia en sus estrategias, y suelen alternar entre indicadores, configuraciones y métodos sin contar con un marco sólido que les sirva de guía. Dicha falta de estructura puede dar lugar a confusión, oportunidades desaprovechadas y una frustración creciente a medida que cambian las condiciones del mercado mientras las herramientas tradicionales dejan de adaptarse con la misma eficacia. En el presente artículo, analizaremos las ventajas de un único EA que unifica varias estrategias de Smart Money Concepts (SMC) en una potente solución.

Los Smart Money Concepts (SMC) ofrecen una forma estructurada de comprender el comportamiento del mercado mediante bloques de órdenes (OB), ruptura de estructura (BOS) y gaps de valor razonable (FVG). Al combinarlos en un único EA, los operadores pueden simplificar su flujo de trabajo, automatizar la toma de decisiones y centrarse en las señales de acción del precio más potentes. Tanto si se utiliza el modo automático para una ejecución fluida como si se seleccionan conceptos individuales, este enfoque reduce las conjeturas y hace que la operativa sea más eficiente, coherente y adaptable a las condiciones cambiantes del mercado.



Lógica del EA

Bloque de órdenes:

Un bloque de órdenes (OB) representa la última vela alcista o bajista antes de un movimiento significativo del mercado, lo que a menudo indica dónde han colocado sus órdenes los operadores institucionales. Cuando el precio vuelve a visitar estas zonas, estas suelen actuar como áreas sólidas de soporte o resistencia, lo que ofrece escenarios operativos de alta probabilidad. En este EA, la detección de bloques de órdenes se sigue mejorando mediante la integración del indicador de Fibonacci para la validación, lo cual garantiza que las operaciones se alineen con niveles clave de retroceso o extensión. Tal y como comentamos en el artículo anterior, combinar los OB con filtros de confirmación de Fibonacci permite descartar las configuraciones más débiles, lo que ofrece a los operadores un marco más fiable para abrir y gestionar sus operaciones.

Gap de valor razonable:

Un gap de valor razonable (FVG) se produce cuando existe un desequilibrio en el movimiento del precio, normalmente creado por un fuerte movimiento impulsivo en el que una o varias velas dejan un gap entre las mechas de las velas anterior y posterior. Este gap refleja una ineficiencia del mercado en la que no todas las órdenes pudieron ejecutarse o compensarse por completo, y el precio suele volver para cubrir el gap antes de continuar en la dirección original del movimiento. Al detectar las FVG y operar en torno a ellas, los operadores pueden anticipar posibles retrocesos hacia estas zonas, lo que ofrece oportunidades de entrada precisas con una relación riesgo-beneficio bien definida.

Ruptura de estructura:

Una ruptura de estructura (BOS) se produce cuando el precio rompe por encima de un máximo de oscilación anterior o por debajo de un mínimo de oscilación anterior, lo que indica un posible cambio en la dirección del mercado. En el artículo anterior, nos centramos en vender cuando se rompía un máximo de oscilación, aprovechando el impulso bajista. No obstante, en este enfoque, la lógica del BOS se ha perfeccionado para alinearse con la tendencia predominante del mercado, lo que significa que, si se rompe un máximo de oscilación, ahora buscamos comprar, confirmando la fortaleza alcista, mientras que la ruptura de un mínimo de oscilación señala oportunidades de venta. Este cambio garantiza que las operaciones sigan el flujo dominante del mercado en lugar de ir en contra de él.

Arquitectura del sistema:



Primeros pasos

//+------------------------------------------------------------------+
//|                                                 SMC_ALL_IN_1.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/en/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/johnhlomohang/"
#property version   "1.01"
#property description "Unified SMC: FVG + Order Blocks + BOS. Detect + Draw + Trade."

#include <Trade/Trade.mqh>
#include <Trade/PositionInfo.mqh>

CTrade         trade;
CPositionInfo  pos;

enum ENUM_STRATEGY
{
   STRAT_OB,         // Use Order Blocks Only
   STRAT_FVG,        // Use FVGs Only
   STRAT_BOS,        // Use Break of Structure Only
   STRAT_AUTO        // Auto (All SMC Concepts)
};
enum SWING_TYPE{
   SWING_OB,
   SWING_BOS,
};

El código incluye las bibliotecas de trading de MetaTrader, concretamente CTrade y CPositionInfo, que permiten al EA abrir y gestionar operaciones, así como acceder a información sobre las posiciones abiertas. Al configurar estas clases desde el principio, el EA se asegura de disponer de las herramientas esenciales necesarias para la ejecución de órdenes y el seguimiento de posiciones.

En la siguiente parte se definen la flexibilidad estratégica y la clasificación de los swings mediante enumeraciones. ENUM_STRATEGY ofrece a los operadores la posibilidad de elegir qué método debe seguir el EA: exclusivamente bloques de órdenes, exclusivamente FVG, exclusivamente BOS o un modo totalmente automatizado (STRAT_AUTO) que utiliza los tres conceptos de forma dinámica. Del mismo modo, la enumeración SWING_TYPE distingue entre los puntos de oscilación utilizados para los bloques de órdenes y los utilizados para el análisis de ruptura de estructura. Esta estructura hace que el código sea modular y adaptable, lo cual permite a los operadores probar diferentes enfoques de SMC o dejar que el EA decida automáticamente en función de las condiciones del mercado.

//----------------------------- Inputs ------------------------------//
input ENUM_STRATEGY TradeStrategy   = STRAT_AUTO;
input double        In_Lot          = 0.02;
input double        StopLoss        = 3500;   // points
input double        TakeProfit      = 7500;   // points
input long          MagicNumber     = 76543;

input int           SwingPeriod     = 5;      // bars each side to confirm swing
input int           SwingProbeBar   = 5;      // bar index we test for swings (>=SwingPeriod)
input double        Fib_Trade_lvls  = 61.8;   // OB retrace must reach this %
input bool          DrawBOSLines    = true;

input int           FVG_MinPoints   = 3;      // minimal gap in points
input int           FVG_ScanBars    = 20;     // how many bars to scan for FVGs
input bool          FVG_TradeAtEQ   = true;   // trade at 50% of the gap (EQ)
input bool          OneTradePerBar  = true;

//---------------------------- Colors -------------------------------//
#define BullOB   clrLime
#define BearOB   clrRed
#define BullFVG  clrPaleGreen
#define BearFVG  clrMistyRose
#define BOSBull  clrDodgerBlue
#define BOSBear  clrTomato

En esta sección del código se definen los parámetros que los operadores pueden personalizar para adaptarlos a su estilo de negociación y a sus preferencias en materia de gestión de riesgos. El parámetro TradeStrategy permite al usuario seleccionar si el EA debe operar utilizando únicamente bloques de órdenes, únicamente gaps de valor razonable, únicamente rupturas de estructura o combinando automáticamente los tres. Los parámetros de gestión del riesgo y del capital se controlan mediante In_Lot, StopLoss, TakeProfit y MagicNumber, que garantizan un tamaño adecuado de los lotes, los stops de protección, los objetivos de beneficios y la identificación única de cada operación. Además, los parámetros de detección de oscilaciones, como SwingPeriod y SwingProbeBar, especifican cuántas barras se tienen en cuenta al identificar máximos y mínimos de oscilación, mientras que Fib_Trade_lvls garantiza que las operaciones con bloques de órdenes cuenten con confirmación por retroceso de Fibonacci. La opción DrawBOSLines ofrece flexibilidad para la visualización del gráfico, lo que permite a los operadores ver los niveles de ruptura de estructura directamente en el gráfico.

A continuación se presentan los parámetros del gap de valor razonable (FVG), que permiten un control más preciso sobre cómo se detectan y se operan dichas brechas. FVG_MinPoints garantiza que solo se tengan en cuenta los desequilibrios significativos, mientras que FVG_ScanBars determina hasta dónde buscará el EA en el historial para encontrar brechas válidas. La opción FVG_TradeAtEQ permite operar en el nivel de equilibrio (el 50 % de la brecha), que a menudo se considera un punto de entrada equilibrado según la teoría SMC. Por último, las definiciones de colores como BullOB, BearOB, BullFVG, BearFVG, BOSBull y BOSBear hacen que los objetos de los gráficos sean visualmente intuitivos, lo cual permite a los operadores distinguir rápidamente, de un solo vistazo, los escenarios alcistas de las bajistas.

//---------------------------- Globals ------------------------------//
double   Bid, Ask;
datetime g_lastBarTime = 0;

// OB state
class COrderBlock : public CObject
{
public:
   int      direction;   // +1 bullish, -1 bearish
   datetime time;        // OB candle time
   double   high;        // OB candle high
   double   low;         // OB candle low

   string Key() const { return TimeToString(time, TIME_DATE|TIME_MINUTES); }

   void draw(datetime tmS, datetime tmE, color clr){
      string objOB = " OB REC" + TimeToString(time);
      ObjectCreate( 0, objOB, OBJ_RECTANGLE, 0, time, low, tmS, high);
      ObjectSetInteger( 0, objOB, OBJPROP_FILL, true);
      ObjectSetInteger( 0, objOB, OBJPROP_COLOR, clr);
      
      string objtrade = " OB trade" + TimeToString(time);
      ObjectCreate( 0, objtrade, OBJ_RECTANGLE, 0, tmS, high, tmE, low); // trnary operator
      ObjectSetInteger( 0, objtrade, OBJPROP_FILL, true);
      ObjectSetInteger( 0, objtrade, OBJPROP_COLOR, clr);
   }
};
COrderBlock* OB = NULL;

// OB fib state
// Track if an OB has already been traded
datetime lastTradedOBTime = 0;
bool tradedOB = false;
double fib_low, fib_high;
datetime fib_t1, fib_t2;
bool isBullishOB = false; 
bool isBearishOB = false;
datetime T1;
datetime T2;
color OBClr;
#define FIB_OB_BULL "FIB_OB_BULL"
#define FIB_OB_BEAR "FIB_OB_BEAR"
#define FIBO_OBJ "Fibo Retracement"

// BOS state 
datetime lastBOSTradeTime = 0;
bool Bull_BOS_traded, Bear_BOS_traded;
int lastBOSTradeDirection = 0; // 1 for buy, -1 for sell
double   swng_High = -1.0, swng_Low = -1.0;
datetime bos_tH = 0, bos_tL = 0;

En esta sección global del código se establecen las variables y estructuras principales que permiten hacer seguimiento del estado del mercado, las oportunidades de trading y los objetos del gráfico. Comienza con valores globales sencillos como Bid, Ask y g_lastBarTime, esenciales para el seguimiento de los precios en tiempo real y para garantizar que el EA solo ejecute la lógica una vez por cada nueva barra. A partir de ahí, se crea una clase especializada, COrderBlock, para encapsular las propiedades de un bloque de órdenes, incluida su dirección (alcista o bajista), la hora y los niveles de precio (máximo/mínimo). La clase también contiene una función draw(), que crea y colorea automáticamente rectángulos en el gráfico, lo que facilita a los operadores la identificación visual de los bloques de órdenes activos y sus zonas de negociación. Centralizando estas funciones en una clase, el código se mantiene organizado y reutilizable.

Además de la identificación básica de bloques de órdenes (OB), se definen variables adicionales para gestionar la validación basada en Fibonacci de los bloques de órdenes. Por ejemplo, fib_low, fib_high y los marcadores temporales fib_t1 y fib_t2 almacenan los límites de los niveles de retroceso de Fibonacci, mientras que indicadores como isBullishOB, isBearishOB y tradedOB garantizan que cada bloque de órdenes se valide y se opere una sola vez. Estas variables permiten al EA hacer referencia a los niveles de Fibonacci de forma dinámica, lo que garantiza que solo se tengan en cuenta los retrocesos válidos (como el nivel del 61,8 % definido en los parámetros) a la hora de abrir posiciones. Para que la visualización resulte más clara, constantes como FIB_OB_BULL, FIB_OB_BEAR y FIBO_OBJ definen los nombres de los objetos del gráfico, lo que garantiza que los retrocesos de Fibonacci se puedan distinguir cuando se trazan en el gráfico.

Por último, se introduce la gestión del estado de ruptura de estructura (BOS). Las variables «lastBOSTradeTime», «Bull_BOS_traded» y «Bear_BOS_traded» evitan que se ejecuten operaciones duplicadas cuando se produce una señal BOS. La variable lastBOSTradeDirection registra si la última operación de BOS fue alcista (+1) o bajista (–1), mientras que swng_High y swng_Low almacenan los niveles de oscilación más recientes para la detección de la estructura. Se incluyen los marcadores temporales bos_tH y bos_tL para identificar los momentos exactos en los que se confirmaron los puntos de oscilación, lo que garantiza la alineación con las reglas de la estructura del mercado. Al gestionar estos estados de forma global, el EA garantiza la coherencia entre las distintas estrategias (bloques de órdenes, BOS, FVG) y evita el solapamiento de operaciones, creando así una base estructurada para la automatización del trading con Smart Money Concepts.

//--------------------------- Helpers -------------------------------//
double  getHigh(int i)   { return iHigh(_Symbol, _Period, i);  }
double  getLow(int i)    { return iLow(_Symbol, _Period, i);   }
double  getOpen(int i)   { return iOpen(_Symbol, _Period, i);  }
double  getClose(int i)  { return iClose(_Symbol, _Period, i); }
datetime getTimeBar(int i){ return iTime(_Symbol, _Period, i); }

bool IsNewBar()
{
   datetime lastbar_time = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
   if(g_lastBarTime == 0) { g_lastBarTime = lastbar_time; return false; }
   if(g_lastBarTime != lastbar_time) { g_lastBarTime = lastbar_time; return true; }
   return false;
}

void ExecuteTrade(ENUM_ORDER_TYPE type)
{
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double price = (type==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                                         : SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double sl = (type==ORDER_TYPE_BUY) ? price - StopLoss*point
                                      : price + StopLoss*point;
   double tp = (type==ORDER_TYPE_BUY) ? price + TakeProfit*point
                                      : price - TakeProfit*point;
   sl = NormalizeDouble(sl, _Digits);
   tp = NormalizeDouble(tp, _Digits);

   trade.SetExpertMagicNumber(MagicNumber);
   trade.PositionOpen(_Symbol, type, In_Lot, price, sl, tp, "SMC");
}

Las funciones auxiliares que se muestran aquí ofrecen una forma clara y reutilizable de interactuar con los datos de los gráficos. En lugar de llamar directamente a funciones integradas como iHigh, iLow o iClose en todo el código, las funciones envoltorio como getHigh(), getLow() y getClose() facilitan el acceso a la información de las barras y mejoran la legibilidad. La función IsNewBar() desempeña un papel fundamental a la hora de garantizar que el EA solo procese la lógica una vez por vela, evitando ejecuciones repetidas dentro de la misma barra. Para ello, almacena la última hora conocida de la barra en g_lastBarTime y la compara con la marca temporal de la barra actual. Este enfoque eficaz garantiza que las señales y las operaciones solo se consideren cuando se abre una nueva barra, lo cual es esencial para las estrategias que se basan en cierres confirmados de velas.

La función ExecuteTrade() encapsula toda la lógica de ejecución de operaciones. Calcula automáticamente el precio de entrada correcto y los niveles de stop loss y take profit según el tipo de orden (compra o venta), al tiempo que garantiza que los valores se normalicen para ajustarse a la precisión decimal del instrumento. Al centralizar esta lógica, el EA evita el código repetitivo y reduce los errores a la hora de abrir operaciones. La función también establece el MagicNumber único del EA para su seguimiento y etiqueta cada operación con el comentario «SMC», lo que permite a los traders identificar fácilmente las posiciones abiertas por este sistema.

//----------------------- Unified Swing Detection -------------------//
// Detects if barIndex is a swing high and/or swing low using len bars on each side.
// If swing high found -> updates fib_high/fib_tH (and swng_High/bos_tH if for BOS).
// If swing low  found -> updates fib_low/fib_tL (and swng_Low/bos_tL if for BOS).
// return: true if at least one swing found.
void DetectSwingForBar(int barIndex, SWING_TYPE type)
{
   const int len = 5;
   bool isSwingH = true, isSwingL = true;   

   for(int i = 1; i <= len; i++){
      int right_bars = barIndex - i;
      int left_bars  = barIndex + i;
      
      if(right_bars < 0) {
         isSwingH = false;
         isSwingL = false;
         break;
      }
      
      if((getHigh(barIndex) <= getHigh(right_bars)) || (left_bars < Bars(_Symbol, _Period) && getHigh(barIndex) < getHigh(left_bars)))
         isSwingH = false;
      
      if((getLow(barIndex) >= getLow(right_bars)) || (left_bars < Bars(_Symbol, _Period) && getLow(barIndex) > getLow(left_bars)))
         isSwingL = false;
   }

   // Assign with ternary operator depending on swing type
   if(isSwingH){
      if(type == SWING_OB) {
         fib_high = getHigh(barIndex);
         fib_t1 = getTimeBar(barIndex);
      } else {
         swng_High = getHigh(barIndex);
         bos_tH = getTimeBar(barIndex);
      }
   }
   if(isSwingL){
      if(type == SWING_OB) {
         fib_low = getLow(barIndex);
         fib_t2 = getTimeBar(barIndex);
      } else {
         swng_Low = getLow(barIndex);
         bos_tL = getTimeBar(barIndex);
      }
   }
}

La función unificada de detección de oscilaciones está diseñada para identificar posibles máximos y mínimos de oscilación en el gráfico, analizando una barra determinada y comparándola con un número fijo de barras adyacentes. Utilizando un rango simétrico (len barras a cada lado), la función comprueba si la barra actual forma un extremo local, es decir, si su máximo es superior al de las barras de la izquierda y de la derecha (máximo de oscilación), o si su mínimo es inferior al de ambos lados (mínimo de oscilación). Si alguna barra a la izquierda o a la derecha invalida estas condiciones, se descarta la designación como oscilación. Este método garantiza que las oscilaciones no se activen erróneamente debido a fluctuaciones menores, centrándose en cambio en puntos de pivote significativos de la estructura del mercado.

Una vez confirmada una oscilación, la función actualiza las variables clave en función del tipo de oscilación. Para la detección de bloques de órdenes (SWING_OB), los máximos y mínimos de oscilación se asignan a puntos de anclaje de Fibonacci (fib_high y fib_low) junto con sus marcas de tiempo, que posteriormente se utilizarán para validar los retrocesos. En la lógica de ruptura de estructura (BOS), se asignan los mismos niveles de oscilación a swng_High o swng_Low, junto con sus respectivas marcas de tiempo (bos_tH o bos_tL). Gestionando tanto OB como BOS en una única función, el EA optimiza la detección de oscilaciones y evita la lógica redundante, lo que garantiza que tanto la validación de la estructura como los escenarios de retroceso de Fibonacci utilicen el mismo proceso coherente de identificación de oscilaciones.

void DetectAndDrawOrderBlocks()
{
   static datetime lastDetect = 0;
   datetime lastBar = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
   
   // Reset OB detection on new bar
   if(lastDetect != lastBar)
   {
      if(OB != NULL) 
      { 
         delete OB; 
         OB = NULL; 
      }
      lastDetect = lastBar;
   }
   
   // Only detect new OB if we don't have one already
   if(OB == NULL)
   {
      for(int i = 1; i < 100; i++)
      {
         // Bullish OB candidate
         if(getOpen(i) < getClose(i) && 
            getOpen(i+2) < getClose(i+2) &&
            getOpen(i+3) > getClose(i+3) && 
            getOpen(i+3) < getClose(i+2))
         {
            OB = new COrderBlock();
            OB.direction = 1;
            OB.time = getTimeBar(i+3);
            OB.high = getHigh(i+3);
            OB.low = getLow(i+3);
            OBClr = BullOB;
            T1 = OB.time;
            Print("Bullish Order Block detected at: ", TimeToString(OB.time));
            break;
         }
         
         // Bearish OB candidate
         if(getOpen(i) > getClose(i) && 
            getOpen(i+2) > getClose(i+2) &&
            getOpen(i+3) < getClose(i+3) && 
            getOpen(i+3) > getClose(i+2)) // Fixed condition
         {
            OB = new COrderBlock();
            OB.direction = -1;
            OB.time = getTimeBar(i+3);
            OB.high = getHigh(i+3);
            OB.low = getLow(i+3);
            OBClr = BearOB;
            T1 = OB.time;
            Print("Bearish Order Block detected at: ", TimeToString(OB.time));
            break;
         }
      }
   }

   if(OB == NULL) return;
   
   // Check if we already traded this OB
   if(lastTradedOBTime == OB.time) return;

   // If price retraces inside OB zone
   Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

   bool inBullZone = (OB.direction > 0 && Ask <= OB.high && Ask >= OB.low);
   bool inBearZone = (OB.direction < 0 && Bid >= OB.low && Bid <= OB.high);

   if(!inBullZone && !inBearZone) return;

   // Use your DetectSwing function to find swings
   // We need to call it multiple times to find the most recent swings
   double mostRecentSwingHigh = 0;
   double mostRecentSwingLow = EMPTY_VALUE;
   datetime mostRecentSwingHighTime = 0;
   datetime mostRecentSwingLowTime = 0;
   
   // Scan recent bars to find the most recent swings
   for(int i = 0; i < 20; i++) // Check the last 20 bars
   {
      // Reset swing variables
      fib_high = 0;
      fib_low = 0;
      fib_t1 = 0;
      fib_t2 = 0;
      
      DetectSwingForBar(i, SWING_OB);
      
      if(fib_high > 0 && (mostRecentSwingHighTime == 0 || fib_t1 > mostRecentSwingHighTime))
      {
         mostRecentSwingHigh = fib_high;
         mostRecentSwingHighTime = fib_t1;
      }
      
      if(fib_low < EMPTY_VALUE && (mostRecentSwingLowTime == 0 || fib_t2 > mostRecentSwingLowTime))
      {
         mostRecentSwingLow = fib_low;
         mostRecentSwingLowTime = fib_t2;
      }
   }
   
   // Ensure we found both swing points
   if(mostRecentSwingHighTime == 0 || mostRecentSwingLowTime == 0) return;
   
   // Draw Fibonacci before trading to validate
   if(OB.direction > 0 && inBullZone)
   {
      // Draw Fibonacci from recent swing low to recent swing high
      ObjectDelete(0, "FIB_OB_BULL");
      if(ObjectCreate(0, "FIB_OB_BULL", OBJ_FIBO, 0, mostRecentSwingLowTime, mostRecentSwingLow, 
                     mostRecentSwingHighTime, mostRecentSwingHigh))
      {
         // Format Fibonacci
         ObjectSetInteger(0, "FIB_OB_BULL", OBJPROP_COLOR, clrBlack);
         for(int i = 0; i < ObjectGetInteger(0, "FIB_OB_BULL", OBJPROP_LEVELS); i++)
         {
            ObjectSetInteger(0, "FIB_OB_BULL", OBJPROP_LEVELCOLOR, i, clrBlack);
         }
         
         double entLvlBull = mostRecentSwingHigh - (mostRecentSwingHigh - mostRecentSwingLow) * (Fib_Trade_lvls / 100.0);
         
         if(Ask <= entLvlBull)
         {
            T2 = getTimeBar(0);
            OB.draw(T1, T2, BullOB);
            ExecuteTrade(ORDER_TYPE_BUY);
            lastTradedOBTime = OB.time; // Mark this OB as traded
            delete OB;
            OB = NULL;
         }
      }
   }
   else if(OB.direction < 0 && inBearZone)
   {
      // Draw Fibonacci from recent swing high to recent swing low
      ObjectDelete(0, "FIB_OB_BEAR");
      if(ObjectCreate(0, "FIB_OB_BEAR", OBJ_FIBO, 0, mostRecentSwingHighTime, mostRecentSwingHigh, 
                     mostRecentSwingLowTime, mostRecentSwingLow))
      {
         // Format Fibonacci
         ObjectSetInteger(0, "FIB_OB_BEAR", OBJPROP_COLOR, clrBlack);
         for(int i = 0; i < ObjectGetInteger(0, "FIB_OB_BEAR", OBJPROP_LEVELS); i++)
         {
            ObjectSetInteger(0, "FIB_OB_BEAR", OBJPROP_LEVELCOLOR, i, clrBlack);
         }
         
         double entLvlBear = mostRecentSwingLow + (mostRecentSwingHigh - mostRecentSwingLow) * (Fib_Trade_lvls / 100.0);
         
         if(Bid >= entLvlBear)
         {
            T2 = getTimeBar(0);
            OB.draw(T1, T2, BearOB);
            ExecuteTrade(ORDER_TYPE_SELL);
            lastTradedOBTime = OB.time; // Mark this OB as traded
            delete OB;
            OB = NULL;
         }
      }
   }
}

La función DetectAndDrawOrderBlocks() se encarga de identificar, validar y operar con bloques de órdenes, al tiempo que integra la confluencia de Fibonacci en el proceso de toma de decisiones. Empieza restableciendo la detección en cada nueva barra y, a continuación, analiza las velas recientes en busca de patrones de bloques de órdenes alcistas o bajistas. Una vez que se encuentra un bloque de órdenes válido, comprueba si el precio actual retrocede hasta esa zona, lo que indicaría una posible oportunidad de operación. Antes de ejecutar una operación, la función recurre a la detección de oscilaciones para localizar el máximo y el mínimo de oscilación más recientes, asegurándose de que se pueda trazar un retroceso de Fibonacci entre ambos.

A continuación, se utiliza la herramienta de Fibonacci para validar la entrada, lo que requiere que el precio se alinee con un nivel de retroceso predefinido antes de confirmar la operación. De este modo, el sistema evita entradas prematuras y garantiza que las operaciones se ejecuten únicamente cuando coincidan tanto la zona del bloque de órdenes como el retroceso de Fibonacci, lo que refuerza la precisión y la coherencia en la validación de las operaciones.

//============================== FVG ================================//
// Definition (ICT-style):
// Let C=i, B=i+1, A=i+2.
// Bullish FVG if Low(A) > High(C) -> gap [High(C), Low(A)]
// Bearish FVG if High(A) < Low(C) -> gap [High(A), Low(C)]
struct SFVG
{
   int      dir;    // +1 bull, -1 bear
   datetime tLeft;  // left time anchor
   double   top;    // zone top price
   double   bot;    // zone bottom price

   string Name() const
   {
      string k = TimeToString(tLeft, TIME_DATE|TIME_MINUTES);
      return (dir>0 ? "FVG_B_" : "FVG_S_") + k + "_" + IntegerToString((int)(top*1000.0));
   }
};

bool FVGExistsAt(const string &name){ return ObjectFind(0, name) != -1; }

void DetectAndDrawFVGs()
{
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   int counted = 0;

   for(int i=2; i<MathMin(FVG_ScanBars, Bars(_Symbol, _Period))-2; i++)
   {
      // Build A,B,C
      double lowA  = getLow(i+2);
      double highA = getHigh(i+2);
      double highC = getHigh(i);
      double lowC  = getLow(i);

      // Bullish FVG: Low of A > High of C
      if(lowA > highC && (lowA - highC >= FVG_MinPoints * point))
      {
         SFVG z;
         z.dir   = +1;
         z.tLeft = getTimeBar(i+2);  // Changed from getTimeBar to getTime
         z.top   = lowA;
         z.bot   = highC;
         DrawFVG(z);
         counted++;
      }
      // Bearish FVG: High of A < Low of C
      else if(highA < lowC && (lowC - highA >= FVG_MinPoints * point))
      {
         SFVG z;
         z.dir   = -1;
         z.tLeft = getTimeBar(i+2);  // Changed from getTimeBar to getTime
         z.top   = lowC;          // Fixed: should be lowC for bearish FVG top
         z.bot   = highA;         // Fixed: should be highA for bearish FVG bottom
         DrawFVG(z);
         counted++;
      }
      
      if(counted > 15) break; // avoid clutter
   }

   // --- Simplified trading for FVGs ---
   Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

   // scan drawn objects and trade on first valid touch of EQ (50%)
   int total = ObjectsTotal(0, 0, -1);
   static datetime lastTradeBar = 0;
   
   if(OneTradePerBar)
   {
      datetime barNow = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
      if(lastTradeBar == barNow) return; // already traded this bar
   }

   for(int idx=0; idx<total; idx++)
   {
      string name = ObjectName(0, idx);
      if(StringFind(name, "FVG_", 0) != 0) continue; // only our FVGs

      // Get object coordinates
      datetime t1 = (datetime)ObjectGetInteger(0, name, OBJPROP_TIME, 0);
      double y1 = ObjectGetDouble(0, name, OBJPROP_PRICE, 0);
      datetime t2 = (datetime)ObjectGetInteger(0, name, OBJPROP_TIME, 1);
      double y2 = ObjectGetDouble(0, name, OBJPROP_PRICE, 1);

      double top = MathMax(y1, y2);
      double bot = MathMin(y1, y2);
      bool isBull = (StringFind(name, "FVG_B_", 0) == 0);
      double mid  = (top + bot) * 0.5;

      if(isBull)
      {
         // trade when Ask is inside the gap and at/under EQ
         if(Ask <= top && Ask >= bot && (!FVG_TradeAtEQ || Ask <= mid))
         {
            ExecuteTrade(ORDER_TYPE_BUY);
            lastTradeBar = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
            break;
         }
      }
      else
      {
         // trade when Bid is inside the gap and at/over EQ
         if(Bid <= top && Bid >= bot && (!FVG_TradeAtEQ || Bid >= mid))
         {
            ExecuteTrade(ORDER_TYPE_SELL);
            lastTradeBar = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
            break;
         }
      }
   }
}

Esta función detecta, representa gráficamente y opera con gaps de valor razonable (FVG) basándose en la lógica ICT. En primer lugar, analiza las barras recientes para identificar las brechas alcistas —en las que el mínimo de la vela A es superior al máximo de la vela C— y las brechas bajistas —en las que el máximo de la vela A es inferior al mínimo de la vela C—, siempre que el gap alcance un tamaño mínimo en puntos. Una vez detectado un FVG, se almacena en una estructura, se dibuja en el gráfico y luego se supervisa con fines operativos. A continuación, el sistema supervisa las zonas FVG activas, comprobando cuándo el precio entra en el gap y se alinea con el nivel de equilibrio (el 50 % de la zona), si está habilitado. Cuando se cumplen las condiciones, la función ejecuta una operación: compra en los FVG alcistas y vende en los FVG bajistas, al tiempo que garantiza que solo se realice una operación por barra para evitar la sobreoperación. Esta combinación de detección, visualización y ejecución hace que la herramienta FVG sea tanto analítica como apta para operar directamente.

//=============================== BOS ===============================//
// Use unified swings (no RSI). Trading logic mirrors your earlier code:
// - Sell when price breaks above last swing high (liquidity run idea)
// - Buy  when price breaks below last swing low
void DetectAndDrawBOS()
{
   // Use DetectSwingForBar to find the most recent swing points
   double mostRecentSwingHigh = 0;
   double mostRecentSwingLow = EMPTY_VALUE;
   datetime mostRecentSwingHighTime = 0;
   datetime mostRecentSwingLowTime = 0;
   
   // Scan recent bars to find the most recent swings for BOS
   for(int i = 0; i < 20; i++) // Check the last 20 bars
   {
      // Reset swing variables
      swng_High = 0;
      swng_Low = 0;
      bos_tH = 0;
      bos_tL = 0;
      
      // Detect swing at this bar for BOS
      DetectSwingForBar(i, SWING_BOS);
      
      if(swng_High > 0 && (mostRecentSwingHighTime == 0 || bos_tH > mostRecentSwingHighTime))
      {
         mostRecentSwingHigh = swng_High;
         mostRecentSwingHighTime = bos_tH;
      }
      
      if(swng_Low < EMPTY_VALUE && (mostRecentSwingLowTime == 0 || bos_tL > mostRecentSwingLowTime))
      {
         mostRecentSwingLow = swng_Low;
         mostRecentSwingLowTime = bos_tL;
      }
   }
   
   // Update the global BOS variables with the most recent swings
   if(mostRecentSwingHighTime > 0)
   {
      if(mostRecentSwingHighTime != bos_tH)
         Bull_BOS_traded = false;
      swng_High = mostRecentSwingHigh;
      bos_tH = mostRecentSwingHighTime;
   }
   
   if(mostRecentSwingLowTime > 0)
   {
      if(mostRecentSwingLowTime != bos_tL)
         Bear_BOS_traded = false;
      swng_Low = mostRecentSwingLow;
      bos_tL = mostRecentSwingLowTime;
   }
   
   // Now check for break of structure
   Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   
   // Get current bar time to prevent multiple trades on same bar
   datetime currentBarTime = iTime(_Symbol, _Period, 0);
   
   // SELL on break above swing high
   if(swng_High > 0 && Ask > swng_High && Bull_BOS_traded == false)
   {
      // Check if we haven't already traded this breakout
      if(lastBOSTradeTime != currentBarTime || lastBOSTradeDirection != -1)
      {
         if(DrawBOSLines)
            DrawBOS("BOS_H_" + TimeToString(bos_tH), bos_tH, swng_High,
                    TimeCurrent(), swng_High, BOSBear, -1);
         
         ExecuteTrade(ORDER_TYPE_BUY);
         
         // Update trade tracking
         lastBOSTradeTime = currentBarTime;
         lastBOSTradeDirection = -1;
         Bull_BOS_traded = true;
         
         // Reset the swing high to prevent immediate re-trading
         swng_High = -1.0;
      }
   }
   
   // BUY on break below swing low
   if(swng_Low > 0 && Bid < swng_Low && Bear_BOS_traded == false)
   {
      // Check if we haven't already traded this breakout
      if(lastBOSTradeTime != currentBarTime || lastBOSTradeDirection != 1)
      {
         if(DrawBOSLines)
            DrawBOS("BOS_L_" + TimeToString(bos_tL), bos_tL, swng_Low,
                    TimeCurrent(), swng_Low, BOSBull, +1);
         
         ExecuteTrade(ORDER_TYPE_SELL);
         
         // Update trade tracking
         Bear_BOS_traded = true;
         lastBOSTradeTime = currentBarTime;
         lastBOSTradeDirection = 1;
         
         // Reset the swing low to prevent immediate re-trading
         swng_Low = -1.0;
      }
   }   
}
La función BOS detecta y opera eventos de ruptura de estructura (BOS) usando una lógica de oscilación unificada. Analiza las últimas 20 barras para identificar el máximo de oscilación y el mínimo de oscilación más recientes, actualiza las variables globales de BOS y garantiza que no se repitan operaciones en la misma barra. Si el precio rompe por encima del último máximo de oscilación, se ejecuta una orden de compra (barrido de liquidez por encima de los máximos), y si el precio rompe por debajo del último mínimo de oscilación, se ejecuta una orden de venta. Se pueden trazar líneas BOS opcionales para facilitar la visualización, y unos marcadores internos impiden volver a operar inmediatamente en la misma ruptura.
//---------------------------- BOS UI -------------------------------//
void DrawBOS(const string name, datetime t1, double p1, datetime t2, double p2, color col, int dir)
{
   if(ObjectFind(0, name) == -1)
   {
      ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2);
      ObjectSetInteger(0, name, OBJPROP_COLOR, col);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);

      string lbl = name + "_lbl";
      ObjectCreate(0, lbl, OBJ_TEXT, 0, t2, p2);
      ObjectSetInteger(0, lbl, OBJPROP_COLOR, col);
      ObjectSetInteger(0, lbl, OBJPROP_FONTSIZE, 10);
      ObjectSetString(0,  lbl, OBJPROP_TEXT, "Break");
      ObjectSetInteger(0, lbl, OBJPROP_ANCHOR, (dir>0)?ANCHOR_RIGHT_UPPER:ANCHOR_RIGHT_LOWER);
   }
}

La función DrawBOS se encarga de marcar visualmente una ruptura de estructura (BOS) en un gráfico mediante la creación de una línea de tendencia entre dos puntos (t1, p1) y (t2, p2) con un color y un grosor específicos. Si el objeto con el nombre indicado aún no existe, primero crea la línea de tendencia, establece su color y grosor, y a continuación añade una etiqueta de texto en el extremo que indica «Break». La posición de la etiqueta se fija en función de la dirección dir, situándola en la parte superior derecha si es alcista o en la parte inferior derecha si es bajista, lo que proporciona de forma eficaz una indicación visual clara de los cambios en la estructura del mercado directamente en el gráfico.

void DrawFVG(const SFVG &z)
{
   string name = z.Name();
   datetime tNow = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
   
   // Delete existing object if it exists
   if(ObjectFind(0, name) != -1) 
      ObjectDelete(0, name);
   
   // Create rectangle object for FVG
   if(!ObjectCreate(0, name, OBJ_RECTANGLE, 0, z.tLeft, z.bot, tNow, z.top))
   {
      Print("Error creating FVG object: ", GetLastError());
      return;
   }
   
   // Set object properties
   ObjectSetInteger(0, name, OBJPROP_COLOR, z.dir>0 ? BullFVG : BearFVG);
   ObjectSetInteger(0, name, OBJPROP_FILL, true);
   ObjectSetInteger(0, name, OBJPROP_BACK, true);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
   
   // Set Z-order to make sure it's visible
   ObjectSetInteger(0, name, OBJPROP_ZORDER, 0);
}

La función DrawFVG visualiza un gap de valor razonable (FVG) en el gráfico; para ello, comprueba primero si ya existe un objeto con el nombre de la FVG y, en caso afirmativo, lo elimina para evitar duplicados. A continuación, crea un rectángulo que se extiende desde el momento tLeft del gap hasta la barra actual y desde la parte inferior (bot) hasta la parte superior (top) de la brecha. Al rectángulo se le aplica un estilo con un color que depende de la dirección de la brecha, se rellena, se envía al fondo del gráfico y se le aplica un borde continuo. Al establecer su orden Z en 0, la FVG permanece visible detrás de otros elementos del gráfico, lo que ofrece a los operadores una referencia visual clara y en tiempo real de las ineficiencias del precio.

void OnTick()
{
   if(!IsNewBar()) return;

   // Strategy switch
   if(TradeStrategy == STRAT_FVG || TradeStrategy == STRAT_AUTO)
      DetectAndDrawFVGs();

   if(TradeStrategy == STRAT_OB  || TradeStrategy == STRAT_AUTO)
      DetectAndDrawOrderBlocks();

   if(TradeStrategy == STRAT_BOS || TradeStrategy == STRAT_AUTO)
      DetectAndDrawBOS();
}

La función OnTick se ejecuta con cada tick del mercado, pero solo continúa cuando se forma una nueva barra, lo que garantiza que los cálculos se realicen una vez por vela. Acto seguido, comprueba la estrategia de negociación seleccionada: si están activas los gaps de valor razonable (FVG) o el modo automático, llama a la función DetectAndDrawFVGs; si están activos los bloques de órdenes (OB) o el modo automático, llama a la función DetectAndDrawOrderBlocks; y si está activa la ruptura de estructura (BOS) o el modo automático, llama a la función DetectAndDrawBOS. Esta estructura permite que el EA o el indicador detecte y visualice de forma dinámica las diferentes estructuras del mercado en tiempo real, en función de la estrategia elegida.


Resultados del backtesting

El backtesting se evaluó en el marco temporal de 1H durante un periodo de prueba de aproximadamente dos meses (del 1 de julio de 2025 al 1 de septiembre de 2025), con los parámetros predeterminados y utilizando BOS como estrategia.



Conclusión

Resumiendo lo visto, hoy hemos desarrollado un sistema unificado de trading de Smart Money Concepts (SMC) que integra tres pilares fundamentales: los bloques de órdenes (OB), la ruptura de estructura (BOS) y los gaps de valor razonable (FVG). Cada concepto se ha programado con lógica de detección, representación gráfica y trading. Los OB se han identificado como huellas institucionales con validación mediante retroceso de Fibonacci; los BOS han detectado barridos de liquidez cuando el precio barría máximos o mínimos de oscilación; y los FVG han resaltado ineficiencias en la formación del precio que a menudo actúan como zonas de fuerte reacción. Al combinar estos elementos en un único sistema, el código garantiza la detección dinámica de escenarios de alta probabilidad directamente en el gráfico, con ayudas visuales y ejecución automatizada.

En conclusión, este enfoque unificado ofrece a los operadores una forma sistemática de aplicar los principios de SMC sin las conjeturas que suelen acompañar al análisis manual de gráficos. El EA no solo marca y realiza un seguimiento de los cambios en la estructura del mercado, sino que también ejecuta operaciones en tiempo real cuando el precio cumple las condiciones SMC predefinidas. Esto proporciona a los operadores un método disciplinado basado en reglas que reduce el sesgo emocional, aumenta la consistencia y ofrece una ventaja profesional a la hora de aprovechar oportunidades de tipo institucional en diferentes condiciones de mercado.

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

Archivos adjuntos |
SMC_ALL_IN_1.mq5 (21.41 KB)
Hlomohang John Borotho
Hlomohang John Borotho | 16 sept 2025 en 15:59
Stanislav Korotky #:

La «brecha del valor razonable» se describe y se representa de forma incorrecta; por favor, consulta la imagen correcta en este artículo.

Te equivocas, es lo mismo; he representado la brecha de valor razonable (FVG) con el precio y las velas ya retrocedidos dentro de la zona de la FVG.
Hlomohang John Borotho
Hlomohang John Borotho | 16 sept 2025 en 16:00
Yata Tema Gea #:

Vaya, tu artículo es genial. Sé a ciencia cierta que hay indonesios en el artículo. Nuestra idea es la misma, pero la diferencia es que yo utilizo Python para SMC+GPT.


Ya lo veo :)
Stanislav Korotky
Stanislav Korotky | 16 sept 2025 en 17:37
Hlomohang John Borotho #:
Te equivocas, es lo mismo; he representado el FVG con el precio y las velas ya retrocedidos dentro de la zona del FVG.

Te equivocas o has elaborado mal tu gráfico. Como la primera y la tercera velas (desde la izquierda) aparecen en rojo, son bajistas y forman dos grandes huecos antes y después de la segunda vela (donde, presumiblemente, se produjo el FVG).

Si buscas «FVG» en Internet, verás que esta formación se refiere a un gran salto o brecha unidireccional en el precio, no de un zigzag de saltos, lo que oscurecería todo el efecto ante posibles retrocesos futuros, ya que no sabes cuál (de las tres brechas) está a punto de cerrarse primero (en tu gráfico es la última brecha junto a la tercera vela, no la segunda ; esta se cierra con la cuarta vela).
Bao Thuan Thai
Bao Thuan Thai | 28 sept 2025 en 11:06
No necesito saber cómo funciona, pero muchas gracias por compartirlo y por tu amabilidad.
ritik pathak
ritik pathak | 4 oct 2025 en 11:01
Buen trabajo, podría mejorar aún más si el stop se situara por debajo o por encima de FVG, o si el bloque de órdenes no tuviera un stop loss fijo.
Arbitraje estadístico mediante acciones cointegradas (Parte 3): Configuración de la base de datos Arbitraje estadístico mediante acciones cointegradas (Parte 3): Configuración de la base de datos
Este artículo presenta un ejemplo de implementación de un servicio MQL5 para actualizar una base de datos recién creada que se utiliza como fuente para el análisis de datos y para la negociación de una cesta de acciones cointegradas. Se explica en detalle la justificación del diseño de la base de datos y se documenta el diccionario de datos para su consulta. Se proporcionan scripts de MQL5 y Python para la creación de la base de datos, la inicialización del esquema y la inserción de datos de mercado.
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 39): Automatización de la detección de BOS y ChoCH en MQL5 Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 39): Automatización de la detección de BOS y ChoCH en MQL5
Este artículo presenta «Fractal Reaction System», un sistema compacto en MQL5 que convierte pivotes fractales en señales de estructura de mercado accionables. Mediante lógica de barras cerradas para evitar el repintado, el EA detecta advertencias de «cambio de carácter» (ChoCH) y confirma «rupturas de estructura» (BOS), dibuja objetos persistentes en el gráfico y registra y emite alertas de cada evento confirmado (de escritorio, móviles y sonoras). Siga leyendo para conocer el diseño del algoritmo, las notas de implementación, los resultados de las pruebas y el código completo del EA, de modo que pueda compilar, probar y desplegar el detector usted mismo.
Desarrollo de un indicador personalizado de sentimiento del mercado Desarrollo de un indicador personalizado de sentimiento del mercado
En este artículo desarrollaremos un indicador personalizado de sentimiento del mercado para clasificar las condiciones como alcistas, bajistas, de apetito por el riesgo (risk-on), de aversión al riesgo (risk-off) o neutras. Utilizando varios marcos temporales, el indicador puede ofrecer a los operadores una perspectiva más clara del sesgo general del mercado y de las confirmaciones a corto plazo.
Creación de un sistema de trading profesional con Heikin Ashi (Parte 1): Desarrollo de un indicador personalizado Creación de un sistema de trading profesional con Heikin Ashi (Parte 1): Desarrollo de un indicador personalizado
Este artículo es la primera entrega de una serie de dos partes ideada para transmitir habilidades prácticas y buenas prácticas para escribir indicadores personalizados en MQL5. Tomando Heikin Ashi como ejemplo práctico, el artículo analiza la teoría que subyace a los gráficos de Heikin Ashi, explica cómo se calculan las velas de Heikin Ashi y muestra su aplicación en el análisis técnico. El elemento central es una guía paso a paso para desarrollar desde cero un indicador Heikin Ashi totalmente funcional, con explicaciones claras que ayudan a los lectores a comprender qué deben programar y por qué. Estos conocimientos fundamentales sientan las bases para la segunda parte, en la que crearemos un asesor experto que opere siguiendo la lógica de Heikin Ashi.