Article-16579-MQL5-Market-P.../Article-16579-MQL5-Market-Profile-Canvas-Optimization.mq5
2026-03-23 22:42:32 +07:00

479 lines
29 KiB
MQL5

//+------------------------------------------------------------------+
//| Article-16579-MQL5-Market-Profile-Canvas-Optimization.mq5 |
//| Copyright 2026, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_plots 0
#include<Canvas\Canvas.mqh>
#include <Generic\ArrayList.mqh>
//--- input parameters
input uint InpStartDate =0; /* day number to start calculation */ // номер дня, с которого начнём расчёт (0 - текущий, 1 - предыдущий, и т.д.)
input uint InpShowDays =7; /* number of days to display */ // количество отображаемых дней, начиная и включая день в InpStartDate
input int InpMultiplier =1; /* histogram length multiplier */ // множитель длины гистограммы
input color InpAsiaSession =clrGold; /* Asian session */ // цвет гистограммы азиатской сессии
input color InpEuropeSession =clrBlue; /* European session */ // цвет гистограммы европейской сессии
input color InpAmericaSession =clrViolet; /* American session */ // цвет гистограммы американской сессии
input uchar InpTransparency =150; /* Transparency, 0 = invisible */ // прозрачность профиля рынка, 0 = полностью прозрачный
input uint InpEuropeStartHour =8; /* European session opening hour */ // час открытия европейской сессии
input uint InpAmericaStartHour=14; /* American session opening hour */ // час открытия американской сессии
//--- уникальный префикс для идентификации графических объектов, принадлежащих индикатору
string ExtPrefixUniq;
//--- декларируем класс CMarketProfile
class CMarketProfile;
//--- объявляем список указателей на объекты класса CMarketProfile
CArrayList<CMarketProfile*> mp_list;
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- создаём префикс для имён объектов
string number=StringFormat("%I64d", GetTickCount64());
ExtPrefixUniq=StringSubstr(number, StringLen(number)-4);
Print("Indicator \"Market Profile Canvas\" started, prefix=", ExtPrefixUniq);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 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 static open_time=0;
//--- номер последнего дня для расчетов
//--- (при InpStartDate = 0 и InpShowDays = 3, lastday = 3)
//--- (при InpStartDate = 1 и InpShowDays = 3, lastday = 4) etc ...
uint lastday=InpStartDate+InpShowDays;
//--- если первый расчет уже был
if(prev_calculated!=0)
{
//--- получаем время открытия текущего дневного бара
datetime current_open=iTime(Symbol(), PERIOD_D1, 0);
//--- если текущий день не рассчитываем
if(InpStartDate!=0)
{
//--- если время открытия не было получено - уходим
if(open_time==current_open)
return(rates_total);
}
//--- обновляем время открытия
open_time=current_open;
//--- далее будем рассчитывать только один день, так как все остальные дни уже посчитаны при первом запуске
lastday=InpStartDate+1;
}
//--- в цикле по указанному количеству дней (либо InpStartDate+InpShowDays при первом запуске, либо InpStartDate+1 на каждом тике)
for(uint day=InpStartDate; day<lastday; day++)
{
//--- получаем в структуру данные дня с индексом day
MqlRates day_rate[];
//--- если индикатор запускается в выходные или праздничные дни, когда нет тиков, сначала нужно открыть дневной график символа
//--- если не получили данные бара по индексу day дневного периода - уходим до следующего вызова OnCalculate()
if(CopyRates(Symbol(), PERIOD_D1, day, 1, day_rate)==-1)
return(prev_calculated);
//--- получаем время начала и окончания дня
datetime start_time=day_rate[0].time;
datetime stop_time=start_time+PeriodSeconds(PERIOD_D1)-1;
//--- получаем все внутредневные бары текущего дня
MqlRates bars_in_day[];
if(CopyRates(Symbol(), PERIOD_CURRENT, start_time, stop_time, bars_in_day)==-1)
return(prev_calculated);
CMarketProfile *market_profile;
//--- если Профиль рынка уже создавался и его рисование ранее было выполнено
if(prev_calculated>0)
{
//--- найдём объект Профиля рынка (класса CMarketProfile) в списке по времени открытия дня с индексом day
market_profile=GetMarketProfileByDate(ExtPrefixUniq, start_time);
//--- если объект не найден возвращаем ноль для полного перерасчёта индикатора
if(market_profile==NULL)
{
PrintFormat("Market Profile not found for %s. Indicator will be recalculated for all specified days",
TimeToString(start_time, TIME_DATE));
return(0);
}
//--- объект CMarketProfile найден в списке; устанавливаем в него значения High и Low дня и передаём массив внутридневных баров
//--- при этом объект смещается на новую координату, соответствующую High дневной свечи, и все массивы (векторы) переинициализируются
market_profile.SetHiLoBars(day_rate[0].high, day_rate[0].low, bars_in_day);
}
//--- если это первый расчёт
else
{
//--- создаём новый объект класса CMarketProfile для хранения Профиля рынка дня с индексом day
market_profile = new CMarketProfile(ExtPrefixUniq, start_time, stop_time, day_rate[0].high, day_rate[0].low, bars_in_day);
//--- добавляем указатель на созданный объект CMarketProfile в список
mp_list.Add(market_profile);
}
//--- устанавливаем размеры холста и параметры рисования линий
market_profile.UpdateSizes();
//--- рассчитываем профили для каждой торговой сесии
market_profile.CalculateSessions();
//--- рисуем Профиль рынка
market_profile.Draw(InpMultiplier);
}
//--- по завершении цикла после создания и обновления всех объектов, перерисуем график
ChartRedraw(0);
//--- возвращаем количество баров для следующего вызова OnCalculate
return(rates_total);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- удаляем все графические объекты Market Profile после использования
Print("Indicator \"Market Profile Canvas\" stopped, delete all objects CMarketProfile with prefix=", ExtPrefixUniq);
//--- в цикле по количеству объектов CMarketProfile в списке
int size=mp_list.Count();
for(int i=0; i<size; i++)
{
//--- получаем указатель на объект CMarketProfile из списка по индексу цикла
CMarketProfile *market_profile;
mp_list.TryGetValue(i, market_profile);
//--- если указатель валидный и объект существует - удаляем его
if(market_profile!=NULL)
if(CheckPointer(market_profile)!=POINTER_INVALID)
delete market_profile;
}
//--- перерисовываем график для немедленного отображения результата
ChartRedraw(0);
}
//+------------------------------------------------------------------+
//| Custom indicator chart's event handler |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
{
//--- если это пользовательское событие - уходим
if(id>=CHARTEVENT_CUSTOM)
return;
//--- если есть изменение чарта, обновляем размеры всех объектов класса CMarketProfile с перерисовкой графика
if(CHARTEVENT_CHART_CHANGE==id)
{
//--- в цикле по количеству объектов CMarketProfile в списке
int size=mp_list.Count();
for(int i=0; i<size; i++)
{
//--- получаем указатель на объект CMarketProfile по индексу цикла
CMarketProfile *market_profile;
mp_list.TryGetValue(i, market_profile);
//--- если объект получен и если он находится в видимой области графика
if(market_profile)
if(market_profile.isVisibleOnChart())
{
//--- обновляем размеры холста и перерисовываем гистограммы профиля рынка
market_profile.UpdateSizes();
market_profile.Draw(InpMultiplier);
}
}
//--- после перерасчёта всех Профилей, обновляем график
ChartRedraw();
}
}
//+------------------------------------------------------------------+
//| Returns CMarketProfile or NULL by the date |
//+------------------------------------------------------------------+
CMarketProfile* GetMarketProfileByDate(string prefix, datetime time)
{
//--- в цикле по количеству объектов CMarketProfile в списке
int size=mp_list.Count();
for(int i=0; i<size; i++)
{
//--- получаем указатель на объект CMarketProfile по индексу цикла
CMarketProfile *market_profile;
mp_list.TryGetValue(i, market_profile);
//--- если указатель валидный и объект существует,
if(market_profile!=NULL)
if(CheckPointer(market_profile)!=POINTER_INVALID)
{
//--- если объект Профиля рынка, полученный по указателю, создан для искомого времени - возвращаем указатель
if(market_profile.Check(prefix, time))
return(market_profile);
}
}
//--- ничего не найдено - возвращаем NULL
return(NULL);
}
//+------------------------------------------------------------------+
//| Class to store and draw Market Profile for the daily bar |
//+------------------------------------------------------------------+
class CMarketProfile
{
public:
CMarketProfile() {};
CMarketProfile(string prefix, datetime time1, datetime time2, double high, double low, MqlRates &bars[]);
~CMarketProfile(void);
//--- проверяет, создан ли объект для указанной даты
bool Check(string prefix, datetime time);
//--- устанавливает максимум/минимум и массив внутридневных баров
void SetHiLoBars(double high, double low, MqlRates &bars[]);
//--- устанавливает размеры холста и параметры рисования
void UpdateSizes(void);
//--- находится ли профиль в видимой части графика?
bool isVisibleOnChart(void);
//--- изменился ли масштаб графика?
bool isChartScaleChanged(void);
//--- рассчитывает профиль по сессиям
bool CalculateSessions(void);
//--- рисует профиль
void Draw(double multiplier=1.0);
//---
protected:
CCanvas m_canvas; // объект класса CCanvas для рисования профиля
uchar m_alpha; // значение альфа-канала, устанавливающее прозрачность
string m_prefix; // уникальный префикс объекта OBJ_BITMAP
string m_name; // имя объекта OBJ_BITMAP, используемого в m_canvas
double m_high; // High дня
double m_low; // Low дня
datetime m_time1; // время начала дня
datetime m_time2; // время окончания дня
int m_day_size_pt; // высота дневного бара в пунктах
int m_height; // высота дневного бара в пикселях на графике
int m_width; // ширина дневного бара в пикселях на графике
MqlRates m_bars[]; // массив баров текущего таймфрейма между m_time1 и m_time2
vector m_asia; // массив счётчиков баров для азиатской сессии
vector m_europe; // массив счётчиков баров для европейской сессии
vector m_america; // массив счётчиков баров для американской сессии
double m_vert_scale; // вертикальный коэффициент масштабирования
double m_hor_scale; // горизонтальный коэффициент масштабирования
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
void CMarketProfile::CMarketProfile(string prefix, datetime time1, datetime time2, double high, double low, MqlRates &bars[]):
m_prefix(prefix),
m_time1(time1),
m_time2(time2),
m_high(high),
m_low(low),
m_vert_scale(NULL),
m_hor_scale(NULL)
{
//--- копируем массив внутридневных баров в массив структур MqlRates,
//--- создаём имя графического объекта и определяем размер дневной свечи
ArrayCopy(m_bars, bars);
m_name=ExtPrefixUniq+"_MP_"+TimeToString(time1, TIME_DATE);
m_day_size_pt=(int)((m_high-m_low)/SymbolInfoDouble(Symbol(), SYMBOL_POINT));
//--- устанавливаем размеры векторов для торговых сессий
m_asia=vector::Zeros(m_day_size_pt);
m_europe=vector::Zeros(m_day_size_pt);
m_america=vector::Zeros(m_day_size_pt);
//--- устанавливаем ширину и высоту холста
UpdateSizes();
//--- если это первый тик в начале дня, то размеры холста будут нулевыми - установим размеры в 1 пиксель по высоте и ширине
m_height=m_height?m_height:1;
m_width=m_width?m_width:1;
//--- создаём графический объект
if(m_canvas.CreateBitmap(m_name, m_time1, m_high, m_width, m_height, COLOR_FORMAT_ARGB_NORMALIZE))
ObjectSetInteger(0, m_name, OBJPROP_BACK, true);
else
{
Print("Error creating canvas: ", GetLastError());
Print("time1=", m_time1, " high=", m_high, " width=", m_width, " height=", m_height);
}
}
//+------------------------------------------------------------------+
//| Checks if CMarketProfile object is for the specified 'time' date |
//+------------------------------------------------------------------+
bool CMarketProfile::Check(string prefix, datetime time)
{
string calculated= prefix+"_MP_"+TimeToString(time, TIME_DATE);
return (m_name==(calculated));
};
//+------------------------------------------------------------------+
//| Sets High/Low and a set of current-timeframe bars |
//+------------------------------------------------------------------+
void CMarketProfile::SetHiLoBars(double high, double low, MqlRates &bars[])
{
//--- если максимум дня изменился, переместим объект OBJ_BITMAP на новую координату Y
if(high>m_high)
{
m_high=high;
if(!ObjectSetDouble(0, m_name, OBJPROP_PRICE, m_high))
PrintFormat("Failed to update canvas for %s, error %d", TimeToString(m_time1, TIME_DATE), GetLastError());
}
ArrayCopy(m_bars, bars);
m_high=high;
m_low=low;
//--- дневной диапазон в пунктах
m_day_size_pt=(int)((m_high-m_low)/SymbolInfoDouble(Symbol(), SYMBOL_POINT));
//--- переустанавливаем размеры векторов для торговых сессий
m_asia=vector::Zeros(m_day_size_pt);
m_europe=vector::Zeros(m_day_size_pt);
m_america=vector::Zeros(m_day_size_pt);
}
//+------------------------------------------------------------------+
//| Sets drawing parameters |
//+------------------------------------------------------------------+
void CMarketProfile::UpdateSizes(void)
{
//--- преобразуем время/цену в координаты x/y
int x1, y1, x2, y2;
ChartTimePriceToXY(0, 0, m_time1, m_high, x1, y1);
ChartTimePriceToXY(0, 0, m_time2, m_low, x2, y2);
//--- рассчитываем размеры холста
m_height=y2-y1;
m_width =x2-x1;
//--- рассчитываем коэффициенты для преобразования вертикальных уровней цен
//--- и горизонтальных счетчиков баров в пиксели графика
m_vert_scale=double(m_height)/(m_day_size_pt);
m_hor_scale =double(m_width*PeriodSeconds(PERIOD_CURRENT))/PeriodSeconds(PERIOD_D1);
//--- изменяем размер холста
m_canvas.Resize(m_width, m_height);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
void CMarketProfile::~CMarketProfile(void)
{
//--- удаляем все графические объекты после использования
ObjectsDeleteAll(0, m_prefix, 0, OBJ_BITMAP);
ChartRedraw();
}
//+------------------------------------------------------------------+
//| Checks that the profile is in the visible part of the chart |
//+------------------------------------------------------------------+
bool CMarketProfile::isVisibleOnChart(void)
{
long last_bar=ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); // последний видимый бар на графике слева
long first_bar=last_bar+-ChartGetInteger(0, CHART_VISIBLE_BARS); // первый видимый бар на графике справа
first_bar=first_bar>0?first_bar:0;
datetime left =iTime(Symbol(), Period(), (int)last_bar); // время левого видимого бара на графике
datetime right=iTime(Symbol(), Period(), (int)first_bar); // время правого видимого бара на графике
//--- возвращаем флаг того, что холст расположен внутри левого и правого видимых баров графика
return((m_time1>= left && m_time1 <=right) || (m_time2>= left && m_time2 <=right));
}
//+------------------------------------------------------------------+
//| Prepares profile arrays by sessions |
//+------------------------------------------------------------------+
bool CMarketProfile::CalculateSessions(void)
{
double point=SymbolInfoDouble(Symbol(), SYMBOL_POINT); // значение одного пункта
//--- если массив внутридневных баров не заполнен - уходим
if(ArraySize(m_bars)==0)
return(false);
//---- перебираем все бары текущего дня и отмечаем ячейки массивов (векторов), в которые попадают перебираемые в цикле бары
int size=ArraySize(m_bars);
for(int i=0; i<size; i++)
{
//--- получаем час бара
MqlDateTime bar_time;
TimeToStruct(m_bars[i].time, bar_time);
uint hour =bar_time.hour;
//--- рассчитываем уровни цены в пунктах от Low дня, которые были достигнуты ценой на каждом баре цикла
int start_box=(int)((m_bars[i].low-m_low)/point); // индекс начала ценовых уровней, которые были достигнуты ценой на баре
int stop_box =(int)((m_bars[i].high-m_low)/point); // индекс конца ценовых уровней, которые были достигнуты ценой на баре
//--- американская сессия
if(hour>=InpAmericaStartHour)
{
//--- в цикле от начала до конца ценовых уровней заполняем счётчики баров, где была цена на этом уровне
for(int ind=start_box; ind<stop_box; ind++)
m_america[ind]++;
}
else
{
//--- европейская сессия
if(hour>=InpEuropeStartHour && hour<InpAmericaStartHour)
//--- в цикле от начала до конца ценовых уровней заполняем счётчики баров, где была цена на этом уровне
for(int ind=start_box; ind<stop_box; ind++)
m_europe[ind]++;
//--- азиатская сессия
else
//--- в цикле от начала до конца ценовых уровней заполняем счётчики баров, где была цена на этом уровне
for(int ind=start_box; ind<stop_box; ind++)
m_asia[ind]++;
}
}
//--- векторы всех сессий готовы
return(true);
}
//+------------------------------------------------------------------+
//| Draw Market Profile on the canvas |
//+------------------------------------------------------------------+
void CMarketProfile::Draw(double multiplier=1.0)
{
//--- суммируем все сессии для отрисовки
vector total_profile=m_asia+m_europe+m_america; // профиль, объединяющий все сессии
vector europe_asia=m_asia+m_europe; // профиль, объединяющий только европейскую и азиатскую сессии
//--- устанавливаем полностью прозрачный фон для холста
m_canvas.Erase(ColorToARGB(clrBlack, 0));
//--- переменные для рисования прямоугольников
int x1=0; // X-координата левого угла прямоугольника всегда начинается с нуля
int y1, x2, y2; // координаты прямоугольников
int size=(int)total_profile.Size(); // размер всех сессий
//--- рисуем американскую сессию закрашенными прямоугольниками
for(int i=0; i<size; i++)
{
//--- нулевые значения векторов не рисуем - пропускаем
if(total_profile[i]==0)
continue;
//--- рассчитываем две точки для рисования прямоугольника, x1 всегда равен 0 (X левого нижнего угла прямоугольника)
y1=m_height-int(i*m_vert_scale); // координата Y нижнего левого угла прямоугольника
y2=(int)(y1+m_vert_scale); // координата Y верхнего правого угла прямоугольника
x2=(int)(total_profile[i]*m_hor_scale*multiplier); // координата X верхнего правого угла прямоугольника
//--- рисуем прямоугольник по рассчитанным координатам с установленным для американской сессии цветом и прозрачностью
m_canvas.FillRectangle(x1, y1, x2, y2, ColorToARGB(InpAmericaSession, InpTransparency));
}
//--- рисуем европейскую сессию закрашенными прямоугольниками
for(int i=0; i<size; i++)
{
//--- нулевые значения векторов не рисуем - пропускаем
if(total_profile[i]==0)
continue;
//--- рассчитываем две точки для рисования прямоугольника
y1=m_height-int(i*m_vert_scale);
y2=(int)(y1+m_vert_scale);
x2=(int)(europe_asia[i]*m_hor_scale*multiplier);
//--- поверх нарисованной американской сессии рисуем прямоугольник по рассчитанным координатам
//--- с установленным для европейской сессии цветом и прозрачностью
m_canvas.FillRectangle(x1, y1, x2, y2, ColorToARGB(InpEuropeSession, InpTransparency));
}
//--- рисуем азиатскую сессию закрашенными прямоугольниками
for(int i=0; i<size; i++)
{
//--- нулевые значения векторов не рисуем - пропускаем
if(total_profile[i]==0)
continue;
//--- рассчитываем две точки для рисования прямоугольника
y1=m_height-int(i*m_vert_scale);
y2=(int)(y1+m_vert_scale);
x2=(int)(m_asia[i]*m_hor_scale*multiplier);
//--- поверх нарисованной европейской сессии рисуем прямоугольник по рассчитанным координатам
//--- с установленным для азиатской сессии цветом и прозрачностью
m_canvas.FillRectangle(x1, y1, x2, y2, ColorToARGB(InpAsiaSession, InpTransparency));
}
//--- обновляем объект OBJ_BITMAP без перерисовки графика
m_canvas.Update(false);
}
//+------------------------------------------------------------------+