How to make objects drawn by XY change smoothly (MT4 vs MT5)

 

Help solve a problem on the MT5 terminal.

Decided to transfer my product from mql4 to mql5.

It uses rectangles that are drawn on XY coordinates.

In MT4 it is very smooth and without jerks when changing vertical scale, but in MT5 the same approach leads to some freezes and noticeable "non-smoothness".

I specially made a simplified prototype to demonstrate the effect. It is the same for MT4 and MT5. Compare the difference when you change the vertical scale (with a mouse on the price scale).


In MT5 everything will be without brakes, but redrawing is jerky. The more objects, the worse. But in MT4 everything is smooth.

I attach source mq4 and mq5 files and paste mq5 code.


Please help me to make it smooth.


I want to understand: "this is" MT5 or "this is" my code for MT5.


//+------------------------------------------------------------------+
//|                                                  PrototypeXY.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window

#property indicator_buffers 0
#property indicator_plots   0


string obj_name = "Asd_";

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){

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[]){
   if(NewBar()){
      DrawObj();
   }
return(rates_total);}

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam){
   if(id == CHARTEVENT_CHART_CHANGE){
      DrawObj();
   }
}

//+------------------------------------------------------------------+
//| Выводим на график                                                |
//+------------------------------------------------------------------+

void DrawObj(){
   string GenName = obj_name;
   double startPricePos = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   int step_Pips = 50;
   for(int i=1; i<=20;i++){
      double stp = (step_Pips*i)*SymbolInfoDouble(Symbol(),SYMBOL_POINT);
      RectLabelCreate(GenName+"UP_"+IntegerToString(i),startPricePos + stp);
      RectLabelCreate(GenName+"DN_"+IntegerToString(i),startPricePos - stp);
   }
   ChartRedraw(0);
}

//+------------------------------------------------------------------+ 
//| Создает прямоугольную метку                                      | 
//+------------------------------------------------------------------+ 

void RectLabelCreate(string name,   // имя метки 
                     double price   // цена
                     ){
   const long             chart_ID=0;               // ID графика
         int              sub_window=0;             // номер подокна
         int              x=0;                      // координата по оси X 
         int              y=0;                      // координата по оси Y
         
   datetime time_pos_X_centr = 0; // Время по центру графика
   double price_pos_Y_tmp = 0;
   x=(int)(ChartGetInteger(chart_ID,CHART_WIDTH_IN_PIXELS,sub_window)/2);
   ChartXYToTimePrice(chart_ID,x,y,sub_window,time_pos_X_centr,price_pos_Y_tmp);         
         
   ChartTimePriceToXY(chart_ID,sub_window,time_pos_X_centr,price,x,y);

   const int              width=50;                 // ширина 
   const int              height=10;                // высота 
   const color            back_clr=C'236,233,216';  // цвет фона 
   const ENUM_BORDER_TYPE border=BORDER_SUNKEN;     // тип границы 
   const ENUM_BASE_CORNER corner=CORNER_LEFT_UPPER; // угол графика для привязки 
   const color            clr=clrRed;               // цвет плоской границы (Flat) 
   const ENUM_LINE_STYLE  style=STYLE_SOLID;        // стиль плоской границы 
   const int              line_width=1;             // толщина плоской границы 
   const bool             back=false;               // на заднем плане 
   const bool             selection=false;          // выделить для перемещений 
   const bool             hidden=true;              // скрыт в списке объектов 
   const long             z_order=0;                // приоритет на нажатие мышью 

   if(!ObjectIsExist(name)){
      if(ObjectCreate(chart_ID,name,OBJ_RECTANGLE_LABEL,sub_window,0,0)){
         ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,x); 
         ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,y); 
         ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,width); 
         ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,height); 
         ObjectSetInteger(chart_ID,name,OBJPROP_BGCOLOR,back_clr); 
         ObjectSetInteger(chart_ID,name,OBJPROP_BORDER_TYPE,border); 
         ObjectSetInteger(chart_ID,name,OBJPROP_CORNER,corner); 
         ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr); 
         ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style); 
         ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,line_width); 
         ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); 
         ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection); 
         ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection); 
         ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden); 
         ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order);
      }
   }else{
      ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,x); 
      ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,y); 
      ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,width); 
      ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,height);
      ObjectSetInteger(chart_ID,name,OBJPROP_BGCOLOR,back_clr);
      ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);   
   }
} 


//+------------------------------------------------------------------+
//| Есть-ли обьект на графике                                        |
//+------------------------------------------------------------------+

bool ObjectIsExist(string name){
   if(ObjectGetString(0,name,OBJPROP_NAME,0)==name)return(true);
return(false);}

//+------------------------------------------------------------------+
//| Появился новый бар                                               |
//+------------------------------------------------------------------+

bool NewBar(){
   static int countLastBar=0;
   int curBars = iBars(Symbol(),PERIOD_CURRENT);
   bool flg = false;
   if(countLastBar!=curBars){
      countLastBar=curBars;
      flg=true;
   }
return(flg);}
Files:
 
Vitaliy Kuznetsov:

Help solve a problem on the MT5 terminal.

Decided to transfer my product from mql4 to mql5.

It uses rectangles that are drawn on XY coordinates.

In MT4 it is all smooth and without jerks when changing vertical scale, but in MT5 the same approach leads to some freezes and noticeable "non-smoothness".

I specially made a simplified prototype to demonstrate the effect. It is the same for MT4 and MT5. Compare the difference when you change the vertical scale (with a mouse on the price scale).


In MT5 everything will be without brakes, but redrawing is jerky. The more objects, the worse. But in MT4 everything is smooth.

I attach source mq4 and mq5 files and paste mq5 code.


Please help me to make it smooth.


I want to understand: is it "such and such" MT5 or "such and such" my code for MT5.


I haven't studied and run this code yet, as I'm not at my computer, but the first thing that catches my eye are the two asynchronous functionsChartXYToTimePrice and ChartTimePriceToXY.
They are unrealistically slow. I've been battling with MQ for a long time on this topic, but it's a total ignore.
I remember that each function is executed for ~16 ms, i.e. in loop 30 it takes 30*2*16 =~1 sec.
No smoothness is even out of the question.
Try first to take these functions out of the loop, then it will work 30 times faster. I have it implemented in iCanvas.
 
Check this in the profiler.
 
well yes, you have run into 4 asynchronous functions that take 99.76% of the time to execute.
I have been trying to prove to MQ for years that these functions should not be asynchronous, because the chart property table already exists and it is enough to just take those properties and there is no point in running an asynchronous process.
But all efforts are in vain.
This really reeks of idiocy.
I know what I'm talking about, because I'm programming in many languages including Kanvas and actively using event-driven model.
And in prehistoric MQL4 this problem is minimal.

 
Maybe it's not a bug, but a feature? Something there is optimised with the graphics. And it works badly because it is desirable to use all things for their intended purpose. There are specialized graphical objects that are tied to price coordinates - so they should be used for this task.
 
Nikolai Semko:
Yeah, you've got 4 asynchronous functions that take 99.76% of all time.
...
And in prehistoric MQL4 this problem is minimal.

Yes, everything flies in MT4.

Nikolai Semko:
Try to take these functions out of the loop first, then it will work 30 times faster. I have implemented this in iCanvas.

If you don't mind, can you give me an example of how to do it better?

 
Vitaliy Kuznetsov:

Yes, everything flies in MT4.

If you don't mind, can you give me an example of how to do it more intelligently?

The shortest way is to attach my iCanvas biblio, which will allow not to use asynchronous functions in loop.
You don't have to use canvas itself. It will hang in your chart for the whole thing, but it will be empty and transparent.
If a longer method without using iCanvas, then you'll have to look it up in this bible again, as it is implemented there, but it's not for the faint-hearted. :))
I will try to briefly explain what iCanvas does.
It has internal handlerOnChartEvent, which catchesCHARTEVENT_CHART_CHANGE event and fills its internal structure W, using all the same asynchronous ChartGet functions, but does it only once with occurrence of this event.
This allows more or less optimize speed.
Also in this library hangs Canvas object, which stretches the entire screen and adjusts to it when screen changes.


Here, for example, three lines of code have been added and many lines discarded:


#property indicator_chart_window
#include <Canvas\iCanvas.mqh> //https://www.mql5.com/ru/code/22164

#property indicator_buffers 0
#property indicator_plots   0


string obj_name = "Asd_";

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit() {
   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[]) {
   if(NewBar()) {
      DrawObj();
   }
   return(rates_total);
}
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam) {
   if(id == CHARTEVENT_CHART_CHANGE) {
      DrawObj();
   }
}
//+------------------------------------------------------------------+
//| Выводим на график                                                |
//+------------------------------------------------------------------+
void DrawObj() {
   string GenName = obj_name;
   double startPricePos = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   int step_Pips = 50;
   for(int i=1; i<=20; i++) {
      double stp = (step_Pips*i)*SymbolInfoDouble(Symbol(),SYMBOL_POINT);
      RectLabelCreate(GenName+"UP_"+IntegerToString(i),startPricePos + stp);
      RectLabelCreate(GenName+"DN_"+IntegerToString(i),startPricePos - stp);
   }
   ChartRedraw(0);
}
//+------------------------------------------------------------------+
//| Создает прямоугольную метку                                      |
//+------------------------------------------------------------------+
void RectLabelCreate(string name,   // имя метки
                     double price   // цена
                    ) {
   const long  chart_ID=0;               // ID графика
   int         sub_window=0;             // номер подокна
   int         x=0;                      // координата по оси X
   int         y=0;                      // координата по оси Y
   x=W.Width/2;
   y = Round(Canvas.Y(price));
   //x=(int)(ChartGetInteger(chart_ID,CHART_WIDTH_IN_PIXELS,sub_window)/2);
   //ChartXYToTimePrice(chart_ID,x,y,sub_window,time_pos_X_centr,price_pos_Y_tmp);
   //ChartTimePriceToXY(chart_ID,sub_window,time_pos_X_centr,price,x,y);
   const int              width=50;                 // ширина
   const int              height=10;                // высота
   const color            back_clr=C'236,233,216';  // цвет фона
   const ENUM_BORDER_TYPE border=BORDER_SUNKEN;     // тип границы
   const ENUM_BASE_CORNER corner=CORNER_LEFT_UPPER; // угол графика для привязки
   const color            clr=clrRed;               // цвет плоской границы (Flat)
   const ENUM_LINE_STYLE  style=STYLE_SOLID;        // стиль плоской границы
   const int              line_width=1;             // толщина плоской границы
   const bool             back=false;               // на заднем плане
   const bool             selection=false;          // выделить для перемещений
   const bool             hidden=true;              // скрыт в списке объектов
   const long             z_order=0;                // приоритет на нажатие мышью
   if(ObjectCreate(chart_ID,name,OBJ_RECTANGLE_LABEL,sub_window,0,0)) {
      ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,width);
      ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,height);
      ObjectSetInteger(chart_ID,name,OBJPROP_BGCOLOR,back_clr);
      ObjectSetInteger(chart_ID,name,OBJPROP_BORDER_TYPE,border);
      ObjectSetInteger(chart_ID,name,OBJPROP_CORNER,corner);
      ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
      ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style);
      ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,line_width);
      ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
      ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
      ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);
      ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden);
      ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order);
   } else Print(_LastError);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool NewBar() {
   static int countLastBar=0;
   int curBars = iBars(Symbol(),PERIOD_CURRENT);
   bool flg = false;
   if(countLastBar!=curBars) {
      countLastBar=curBars;
      flg=true;
   }
   return(flg);
}
//+------------------------------------------------------------------+
In any case, you can increase the number of objects with this algorithm and it won't affect performance too much.
Files:
iCanvas.mqh  52 kb
 
Dmitry Fedoseev:
Or maybe it is not a bug but a feature? Something there is optimized with graphics. And it works badly because it is desirable to use all things for their intended purpose. There are specialized graphical objects which are tied to price coordinates - so they should be used for this task.

I used to think so too, until I gained more knowledge on the subject.
The reason is unambiguous - under-thinking.
Some "authority" made a mistake in the beginning (about 10 years ago) and no one still thinks that he could have made a mistake.
https://www.mql5.com/ru/forum/1111/page2780#comment_16886162
They even seem to have agreed and promised to improve it, but no - it's over. It's still there.
https://www.mql5.com/ru/forum/1111/page2781#comment_16904132


 
Nikolai Semko:

The shortest way is to attach my iCanvas bibblet, which will allow you to avoid using asynchronous functions in the loop.
You don't have to use the canvas itself at all. It will hang in your chart for the whole thing, but it will be empty and transparent.
If a longer way without using iCanvas, then you'll have to look it up in this bible again, as it is implemented there, but it's not for the faint-hearted. :))
I will try to briefly explain what iCanvas does.
It has internal handlerOnChartEvent, which catchesCHARTEVENT_CHART_CHANGE event and fills its internal structure W, using all the same asynchronous ChartGet functions, but does it only once with occurrence of this event.
This allows more or less optimize speed.
Also in this library hangs Canvas object, which stretches the entire screen and adjusts to it when screen changes.


Here, for example, three lines of code have been added and many lines discarded:

In any case, you can increase the number of objects with this algorithm and it won't affect performance too much.

Thanks for a similar solution to the problem. Indeed, the rendering speed has increased. Looks like I'll have to master the library.

I also would like to specify the following nuance. It compiles with this formulation with a warning:

y = Canvas.Y(price);

And it compiles with this one without a warning, but the speed is a bit slower.

y = (int)Canvas.Y(price);

What is more correct?)

 
Vitaliy Kuznetsov:

Thank you for this kind of solution to the problem. Indeed, the rendering speed has improved. Looks like I'll have to learn the library.

I would also like to clarify the following nuance. It compiles with this formulation with a warning:

And it compiles with this one without a warning, but the speed is a bit slower.

What's more correct?)

What type of "y" do you have?

because if int and objectively"speed drops a bit" this is a BAG

 
I'm not quite sure what type Canvas.Y(price);

what type it returns
Reason: