Перевод экранных координат во время/цену и обратно

Наличие разных принципов измерения рабочего пространства графика приводит к необходимости пересчитывать единицы измерения между собой. Для этого предназначены две следующих функции.

bool ChartTimePriceToXY(long chartId, int window, datetime time, double price, int &x, int &y)

bool ChartXYToTimePrice(long chartId, int x, int y, int &window, datetime &time, double &price)

Функция ChartTimePriceToXY преобразует координаты графика из представления время/цена (time/price) в координаты по оси X и Y в пикселях (x/y). Функция ChartXYToTimePrice выполняет обратную операцию: преобразует координаты X и Y в значения времени и цены.

Обе функции требуют указания идентификатора графика в первом параметре chartId. Дополнительно к этому в ChartTimePriceToXY передается номер подокна window (должен быть в пределах количества окон). При наличии нескольких подокон в каждом из них существует собственная таймсерия и шкала по вертикальной оси (называемая условно "ценой" и параметром price).

В функции ChartXYToTimePrice параметр window является выходным и заполняется самой функцией наравне с time и price. Это связано с тем, что пиксельные координаты являются общими для всего экрана, и исходная точка x/y может попасть в любое подокно.

Координаты времени, цен и экранные

Координаты времени, цен и экранные

Функции возвращают true в случае успешного выполнения.

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

Мы рассмотрим интерактивный пример пересчета в главе про события на графиках, а пока вернемся к примеру с буксировкой.

В предыдущем разделе мы видели, как можно узнать место запуска MQL-программы. Хотя физически точка окончания буксировки одна, её представление в котировочных и экранных координатах, как правило, содержит погрешность вычислений. Убедиться в этом нам как раз помогут две новых функции для перевода пикселей в цену/время и обратно.

Модифицированный скрипт называется ChartXY.mq5. Его можно условно разделить на 3 этапа. На первом этапе выведем координаты точки сброса, как и раньше.

void OnStart()
{
   const int w1 = PRTF(ChartWindowOnDropped());
   const datetime t1 = PRTF(ChartTimeOnDropped());
   const double p1 = PRTF(ChartPriceOnDropped());
   const int x1 = PRTF(ChartXOnDropped());
   const int y1 = PRTF(ChartYOnDropped());
   ...

На втором этапе попробуем преобразовать экранные координаты x1 и y1 во время (t2) и цену (p2), и сравним их с теми, что получены из OnDropped-функций выше.

   int w2;
   datetime t2;
   double p2;
   PRTF(ChartXYToTimePrice(0x1y1w2t2p2));
   Print(w2" "p2" "t2);
   PRTF(w1 == w2 && t1 == t2 && p1 == p2);
   ...

Затем выполним обратное преобразование: из полученных котировочных координат t1 и p1 рассчитаем экранные — x2 и y2, и также сравним с исходными значениями x1 и y1.

   int x2y2;
   PRTF(ChartTimePriceToXY(0w1t1p1x2y2));
   Print(x2" "y2);
   PRTF(x1 == x2 && y1 == y2);
   ...

Как мы увидим далее из примера журнала, все вышеприведенные проверки закончатся неудачей (в значениях будут небольшие расхождения). Поэтому нам требуется третий этап.

Пересчитаем еще раз экранные и котировочные координаты с суффиксом 2 в названиях переменных и сохраним их в переменных с новым суффиксом 3. Затем сравним все значения с первого и третьего этапа между собой.

   int w3;
   datetime t3;
   double p3;
   PRTF(ChartXYToTimePrice(0x2y2w3t3p3));
   Print(w3" "p3" "t3);
   PRTF(w1 == w3 && t1 == t3 && p1 == p3);
   
   int x3y3;
   PRTF(ChartTimePriceToXY(0w2t2p2x3y3));
   Print(x3" "y3);
   PRTF(x1 == x3 && y1 == y3);
}

Запустим скрипт на графике XAUUSD,H1. Вот исходные данные точки.

ChartWindowOnDropped()=0 / ok
ChartTimeOnDropped()=2021.11.22 18:00:00 / ok
ChartPriceOnDropped()=1797.7 / ok
ChartXOnDropped()=234 / ok
ChartYOnDropped()=280 / ok

Пересчет пикселей в котировки дает следующие результаты.

ChartXYToTimePrice(0,x1,y1,w2,t2,p2)=true / ok
0 1797.16 2021.11.22 18:30:00
w1==w2&&t1==t2&&p1==p2=false / ok

Налицо отличия как по времени, так и цене. Обратный пересчет также небезупречен в плане точности.

ChartTimePriceToXY(0,w1,t1,p1,x2,y2)=true / ok
232 278
x1==x2&&y1==y2=false / ok

Потеря точности возникает из-за квантования значений на осях согласно единицам измерения, в частности, пикселям и пунктам.

Наконец, последний этап доказывает, что полученные выше погрешности не являются артефактами функций, потому что пересчет по кругу приводит к исходному результату.

ChartXYToTimePrice(0,x2,y2,w3,t3,p3)=true / ok
0 1797.7 2021.11.22 18:00:00
w1==w3&&t1==t3&&p1==p3=true / ok
ChartTimePriceToXY(0,w2,t2,p2,x3,y3)=true / ok
234 280
x1==x3&&y1==y3=true / ok

В псевдокоде это можно выразить следующими равенствами:

ChartTimePriceToXY(ChartXYToTimePrice(XY)) = XY
ChartXYToTimePrice(ChartTimePriceToXY(TP)) = TP

Применение функции ChartTimePriceToXY к результатам работы ChartXYToTimePrice даст исходные координаты. То же самое верно и для преобразований в другую сторону: применение ChartXYToTimePrice к результатам ChartTimePriceToXY даст совпадение.

Таким образом, следует внимательно относиться к реализации алгоритмов, использующих функции пересчета, если к ним предъявляются повешенные требования по точности.

Еще один пример использования ChartWindowOnDropped будет приведен в скрипте ChartIndicatorMove.mq5 в разделе Управление индикаторами на графике.