//+------------------------------------------------------------------+ //| ProfileHeatmap.mq5 | //| Copyright 2026, Google Gemini | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Google Gemini" #property link "https://www.google.com" #property version "1.00" #property indicator_chart_window #property indicator_buffers 1 #property indicator_plots 1 #property indicator_type1 DRAW_NONE #include #include //--- Input Parameters input int InpRangeBars = 300; // Bars to Analyze input int InpPriceStep = 10; // Price Step (Ticks per bin) input color InpBaseColor = clrOrange; // Heatmap Color input int InpMaxOpacity = 180; // Max Opacity (0-255) input int InpWidthRatio = 200; // Width of Heatmap (Pixels) //--- GPR Parameters input int InpGPR_Bars = 100; // GPR: Training Bars input int InpGPR_Future = 50; // GPR: Future Bars to Predict input double InpGPR_LS = 10.0; // GPR: Length Scale (Bars) input double InpGPR_SigmaF = 1.0; // GPR: Signal Variance input double InpGPR_SigmaN = 0.05; // GPR: Noise Variance input color InpGPR_Color = clrCyan; // GPR: Heatmap Color //--- Global Variables CCanvas canvas; string canvasName = "ProfileHeatmapCanvas"; int chartWidth, chartHeight; double minPrice, maxPrice; double bins[]; int numBins; int lastBarCount = 0; datetime lastUpdate = 0; double DummyBuffer[]; // GPR Results vector gprMean; vector gprVar; double gprMinPrice, gprMaxPrice; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { chartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); chartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); if(!canvas.CreateBitmapLabel(canvasName, 0, 0, chartWidth, chartHeight, COLOR_FORMAT_ARGB_NORMALIZE)) { Print("Canvas creation failed"); return(INIT_FAILED); } SetIndexBuffer(0, DummyBuffer, INDICATOR_DATA); PlotIndexSetString(0, PLOT_LABEL, "Dummy"); canvas.Erase(0); canvas.Update(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { canvas.Destroy(); } //+------------------------------------------------------------------+ //| 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 < InpRangeBars) return(0); // Update chart dimensions int w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); if(w != chartWidth || h != chartHeight) { chartWidth = w; chartHeight = h; canvas.Resize(chartWidth, chartHeight); ChartRedraw(); } // Throttle updates: every 2 seconds or on new bar datetime now = TimeCurrent(); if(rates_total == lastBarCount && (now - lastUpdate) < 2) return(rates_total); lastBarCount = rates_total; lastUpdate = now; CalculateHeatmap(high, low, tick_volume, rates_total); CalculateGPR(close, rates_total); DrawAll(); return(rates_total); } //+------------------------------------------------------------------+ //| Chart Event Handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_CHART_CHANGE) { int w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); if(w != chartWidth || h != chartHeight) { chartWidth = w; chartHeight = h; canvas.Resize(chartWidth, chartHeight); lastBarCount = 0; // Force redraw } } } //+------------------------------------------------------------------+ //| Calculate the Profile with Gaussian Distribution | //+------------------------------------------------------------------+ void CalculateHeatmap(const double &high[], const double &low[], const long &tick_volume[], int total) { double point = _Point; if(point == 0) point = 0.00001; double tickSize = _Point; if(tickSize == 0) tickSize = point; double stepPrice = InpPriceStep * tickSize; // 1. Find Price Range minPrice = 999999; maxPrice = -1; for(int i = 0; i < InpRangeBars; i++) { int idx = total - 1 - i; if(idx < 0) break; if(high[idx] > maxPrice) maxPrice = high[idx]; if(low[idx] < minPrice) minPrice = low[idx]; } if(maxPrice <= minPrice) return; numBins = (int)((maxPrice - minPrice) / stepPrice) + 1; ArrayResize(bins, numBins); ArrayInitialize(bins, 0); // 2. Accumulate Volume in Bins (Raw Volume Profile) for(int i = 0; i < InpRangeBars; i++) { int idx = total - 1 - i; if(idx < 0) break; int startBin = (int)((low[idx] - minPrice) / stepPrice); int endBin = (int)((high[idx] - minPrice) / stepPrice); long vol = tick_volume[idx]; int span = (endBin - startBin + 1); double volPerBin = (double)vol / span; for(int b = startBin; b <= endBin; b++) { if(b >= 0 && b < numBins) bins[b] += volPerBin; } } } //+------------------------------------------------------------------+ //| Calculate GPR Prediction | //+------------------------------------------------------------------+ void CalculateGPR(const double &close[], int total) { int n = InpGPR_Bars; int m = InpGPR_Future; if(total < n) return; vector y(n); vector x(n); double firstPrice = close[total - n]; double lastPrice = close[total - 1]; // Normalize y (price) around the current price for stability for(int i = 0; i < n; i++) { y[i] = close[total - n + i] - lastPrice; x[i] = i; } matrix K(n, n); double lsSq = 2.0 * InpGPR_LS * InpGPR_LS; double sfSq = InpGPR_SigmaF * InpGPR_SigmaF; double snSq = InpGPR_SigmaN * InpGPR_SigmaN; for(int i = 0; i < n; i++) { for(int j = 0; j <= i; j++) { double dist = x[i] - x[j]; double val = sfSq * exp(-(dist * dist) / lsSq); if(i == j) val += snSq; K[i][j] = val; if(i != j) K[j][i] = val; } } matrix Kinv = K.Inv(); if(Kinv.Rows() == 0) return; // Inverse failed vector alpha = Kinv.MatMul(y); gprMean.Resize(m); gprVar.Resize(m); vector xStar(m); for(int i = 0; i < m; i++) xStar[i] = n + i; matrix KStar(m, n); for(int i = 0; i < m; i++) { for(int j = 0; j < n; j++) { double dist = xStar[i] - x[j]; KStar[i][j] = sfSq * exp(-(dist * dist) / lsSq); } } gprMean = KStar.MatMul(alpha); // Posterior Variance: diag(K** - K* K^-1 K*T) for(int i = 0; i < m; i++) { vector kStar_i = KStar.Row(i); double kss = sfSq + snSq; double q = kStar_i.MatMul(Kinv.MatMul(kStar_i)); gprVar[i] = MathMax(0, kss - q); } // Restore normalization for(int i = 0; i < m; i++) gprMean[i] += lastPrice; } //+------------------------------------------------------------------+ //| Draw Everything | //+------------------------------------------------------------------+ void DrawAll() { canvas.Erase(0); DrawVolumeProfile(); DrawGPRHeatmap(); canvas.Update(); } //+------------------------------------------------------------------+ //| Draw the Volume Profile Heatmap | //+------------------------------------------------------------------+ void DrawVolumeProfile() { double maxVal = -1; for(int i = 0; i < numBins; i++) if(bins[i] > maxVal) maxVal = bins[i]; if(maxVal <= 0) return; double chartMinP = ChartGetDouble(0, CHART_PRICE_MIN); double chartMaxP = ChartGetDouble(0, CHART_PRICE_MAX); double priceRange = chartMaxP - chartMinP; if(priceRange <= 0) return; double stepPrice = (maxPrice - minPrice) / numBins; for(int i = 0; i < numBins; i++) { double price = minPrice + i * stepPrice; double intensity = bins[i] / maxVal; int y = (int)((chartMaxP - price) / priceRange * chartHeight); int yNext = (int)((chartMaxP - (price + stepPrice)) / priceRange * chartHeight); if(y < 0 && yNext < 0) continue; if(y >= chartHeight && yNext >= chartHeight) continue; uchar alpha = (uchar)(intensity * InpMaxOpacity); uint c = ColorToARGB(InpBaseColor, alpha); int drawWidth = (int)(intensity * InpWidthRatio); canvas.FillRectangle(chartWidth - drawWidth, yNext, chartWidth, y, c); } } //+------------------------------------------------------------------+ //| Draw the GPR Heatmap | //+------------------------------------------------------------------+ void DrawGPRHeatmap() { if(gprMean.Size() == 0) return; double chartMinP = ChartGetDouble(0, CHART_PRICE_MIN); double chartMaxP = ChartGetDouble(0, CHART_PRICE_MAX); double priceRange = chartMaxP - chartMinP; if(priceRange <= 0) return; int barsVisible = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); int lastBarX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); // Calculate pixel width per bar double barWidth = (double)chartWidth / barsVisible; // Find price steps for the heatmap bins at each future step int futureSteps = (int)gprMean.Size(); for(int i = 0; i < futureSteps; i++) { double mean = gprMean[i]; double std = sqrt(gprVar[i]); if(std <= 0) std = _Point; int xStart = (int)(chartWidth - (barsVisible - i) * barWidth); // This is complex, let's use a simpler mapping // Better way: use ChartTimePriceToXY datetime futureDate = TimeCurrent() + PeriodSeconds() * (i + 1); int x, y_mean; if(!ChartTimePriceToXY(0, 0, futureDate, mean, x, y_mean)) continue; // Draw a vertical density for this time step // Heatmap bins for price probability int p_bins = 40; double range_std = 3.0; for(int j = 0; j < p_bins; j++) { double p_offset = (double)(j - p_bins / 2) / (p_bins / 2) * (range_std * std); double price = mean + p_offset; double z = p_offset / std; double prob = exp(-0.5 * z * z); // Gaussian density (approx) int py1, py2; double priceNext = mean + (double)(j + 1 - p_bins / 2) / (p_bins / 2) * (range_std * std); ChartTimePriceToXY(0, 0, futureDate, price, x, py1); ChartTimePriceToXY(0, 0, futureDate, priceNext, x, py2); uchar alpha = (uchar)(prob * InpMaxOpacity); uint c = ColorToARGB(InpGPR_Color, alpha); int x2 = x + (int)barWidth; canvas.FillRectangle(x, py2, x2, py1, c); } } } //+------------------------------------------------------------------+