//+------------------------------------------------------------------+ //| MarketProfile Canvas.mq5 | //| Copyright 2009-2025, MetaQuotes Ltd | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #property indicator_plots 0 #include #include //--- input parameters input uint InpStartDate =0; // day number to start calculation input uint InpShowDays =7; // number of days to show 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 input uint InpEuropeStartHour =8; // European session opening hour input uint InpAmericaStartHour=14; // American session opening hour //--- unique prefix to identify indicator objects string ExtPrefixUniq; //--- forward class declaration class CMarketProfile; //--- collection of pointers of CMarketProfile type CArrayList mp_list; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- prepare prefix for objects 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[]) { //--- opening time of the current daily bar datetime static open_time=0; //--- number of the last day for calculations uint lastday=InpStartDate+InpShowDays; //--- if the calculation has already been done if(prev_calculated!=0) { datetime current_open=iTime(Symbol(), PERIOD_D1, 0); //--- and we don't need to calculate Market Profile for the first day if(InpStartDate!=0) { //--- if the opening time has not been updated if(open_time==current_open) return(rates_total); // no calculation needed, exit } //--- otherwise, updated the opening time open_time=current_open; //--- further in the code, we will calculate Market Profile for only one day lastday=InpStartDate+1; } //--- make calculations for the specified days range for(uint day=InpStartDate; day0) // then update the profile of the current day having the index of 0 { //--- search for the required CMarketProfile profile in the mp_list collection: it must be there market_profile=GetMarketProfileByDate(ExtPrefixUniq, start_time); //--- check 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); // exit with zero value to ensure all calculations are done again } //--- the CMarketProfile object not found; pass valid High/Low values and a set of current-timeframe bars to it market_profile.SetHiLoBars(day_rate[0].high, day_rate[0].low, bars_in_day); } else { //--- create a new object to store the profile market_profile = new CMarketProfile(ExtPrefixUniq, start_time, stop_time, day_rate[0].high, day_rate[0].low, bars_in_day); //--- add the created CMarketProfile to the collection mp_list.Add(market_profile); } //--- set drawing parameters market_profile.UpdateSizes(); //--- calculate profiles for each session market_profile.CalculateSessions(); //--- draw the profile market_profile.Draw(InpMultiplier); } //--- redraw all objects ChartRedraw(0); //--- return value of prev_calculated for the next call return(rates_total); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- delete all our Market Profile graphical objects after use Print("Indicator \"Market Profile Canvas\" stopped, delete all objects CMarketProfile with prefix=", ExtPrefixUniq); int size=mp_list.Count(); for(int i=0; i=CHARTEVENT_CUSTOM) return; //--- there have been changes on the chart if(CHARTEVENT_CHART_CHANGE==id) { int size=mp_list.Count(); for(int i=0; i y double m_hor_scale; // factor conversion count -> x }; //+------------------------------------------------------------------+ //| 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(0.0), m_hor_scale(0.0) { 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)); //--- set the sizes of vectors for trading sessions m_asia=vector::Zeros(m_day_size_pt); m_europe=vector::Zeros(m_day_size_pt); m_america=vector::Zeros(m_day_size_pt); //--- create an object UpdateSizes(); //--- if it is the first tick at the day opening, the canvas sizes will be equal to zero - set 1 px for both coordinates 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[]) { //--- if the day's High changed, move the OBJ_BITMAP object linked by m_name 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; //--- day range in points m_day_size_pt=(int)((m_high-m_low)/SymbolInfoDouble(Symbol(), SYMBOL_POINT)); //--- re-set the sizes of vectors for trading sessions 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) { //--- convert time/price to x/y coordinates int x1, y1, x2, y2; ChartTimePriceToXY(0, 0, m_time1, m_high, x1, y1); ChartTimePriceToXY(0, 0, m_time2, m_low, x2, y2); //--- calculate window size m_height=y2-y1; m_width =x2-x1; //--- calculate coefficients for converting boxes and counters into chart pixels 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) { //--- delete all our graphical objects after use 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 check result 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); //---- iterate through all bars of the current day and mark the boxes they fall into int size=ArraySize(m_bars); for(int i=0; i=InpAmericaStartHour) { for(int ind=start_box; ind=InpEuropeStartHour && hour