2026-01-26 17:18:21 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| 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 <Canvas\Canvas.mqh>
|
|
|
|
|
#include <Math\Stat\Normal.mqh>
|
|
|
|
|
|
|
|
|
|
//--- Input Parameters
|
|
|
|
|
input int InpRangeBars = 300; // Bars to Analyze
|
|
|
|
|
input int InpPriceStep = 10; // Price Step (Ticks per bin)
|
2026-01-29 11:34:39 +03:00
|
|
|
|
2026-01-26 17:18:21 +03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|