mql5/Indicators/FixedRangeVolumeProfile.mq5

400 lignes
14 Kio
MQL5
Brut Lien permanent Vue normale Historique

2026-02-04 11:18:33 +03:00
//+------------------------------------------------------------------+
//| FixedRangeVolumeProfile.mq5 |
//| Copyright 2026, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#property indicator_chart_window
2026-02-04 13:24:00 +03:00
#property indicator_buffers 1
#property indicator_plots 1
#property indicator_color1 clrDarkGray, clrDimGray
#property indicator_label1 "Close Prices"
2026-02-04 11:18:33 +03:00
#include <Canvas\Canvas.mqh>
#include <ChartObjects\ChartObjectsLines.mqh>
//--- input parameters
input int InpTicksPerBin = 10; // Ticks per bin
input int InpValueAreaPercent = 70; // Value Area Percentage
input int InpWidthPercent = 30; // Width % of Chart
2026-02-17 11:11:10 +03:00
input string InpSessionAsian = "23:00-08:00"; // Asian Session
2026-02-04 13:24:00 +03:00
input color InpColorAsian = clrSkyBlue; // Asian Color
2026-02-17 11:11:10 +03:00
input string InpSessionLondon = "07:00-16:30"; // London Session
2026-02-04 13:24:00 +03:00
input color InpColorLondon = clrLimeGreen; // London Color
2026-02-17 11:11:10 +03:00
input string InpSessionNY = "12:00-21:30"; // NY Session
2026-02-04 13:24:00 +03:00
input color InpColorNY = clrOrange; // NY Color
input color InpColorPOC = clrRed; // POC Color
2026-02-04 11:18:33 +03:00
//--- global variables
CCanvas m_canvas;
CChartObjectVLine m_line_start;
CChartObjectVLine m_line_end;
string m_canvas_name = "FRVP_Canvas";
string m_line_start_name = "FRVP_Start";
string m_line_end_name = "FRVP_End";
datetime m_last_start;
datetime m_last_end;
struct BinData
{
double price;
long volume;
bool is_va;
};
2026-02-04 13:24:00 +03:00
struct SessionData
{
BinData bins[];
long total_volume;
double poc_price;
int start_hour, start_min;
int end_hour, end_min;
color base_color;
};
SessionData m_sessions[3];
//+------------------------------------------------------------------+
//| Parse Session String |
//+------------------------------------------------------------------+
void ParseSessions()
{
string sessions[3] = {InpSessionAsian, InpSessionLondon, InpSessionNY};
color colors[3] = {InpColorAsian, InpColorLondon, InpColorNY};
for(int i = 0; i < 3; i++)
{
string parts[];
if(StringSplit(sessions[i], '-', parts) == 2)
{
string start_parts[];
string end_parts[];
if(StringSplit(parts[0], ':', start_parts) == 2 && StringSplit(parts[1], ':', end_parts) == 2)
{
m_sessions[i].start_hour = (int)StringToInteger(start_parts[0]);
m_sessions[i].start_min = (int)StringToInteger(start_parts[1]);
m_sessions[i].end_hour = (int)StringToInteger(end_parts[0]);
m_sessions[i].end_min = (int)StringToInteger(end_parts[1]);
}
}
m_sessions[i].base_color = colors[i];
}
}
//+------------------------------------------------------------------+
//| Check if time is in session |
//+------------------------------------------------------------------+
bool IsInSession(datetime t, int s_idx)
{
MqlDateTime dt;
TimeToStruct(t, dt);
int current_minutes = dt.hour * 60 + dt.min;
int start_minutes = m_sessions[s_idx].start_hour * 60 + m_sessions[s_idx].start_min;
int end_minutes = m_sessions[s_idx].end_hour * 60 + m_sessions[s_idx].end_min;
if(start_minutes < end_minutes)
return (current_minutes >= start_minutes && current_minutes < end_minutes);
else // Overnight session
return (current_minutes >= start_minutes || current_minutes < end_minutes);
}
2026-02-04 11:18:33 +03:00
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
2026-02-04 13:24:00 +03:00
ParseSessions();
2026-02-04 11:18:33 +03:00
//--- create lines
if(!m_line_start.Create(0, m_line_start_name, 0, iTime(_Symbol, _Period, 100)))
return(INIT_FAILED);
m_line_start.Color(clrGray);
m_line_start.Style(STYLE_DOT);
m_line_start.Selectable(true);
m_line_start.Selected(true);
m_line_start.Tooltip("FRVP Start");
if(!m_line_end.Create(0, m_line_end_name, 0, iTime(_Symbol, _Period, 0)))
return(INIT_FAILED);
m_line_end.Color(clrGray);
m_line_end.Style(STYLE_DOT);
m_line_end.Selectable(true);
m_line_end.Selected(true);
m_line_end.Tooltip("FRVP End");
//--- initialize canvas
if(!m_canvas.CreateBitmapLabel(m_canvas_name, 0, 0, (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS), (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS), COLOR_FORMAT_ARGB_NORMALIZE))
return(INIT_FAILED);
m_canvas.Erase(0);
m_canvas.Update();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
m_line_start.Delete();
m_line_end.Delete();
m_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[])
{
datetime t_start = m_line_start.Time(0);
datetime t_end = m_line_end.Time(0);
if(t_start != m_last_start || t_end != m_last_end)
{
CalculateProfile(t_start, t_end);
RenderProfile();
m_last_start = t_start;
m_last_end = t_end;
}
return(rates_total);
}
//+------------------------------------------------------------------+
//| ChartEvent function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
if(id == CHARTEVENT_OBJECT_DRAG || id == CHARTEVENT_CHART_CHANGE)
{
datetime t_start = m_line_start.Time(0);
datetime t_end = m_line_end.Time(0);
if(t_start != m_last_start || t_end != m_last_end || id == CHARTEVENT_CHART_CHANGE)
{
CalculateProfile(t_start, t_end);
RenderProfile();
m_last_start = t_start;
m_last_end = t_end;
}
}
}
//+------------------------------------------------------------------+
//| Calculate Volume Profile |
//+------------------------------------------------------------------+
void CalculateProfile(datetime t1, datetime t2)
{
datetime start_time = (t1 < t2) ? t1 : t2;
datetime end_time = (t1 < t2) ? t2 : t1;
int start_bar = iBarShift(_Symbol, _Period, start_time);
int end_bar = iBarShift(_Symbol, _Period, end_time);
if(start_bar < 0 || end_bar < 0) return;
//--- Find price range
double min_price = 0, max_price = 0;
for(int i = end_bar; i <= start_bar; i++)
{
double h = iHigh(_Symbol, _Period, i);
double l = iLow(_Symbol, _Period, i);
if(i == end_bar) { min_price = l; max_price = h; }
else { if(l < min_price) min_price = l; if(h > max_price) max_price = h; }
}
double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double bin_size = tick_size * InpTicksPerBin;
if(bin_size <= 0) return;
int bin_count = (int)((max_price - min_price) / bin_size) + 1;
2026-02-04 13:24:00 +03:00
for(int s = 0; s < 3; s++)
2026-02-04 11:18:33 +03:00
{
2026-02-04 13:24:00 +03:00
ArrayResize(m_sessions[s].bins, bin_count);
for(int i = 0; i < bin_count; i++)
{
m_sessions[s].bins[i].price = min_price + i * bin_size;
m_sessions[s].bins[i].volume = 0;
m_sessions[s].bins[i].is_va = false;
}
m_sessions[s].total_volume = 0;
2026-02-04 11:18:33 +03:00
}
for(int i = end_bar; i <= start_bar; i++)
{
2026-02-04 13:24:00 +03:00
datetime bar_time = iTime(_Symbol, _Period, i);
2026-02-04 11:18:33 +03:00
double h = iHigh(_Symbol, _Period, i);
double l = iLow(_Symbol, _Period, i);
long vol = iTickVolume(_Symbol, _Period, i);
int h_idx = (int)((h - min_price) / bin_size);
int l_idx = (int)((l - min_price) / bin_size);
if(h_idx >= bin_count) h_idx = bin_count - 1;
if(l_idx < 0) l_idx = 0;
2026-02-04 13:24:00 +03:00
int bins_covered = h_idx - l_idx + 1;
long vol_per_bin = vol / bins_covered;
2026-02-04 11:18:33 +03:00
2026-02-04 13:24:00 +03:00
for(int s = 0; s < 3; s++)
2026-02-04 11:18:33 +03:00
{
2026-02-04 13:24:00 +03:00
if(IsInSession(bar_time, s))
{
for(int j = l_idx; j <= h_idx; j++)
{
m_sessions[s].bins[j].volume += vol_per_bin;
m_sessions[s].total_volume += vol_per_bin;
}
}
2026-02-04 11:18:33 +03:00
}
}
2026-02-04 13:24:00 +03:00
//--- Calculate POC and VA for each session
for(int s = 0; s < 3; s++)
2026-02-04 11:18:33 +03:00
{
2026-02-04 13:24:00 +03:00
if(m_sessions[s].total_volume <= 0) continue;
long max_vol = -1;
int poc_idx = 0;
for(int i = 0; i < bin_count; i++)
2026-02-04 11:18:33 +03:00
{
2026-02-04 13:24:00 +03:00
if(m_sessions[s].bins[i].volume > max_vol)
{
max_vol = m_sessions[s].bins[i].volume;
poc_idx = i;
}
2026-02-04 11:18:33 +03:00
}
2026-02-04 13:24:00 +03:00
m_sessions[s].poc_price = m_sessions[s].bins[poc_idx].price + bin_size / 2.0;
2026-02-04 11:18:33 +03:00
2026-02-04 13:24:00 +03:00
long target_va_vol = (long)(m_sessions[s].total_volume * (InpValueAreaPercent / 100.0));
long current_va_vol = m_sessions[s].bins[poc_idx].volume;
m_sessions[s].bins[poc_idx].is_va = true;
2026-02-04 11:18:33 +03:00
2026-02-04 13:24:00 +03:00
int up_idx = poc_idx + 1;
int dn_idx = poc_idx - 1;
2026-02-04 11:18:33 +03:00
2026-02-04 13:24:00 +03:00
while(current_va_vol < target_va_vol && (up_idx < bin_count || dn_idx >= 0))
2026-02-04 11:18:33 +03:00
{
2026-02-04 13:24:00 +03:00
long up_vol = (up_idx < bin_count) ? m_sessions[s].bins[up_idx].volume : -1;
long dn_vol = (dn_idx >= 0) ? m_sessions[s].bins[dn_idx].volume : -1;
if(up_vol >= dn_vol && up_vol != -1)
{
current_va_vol += up_vol;
m_sessions[s].bins[up_idx].is_va = true;
up_idx++;
}
else if(dn_vol != -1)
{
current_va_vol += dn_vol;
m_sessions[s].bins[dn_idx].is_va = true;
dn_idx--;
}
else break;
2026-02-04 11:18:33 +03:00
}
}
}
//+------------------------------------------------------------------+
//| Render Volume Profile |
//+------------------------------------------------------------------+
void RenderProfile()
{
int width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
int height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
if(width != m_canvas.Width() || height != m_canvas.Height())
{
m_canvas.Resize(width, height);
}
m_canvas.Erase(0);
2026-02-04 13:24:00 +03:00
int bin_count = ArraySize(m_sessions[0].bins);
2026-02-04 11:18:33 +03:00
if(bin_count == 0) { m_canvas.Update(); return; }
2026-02-04 13:24:00 +03:00
long global_max_vol = 0;
2026-02-04 16:13:27 +03:00
for(int i = 0; i < bin_count; i++)
{
long total_bin_vol = 0;
for(int s = 0; s < 3; s++) total_bin_vol += m_sessions[s].bins[i].volume;
if(total_bin_vol > global_max_vol) global_max_vol = total_bin_vol;
}
2026-02-04 11:18:33 +03:00
2026-02-04 13:24:00 +03:00
if(global_max_vol <= 0) { m_canvas.Update(); return; }
2026-02-04 11:18:33 +03:00
int max_draw_width = (int)(width * (InpWidthPercent / 100.0));
2026-02-04 16:13:27 +03:00
int x_right = width - 1;
2026-02-04 11:18:33 +03:00
2026-02-04 13:24:00 +03:00
double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double bin_height_price = tick_size * InpTicksPerBin;
2026-02-04 16:13:27 +03:00
for(int i = 0; i < bin_count; i++)
2026-02-04 11:18:33 +03:00
{
2026-02-04 16:13:27 +03:00
double p_top = m_sessions[0].bins[i].price + bin_height_price;
double p_bot = m_sessions[0].bins[i].price;
int y_top = PriceToY(p_top);
int y_bot = PriceToY(p_bot);
2026-02-04 13:24:00 +03:00
2026-02-04 16:13:27 +03:00
int current_x = x_right;
for(int s = 0; s < 3; s++)
2026-02-04 13:24:00 +03:00
{
if(m_sessions[s].bins[i].volume <= 0) continue;
2026-02-04 16:13:27 +03:00
int segment_w = (int)((double)m_sessions[s].bins[i].volume / global_max_vol * max_draw_width);
if(segment_w <= 0) continue;
uint base_clr_rgb = COLOR2RGB(m_sessions[s].base_color);
uint clr = ARGB(m_sessions[s].bins[i].is_va ? 180 : 80, (base_clr_rgb>>16)&0xFF, (base_clr_rgb>>8)&0xFF, base_clr_rgb&0xFF);
2026-02-04 13:24:00 +03:00
2026-02-04 16:13:27 +03:00
m_canvas.FillRectangle(current_x - segment_w, y_top, current_x, y_bot, clr);
current_x -= segment_w;
2026-02-04 13:24:00 +03:00
}
2026-02-04 16:13:27 +03:00
}
2026-02-04 11:18:33 +03:00
2026-02-04 16:13:27 +03:00
//--- Draw POCs
for(int s = 0; s < 3; s++)
{
if(m_sessions[s].total_volume <= 0) continue;
2026-02-04 13:24:00 +03:00
int y_poc = PriceToY(m_sessions[s].poc_price);
2026-02-04 16:13:27 +03:00
uint poc_rgb = COLOR2RGB(m_sessions[s].base_color); // Use session color for its POC
2026-02-04 13:24:00 +03:00
uint poc_clr = ARGB(255, (poc_rgb>>16)&0xFF, (poc_rgb>>8)&0xFF, poc_rgb&0xFF);
2026-02-04 16:13:27 +03:00
m_canvas.LineHorizontal(x_right - max_draw_width, x_right, y_poc, poc_clr);
2026-02-04 13:24:00 +03:00
}
2026-02-04 11:18:33 +03:00
m_canvas.Update();
}
//+------------------------------------------------------------------+
//| Helper to convert price to Y coordinate |
//+------------------------------------------------------------------+
int PriceToY(double price)
{
double chart_min = ChartGetDouble(0, CHART_PRICE_MIN);
double chart_max = ChartGetDouble(0, CHART_PRICE_MAX);
int height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
if(chart_max == chart_min) return 0;
return (int)((chart_max - price) / (chart_max - chart_min) * height);
}
//+------------------------------------------------------------------+
2026-02-04 13:24:00 +03:00