Discusión sobre el artículo "Análisis de las brechas temporales de precios en MQL5 (Parte II): Creamos un mapa de calor de la distribución de liquidez a lo largo del tiempo"
Me recuerda: yo solía hacer esto hace mucho tiempo,
En mi opinión, usted tiene sólo un poco incompleta - que queda por revelar la estructura regular (o mostrar que no existe).
Esto no es un mapa de calor, por supuesto - sólo los extremos de la parte regular de un mapa de temperatura similar.
¿En qué se diferencia del conocido "perfil de mercado" (aparte del color)?
Por cierto, el mapa se calcula utilizando el método más laborioso y propenso a errores.
Todo es más simple - para cada vela 2 pares {precio,peso} : { precio=alto, peso=-1; } { precio=bajo,peso=+1;} La colección se ordena por precio, la suma con acumulación por peso es el mapa de calor. Luego se cuantifica como se quiera.
Decidí experimentar con ChatGPT para atornillar un par de "mejoras" y crear algo útil para mí - esto es lo que salió de ello. La parte comentada es mi intento de detectar cuando el precio entra en una de las zonas coloreadas y mostrar visualmente su reacción. Pero la idea era captar los matices de estos colores: por ejemplo, cuando la zona es naranja ligeramente más brillante, según mis observaciones, el precio se desplaza lateralmente.
//+------------------------------------------------------------------+ //| Mapa de calor Plus - v3.00 modificada //| Mantén el buffer/alertas y añade ADX, estadísticas y clasificación. //+------------------------------------------------------------------+ #property copyright "Chart Coloring by Time and Volume Distribution" #property link "https://www.mql5.com" #property version "3.00" #property indicator_chart_window #property strict #include <Math\Stat\Math.mqh> // MathMean, MathStdDev #include <Indicators\Trend.mqh> // ADX para filtrar tendencias // buffers para EA #property indicator_buffers 3 #property indicator_plots 0 double LevelPriceBuffer[]; double LevelStrengthBuffer[]; double LevelTypeBuffer[]; //--- Parámetros de entrada sinput group "=== Heat Map Settings ===" input int AnalysisPeriod = 500; input int MaxHistory = 10000; input double TickSize = 0; input bool UseTickVolume = true; input double VolumeWeight = 0.5; input int SessStartHour = 8; input int SessEndHour = 17; input double MinBarRange = 10; input bool EnableAlerts = true; //+------------------------------------------------------------------+ //|| //+------------------------------------------------------------------+ sinput group "=== Colores (1%→100%) ===" input color Color1Percent = clrRed; input color Color25Percent = clrOrange; input color Color50Percent = clrYellow; input color Color75Percent = clrAqua; input color Color100Percent = clrBlue; input int Transparency = 70; //--- globales struct PriceLevel { double price, price_high, price_low; long time_spent; long volume; double presence_percent; color level_color; string object_name; }; PriceLevel levels[]; datetime startTime, endTime, lastUpdate; int totalPriceLevels = 0; long maxTimeSpent = 0, minTimeSpent = LONG_MAX; long maxVolume = 0, minVolume = LONG_MAX; bool calculated = false; //--- Asa ADX CiADX adx; //+------------------------------------------------------------------+ //| Inicialización| //+------------------------------------------------------------------+ int OnInit() { // buffers para EA SetIndexBuffer(0, LevelPriceBuffer); SetIndexBuffer(1, LevelStrengthBuffer); SetIndexBuffer(2, LevelTypeBuffer); if(AnalysisPeriod < 50 || MaxHistory < AnalysisPeriod) { Print("Parámetros incorrectos: 50 <= AnalysisPeriod <= MaxHistory"); return(INIT_PARAMETERS_INCORRECT); } if(VolumeWeight < 0 || VolumeWeight > 1) { Print("VolumeWeight deve estar em [0,1]"); return(INIT_PARAMETERS_INCORRECT); } // ADX(14) if(!adx.Create(_Symbol, _Period, 14)) { Print("Erro ao criar ADX"); return(INIT_FAILED); } ArrayResize(levels, 0); lastUpdate = 0; calculated = false; totalPriceLevels = 0; return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Cálculo| //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &/*abrir*/[], const double &high[], const double &low[], const double &/*cerrar*/.[], const long &tick_volume[], const long &volume[], const int &/*spread*/.[]) { if(rates_total < AnalysisPeriod) return(0); // só recalcula em novo vela if(lastUpdate == time[rates_total - 1]) return(rates_total); lastUpdate = time[rates_total - 1]; calculated = false; if(!calculated) { // atualiza ADX adx.Refresh(1); // cálculo principal CalculateTimeDistribution(time, high, low, tick_volume, volume, rates_total); ColorChart(); ExportBuffers(rates_total); calculated = true; } return(rates_total); } //+------------------------------------------------------------------+ //| Ventana corredera + filtros de caudal y volatilidad. //+------------------------------------------------------------------+ void CalculateTimeDistribution(const datetime &time[], const double &high[], const double &low[], const long &tick_volume[], const long &volume[], int rates_total) { ArrayResize(levels, 0); int historyStart = MathMax(0, rates_total - MaxHistory); startTime = time[historyStart]; endTime = time[rates_total - 1]; // gama de precios double maxP = high[ArrayMaximum(high, historyStart, MaxHistory)]; double minP = low [ArrayMinimum(low, historyStart, MaxHistory)]; double priceRange = maxP - minP; if(priceRange <= 0) return; // tamaño del tick double tk = (TickSize > 0 ? TickSize : Point()); totalPriceLevels = (int)(priceRange / tk); totalPriceLevels = MathMin(1000, MathMax(50, totalPriceLevels)); double realTick = priceRange / totalPriceLevels; // niveles init ArrayResize(levels, totalPriceLevels); for(int i = 0; i < totalPriceLevels; i++) { levels[i].price = minP + i * realTick; levels[i].price_high = levels[i].price + realTick / 2; levels[i].price_low = levels[i].price - realTick / 2; levels[i].time_spent = 0; levels[i].volume = 0; levels[i].object_name = "HeatLevel_" + IntegerToString(i); } // percorre barras for(int bar = historyStart; bar < rates_total; bar++) { // filtro de sesión int hr = TimeHour(time[bar]); if(hr < SessStartHour || hr > SessEndHour) continue; // filtro de volatilidad double amp = (high[bar] - low[bar]) / Point(); if(amp < MinBarRange) continue; long barVol = UseTickVolume ? tick_volume[bar] : volume[bar]; // encontrar los niveles afectados int st = MathMax(0, (int)((low[bar] - minP) / realTick)); int en = MathMin(totalPriceLevels - 1, (int)((high[bar] - minP) / realTick)); for(int li = st; li <= en; li++) { if(DoesBarTouchLevel(high[bar], low[bar], levels[li])) { levels[li].time_spent++; levels[li].volume += barVol; } } } // acha min/max maxTimeSpent = 0; minTimeSpent = LONG_MAX; maxVolume = 0; minVolume = LONG_MAX; for(int i = 0; i < totalPriceLevels; i++) { long t = levels[i].time_spent; long v = levels[i].volume; if(t > maxTimeSpent) maxTimeSpent = t; if(t > 0 && t < minTimeSpent) minTimeSpent = t; if(v > maxVolume) maxVolume = v; if(v > 0 && v < minVolume) minVolume = v; } if(minTimeSpent == LONG_MAX) minTimeSpent = 0; if(minVolume == LONG_MAX) minVolume = 0; // porcentaje de núcleos CalculatePercentsAndColors(); } //+------------------------------------------------------------------+ //| Porcentajes y núcleos| //+------------------------------------------------------------------+ void CalculatePercentsAndColors() { long tr = maxTimeSpent - minTimeSpent; if(tr <= 0) tr = 1; long vr = maxVolume - minVolume; if(vr <= 0) vr = 1; for(int i = 0; i < totalPriceLevels; i++) { double tp = levels[i].time_spent > 0 ? ((levels[i].time_spent - minTimeSpent) / (double)tr) * 100.0 : 0; double vp = levels[i].volume > 0 ? ((levels[i].volume - minVolume) / (double)vr) * 100.0 : 0; double comb = (1.0 - VolumeWeight) * tp + VolumeWeight * vp; levels[i].presence_percent = MathMax(1.0, MathMin(100.0, comb)); levels[i].level_color = GetPercentageColor(levels[i].presence_percent); } } //+------------------------------------------------------------------+ //| Gráfico Colorear + Alertas| //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //|| //+------------------------------------------------------------------+ void ColorChart() { ClearPreviousObjects(); datetime nowTime = TimeCurrent(); double lastBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); // Array de nomes de zona string zoneNames[5] = {"", "Reversão"., "Tendência"., "Lateralização"., "Barreira" }; // 1) Armazena ztype e preço médio int types[]; ArrayResize(types, totalPriceLevels); double mids[]; ArrayResize(mids, totalPriceLevels); for(int i = 0; i < totalPriceLevels; i++) { types[i] = (int)LevelTypeBuffer[ArraySize(LevelTypeBuffer) - 1 - i]; if(types[i] < 1 || types[i] > 4) types[i] = 4; mids[i] = (levels[i].price_low + levels[i].price_high) / 2.0; } // 2) Cria objetos de zona (retângulos coloridos) for(int i = 0; i < totalPriceLevels; i++) { string nm = levels[i].object_name; // retângulo da zona if(ObjectCreate(ChartID(), nm, OBJ_RECTANGLE, 0, startTime, levels[i].price_low, nowTime, levels[i].price_high)) { ObjectSetInteger(ChartID(), nm, OBJPROP_COLOR, levels[i].level_color); ObjectSetInteger(ChartID(), nm, OBJPROP_BACK, true); ObjectSetInteger(ChartID(), nm, OBJPROP_FILL, true); ENUM_LINE_STYLE style = (Transparency > 50) ? STYLE_DOT : STYLE_SOLID; ObjectSetInteger(ChartID(), nm, OBJPROP_STYLE, style); ObjectSetInteger(ChartID(), nm, OBJPROP_WIDTH, 1); } // alertas if(EnableAlerts) { static double lastPrice = 0; bool cross = ((lastPrice < levels[i].price && lastBid >= levels[i].price) || (lastPrice > levels[i].price && lastBid <= levels[i].price)); if(cross) { Alert(" HeatMap: preço cruzou nível ", DoubleToString(levels[i].price, _Digits), " [", zoneNames[types[i]], "]"); } lastPrice = lastBid; } } //// 3) Agrupa por tipo de contíguo y dibuja una línea por grupo // struct Segmento { // tipo int; // int inicio; // int fin; }; // Segmento segs[]; // ArrayResize(segs, 0); // int tipoactual = tipos[0]; // int segStart = 0; // for(int i = 1; i < totalNivelesPrecios; i++) { // if(types[i] != currentType) { // Segmento seg; // seg.type = currentType; // seg.start = segStart; // seg.end = i - 1; // ArrayResize(segs, ArraySize(segs) + 1); // segs[ArraySize(segs) - 1] = seg; // currentType = tipos[i]; // segStart = i; } } //// último segmento // Segmento lastSeg; // lastSeg.type = currentType; // lastSeg.start = segStart; // lastSeg.end = totalPreciosNiveles - 1; // ArrayResize(segs, ArraySize(segs) + 1); // segs[ArraySize(segs) - 1] = lastSeg; //// 4) Desenha linha horizontal média por segmento // for(int j = 0; j < ArraySize(segs); j++) { // int s = segs[j].start; // int e = segs[j].end; // int tipo = segs[j].tipo; // // média dos preços médios // double avgPrice = 0; // for(int k = s; k <= e; k++) // avgPrice += mids[k]; // avgPrice /= (e - s + 1); //// Criar linha horizontal // string lineName = StringFormat(zoneNames[types[s]] + "linha", s, e); // if(ObjectCreate(ChartID(), lineName, OBJ_HLINE, 0, 0, avgPrice)) { // ObjectSetInteger(ChartID(), lineName, OBJPROP_COLOR, clrWhite); // ObjectSetInteger(ChartID(), lineName, OBJPROP_WIDTH, 4); // ObjectSetInteger(ChartID(), lineName, OBJPROP_STYLE, STYLE_SOLID); } //// Criar texto a direita (última vela visible) // double textOffset = SymbolInfoDouble(_Symbol, SYMBOL_POINT) * 100; // desplazamiento vertical sobre la línea // double textPrice = avgPrice + textOffset; // datetime timeRight = iTime(_Symbol, _Period, 0); // tempo do candle mais recente // cadena textName = StringFormat(zoneNames[types[s]] + "texto", s, e); // if(ObjectCreate(ChartID(), textName, OBJ_TEXT, 0, timeRight, textPrice)) { // ObjectSetInteger(ChartID(), textName, OBJPROP_COLOR, clrBlack); // ObjectSetInteger(ChartID(), textName, OBJPROP_FONTSIZE, 10); // ObjectSetInteger(ChartID(), textName, OBJPROP_CORNER, CORNER_RIGHT_LOWER); // opcional si se quiere en pixel // ObjectSetString(ChartID(), textName, OBJPROP_TEXT, zoneNames[types[s]]); // ObjectSetInteger(ChartID(), textName, OBJPROP_ANCHOR, ANCHOR_RIGHT); // ancora à direita do ponto // } } } //+------------------------------------------------------------------+ //|| //+------------------------------------------------------------------+ int CountZLineObjects() { int total = ObjectsTotal(ChartID()); int count = 0; for(int i = 0; i < total; i++) { string name = ObjectName(ChartID(), i); if(StringFind(name, "ZLine_") == 0) // verifica si começa com "ZLine_" count++; } return count; } //+------------------------------------------------------------------+ //| Exporta buffers para EA + classificação de zona || //+------------------------------------------------------------------+ void ExportBuffers(int rates_total) { // monta array de stats double stats[]; ArrayResize(stats, totalPriceLevels); for(int i = 0; i < totalPriceLevels; i++) stats[i] = levels[i].presence_percent; double mean = MathMean(stats); double sd = MathStdDev(stats, mean); if(sd <= 0) sd = mean * 0.1; double adxValue = adx.Main(0); int cnt = MathMin(totalPriceLevels, ArraySize(LevelPriceBuffer)); for(int i = 0; i < cnt; i++) { // tipo de zona double p = levels[i].presence_percent; int ztype = 4; // BARREIRA por padrão. if(p < mean - sd) ztype = 1; // REVERSÃO else if(p > mean + sd && adxValue >= 25.0) ztype = 2; // TENDÊNCIA else if(adxValue < 20.0) ztype = 3; // LATERALIZACIÓN LevelPriceBuffer [rates_total - 1 - i] = levels[i].price; LevelStrengthBuffer[rates_total - 1 - i] = p; LevelTypeBuffer [rates_total - 1 - i] = ztype; } } //+------------------------------------------------------------------+ //| Ayudantes| //+------------------------------------------------------------------+ bool DoesBarTouchLevel(double high, double low, const PriceLevel &L) { return(high >= L.price_low && low <= L.price_high); } //+------------------------------------------------------------------+ //|| //+------------------------------------------------------------------+ void ClearPreviousObjects() { int tot = ObjectsTotal(ChartID()); for(int i = tot - 1; i >= 0; i--) { string nm = ObjectName(ChartID(), i); if(StringFind(nm, "HeatLevel_") == 0) ObjectDelete(ChartID(), nm); } } //+------------------------------------------------------------------+ //|| //+------------------------------------------------------------------+ color GetPercentageColor(double p) { if(p <= 1.0) return Color1Percent; else if(p <= 25.) return InterpolateColor( Color1Percent, Color25Percent, (p - 1) / 24); else if(p <= 50.) return InterpolateColor( Color25Percent, Color50Percent, (p - 25) / 25); else if(p <= 75.) return InterpolateColor( Color50Percent, Color75Percent, (p - 50) / 25); else return InterpolateColor( Color75Percent, Color100Percent, (p - 75) / 25); } //+------------------------------------------------------------------+ //|| //+------------------------------------------------------------------+ color InterpolateColor(color c1, color c2, double f) { int r1 = (c1 >> 16) & 0xFF, g1 = (c1 >> 8) & 0xFF, b1 = c1 & 0xFF; int r2 = (c2 >> 16) & 0xFF, g2 = (c2 >> 8) & 0xFF, b2 = c2 & 0xFF; int r = int(r1 + (r2 - r1) * f), g = int(g1 + (g2 - g1) * f), b = int(b1 + (b2 - b1) * f); return (r << 16) | (g << 8) | b; } //+------------------------------------------------------------------+ //|| //+------------------------------------------------------------------+ int TimeHour(const datetime time) { MqlDateTime dt; TimeToStruct(time, dt); return dt.hour; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| DrawAndExport| //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Calcula la media de un array de dobles || //+------------------------------------------------------------------+ double MathMean(const double &a[], const int count) { if(count <= 0) return 0.0; double sum = 0.0; for(int i = 0; i < count; i++) sum += a[i]; return sum / count; } //+------------------------------------------------------------------+ //| Calcula el desvío padrón amostral de un array de double || //+------------------------------------------------------------------+ double MathStdDev(const double &a[], const int count) { if(count < 2) return 0.0; double mean = MathMean(a, count); double sumSq = 0.0; for(int i = 0; i < count; i++) sumSq += MathPow(a[i] - mean, 2); return MathSqrt(sumSq / (count - 1)); // Desvio padrão amostral. } //+------------------------------------------------------------------+
Discover new MetaTrader 5 opportunities with MQL5 community and services
- 2025.07.15
- www.mql5.com
MQL5: language of trade strategies built-in the MetaTrader 5 Trading Platform, allows writing your own trading robots, technical indicators, scripts and libraries of functions
Está perdiendo oportunidades comerciales:
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Registro
Entrada
Usted acepta la política del sitio web y las condiciones de uso
Si no tiene cuenta de usuario, regístrese

Artículo publicado Análisis de las brechas temporales de precios en MQL5 (Parte II): Creamos un mapa de calor de la distribución de liquidez a lo largo del tiempo:
En la primera parte del artículo, examinamos el concepto de brechas temporales y su relación con la actividad institucional. Sin embargo, detectar estas deficiencias supone solo la mitad del trabajo. Para negociar con eficacia, los tráders necesitan tener una visión completa: dónde el precio permanece mucho tiempo, dónde permanece poco tiempo y cómo interactúan estas zonas entre sí.
Aquí es donde entra en juego nuestro indicador: una herramienta que convierte patrones temporales invisibles en un mapa de calor visual. Si en la primera parte buscábamos anomalías (brechas), ahora estamos construyendo un mapa completo del comportamiento normal de los precios.
La idea básica es sencilla: representar todo el rango de precios como un conjunto de microzonas y, para cada zona, calcular cuánto tiempo ha permanecido el precio dentro de ella. Cuanto más tiempo transcurra, más "caliente" será la zona y más importante resultará para el mercado. El resultado se visualizará mediante una gama de colores que va del rojo frío al azul cálido.
Autor: Yevgeniy Koshtenko