//+------------------------------------------------------------------+ //| LiquidityHeatmap.mq5 | //| Copyright 2024, Your Name | //| https://www.yourwebsite.com| //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Your Name" #property link "https://www.yourwebsite.com" #property version "1.00" #property indicator_chart_window #property indicator_buffers 0 #property indicator_plots 0 //+------------------------------------------------------------------+ //| Input parameters | //+------------------------------------------------------------------+ input int iBarsBack = 500; // Number of bars to analyze input int iRows = 50; // Number of price rows (resolution) input double dLiquidityFactor = 2.0; // Sensitivity (higher = smoother) input color clrLowLiquidity = clrDarkBlue; // Low liquidity color input color clrHighLiquidity = clrRed; // High liquidity color input bool bShowLegend = true; // Show legend on chart input ENUM_TIMEFRAMES tfAnalysis = PERIOD_CURRENT; // Timeframe for analysis //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ double g_dPriceMin, g_dPriceMax; int g_iRows; double g_dHeatmap[]; // Heatmap values per price level double g_dVolumeProfile[]; double g_dPriceLevels[]; int g_iHandleVolumes; string g_sIndicatorName = "LiquidityHeatmap"; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { // Set rows based on input g_iRows = iRows; // Calculate initial price range CalculatePriceRange(); // Initialize arrays ArrayResize(g_dVolumeProfile, g_iRows); ArrayResize(g_dPriceLevels, g_iRows); ArrayResize(g_dHeatmap, g_iRows); // Fill price levels double step = (g_dPriceMax - g_dPriceMin) / g_iRows; for(int i = 0; i < g_iRows; i++) { g_dPriceLevels[i] = g_dPriceMin + (i * step); } // Calculate volume profile CalculateVolumeProfile(); // Normalize and create heatmap colors NormalizeHeatmap(); // Redraw chart ChartRedraw(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Calculate price range across specified bars | //+------------------------------------------------------------------+ void CalculatePriceRange() { g_dPriceMin = DBL_MAX; g_dPriceMax = -DBL_MAX; int totalBars = iBarsBack; int startBar = 1; // skip current incomplete bar int endBar = startBar + totalBars; for(int i = startBar; i < endBar; i++) { double high = iHigh(_Symbol, tfAnalysis, i); double low = iLow(_Symbol, tfAnalysis, i); if(high > g_dPriceMax) g_dPriceMax = high; if(low < g_dPriceMin) g_dPriceMin = low; } // Add some padding (2% above and below) double padding = (g_dPriceMax - g_dPriceMin) * 0.02; g_dPriceMin -= padding; g_dPriceMax += padding; } //+------------------------------------------------------------------+ //| Calculate volume profile using tick volume or real volume | //+------------------------------------------------------------------+ void CalculateVolumeProfile() { // Initialize volume profile array ArrayInitialize(g_dVolumeProfile, 0.0); int totalBars = iBarsBack; int startBar = 1; int endBar = startBar + totalBars; double step = (g_dPriceMax - g_dPriceMin) / g_iRows; for(int i = startBar; i < endBar; i++) { double high = iHigh(_Symbol, tfAnalysis, i); double low = iLow(_Symbol, tfAnalysis, i); long volume = iVolume(_Symbol, tfAnalysis, i); // Determine which price levels this bar affects int startRow = (int)((low - g_dPriceMin) / step); int endRow = (int)((high - g_dPriceMin) / step); // Cap to array bounds startRow = MathMax(0, startRow); endRow = MathMin(g_iRows - 1, endRow); // Distribute volume across price levels using linear weighting double volumePerRow = (double)volume / (endRow - startRow + 1); for(int row = startRow; row <= endRow; row++) { g_dVolumeProfile[row] += volumePerRow; } } // Apply smoothing SmoothVolumeProfile(dLiquidityFactor); } //+------------------------------------------------------------------+ //| Smooth the volume profile using moving average | //+------------------------------------------------------------------+ void SmoothVolumeProfile(double factor) { if(factor <= 0) return; int window = (int)factor; if(window < 1) window = 1; double smoothCopy[]; ArrayResize(smoothCopy, g_iRows); ArrayCopy(smoothCopy, g_dVolumeProfile); for(int i = 0; i < g_iRows; i++) { double sum = 0; int count = 0; for(int j = -window; j <= window; j++) { int idx = i + j; if(idx >= 0 && idx < g_iRows) { sum += smoothCopy[idx]; count++; } } if(count > 0) g_dVolumeProfile[i] = sum / count; } } //+------------------------------------------------------------------+ //| Normalize heatmap values to range 0-1 for coloring | //+------------------------------------------------------------------+ void NormalizeHeatmap() { double maxVol = 0; for(int i = 0; i < g_iRows; i++) { if(g_dVolumeProfile[i] > maxVol) maxVol = g_dVolumeProfile[i]; } if(maxVol > 0) { for(int i = 0; i < g_iRows; i++) { g_dHeatmap[i] = g_dVolumeProfile[i] / maxVol; // 0 to 1 } } } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Remove all objects created by this indicator ObjectsDeleteAll(0, g_sIndicatorName); ChartRedraw(); } //+------------------------------------------------------------------+ //| 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[]) { // Recalculate on new bar static datetime lastBarTime = 0; datetime currentBarTime = iTime(_Symbol, tfAnalysis, 0); if(currentBarTime != lastBarTime || prev_calculated == 0) { lastBarTime = currentBarTime; CalculatePriceRange(); // Reinitialize arrays for new range ArrayResize(g_dVolumeProfile, g_iRows); ArrayResize(g_dPriceLevels, g_iRows); ArrayResize(g_dHeatmap, g_iRows); double step = (g_dPriceMax - g_dPriceMin) / g_iRows; for(int i = 0; i < g_iRows; i++) { g_dPriceLevels[i] = g_dPriceMin + (i * step); } CalculateVolumeProfile(); NormalizeHeatmap(); DrawHeatmap(); } return(rates_total); } //+------------------------------------------------------------------+ //| Draw heatmap rectangles on chart | //+------------------------------------------------------------------+ void DrawHeatmap() { // Delete old objects ObjectsDeleteAll(0, g_sIndicatorName); datetime startTime = iTime(_Symbol, tfAnalysis, iBarsBack); datetime endTime = iTime(_Symbol, tfAnalysis, 0); // Extend to current time + some future space endTime += (endTime - startTime) / 10; double step = (g_dPriceMax - g_dPriceMin) / g_iRows; for(int i = 0; i < g_iRows; i++) { double priceLow = g_dPriceLevels[i]; double priceHigh = priceLow + step; double intensity = g_dHeatmap[i]; // Get color based on intensity color rectColor = GetHeatmapColor(intensity); // Create rectangle name string rectName = StringFormat("%s_row_%d", g_sIndicatorName, i); // Create rectangle object if(ObjectCreate(0, rectName, OBJ_RECTANGLE, 0, startTime, priceLow, endTime, priceHigh)) { ObjectSetInteger(0, rectName, OBJPROP_FILL, true); ObjectSetInteger(0, rectName, OBJPROP_COLOR, rectColor); ObjectSetInteger(0, rectName, OBJPROP_WIDTH, 1); ObjectSetInteger(0, rectName, OBJPROP_BACK, true); ObjectSetInteger(0, rectName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, rectName, OBJPROP_HIDDEN, true); } } // Draw legend if enabled if(bShowLegend) DrawLegend(); } //+------------------------------------------------------------------+ //| Map intensity (0-1) to RGB color | //+------------------------------------------------------------------+ color GetHeatmapColor(double intensity) { // intensity: 0 = low liquidity (blue), 1 = high liquidity (red) int r = (int)(intensity * 255); int g = 0; int b = (int)((1.0 - intensity) * 255); // Alternative gradient: Blue -> Cyan -> Yellow -> Red // But simple RGB works well return (color)((r << 16) | (g << 8) | b); } //+------------------------------------------------------------------+ //| Draw color legend | //+------------------------------------------------------------------+ void DrawLegend() { string legendName = g_sIndicatorName + "_legend"; ObjectDelete(0, legendName); // Legend position (top right corner) int x = 50; int y = 50; int width = 150; int height = 20; // Background string bgName = legendName + "_bg"; if(ObjectCreate(0, bgName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { ObjectSetInteger(0, bgName, OBJPROP_XDISTANCE, x - 5); ObjectSetInteger(0, bgName, OBJPROP_YDISTANCE, y - 20); ObjectSetInteger(0, bgName, OBJPROP_XSIZE, width + 10); ObjectSetInteger(0, bgName, OBJPROP_YSIZE, height + 30); ObjectSetInteger(0, bgName, OBJPROP_BGCOLOR, clrBlack); ObjectSetInteger(0, bgName, OBJPROP_BORDER_COLOR, clrWhite); ObjectSetInteger(0, bgName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(0, bgName, OBJPROP_BACK, false); ObjectSetInteger(0, bgName, OBJPROP_FILL, true); } // Color bar for(int i = 0; i <= 100; i++) { double intensity = i / 100.0; color barColor = GetHeatmapColor(intensity); string barName = legendName + "_bar_" + IntegerToString(i); if(ObjectCreate(0, barName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { int barX = x + (int)(i * width / 100.0); int barY = y; ObjectSetInteger(0, barName, OBJPROP_XDISTANCE, barX); ObjectSetInteger(0, barName, OBJPROP_YDISTANCE, barY); ObjectSetInteger(0, barName, OBJPROP_XSIZE, width / 100); ObjectSetInteger(0, barName, OBJPROP_YSIZE, height); ObjectSetInteger(0, barName, OBJPROP_BGCOLOR, barColor); ObjectSetInteger(0, barName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(0, barName, OBJPROP_BACK, false); } } // Labels if(ObjectCreate(0, legendName + "_low", OBJ_LABEL, 0, 0, 0)) { ObjectSetInteger(0, legendName + "_low", OBJPROP_XDISTANCE, x); ObjectSetInteger(0, legendName + "_low", OBJPROP_YDISTANCE, y + height + 2); ObjectSetString(0, legendName + "_low", OBJPROP_TEXT, "Low Liquidity"); ObjectSetInteger(0, legendName + "_low", OBJPROP_COLOR, clrWhite); ObjectSetInteger(0, legendName + "_low", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(0, legendName + "_low", OBJPROP_FONTSIZE, 10); } if(ObjectCreate(0, legendName + "_high", OBJ_LABEL, 0, 0, 0)) { ObjectSetInteger(0, legendName + "_high", OBJPROP_XDISTANCE, x + width - 60); ObjectSetInteger(0, legendName + "_high", OBJPROP_YDISTANCE, y + height + 2); ObjectSetString(0, legendName + "_high", OBJPROP_TEXT, "High Liquidity"); ObjectSetInteger(0, legendName + "_high", OBJPROP_COLOR, clrWhite); ObjectSetInteger(0, legendName + "_high", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(0, legendName + "_high", OBJPROP_FONTSIZE, 10); } } //+------------------------------------------------------------------+