Indicador personalizado para MetaTrader 5

Os dejo por aquí el último indicdor que he preparado y con el que actualmente estoy testeando la estrategia del DAX.

Es algo muy sencillo y limpio, simplemente marca el rango mínimo y maximo de la apertura del mercado desde al noche hasta las 8:00 am hora española (GMT +2) que en MT5 son las 6:00 am UTC, y tambien marcará, si es que son diferentes, el máximo y el mínimo entre las 8:00 am y 9:00 am hora española, que en MT5 son entre las 6:00 am y 7:am UTC.

Todos llevan su etiqueta. Obviamente, hasta las 9 se mueven, como si fueran soportes y resistencias dinámicos, pero a partir de las 9 se quedan fijos (porque a mi solo me interesa ese rango horario).

Tambien calcula el VWAP: como no hay un volumen real de contratos, lo hace en base a los ticks, pero la correlación es bastante buena, simplemente como soporte visual.

Y por último, lleva también una linea que marca el 50% del rango. ¿Por que?

Es muy normal en este indice, que el precio justo en apertura busque una zona de liquidez, barriendo e inmediatamente girandose, para despues revertir y continuar con el movimiento iniciado en la madrugada. Esa linea del 50% se puede usar como gatillo de entrada a favor del movimiento.

No puedo decir que lo haga a diario, pero la probalidad en este índice es alta. En este día (21 de agosto) el 50% del rango funciona como soporte toda la madrugada, es roto con violencia en la apertura, el precio se va a buscar el mínimo relativo de la sesión anterior y retoma su direción inicial. Podeis llamarlo manipulación, liquidity pool, barrido de SL, toma de Sell Stops pendientes… me da igual, no es el objeto de esta entrada.

Puedes buscar en el DAX varios ejemplos de estos a la semana.

La entrada ya la buscais cada uno con su estategia 😀

//+------------------------------------------------------------------+
//|                                                 Apertura DAX.mq5 |
//|                     Copyright 2026, AI Assistant, Raulito 073    |
//+------------------------------------------------------------------+
#property copyright "Raulito073 Copyright 2026"
#property indicator_chart_window

#property indicator_buffers 6
#property indicator_plots   6

// --- Configuración Visual de Líneas ---
#property indicator_label1  "VWAP Diario"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDarkOrange
#property indicator_style1  STYLE_DASH

#property indicator_label2  "Max Asia (00:00-06:00)"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrLightCoral
#property indicator_style2  STYLE_DOT

#property indicator_label3  "Min Asia (00:00-06:00)"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrOrange
#property indicator_style3  STYLE_DOT

#property indicator_label4  "Asia 50% (00:00-06:00)"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrFuchsia
#property indicator_style4  STYLE_DOT
#property indicator_width4  1

#property indicator_label5  "Max Pre-Euro (00:00-07:00)"
#property indicator_type5   DRAW_LINE
#property indicator_color5  clrGreen
#property indicator_style5  STYLE_DOT

#property indicator_label6  "Min Pre-Euro (00:00-07:00)"
#property indicator_type6   DRAW_LINE
#property indicator_color6  clrRed
#property indicator_style6  STYLE_DOT

// Buffers del indicador
double vwapDiarioBuffer[];
double max6Buffer[];
double min6Buffer[];
double mid6Buffer[];
double max7Buffer[];
double min7Buffer[];

// Estructura para ordenar y posicionar etiquetas sin pisarse
struct TEtiqueta {
   string name;
   string texto;
   double precio;
   color  col;
};

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, vwapDiarioBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, max6Buffer, INDICATOR_DATA);
   SetIndexBuffer(2, min6Buffer, INDICATOR_DATA);
   SetIndexBuffer(3, mid6Buffer, INDICATOR_DATA);
   SetIndexBuffer(4, max7Buffer, INDICATOR_DATA);
   SetIndexBuffer(5, min7Buffer, INDICATOR_DATA);
   
   IndicatorSetString(INDICATOR_SHORTNAME, "apertura DAX");

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ObjectsDeleteAll(0, "Asia_Text_");
   ObjectsDeleteAll(0, "Asia_VLine");
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   if(rates_total < 10) return(0);

   int start = 0;
   datetime limitTime = time[rates_total - 1] - (30 * 86400);

   double diarioSumPV = 0.0; long diarioSumV = 0; int lastDay = -1;

   for(int i = start; i < rates_total; i++)
   {
      datetime barTime = time[i];
      if(barTime < limitTime)
      {
         vwapDiarioBuffer[i] = close[i];
         max6Buffer[i] = EMPTY_VALUE; min6Buffer[i] = EMPTY_VALUE; mid6Buffer[i] = EMPTY_VALUE;
         max7Buffer[i] = EMPTY_VALUE; min7Buffer[i] = EMPTY_VALUE;
         continue; 
      }

      MqlDateTime dt;
      TimeToStruct(barTime, dt);
      
      if(dt.day != lastDay)
      {
         diarioSumPV = 0.0; diarioSumV = 0;
         lastDay = dt.day;
      }

      // =================================================================
      // 1. CÁLCULO DE NIVELES FIJOS (0-6 y 0-7)
      // =================================================================
      int dayStartIndex = i;
      while(dayStartIndex >= 0)
      {
         MqlDateTime tDt; TimeToStruct(time[dayStartIndex], tDt);
         if(tDt.day != dt.day) { dayStartIndex++; break; }
         if(dayStartIndex == 0) break;
         dayStartIndex--;
      }

      double tempMax6 = 0, tempMin6 = 999999;
      double tempMax7 = 0, tempMin7 = 999999;
      bool count6 = false, count7 = false;

      for(int k = dayStartIndex; k < rates_total; k++)
      {
         MqlDateTime kDt; TimeToStruct(time[k], kDt);
         if(kDt.day != dt.day) break;

         // RANGO 00:00 A 05:59
         if(kDt.hour >= 0 && kDt.hour < 6)
         {
            if(high[k] > tempMax6) tempMax6 = high[k];
            if(low[k] < tempMin6)  tempMin6 = low[k];
            count6 = true;
         }
         // RANGO 00:00 A 06:59 (Tu modificación corregida)
         if(kDt.hour >= 0 && kDt.hour < 7)
         {
            if(high[k] > tempMax7) tempMax7 = high[k];
            if(low[k] < tempMin7)  tempMin7 = low[k];
            count7 = true;
         }
      }

      if(count6) 
      { 
         max6Buffer[i] = tempMax6; 
         min6Buffer[i] = tempMin6; 
         mid6Buffer[i] = tempMin6 + ((tempMax6 - tempMin6) / 2.0); 
      }
      else { max6Buffer[i] = EMPTY_VALUE; min6Buffer[i] = EMPTY_VALUE; mid6Buffer[i] = EMPTY_VALUE; }

      if(count7) { max7Buffer[i] = tempMax7; min7Buffer[i] = tempMin7; }
      else { max7Buffer[i] = EMPTY_VALUE; min7Buffer[i] = EMPTY_VALUE; }

      // =================================================================
      // 2. CÁLCULO DE VWAP DIARIO
      // =================================================================
      double typicalPriceDiario = (high[i] + low[i] + close[i]) / 3.0;
      diarioSumPV += typicalPriceDiario * tick_volume[i];
      diarioSumV += tick_volume[i];
      vwapDiarioBuffer[i] = (diarioSumV > 0) ? (diarioSumPV / (double)diarioSumV) : close[i];
   }

   // =================================================================
   // 3. MATRIZ DE ETIQUETAS COLECTIVA Y ALGORITMO ANTISOLAPAMIENTO
   // =================================================================
   int lastIndex = rates_total - 1;
   datetime lastBarTime = time[lastIndex];

   TEtiqueta lista[6];
   lista[0].name = "Asia_Text_Max6";  lista[0].texto = "  Max Asia: " + DoubleToString(max6Buffer[lastIndex], _Digits);       lista[0].precio = max6Buffer[lastIndex];        lista[0].col = clrLightCoral;
   lista[1].name = "Asia_Text_Min6";  lista[1].texto = "  Min Asia: " + DoubleToString(min6Buffer[lastIndex], _Digits);       lista[1].precio = min6Buffer[lastIndex];        lista[1].col = clrOrange;
   lista[2].name = "Asia_Text_Mid6";  lista[2].texto = "  Asia 50%: " + DoubleToString(mid6Buffer[lastIndex], _Digits);       lista[2].precio = mid6Buffer[lastIndex];        lista[2].col = clrFuchsia;
   lista[3].name = "Asia_Text_Max7";  lista[3].texto = "  Max Pre-Euro: " + DoubleToString(max7Buffer[lastIndex], _Digits);   lista[3].precio = max7Buffer[lastIndex];        lista[3].col = clrGreen;
   lista[4].name = "Asia_Text_Min7";  lista[4].texto = "  Min Pre-Euro: " + DoubleToString(min7Buffer[lastIndex], _Digits);   lista[4].precio = min7Buffer[lastIndex];        lista[4].col = clrRed;
   lista[5].name = "Asia_Text_VwapD"; lista[5].texto = "  VWAP Diario: " + DoubleToString(vwapDiarioBuffer[lastIndex], _Digits); lista[5].precio = vwapDiarioBuffer[lastIndex]; lista[5].col = clrDarkOrange;

   // Método Bubble Sort: Ordenamos las etiquetas de mayor precio a menor precio
   for(int m = 0; m < 5; m++) {
      for(int n = m + 1; n < 6; n++) {
         if(lista[m].precio < lista[n].precio) {
            TEtiqueta temp = lista[m];
            lista[m] = lista[n];
            lista[n] = temp;
         }
      }
   }

   // Filtro de proximidad (Evita colisiones solapando verticalmente en base a píxeles simulados)
   // Calculamos el umbral mínimo de separación en puntos del precio (aprox 12 píxeles de altura de fuente)
   double chartMax = ChartGetDouble(0, CHART_PRICE_MAX);
   double chartMin = ChartGetDouble(0, CHART_PRICE_MIN);
   double umbralSeparacion = (chartMax - chartMin) * 0.025; // 2.5% de la altura visible del gráfico

   for(int m = 1; m < 6; m++) {
      if(lista[m].precio == EMPTY_VALUE || lista[m-1].precio == EMPTY_VALUE) continue;
      
      // Si la etiqueta actual está demasiado pegada a la de arriba, la empujamos hacia abajo
      if((lista[m-1].precio - lista[m].precio) < umbralSeparacion) {
         lista[m].precio = lista[m-1].precio - umbralSeparacion;
      }
   }
   
   // Dibujamos las etiquetas finales reubicadas matemáticamente
   for(int m = 0; m < 6; m++) {
      if(lista[m].precio <= 0 || lista[m].precio == EMPTY_VALUE) continue;
      if(ObjectFind(0, lista[m].name) < 0) {
         ObjectCreate(0, lista[m].name, OBJ_TEXT, 0, lastBarTime, lista[m].precio);
      }
      ObjectMove(0, lista[m].name, 0, lastBarTime, lista[m].precio);
      ObjectSetString(0, lista[m].name, OBJPROP_TEXT, lista[m].texto);
      ObjectSetInteger(0, lista[m].name, OBJPROP_COLOR, lista[m].col);
      ObjectSetInteger(0, lista[m].name, OBJPROP_FONTSIZE, 9);
      ObjectSetString(0, lista[m].name, OBJPROP_FONT, "Arial Bold");
      ObjectSetInteger(0, lista[m].name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
   }
   // =================================================================
   // 4. LÍNEAS VERTICALES DIARIAS A LAS 06:00 Y 07:00
   // =================================================================
   int vlineCount = 0;
   string prevDay = "";
   for(int i = rates_total - 1; i >= 0; i--)
   {
      MqlDateTime bDt;
      TimeToStruct(time[i], bDt);
      string key = IntegerToString(bDt.year) + "_" + IntegerToString(bDt.mon) + "_" + IntegerToString(bDt.day);
      if(key != prevDay)
      {
         prevDay = key;

         // Línea vertical a las 06:00
         bDt.hour = 6; bDt.min = 0; bDt.sec = 0;
         datetime t6 = StructToTime(bDt);
         string n6 = "Asia_VLine6_" + key;
         if(ObjectFind(0, n6) < 0)
            ObjectCreate(0, n6, OBJ_VLINE, 0, t6, 0);
         ObjectSetInteger(0, n6, OBJPROP_TIME, t6);
         ObjectSetInteger(0, n6, OBJPROP_COLOR, clrLightCoral);
         ObjectSetInteger(0, n6, OBJPROP_WIDTH, 1);
         ObjectSetInteger(0, n6, OBJPROP_STYLE, STYLE_DASH);
         ObjectSetInteger(0, n6, OBJPROP_BACK, false);

         // Línea vertical a las 07:00
         bDt.hour = 7;
         datetime t7 = StructToTime(bDt);
         string n7 = "Asia_VLine7_" + key;
         if(ObjectFind(0, n7) < 0)
            ObjectCreate(0, n7, OBJ_VLINE, 0, t7, 0);
         ObjectSetInteger(0, n7, OBJPROP_TIME, t7);
         ObjectSetInteger(0, n7, OBJPROP_COLOR, clrGreen);
         ObjectSetInteger(0, n7, OBJPROP_WIDTH, 1);
         ObjectSetInteger(0, n7, OBJPROP_STYLE, STYLE_DASH);
         ObjectSetInteger(0, n7, OBJPROP_BACK, false);

         vlineCount++;
         if(vlineCount >= 30) break; // máximo 30 días hacia atrás
      }
   }
   return(rates_total);
}

Queda así:

Saludos. Si te gusta, compartelo. El código es libre y puedes copiarlo y modificarlo a tu gusto.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *