Как сделать плавность изменения объектов, нарисованных по XY (MT4 vs MT5)

 

Помогите решить проблему на терминале МТ5.

Решил перевести свой продукт из mql4 в mql5.

В нём используются прямоугольники, которые рисуются по координатам XY.

И если в МТ4 при изменении вертикального масштаба всё плавно и без рывков, то на МТ5 тот же подход даёт фризы и заметную "неплавность".

Специально сделал упрощённый прототип для демонстрации эффекта. Одинаковый для МТ4 и МТ5. Сравните разницу при изменении вертикального масштаба (мышкой по ценовой шкале)


В МТ5 всё будет без тормозов, но перерисовка рывками. И чем больше объектов, тем хуже. А МТ4 всё плавненько.

Прикладываю исходники mq4 и mq5 файлами и вставка кода mq5.


Помогите сделать плавность.


Хочу понять: "Такой вот" МТ5 или "такой вот" мой код под МТ5.


//+------------------------------------------------------------------+
//|                                                  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);}
Файлы:
 
Vitaliy Kuznetsov:

Помогите решить проблему на терминале МТ5.

Решил перевести свой продукт из mql4 в mql5.

В нём используются прямоугольники, которые рисуются по координатам XY.

И если в МТ4 при изменении вертикального масштаба всё плавно и без рывков, то на МТ5 тот же подход даёт фризы и заметную "неплавность".

Специально сделал упрощённый прототип для демонстрации эффекта. Одинаковый для МТ4 и МТ5. Сравните разницу при изменении вертикального масштаба (мышкой по ценовой шкале)


В МТ5 всё будет без тормозов, но перерисовка рывками. И чем больше объектов, тем хуже. А МТ4 всё плавненько.

Прикладываю исходники mq4 и mq5 файлами и вставка кода mq5.


Помогите сделать плавность.


Хочу понять: "Такой вот" МТ5 или "такой вот" мой код под МТ5.


Я ещё не изучал и не запускал этот код, т.к. не за компом, но первое что бросается в глаза, это две асинхронные функции ChartXYToTimePrice и ChartTimePriceToXY.
Они нереально медленные. Я уже бьюсь давно с MQ на эту тему, но полный игнор.
Помню вроде каждая эта функция выполняется ~16 миллисекунд, т.е при цикле 30 время на выполнение только этих функций 30*2*16 =~1 сек.
Ни о какой плавности даже речи нет.
Попробуйте для начала эти функции из цикла вывести, тогда будет работать в 30 раз быстрее. У меня в iCanvas это реализовано.
 
Проверьте это в профилировщике.
 
ну да, Вы нарвались на 4 асинхронные функции,  выполнение которых занимает 99.76% всего времени. 
Я уже много лет пытаюсь доказать MQ, что эти функции не должны быть асинхронными, так как таблица свойств чарта уже существует, и достаточно просто взять эти свойства и нет никакого смысла запускать асинхронный процесс.
Но все усилия тщетны. 
Это реально отдает маразмом.
Я знаю о чем говорю, так как я на многих языках программирую и в том числе и с канвасом и с активным использованием событийной модели. 
Причем в доисторическом MQL4 такая проблема минимальна.

 
А может это не баг, а фича? Что-нибуь там оптимизировано с графикой. А плохо работает, потому что всеми вещами желательно пользоваться по назначению. Есть специализированные графические объекты, которые привязаны к координатам цены - вот ими и надо пользоваться для данной задачи. 
 
Nikolai Semko:
ну да, Вы нарвались на 4 асинхронные функции,  выполнение которых занимает 99.76% всего времени. 
...
Причем в доисторическом MQL4 такая проблема минимальна.

Да, в МТ4 всё летает.

Nikolai Semko:
Попробуйте для начала эти функции из цикла вывести, тогда будет работать в 30 раз быстрее. У меня в iCanvas это реализовано.

Если не затруднит, сможете пример подсказать, как грамотнее сделать?

 
Vitaliy Kuznetsov:

Да, в МТ4 всё летает.

Если не затруднит, сможете пример подсказать, как грамотнее сделать?

Самый короткий способ - это прикрепить мою библу iCanvas, которая позволит не использовать асинхронные функции в цикле.
При этом совсем не обязательно пользоваться самим канвасом. Он будет висеть у вас в чарте на весь, но он будет пустой и прозрачный.
Если более длинный способ без использования iCanvas, то вам придется этот способ опять же подсмотреть в этой библе, т.к. он там и реализован, но это занятие не для слабонервных. :))
Попробую коротко объяснить, что делает iCanvas.
В ней есть внутренний обработчик OnChartEvent, который ловит событие CHARTEVENT_CHART_CHANGE и заполняет свою внутреннюю структуру W, используя все те же асинхронные ChartGet функции, но делает это только раз с наступлением этого события. 
Это и позволяет более-менее оптимизировать скорость. 
Так же в этой библиотеке висит объект Canvas, который растянут на весь экран и подстраивается под него, когда экран меняется. 


Вот, например, добавлено три строки кода и много строк выброшено:


#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);
}
//+------------------------------------------------------------------+
Во всяком случае, Вы можете увеличивать количество объектов при таком алгоритме и это не будет сильно влиять на производительность.
Файлы:
iCanvas.mqh  52 kb
 
Dmitry Fedoseev:
А может это не баг, а фича? Что-нибуь там оптимизировано с графикой. А плохо работает, потому что всеми вещами желательно пользоваться по назначению. Есть специализированные графические объекты, которые привязаны к координатам цены - вот ими и надо пользоваться для данной задачи. 

я тоже так думал раньше грешным делом, пока знаний не прибавилось по этому вопросу. 
Причина однозначная - недомыслие.
Какой-то "авторитет" накосячил в самом начале (лет 10 назад) и никому до сих пор в голову не приходит, что он мог накосячить. 
https://www.mql5.com/ru/forum/1111/page2780#comment_16886162
Вроде даже как бы согласились и обещали доработать, но нет - забили. Воз и ныне там.
https://www.mql5.com/ru/forum/1111/page2781#comment_16904132


 
Nikolai Semko:

Самый короткий способ - это прикрепить мою библу iCanvas, которая позволит не использовать асинхронные функции в цикле.
При этом совсем не обязательно пользоваться самим канвасом. Он будет висеть у вас в чарте на весь, но он будет пустой и прозрачный.
Если более длинный способ без использования iCanvas, то вам придется этот способ опять же подсмотреть в этой библе, т.к. он там и реализован, но это занятие не для слабонервных. :))
Попробую коротко объяснить, что делает iCanvas.
В ней есть внутренний обработчик OnChartEvent, который ловит событие CHARTEVENT_CHART_CHANGE и заполняет свою внутреннюю структуру W, используя все те же асинхронные ChartGet функции, но делает это только раз с наступлением этого события. 
Это и позволяет более-менее оптимизировать скорость. 
Так же в этой библиотеке висит объект Canvas, который растянут на весь экран и подстраивается под него, когда экран меняется. 


Вот, например, добавлено три строки кода и много строк выброшено:

Во всяком случае, Вы можете увеличивать количество объектов при таком алгоритме и это не будет сильно влиять на производительность.

Спасибо за подобное решение проблемы. Действительно, скорость отрисовки возросла. Похоже придётся осваивать библиотеку.

Хотел бы ещё уточнить следующий нюанс. С такой формулировкой компилируется с предупреждением:

y = Canvas.Y(price);

А с такой без предупреждения, но скорость чутка падает

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

Как правильнее?)

 
Vitaliy Kuznetsov:

Спасибо за подобное решение проблемы. Действительно, скорость отрисовки возросла. Похоже придётся осваивать библиотеку.

Хотел бы ещё уточнить следующий нюанс. С такой формулировкой компилируется с предупреждением:

А с такой без предупреждения, но скорость чутка падает

Как правильнее?)

у вас "y" какого типа ?

потому как если int и объективно  "скорость чутка упала" это БАГ

 
Я не совсем понимаю какого типа Canvas.Y(price);

какой тип он возвращает