English Русский 中文 Deutsch 日本語
preview
Descifrando las estrategias de trading intradía de ruptura del rango de apertura

Descifrando las estrategias de trading intradía de ruptura del rango de apertura

MetaTrader 5Trading |
27 15
Zhuo Kai Chen
Zhuo Kai Chen

Introducción

Las estrategias de ruptura del rango de apertura (Opening Range Breakout, ORB) se basan en la idea de que el rango de negociación inicial establecido poco después de la apertura del mercado refleja niveles de precios significativos en los que compradores y vendedores acuerdan el valor. Al identificar rupturas por encima o por debajo de un determinado rango, los operadores pueden aprovechar el impulso que suele producirse cuando la dirección del mercado se vuelve más clara. 

En este artículo, exploraremos tres estrategias ORB adaptadas de los artículos del Grupo Concretum. En primer lugar, abordaremos los antecedentes de la investigación, incluidos los conceptos clave y la metodología utilizada. A continuación, para cada estrategia, explicaremos cómo funcionan, enumeraremos sus reglas de señal y analizaremos su rendimiento estadísticamente. Por último, los examinaremos desde una perspectiva de cartera, centrándonos en el tema de la diversificación.  

Este artículo no profundizará en la programación, sino que se centrará en el proceso de investigación, incluyendo la recreación, el análisis y la prueba de las estrategias de estos tres artículos. Esto será adecuado para lectores que busquen posibles ventajas comerciales o que sientan curiosidad por saber cómo se estudiaron y replicaron estas estrategias. No obstante, se revelará todo el código MQL5 de los Asesores Expertos. Los lectores pueden ampliar este marco según sus propias necesidades.


Antecedentes de la investigación

Esta sección trata sobre la metodología de investigación que utilizaremos para analizar las estrategias, junto con los conceptos clave que aparecerán más adelante en el artículo.  

El Grupo Concretum es uno de los pocos equipos de investigación académica que desarrolla estrategias de negociación intradía. En los estudios que estamos adaptando, se centran en estrategias que operan entre la apertura y el cierre del mercado (de 9:30 a. m. a 4:00 p. m., hora del este). Dado que nuestro bróker utiliza UTC+2/3, esto se traduce en 18:30-24:00 hora del servidor; asegúrese de ajustar la zona horaria de su propio bróker cuando realice la prueba.  

La investigación original opera con QQQ, un ETF que replica el índice Nasdaq-100. Es importante señalar que el Nasdaq-100 representa el rendimiento de 100 grandes empresas tecnológicas que cotizan en la bolsa Nasdaq. El índice en sí mismo no es negociable, solo lo son sus derivados. QQQ permite a los inversores obtener exposición a estas empresas a través de una sola acción. Para nuestras pruebas, operaremos con USTEC (un CFD sobre el Nasdaq-100), que permite especular sobre los movimientos de precios sin poseer los activos subyacentes, a menudo utilizando el apalancamiento para magnificar las ganancias o pérdidas.

En este artículo presentaremos dos métricas clave: alfa y beta. En el ámbito bursátil, el alfa representa el rendimiento adicional que genera una inversión en comparación con un índice de referencia, como un índice bursátil. Muestra si la inversión supera las expectativas y, en esencia, refleja la ventaja competitiva. El beta mide la sensibilidad de una inversión a los movimientos del mercado. Una beta de 1 significa que refleja las fluctuaciones del mercado. Un valor superior a 1 indica una mayor volatilidad, mientras que un valor inferior a 1 sugiere una menor volatilidad. Estas métricas son esenciales para comprender en qué medida su estrategia depende de las tendencias del mercado frente a su ventaja competitiva única. Este conocimiento le ayuda a evaluar el posible sesgo direccional en activos con tendencia, como índices o criptomonedas.

Alfa y beta se calculan de la siguiente manera:

Alfa y beta

Ri es el rendimiento de la inversión, Rf es la tasa libre de riesgo, que a menudo se basa en el rendimiento de los bonos del Tesoro o se ignora, y Rm es el rendimiento del mercado. La covarianza y la varianza se calculan normalmente utilizando los rendimientos diarios.

Un indicador clave que se utilizará más adelante en este artículo es el precio medio ponderado por volumen (VWAP). Se calcula como:

VWAP

La intuición detrás del VWAP es medir el precio medio al que se negocia un valor, ponderado por volumen, reflejando el coste «real» de la negociación durante un periodo determinado. A diferencia de una media simple, otorga más peso a los precios con mayor actividad comercial, lo que lo convierte en un punto de referencia más justo.

Sus usos habituales en el trading algorítmico incluyen:

  • Usándolo como filtro de tendencias.
  • Utilizarlo como stop dinámico.
  • Utilizarlo como generador de señales (por ejemplo, entrar cuando el precio cruce el VWAP).

Normalmente comenzamos a calcular el VWAP desde la primera vela al inicio de la sesión bursátil. En la ecuación anterior, Pi representa el precio de la vela i-ésima, normalmente el cierre, y Vi es el volumen de negociación de la vela i-ésima. El volumen de negociación puede variar entre los distintos brókers de CFD debido a los diferentes proveedores de liquidez, pero la ponderación relativa debería ser, en general, coherente entre todos los brókers.

Este artículo implementa el modelo de riesgo del espacio de apalancamiento. Este método arriesga un porcentaje fijo de nuestro saldo por operación, que se activa cuando se alcanza el stop loss. El rango de stop loss será un porcentaje fijo del precio del activo para alinearse con su valor cambiante y volatilidad. El riesgo por operación se fijará utilizando números redondos para alcanzar una caída máxima de alrededor del 20 % por simplicidad. Probaremos cada estrategia durante un periodo de cinco años, desde el 1 de enero de 2020 hasta el 1 de enero de 2025, con el fin de recopilar datos recientes suficientes para evaluar la rentabilidad actual. El análisis estadístico exhaustivo incluirá comparaciones con la estrategia de compra y retención basadas en las ganancias porcentuales acumuladas y los indicadores de rendimiento individuales.


Estrategia uno: dirección de la vela de apertura

La primera estrategia que analizaremos es una estrategia clásica de ruptura de apertura presentada en el artículo ¿Puede el day trading ser realmente rentable? del Grupo Concretum. La motivación detrás de las reglas de señalización de la estrategia radica en capturar el impulso de los precios a corto plazo, al tiempo que se equilibra la practicidad y la gestión de riesgos para los operadores intradía. Los autores eligieron el enfoque ORB para aprovechar la mayor volatilidad y el impulso direccional que suelen observarse al inicio de la jornada bursátil. Este periodo se considera una ventana crítica en la que la actividad institucional suele quedar al descubierto, y los operadores minoristas pueden utilizar la dirección de los precios de este periodo como indicador para determinar la tendencia de todo el día.

Tras revisar el documento, identificamos varias formas de mejorar la estrategia original. El enfoque original utilizaba el máximo o mínimo de la primera vela de cinco minutos como stop loss y un take profit de 10R. Aunque rentable, este método resultaba poco práctico para los operadores minoristas en el trading en tiempo real. El estricto stop loss de la primera vela de cinco minutos aumentó los costes relativos de negociación. Además, el take profit de 10R era innecesario, ya que cerramos todas las operaciones al final del día y rara vez se alcanzaba. Por último, la estrategia original carecía de un filtro de régimen, por lo que añadir una media móvil podría mejorarla al servir como filtro de régimen.

Nuestras reglas de señal modificadas son las siguientes:

  • Cinco minutos después de la apertura del mercado, compre si la vela de apertura de cinco minutos es alcista y su cierre está por encima de la media móvil de 350 períodos.
  • Cinco minutos después de la apertura del mercado, venda si la vela de apertura de cinco minutos es bajista y su cierre está por debajo de la media móvil de 350 períodos.
  • Cinco minutos antes del cierre del mercado, salga de las posiciones existentes.
  • Stop loss fijado en el 1 % del precio desde el nivel de entrada.
  • 2 % de riesgo por operación.

Código MQL5 completo del EA:

//USTEC-M5
#include <Trade/Trade.mqh>
CTrade trade;

input int startHour = 18;
input int startMinute = 35;
input int endHour = 23;
input int endMinute = 55;
input double risk = 2.0;
input double slp = 0.01;
input int MaPeriods = 350;
input int Magic = 0;

int barsTotal = 0;
int handleMa;
double lastClose=0;
double lastOpen = 0;
double lot = 0.1;

//+------------------------------------------------------------------+
//|Initialization function                                           |
//+------------------------------------------------------------------+ 
int OnInit()
  {
   trade.SetExpertMagicNumber(Magic);
   handleMa = iMA(_Symbol,PERIOD_CURRENT,MaPeriods,0,MODE_SMA,PRICE_CLOSE);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//|Deinitialization function                                         |
//+------------------------------------------------------------------+ 
void OnDeinit(const int reason)
  {
  }

//+------------------------------------------------------------------+
//|On tick function                                                  |
//+------------------------------------------------------------------+ 
void OnTick()
  {
   int bars = iBars(_Symbol, PERIOD_CURRENT);
   if(barsTotal != bars){
      barsTotal=bars;
      double ma[];
      CopyBuffer(handleMa,0,1,1,ma);
      
      if(MarketOpen()){
         lastClose = iClose(_Symbol,PERIOD_CURRENT,1);
         lastOpen = iOpen(_Symbol,PERIOD_CURRENT,1);
         if(lastClose<lastOpen&&lastClose<ma[0])executeSell();
         if (lastClose>lastOpen&&lastClose>ma[0]) executeBuy();
      }
      
      if(MarketClose()){
         for(int i = PositionsTotal()-1; i>=0; i--){
            ulong pos = PositionGetTicket(i);
            string symboll = PositionGetSymbol(i);
            if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol)trade.PositionClose(pos);
            }
       }  
   }
}

//+------------------------------------------------------------------+
//| Detect if market is opened                                       |
//+------------------------------------------------------------------+
bool MarketOpen()
{
    datetime currentTime = TimeTradeServer(); 
    MqlDateTime timeStruct;
    TimeToStruct(currentTime, timeStruct);
    int currentHour = timeStruct.hour;
    int currentMinute = timeStruct.min;
    if (currentHour == startHour &&currentMinute==startMinute)return true;
    else return false;
}

//+------------------------------------------------------------------+
//| Detect if market is closed                                       |
//+------------------------------------------------------------------+
bool MarketClose()
{
    datetime currentTime = TimeTradeServer(); 
    MqlDateTime timeStruct;
    TimeToStruct(currentTime, timeStruct);
    int currentHour = timeStruct.hour;
    int currentMinute = timeStruct.min;

    if (currentHour == endHour && currentMinute == endMinute)return true;
    else return false;
}

//+------------------------------------------------------------------+
//| Sell execution function                                          |
//+------------------------------------------------------------------+        
void executeSell() {      
       double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
       bid = NormalizeDouble(bid,_Digits);
       double sl = bid*(1+slp);
       sl = NormalizeDouble(sl, _Digits);
       lot = calclots(bid*slp);
       trade.Sell(lot,_Symbol,bid,sl);    
}
  
//+------------------------------------------------------------------+
//| Buy execution function                                           |
//+------------------------------------------------------------------+   
void executeBuy() {
       double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
       ask = NormalizeDouble(ask,_Digits);
       double sl = ask*(1-slp);
       sl = NormalizeDouble(sl, _Digits);
       lot = calclots(ask*slp);
       trade.Buy(lot,_Symbol,ask,sl);
}

//+------------------------------------------------------------------+
//| Calculate lot size based on risk and stop loss range             |
//+------------------------------------------------------------------+
double calclots(double slpoints) {
    double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * risk / 100;
    double ticksize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    double tickvalue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double lotstep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    double moneyperlotstep = slpoints / ticksize * tickvalue * lotstep;
    double lots = MathFloor(riskAmount / moneyperlotstep) * lotstep;
    lots = MathMin(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX));
    lots = MathMax(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN));
    return lots;
}

Una operación típica sería así:

Ejemplo de orb1

Resultados de la prueba retrospectiva:

Configuración de orb1

Parámetros orb1

Curva de equidad de orb1

Resultados de orb1

Sin el filtro de media móvil, las reglas originales de la estrategia generarían una operación cada día de negociación. El filtro redujo las operaciones a la mitad. Dado que el periodo medio de tenencia abarca toda la sesión bursátil, los resultados reflejan en cierta medida la tendencia macroeconómica, con operaciones largas que se producen con mayor frecuencia y tienen una tasa de éxito más alta. En general, la estrategia alcanza un factor de beneficio de 1,23 y un ratio de Sharpe de 2,81, lo que refleja un sólido rendimiento. Estas reglas sencillas son menos propensas al sobreajuste, lo que sugiere que los resultados sólidos de las pruebas retrospectivas probablemente se mantengan en el trading real.

Comparación de orb1

Reducción de orb1

La EA supera de manera impresionante al enfoque de compra y retención para USTEC durante este periodo de cinco años, al tiempo que mantiene la caída máxima en un 18 %, la mitad que el índice de referencia. La curva de capital se mantiene estable, con solo un breve estancamiento entre finales de 2022 y principios de 2023, momento en el que USTEC se enfrentó a una mayor caída.

Rendimiento mensual de orb1

Reducción mensual de orb1

Alfa: 1,6017
Beta: 0,0090

Un beta del 0,9 % indica que la rentabilidad diaria solo tiene una correlación del 0,9 % con el activo subyacente, lo que indica que la ventaja de la estrategia proviene principalmente de sus reglas y no de las tendencias del mercado. Las caídas y los rendimientos se mantienen constantes, lo que sugiere resistencia frente a periodos extremos como la crisis provocada por la COVID en 2020. La mayoría de los meses son rentables y los meses de caída son moderados, con un máximo del 10,2 %. En general, se trata de una estrategia negociable y rentable.


Estrategia dos: seguimiento de la tendencia VWAP

La segunda estrategia que analizaremos es más bien una estrategia de seguimiento de tendencias en apertura de mercado presentada en el artículo Volume Weighted Average Price (VWAP) The Holy Grail for Day Trading Systems. La motivación detrás de las reglas de señalización es aprovechar el VWAP como un punto de referencia claro y ponderado por volumen para identificar las tendencias intradía. Una posición larga se activa cuando el precio cierra por encima del VWAP, y una posición corta cuando cierra por debajo, con el objetivo de capturar el impulso confirmado y filtrar el ruido. Esta simplicidad garantiza señales reproducibles y procesables para los operadores intradía. Este enfoque clásico de seguimiento de tendencias funciona mejor en condiciones de alta volatilidad, captando tendencias de largo plazo y generando altos beneficios en relación con el riesgo. Durante las cinco horas que permanece abierto el mercado bursátil, el índice experimenta movimientos significativos, lo que proporciona una excelente liquidez temporal para que la estrategia tenga éxito.

El artículo original se basaba en un intervalo de tiempo de un minuto, afirmando que era el más eficaz entre los distintos intervalos de tiempo. Sin embargo, mis pruebas personales revelaron que un marco temporal de 15 minutos funciona mejor para esta estrategia, probablemente debido a los mayores costes de negociación de los CFD en comparación con los ETF, lo que hace que las operaciones frecuentes sean menos viables. El documento también omitía un límite de pérdidas. En nuestro enfoque, incluiremos uno, ya que estamos utilizando un marco temporal más amplio. Esta adición sirve como protección contra accidentes y proporciona un rango de referencia para calcular los riesgos. Por último, añadimos un filtro de tendencia de media móvil como hicimos anteriormente.

Nuestras reglas de señal modificadas son las siguientes:

  • Después de la apertura del mercado, compre si no hay ninguna posición abierta y el último cierre de 15 minutos está por encima del VWAP y la media móvil de 300 períodos.
  • Después de la apertura del mercado, venda si no hay ninguna posición abierta y el último cierre de 15 minutos está por debajo del VWAP y la media móvil de 300 períodos.
  • Stop loss fijado en el 0,8 % del precio desde el nivel de entrada.
  • 2 % de riesgo por operación.

El código MQL5 completo del EA:

//USTEC-M15
#include <Trade/Trade.mqh>
CTrade trade;

input int startHour = 18;
input int startMinute = 35;
input int endHour = 23;
input int endMinute = 45;

input double risk = 2.0;
input double slp = 0.008;
input int MaPeriods = 300;
input int Magic = 0;

int barsTotal = 0;
int handleMa;
double lastClose=0;
double lot = 0.1;

//+------------------------------------------------------------------+
//|Initialization function                                           |
//+------------------------------------------------------------------+ 
int OnInit()
  {
   trade.SetExpertMagicNumber(Magic);
   handleMa = iMA(_Symbol,PERIOD_CURRENT,MaPeriods,0,MODE_SMA,PRICE_CLOSE);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//|Deinitialization function                                         |
//+------------------------------------------------------------------+ 
void OnDeinit(const int reason)
  { 
  }

//+------------------------------------------------------------------+
//|On tick function                                                  |
//+------------------------------------------------------------------+ 
void OnTick()
  {
   int bars = iBars(_Symbol, PERIOD_CURRENT);
   if(barsTotal != bars){
      barsTotal=bars;
      bool NotInPosition = true;
      double ma[];
      CopyBuffer(handleMa,0,1,1,ma);
      if(MarketOpened()&&!MarketClosed()){
         lastClose = iClose(_Symbol,PERIOD_CURRENT,1);
         int startIndex = getSessionStartIndex();
         double vwap = getVWAP(startIndex);
         for(int i = PositionsTotal()-1; i>=0; i--){
            ulong pos = PositionGetTicket(i);
            string symboll = PositionGetSymbol(i);
            if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol){
               if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY&&lastClose<vwap)||(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL&&lastClose>vwap))trade.PositionClose(pos);
               else NotInPosition=false;
            }
         }
         if(lastClose<vwap&&NotInPosition&&lastClose<ma[0])executeSell();
         if(lastClose>vwap&&NotInPosition&&lastClose>ma[0]) executeBuy();
       } 
       if(MarketClosed()){
          for(int i = PositionsTotal()-1; i>=0; i--){
            ulong pos = PositionGetTicket(i);
            string symboll = PositionGetSymbol(i);
            if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol)trade.PositionClose(pos);
          }
       }
        
   }
}

//+------------------------------------------------------------------+
//| Detect if market is opened                                       |
//+------------------------------------------------------------------+ 
bool MarketOpened()
{
    datetime currentTime = TimeTradeServer(); 
    MqlDateTime timeStruct;
    TimeToStruct(currentTime, timeStruct);
    int currentHour = timeStruct.hour;
    int currentMinute = timeStruct.min;
    if (currentHour >= startHour &&currentMinute>=startMinute)return true;
    else return false;
}

//+------------------------------------------------------------------+
//| Detect if market is closed                                       |
//+------------------------------------------------------------------+ 
bool MarketClosed()
{
    datetime currentTime = TimeTradeServer(); 
    MqlDateTime timeStruct;
    TimeToStruct(currentTime, timeStruct);
    int currentHour = timeStruct.hour;
    int currentMinute = timeStruct.min;

    if (currentHour >= endHour && currentMinute >= endMinute)return true;
    else return false;
}

//+------------------------------------------------------------------+
//| Sell execution function                                          |
//+------------------------------------------------------------------+        
void executeSell() {      
       double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
       bid = NormalizeDouble(bid,_Digits);
       double sl = bid*(1+slp);
       sl = NormalizeDouble(sl, _Digits);
       lot = calclots(bid*slp);
       trade.Sell(lot,_Symbol,bid,sl);    
}

//+------------------------------------------------------------------+
//| Buy execution function                                           |
//+------------------------------------------------------------------+    
void executeBuy() {
       double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
       ask = NormalizeDouble(ask,_Digits);
       double sl = ask*(1-slp);
       sl = NormalizeDouble(sl, _Digits);
       lot = calclots(ask*slp);
       trade.Buy(lot,_Symbol,ask,sl);
}

//+------------------------------------------------------------------+
//| Get VWAP function                                                |
//+------------------------------------------------------------------+
double getVWAP(int startCandle)
{
   double sumPV = 0.0;  // Sum of (price * volume)
   long sumV = 0.0;    // Sum of volume

   // Loop from the starting candle index down to 1 (excluding current candle)
   for(int i = startCandle; i >= 1; i--)
   {
      // Calculate typical price: (High + Low + Close) / 3
      double high = iHigh(_Symbol, PERIOD_CURRENT, i);
      double low = iLow(_Symbol, PERIOD_CURRENT, i);
      double close = iClose(_Symbol, PERIOD_CURRENT, i);
      double typicalPrice = (high + low + close) / 3.0;

      // Get volume and update sums
      long volume = iVolume(_Symbol, PERIOD_CURRENT, i);
      sumPV += typicalPrice * volume;
      sumV += volume;
   }

   // Calculate VWAP or return 0 if no volume
   if(sumV == 0)
      return 0.0;
   
   double vwap = sumPV / sumV;

   // Plot the dot
   datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
   string objName = "VWAP" + TimeToString(currentBarTime, TIME_MINUTES);
   ObjectCreate(0, objName, OBJ_ARROW, 0, currentBarTime, vwap);
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrGreen);    // Green dot
   ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DOT);   // Dot style
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1);           // Size of the dot
   
   return vwap;
}

//+------------------------------------------------------------------+
//| Find the index of the candle corresponding to the session open   |
//+------------------------------------------------------------------+
int getSessionStartIndex()
{
   int sessionIndex = 1;
   // Loop over bars until we find the session open
   for(int i = 1; i <=1000; i++)
   {
      datetime barTime = iTime(_Symbol, PERIOD_CURRENT, i);
      MqlDateTime dt;
      TimeToStruct(barTime, dt);
      
      if(dt.hour == startHour && dt.min == startMinute-5)
      {
         sessionIndex = i;
         break;
      }
   }
      
   return sessionIndex;
}

//+------------------------------------------------------------------+
//| Calculate lot size based on risk and stop loss range             |
//+------------------------------------------------------------------+
double calclots(double slpoints) {
    double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * risk / 100;
    double ticksize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    double tickvalue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double lotstep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    double moneyperlotstep = slpoints / ticksize * tickvalue * lotstep;
    double lots = MathFloor(riskAmount / moneyperlotstep) * lotstep;
    lots = MathMin(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX));
    lots = MathMax(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN));
    return lots;
}

Una operación típica sería así:

Ejemplo de orb2

Resultados de la prueba retrospectiva:

Configuración de orb2

Parámetros de orb2

Curva de equidad de orb2

Resultado de orb2

En comparación con la primera estrategia de ruptura de apertura, esta opera con más frecuencia, con un promedio de más de una operación al día. Este aumento se debe a que se permite volver a entrar cada vez que el precio vuelve a cruzar el VWAP durante el horario de mercado. La tasa de ganancias es más baja, del 42 %, por debajo del 50 %, lo cual es habitual en un enfoque que sigue las tendencias con un trailing stop dinámico. Esta configuración favorece las operaciones con una mayor relación recompensa-riesgo, pero aumenta la probabilidad de que se produzca una salida. El ratio de Sharpe y el factor de beneficio son excepcionalmente altos, con valores de 3,57 y 1,26, respectivamente.

Comparación de orb2

Reducción de orb2

La estrategia supera significativamente a la estrategia de comprar y mantener, logrando un rendimiento del 501 % en cinco años. Lo hace con una caída máxima del 16 %, con su peor periodo a finales de 2021, lo que difiere de la peor fase de USTEC, lo que sugiere un rendimiento no correlacionado.

Rendimiento mensual de orb2

Reducción mensual de orb2

Alfa: 4,8714
Beta: 0,0985

La beta coincide con la de la primera estrategia, lo que también indica una baja correlación con el activo subyacente. Cabe destacar que el alfa de esta estrategia es tres veces superior al de la primera, manteniendo una caída máxima similar. Esta ventaja probablemente proviene de operaciones más frecuentes, períodos de tenencia más cortos y una mayor diversificación interna a través de oportunidades largas y cortas dentro del mismo día. La tabla mensual confirma un rendimiento sólido, con caídas y rendimientos distribuidos de manera uniforme y constante a lo largo de los meses.


Estrategia tres: ruptura de las bandas de Concretum

La tercera estrategia es una estrategia de ruptura del rango de ruido que se negocia durante el horario de apertura del mercado. Se introdujo por primera vez en Beat the Market An Effective Intraday Momentum Strategy for S&P500 ETF (SPY), y más tarde se hizo viral en X/Twitter. Las motivaciones detrás de las reglas de señalización de la estrategia de ruptura de las bandas de Concretum provienen del objetivo de identificar movimientos significativos de precios impulsados por desequilibrios de oferta y demanda en el comercio intradía. La estrategia utiliza bandas basadas en la volatilidad, calculadas a partir del cierre del día anterior o la apertura del día actual y ajustadas por un multiplicador de volatilidad, para definir un «área de ruido» en la que se producen fluctuaciones aleatorias de los precios. Las reglas tienen como objetivo filtrar el ruido del mercado, aprovechar los cambios de impulso de alta probabilidad y adaptarse a la volatilidad variable, garantizando que las operaciones se alineen con los inicios de tendencias genuinas en lugar de con fluctuaciones fugaces.

Aquí están los cálculos de las bandas.

Fórmula de bandas

Dado que las reglas de señalización del documento original están bien pensadas, no vamos a cambiar mucho en este artículo. Para simplificar, mantendremos el mismo activo de negociación (USTEC) y el mismo enfoque de gestión de riesgos, lo que puede dar lugar a resultados diferentes a los del enfoque del artículo. Las reglas de señalización son las siguientes:

  • Después de la apertura del mercado, compre cuando la barra de 1 minuto cruce por encima de la banda superior.
  • Después de la apertura del mercado, venda cuando la barra de 1 minuto cruce por debajo de la banda inferior.
  • Salga de todas las posiciones cuando el mercado esté cerrado.
  • Stop loss fijado en el 1 % del precio desde la posición de entrada, junto con el VWAP como trailing stop.
  • 4 % de riesgo por operación.

Código MQL5 completo del EA:

//USTEC-M1
#include <Trade/Trade.mqh>
CTrade trade;

input int startHour = 18;
input int startMinute = 35;
input int endHour = 23;
input int endMinute = 55;

input double risk = 4.0;
input double slp = 0.01;
input int Magic = 0;
input int maPeriod = 400;

int barsTotal = 0;
int handleMa;
double lastClose=0;
double lastOpen = 0;
double lot = 0.1;

//+------------------------------------------------------------------+
//|Initialization function                                           |
//+------------------------------------------------------------------+ 
int OnInit()
  {
   trade.SetExpertMagicNumber(Magic);
   handleMa = iMA(_Symbol,PERIOD_CURRENT,maPeriod,0,MODE_SMA,PRICE_CLOSE);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//|Deinitialization function                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {  
  }

//+------------------------------------------------------------------+
//|On tick function                                                  |
//+------------------------------------------------------------------+ 
void OnTick()
  {
   int bars = iBars(_Symbol, PERIOD_CURRENT);
   if(barsTotal != bars){
      barsTotal=bars;
      bool NotInPosition = true;
      double ma[];
      CopyBuffer(handleMa,0,1,1,ma);
      if(MarketOpened()&&!MarketClosed()){
         lastClose = iClose(_Symbol,PERIOD_CURRENT,1);
         lastOpen = iOpen(_Symbol,PERIOD_CURRENT,1);
         int startIndex = getSessionStartIndex();
         double vwap = getVWAP(startIndex);
         for(int i = PositionsTotal()-1; i>=0; i--){
            ulong pos = PositionGetTicket(i);
            string symboll = PositionGetSymbol(i);
            if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol){
               if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY&&lastClose<vwap)||(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL&&lastClose>vwap))trade.PositionClose(pos);
               else NotInPosition=false;
            }
         }
         double lower = getLowerBand();
         double upper = getUpperBand();
         if(NotInPosition&&lastOpen>lower&&lastClose<lower&&lastClose<ma[0])executeSell();
         if(NotInPosition&&lastOpen<upper&&lastClose>upper&&lastClose>ma[0]) executeBuy();
       } 
       if(MarketClosed()){
          for(int i = PositionsTotal()-1; i>=0; i--){
            ulong pos = PositionGetTicket(i);
            string symboll = PositionGetSymbol(i);
            if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol)trade.PositionClose(pos);
          }
       }
        
   }
}

//+------------------------------------------------------------------+
//| Detect if market is opened                                       |
//+------------------------------------------------------------------+ 
bool MarketOpened()
{
    datetime currentTime = TimeTradeServer(); 
    MqlDateTime timeStruct;
    TimeToStruct(currentTime, timeStruct);
    int currentHour = timeStruct.hour;
    int currentMinute = timeStruct.min;
    if (currentHour >= startHour &&currentMinute>=startMinute)return true;
    else return false;
}

//+------------------------------------------------------------------+
//| Detect if market is closed                                       |
//+------------------------------------------------------------------+ 
bool MarketClosed()
{
    datetime currentTime = TimeTradeServer(); 
    MqlDateTime timeStruct;
    TimeToStruct(currentTime, timeStruct);
    int currentHour = timeStruct.hour;
    int currentMinute = timeStruct.min;
    if (currentHour >= endHour && currentMinute >= endMinute)return true;
    else return false;
}

//+------------------------------------------------------------------+
//| Sell execution function                                          |
//+------------------------------------------------------------------+        
void executeSell() {      
       double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
       bid = NormalizeDouble(bid,_Digits);
       double sl = bid*(1+slp);
       sl = NormalizeDouble(sl, _Digits);
       lot = calclots(bid*slp);
       trade.Sell(lot,_Symbol,bid,sl);    
}

//+------------------------------------------------------------------+
//| Buy execution function                                           |
//+------------------------------------------------------------------+     
void executeBuy() {
       double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
       ask = NormalizeDouble(ask,_Digits);
       double sl = ask*(1-slp);
       sl = NormalizeDouble(sl, _Digits);
       lot = calclots(ask*slp);
       trade.Buy(lot,_Symbol,ask,sl);
}

//+------------------------------------------------------------------+
//| Get VWAP function                                                |
//+------------------------------------------------------------------+
double getVWAP(int startCandle)
{
   double sumPV = 0.0;  // Sum of (price * volume)
   long sumV = 0.0;    // Sum of volume

   // Loop from the starting candle index down to 1 (excluding current candle)
   for(int i = startCandle; i >= 1; i--)
   {
      // Calculate typical price: (High + Low + Close) / 3
      double high = iHigh(_Symbol, PERIOD_CURRENT, i);
      double low = iLow(_Symbol, PERIOD_CURRENT, i);
      double close = iClose(_Symbol, PERIOD_CURRENT, i);
      double typicalPrice = (high + low + close) / 3.0;

      // Get volume and update sums
      long volume = iVolume(_Symbol, PERIOD_CURRENT, i);
      sumPV += typicalPrice * volume;
      sumV += volume;
   }

   // Calculate VWAP or return 0 if no volume
   if(sumV == 0)
      return 0.0;
   
   double vwap = sumPV / sumV;

   // Plot the dot
   datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
   string objName = "VWAP" + TimeToString(currentBarTime, TIME_MINUTES);
   ObjectCreate(0, objName, OBJ_ARROW, 0, currentBarTime, vwap);
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrGreen);    // Green dot
   ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DOT);   // Dot style
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1);           // Size of the dot
   
   return vwap;
}

//+------------------------------------------------------------------+
//| Find the index of the candle corresponding to the session open   |
//+------------------------------------------------------------------+
int getSessionStartIndex()
{
   int sessionIndex = 1;
   // Loop over bars until we find the session open
   for(int i = 1; i <=1000; i++)
   {
      datetime barTime = iTime(_Symbol, PERIOD_CURRENT, i);
      MqlDateTime dt;
      TimeToStruct(barTime, dt);
      
      if(dt.hour == startHour && dt.min == 30)
      {
         sessionIndex = i;
         break;
      }
   }
      
   return sessionIndex;
}

//+------------------------------------------------------------------+
//| Get the number of bars from now to market open                   |
//+------------------------------------------------------------------+
int getBarShiftForTime(datetime day_start, int hour, int minute) {
    MqlDateTime dt;
    TimeToStruct(day_start, dt);
    dt.hour = hour;
    dt.min = minute;
    dt.sec = 0;
    datetime target_time = StructToTime(dt);
    int shift = iBarShift(_Symbol, PERIOD_M1, target_time, true);
    return shift;
}

//+------------------------------------------------------------------+
//| Get the upper Concretum band value                               |
//+------------------------------------------------------------------+
double getUpperBand() {
    // Get the time of the current bar
    datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0);
    MqlDateTime current_dt;
    TimeToStruct(current_time, current_dt);
    int current_hour = current_dt.hour;
    int current_min = current_dt.min;
    
    // Find today's opening price at 9:30 AM
    datetime today_start = iTime(_Symbol, PERIOD_D1, 0);
    int bar_at_930_today = getBarShiftForTime(today_start, 9, 30);
    if (bar_at_930_today < 0) return 0; // Return 0 if no 9:30 bar exists
    double open_930_today = iOpen(_Symbol, PERIOD_M1, bar_at_930_today);
    if (open_930_today == 0) return 0; // No valid price
    
    // Calculate sigma based on the past 14 days
    double sum_moves = 0;
    int valid_days = 0;
    for (int i = 1; i <= 14; i++) {
        datetime day_start = iTime(_Symbol, PERIOD_D1, i);
        int bar_at_930 = getBarShiftForTime(day_start, 9, 30);
        int bar_at_HHMM = getBarShiftForTime(day_start, current_hour, current_min);
        if (bar_at_930 < 0 || bar_at_HHMM < 0) continue; // Skip if bars don't exist
        double open_930 = iOpen(_Symbol, PERIOD_M1, bar_at_930);
        double close_HHMM = iClose(_Symbol, PERIOD_M1, bar_at_HHMM);
        if (open_930 == 0) continue; // Skip if no valid opening price
        double move = MathAbs(close_HHMM / open_930 - 1);
        sum_moves += move;
        valid_days++;
    }
    if (valid_days == 0) return 0; // Return 0 if no valid data
    double sigma = sum_moves / valid_days;
    
    // Calculate the upper band
    double upper_band = open_930_today * (1 + sigma);
    
    // Plot a blue dot at the upper band level
    string obj_name = "UpperBand_" + TimeToString(current_time, TIME_DATE|TIME_MINUTES|TIME_SECONDS);
    ObjectCreate(0, obj_name, OBJ_ARROW, 0, current_time, upper_band);
    ObjectSetInteger(0, obj_name, OBJPROP_ARROWCODE, 159); // Dot symbol
    ObjectSetInteger(0, obj_name, OBJPROP_COLOR, clrBlue);
    ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 2);
    
    return upper_band;
}

//+------------------------------------------------------------------+
//| Get the lower Concretum band value                               |
//+------------------------------------------------------------------+
double getLowerBand() {
    // Get the time of the current bar
    datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0);
    MqlDateTime current_dt;
    TimeToStruct(current_time, current_dt);
    int current_hour = current_dt.hour;
    int current_min = current_dt.min;
    
    // Find today's opening price at 9:30 AM
    datetime today_start = iTime(_Symbol, PERIOD_D1, 0);
    int bar_at_930_today = getBarShiftForTime(today_start, 9, 30);
    if (bar_at_930_today < 0) return 0; // Return 0 if no 9:30 bar exists
    double open_930_today = iOpen(_Symbol, PERIOD_M1, bar_at_930_today);
    if (open_930_today == 0) return 0; // No valid price
    
    // Calculate sigma based on the past 14 days
    double sum_moves = 0;
    int valid_days = 0;
    for (int i = 1; i <= 14; i++) {
        datetime day_start = iTime(_Symbol, PERIOD_D1, i);
        int bar_at_930 = getBarShiftForTime(day_start, 9, 30);
        int bar_at_HHMM = getBarShiftForTime(day_start, current_hour, current_min);
        if (bar_at_930 < 0 || bar_at_HHMM < 0) continue; // Skip if bars don't exist
        double open_930 = iOpen(_Symbol, PERIOD_M1, bar_at_930);
        double close_HHMM = iClose(_Symbol, PERIOD_M1, bar_at_HHMM);
        if (open_930 == 0) continue; // Skip if no valid opening price
        double move = MathAbs(close_HHMM / open_930 - 1);
        sum_moves += move;
        valid_days++;
    }
    if (valid_days == 0) return 0; // Return 0 if no valid data
    double sigma = sum_moves / valid_days;
    
    // Calculate the lower band
    double lower_band = open_930_today * (1 - sigma);
    
    // Plot a red dot at the lower band level
    string obj_name = "LowerBand_" + TimeToString(current_time, TIME_DATE|TIME_MINUTES|TIME_SECONDS);
    ObjectCreate(0, obj_name, OBJ_ARROW, 0, current_time, lower_band);
    ObjectSetInteger(0, obj_name, OBJPROP_ARROWCODE, 159); // Dot symbol
    ObjectSetInteger(0, obj_name, OBJPROP_COLOR, clrRed);
    ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 2);
    
    return lower_band;
}

//+------------------------------------------------------------------+
//| Calculate lot size based on risk and stop loss range             |
//+------------------------------------------------------------------+
double calclots(double slpoints) {
    double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * risk / 100;
    double ticksize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    double tickvalue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double lotstep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    double moneyperlotstep = slpoints / ticksize * tickvalue * lotstep;
    double lots = MathFloor(riskAmount / moneyperlotstep) * lotstep;
    lots = MathMin(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX));
    lots = MathMax(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN));
    return lots;
}

Una operación típica sería así:

Ejemplo de orb3

Resultados de la prueba retrospectiva:

Configuración de orb3

Parámetros de orb3

Curva de equidad de orb3

Resultados de orb3

La estrategia opera con una frecuencia similar a la primera estrategia ORB, con una media de una operación cada dos días hábiles. No opera a diario porque los movimientos de precios a veces permanecen dentro de un rango de ruido, sin lograr romper las bandas. La tasa de ganancias es inferior al 50 %, como resultado del uso del VWAP como stop dinámico. Un factor de beneficio de 1,3 y un ratio de Sharpe de 5,9 indican una fuerte rentabilidad en relación con la caída.

Comparación de orb3

Reducción de orb3

La estrategia supera ligeramente al método de comprar y mantener, al tiempo que mantiene la mitad de la caída máxima. Sin embargo, experimenta períodos de caída significativa con mayor frecuencia que la estrategia anterior. Esto indica que, a pesar de su rendimiento superior, la estrategia suele sufrir prolongadas caídas antes de alcanzar nuevos máximos de capital.

Rendimiento mensual de orb3

Reducción mensual de orb3

Alfa: 1,6562
Beta: -0,1183

Esta estrategia tiene una beta del -11 %, lo que indica una ligera correlación negativa con el activo subyacente. Este es un resultado favorable para los operadores que buscan una ventaja que se mueva en dirección opuesta a las tendencias del mercado. En comparación con las otras dos estrategias, esta tiene más meses de caída, alrededor del 50 %, pero ofrece mayores rendimientos durante los meses rentables. Este patrón sugiere que los operadores deben prepararse para períodos prolongados de caídas en las operaciones reales y esperar pacientemente a que lleguen las fases de mayor rentabilidad. Con un tamaño de muestra sustancial durante un periodo de tiempo sólido, esta estrategia sigue siendo negociable.


Reflexiones

En nuestro artículo anterior, exploramos la creación de sistemas modelo en lugar de estrategias independientes. Hemos aplicado el mismo concepto en este artículo. Las tres estrategias se basan en rupturas del rango de apertura del mercado bursátil, con variaciones que han demostrado ser rentables. También compartimos ideas sobre cómo encontrar una ventaja estratégica adaptando artículos académicos con nuestros propios conocimientos e intuición. Este enfoque es una forma excelente de descubrir conceptos sólidos de negociación y ampliar nuestros conocimientos.

Con tres estrategias rentables en mano, ahora deberíamos considerar una perspectiva de cartera. Debemos examinar sus resultados combinados, correlaciones y caída máxima general antes de operar con ellos simultáneamente. En el trading algorítmico, la diversificación es el verdadero santo grial. Ayuda a compensar las pérdidas de diferentes estrategias en distintos periodos. En cierta medida, tu rendimiento máximo está limitado por la caída que estás dispuesto a tolerar. La combinación de diversas estrategias le permite aumentar la exposición manteniendo una caída similar, lo que aumenta los rendimientos. Sin embargo, el riesgo no se puede escalar infinitamente de esta manera, ya que el riesgo mínimo siempre superará al de las operaciones individuales.

Algunas formas comunes de lograr la diversificación incluyen:

  • Operar con el mismo modelo estratégico y distribuirlo entre diferentes activos no correlacionados.
  • Operar con diferentes modelos estratégicos en un único activo.
  • Distribuir capitales a diferentes enfoques comerciales, tales como opciones, arbitraje y selección de acciones.

Es fundamental comprender que una mayor diversificación no siempre es mejor; lo que importa es la diversificación no correlacionada. Por ejemplo, aplicar la misma estrategia en todos los mercados de criptomonedas no es lo ideal, ya que la mayoría de los activos criptográficos están muy correlacionados a gran escala. Además, basarse únicamente en la diversificación de backtest también puede ser engañoso, ya que la correlación depende del periodo de tiempo, como los rendimientos diarios o mensuales. Además, en cambios severos del régimen del mercado, las correlaciones estratégicas pueden distorsionarse y sesgarse de forma inesperada. Por eso, algunos operadores prefieren utilizar correlaciones de resultados de operaciones en vivo en lugar de correlaciones de resultados de backtesting para evaluar si la ventaja de sus estrategias se ha deteriorado.

Teniendo en cuenta lo anterior, a continuación se presentan las estadísticas de backtesting del rendimiento combinado de las tres estrategias.

Curva de capital combinada

Reducción combinada

Las curvas de capital y reducción muestran visualmente cómo las diferentes estrategias compensan entre sí las reducciones en distintos periodos. La reducción máxima es ahora de alrededor del 10 %, notablemente inferior a las reducciones máximas de las estrategias individuales, todas las cuales superan el 15 %.

Rendimiento mensual combinado

Reducción mensual combinada

Las caídas y las rentabilidades parecen distribuirse uniformemente a lo largo de los meses, lo que sugiere que ningún periodo extremo afecta de manera desproporcionada al rendimiento de las pruebas retrospectivas. Esto tiene sentido con más de 3000 muestras y una asignación de riesgo consistente por operación.

Matriz de correlación

La correlación mide el grado de similitud entre las curvas de capital de las pruebas retrospectivas de cada estrategia, con valores que van desde -1 para comportamientos opuestos hasta 1 para comportamientos idénticos, y suele comparar dos sujetos. Lo calculamos utilizando x para el eje temporal de la curva de capital y y para el eje de rentabilidad.

Correlación

La matriz de correlación ayuda a visualizar las correlaciones de rendimiento entre tres o más estrategias. Utilizando un periodo mensual, observamos que los rendimientos mensuales de cada estrategia están ligeramente correlacionados, con una media de alrededor de 0,3. Las correlaciones inferiores a 0,5 son aceptables, aunque serían preferibles las correlaciones negativas. A pesar de operar tanto en largo como en corto, todas las estrategias muestran correlaciones positivas, probablemente porque operan con el mismo activo. Esta visión más profunda revela que, aunque la caída máxima combinada es inferior a las individuales, los rendimientos mensuales siguen siendo similares porque estamos negociando estrategias comparables sobre el mismo activo. Esto sugiere que deberíamos combinar estas estrategias con otras diferentes, en lugar de agruparlas en la misma cartera.


Conclusión

Este artículo revisó tres estrategias de ruptura del rango de apertura intradía de los trabajos académicos del Grupo Concretum. Comenzamos describiendo los antecedentes de la investigación y explicando los conceptos clave y las metodologías utilizadas a lo largo de la misma. A continuación, exploramos las motivaciones detrás de las tres estrategias, identificamos áreas de mejora, proporcionamos reglas de señal claras, código MQL5 y análisis estadístico de pruebas retrospectivas. Por último, reflexionamos sobre el proceso e introdujimos la diversificación, analizando los resultados combinados.

El artículo ofrece información sobre la verdadera solidez del desarrollo de estrategias. Un análisis estadístico más profundo proporciona una visión más amplia del rendimiento de una estrategia y su papel dentro de una cartera. Todos los esfuerzos tienen como objetivo profundizar en el conocimiento y generar confianza antes de comenzar a operar en vivo. Se anima a los lectores a replicar el proceso de investigación y desarrollar asesores expertos utilizando el marco presentado.


Tabla de archivos

Nombre del archivo Uso de archivos
ORB1.mq5.                                      El script MQL5 EA de la primera estrategia
ORB2.mq5 El script MQL5 EA de la segunda estrategia
ORB3.mq5 El script MQL5 EA de la tercera estrategia

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

Archivos adjuntos |
ORB.zip (7.11 KB)
Muhammad Syamil Bin Abdullah
Muhammad Syamil Bin Abdullah | 20 jun 2025 en 15:19
Gracias por compartirlo.
1149190
1149190 | 12 oct 2025 en 09:58
¡Gran artículo!

Es una lástima que no puedo ni remotamente reproducir los resultados probados con la configuración predeterminada - Este es el caso de todos los 3 los strats.

Utilizando los datos de [redactado] Cuenta Demo.

¿Alguna sugerencia de por qué no puedo reproducir los resultados?

Serge Rosenberg
Serge Rosenberg | 12 oct 2025 en 17:43
1149190 #¡¡¡Gran artículo!!! Sólo me gustaría poder siquiera remotamente reproducir los resultados probados con la configuración predeterminada - esto se aplica a los 3 strats. Estoy utilizando los datos de un [redactado] cuenta demo. ¿Alguna sugerencia de por qué no puedo reproducir los resultados?

Esto sólo funciona en futuros, como NQ.

1149190
1149190 | 12 oct 2025 en 18:58
Serge Rosenberg #:

Esto sólo funciona con futuros, como NQ.

Gracias por su respuesta. Voy a echar un vistazo. Me las arreglé para conseguir resultados decentes para la estrategia 2, pero no para las estrategias 1 y 3.

¿Está ejecutando la estrategia en una cuenta real?
Wu簡單
Wu簡單 | 17 ene 2026 en 10:24
Hola a todos, modifiqué orb1 para cerrar automáticamente una posición después de un número especificado de barras después de abrirla a una hora especificada, y funcionó bien.
orb2 no funciona, no funciona como se esperaba.

Criterios de tendencia. Final Criterios de tendencia. Final
En este artículo veremos cómo aplicar en la práctica algunos criterios de tendencia, y también intentaremos desarrollar algunos criterios nuevos. La atención se centrará en la eficacia de la aplicación de estos criterios al análisis de datos de mercado y al trading.
Clases de tabla y encabezado basadas en el modelo de tabla de MQL5: Aplicación del concepto MVC Clases de tabla y encabezado basadas en el modelo de tabla de MQL5: Aplicación del concepto MVC
Esta es la segunda parte del artículo dedicado a la implementación del modelo de tabla en MQL5 utilizando el paradigma constructivo MVC (Model-View-Controller). Este artículo trata sobre el desarrollo de clases de tabla y su encabezado a partir de un modelo de tabla previamente creado. Las clases desarrolladas serán la base para la posterior implementación de los componentes Vista (View) y Controlador (Controller), que se tratarán en los siguientes artículos.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Formulación de un Asesor Experto Multipar Dinámico (Parte 2): Diversificación y optimización de carteras Formulación de un Asesor Experto Multipar Dinámico (Parte 2): Diversificación y optimización de carteras
La diversificación y optimización de la cartera distribuye estratégicamente las inversiones entre múltiples activos para minimizar el riesgo, al tiempo que selecciona la combinación ideal de activos para maximizar la rentabilidad basándose en métricas de rendimiento ajustadas al riesgo.