374 lines
13 KiB
MQL5
374 lines
13 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|