Аналог ObjectGetValueByTime может кто то реализовывал ? - страница 5

 
Aleksandr Slavskii #:

Нашёл причину. Галочка "Точная шкала времени.

Если стоит галка то формула тангенса полностью совпадает с ObjectGetValueByTime(), если галки нет, то чем больше угол трендовой тем больше разница в результатах.

Вывод, если хотим, чтоб при убранной галке ваша пользовательская функция совпадала с ObjectGetValueByTime(), то нужно округлять значение времени до времени открытия свечи таймфрейма.

Гифка работает только если на неё кликнуть.


ps вот такой вариант округления в пользовательской функции, даёт одинаковый результат с ObjectGetValueByTime(), не зависимо от галки в настройках.


Думаю здесь все понимают, что вот это 

и то, что написал я, это одно и то же. Формула тангенса. 

Об этом уже сказано. Только есть вариант преобразования попроще, на мой взгляд

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Аналог ObjectGetValueByTime может кто то реализовывал ?

Alexey Viktorov, 2026.04.17 13:24

Ну ведь преобразовать ничего не сто́ит.

double PriceLineTime(datetime tim_0, double pr_0, datetime tim_1, double pr_1, datetime tim_2)
 {
  int perSec = PeriodSeconds();
  tim_0 = datetime(int(tim_0/perSec)*perSec);
  tim_1 = datetime(int(tim_1/perSec)*perSec);

Вот такое время

2026.04.17 16:20:01.158 Test (EURUSD,H1)        2026.04.13 21:59:00 : 2026.04.14 07:53:00

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


Ещё: функция ObjectGetValueByTime не видит правую координату если она находится «в будущем».


 
Vladimir Pastushak #:

Экспериментирую

Результат

Не сходятся значения.

Пробовал ставить 0 то же не сходятся.

Исключил бар за пределами.

Вы почему упорно не хотите сказать на каком графике и по каким координатам нарисована линия?

Как я погляжу у всех участников обсуждения в том или ином варианте значения сходятся, а у вас ни при каких условиях не получается…

 
Alexey Viktorov #:

Об этом уже сказано. Только есть вариант преобразования попроще, на мой взгляд

Согласен с вами, это тот же ху# только в левой руке.  Разницы нет. 

Я бы только убрал лишние буквы(приведение к int), а так да, ваше округление немного красивее моего.

  int perSec = PeriodSeconds();
  tim_0 = tim_0/perSec*perSec;
  tim_1 = tim_1/perSec*perSec;
 
Vladimir Pastushak:

Друзья, может кто то реализовывал аналог ObjectGetValueByTime  ?

Пробовал формулу линейной интерполяции но результат сильно отличается от ObjectGetValueByTime.


Может кто то уже решал подобную задачу, поделитесь примером.

Привет, Владимир. 
Да, Игорь правильно все сказал, что нужно все пересчитывать через индексы баров, так как время по оси X распределено не равномерно (имеет разную плотность)
Так как у меня все преобразования Цена-Время <-> XY давно реализованы в iCanvas, то я набросал демонстрирующий пример с правильными преобразованиями. Можешь заглянуть в класс iCanvas чтобы посмотреть все методы.
В примере все координаты сохраняются в формате Цена-Время. На входе имеется тестовое время (вертикальная красная линия) и также тестируется пересечение всех линий с временем указателя мышки. 
#include <Canvas\iCanvas_CB.mqh> //https://www.mql5.com/ru/code/22164
input datetime test_time  = D'2026.04.17 8:00';
struct iPoint {
   double price;
   datetime time;
};
iPoint points[];
int cur_size = 0;
//+------------------------------------------------------------------+
int OnInit() {
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnTick() {
}
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam) {
   if (id == CHARTEVENT_CLICK) {
      cur_size++;
      ArrayResize(points,cur_size,100);
      points[cur_size-1].price = Canvas.Price(W.MouseY);
      points[cur_size-1].time = Canvas.TimePos(double(W.MouseX));
   }
   if (id==CHARTEVENT_MOUSE_MOVE || id == CHARTEVENT_CHART_CHANGE) Draw();
}
//+------------------------------------------------------------------+
void Draw() {
   Canvas.Erase(0x00FFFFFF);
   int total_lines = cur_size/2+cur_size%2;
   Canvas.LineVertical(int(Canvas.X(test_time)),0,W.Height-1,0x40FF0000);
   for (int i = 0; i<total_lines; i++) {
      double p1 = points[2*i].price;
      double p2 = (cur_size%2>0 && i==(total_lines-1))?Price(double(W.MouseY)):points[2*i+1].price;
      datetime t1 = points[2*i].time;
      datetime t2 = (cur_size%2>0 && i==(total_lines-1))?Canvas.TimePos(double(W.MouseX)):points[2*i+1].time;
      double x1 = Canvas.X(t1);
      double x2 = Canvas.X(t2);
      double y1 = Canvas.Y(p1);
      double y2 = Canvas.Y(p2);
      Canvas.LineD(x1,y1,x2,y2,0xFF8000FF);
      Canvas.Circle(int(x1),int(y1),7,0xFF8000FF);iBarShift
      Canvas.Circle(int(x2),int(y2),7,0xFF8000FF);
      Canvas.Circle(int(Canvas.X(test_time)),int(Canvas.Y(InterpolatePrice(t1,p1,t2,p2,test_time))),7,0xFFFF0000);
      Canvas.Circle(int(Canvas.X(W.MouseTime)),int(Canvas.Y(InterpolatePrice(t1,p1,t2,p2,W.MouseTime))),7,0xFF80FF00);
   } 
   Canvas.Update();
}
//+------------------------------------------------------------------+
double InterpolatePrice(datetime t1, double p1, datetime t2, double p2, datetime t) {
   double x1 = Canvas.X(t1);
   double y1 = Canvas.Y(p1);
   double x2 = Canvas.X(t2);
   double y2 = Canvas.Y(p2);
   double x3 = Canvas.X(t);
   double dx = (x1!=x2)?(x2-x1):0.0000001;
   return Price(y1+(x3-x1)*(y2-y1)/dx);
}
//+------------------------------------------------------------------+
double  Price(double y) {
   return (W.Y_max-y*(W.Y_max-W.Y_min)/W.Height);
}
//+------------------------------------------------------------------+

Файлы:
 

Правильно ли я перебрал функцию ?

double PredictPriceFromTwoPoints(datetime time0, double price0, datetime time1, double price1, datetime time2)
  {
   int persec = PeriodSeconds();
   long t0 = time0 - time0 % persec;
   long t1 = time1 - time1 % persec;
   long t2 = time2 - time2 % persec;
//
   int bar0 = iBarShift(_Symbol, PERIOD_CURRENT, (datetime)t0, false);
   int bar1 = iBarShift(_Symbol, PERIOD_CURRENT, (datetime)t1, false);
   int bar2 = iBarShift(_Symbol, PERIOD_CURRENT, (datetime)t2, false);
//
//  ::Print("Bar "+bar0 + "     " + bar1 + "    " + bar2);
// Расчет по времени для принта
   double price2 = price0 + (price1 - price0) * (t2 - t0) / (t1 - t0);
   ::Print("Calc time " + DoubleToString(price2, _Digits));

// === Перебиваем на расчет по барам для возврата
   price2 = price0 + (price1 - price0) * (bar2 - bar0) / (bar1 - bar0);
   ::Print("Calc bars " + DoubleToString(price2, _Digits));
//
//
   return(price2);
  }

Накидал советник для экспериментов.

Но результаты все равно не сходятся.

Файлы:
TestLine.mq5  12 kb
 
Vladimir Pastushak #:

Правильно ли я перебрал функцию ?

Накидал советник для экспериментов.

Но результаты все равно не сходятся.

Владимир, я дал решение, но чтобы его понять, нужно залесть в мой код глубже. Я понимаю, что это большая аскеза. Сам не люблю разбираться в чужом коде. Забегая вперед, сразу скажу что решение тебе не понравится, так как оно очень объемное. 
Во-первых нужно переходить с int на double, когда дело касается номера бара. 
Во-вторых твоя задача сводится к правильному вычислению номера бара. Увы iBarShift для этого не годится. Так как она возвращает только целочисленные номера реальных баров. А нам нужны дробные и мнимые будущие со знаком минус. Поэтому мне пришлось написать свою функцию double wBarShift, для которой нужно держать массив времени открытия баров в актуальном состоянии:
double wBarShift(datetime t,datetime &arr[],ENUM_TIMEFRAMES tf) {
   static ENUM_TIMEFRAMES lastTF=tf;
   int size=ArraySize(arr);
   if (size<3) return 0;
   int PerSec=PeriodSeconds(tf);
   int cur,Start=0;
   int fin=size-1;
   static datetime last_t=ULONG_MAX;
   if (last_t ==ULONG_MAX) last_t=(W.Right_bar==0 && tf==_Period)?arr[fin]:(TimeCurrent()-GetTickCount()/1000);
   if (t>arr[0] && t<arr[fin] && tf==_Period)
      if(tf>PERIOD_D1)
         while(true) {
            cur=(Start+fin)/2;
            if(t<arr[cur]) {
               if(t>=arr[cur-1]) return(size+(int)W.Right_bar-cur-double(t-arr[cur-1])/PerSec);
               fin=cur;
            } else {
               if(t< arr[cur+1]) return(size-1+(int)W.Right_bar-cur-double(t-arr[cur])/PerSec);
               if(Start==cur) return(0);
               Start=cur;
            }
         } else
         while(true) {
            cur=(Start+fin)/2;
            if(t<arr[cur]) {
               if(t>=arr[cur-1]) return(size+(int)W.Right_bar-cur-double(t%PerSec)/PerSec);
               fin=cur;
            } else {
               if(t< arr[cur+1]) return(size-1+(int)W.Right_bar-cur-double(t%PerSec)/PerSec);
               if(Start==cur) return(0);
               Start=cur;
            }
            if((arr[fin]-arr[Start])==((fin-Start)*PerSec))
               return(size-1+(int)W.Right_bar-Start-(double(t-arr[Start])/PerSec));
         }
   datetime tc1;
   if (W.Right_bar==0 && tf==_Period)
      tc1=arr[fin];
   else {
      if(tf==lastTF) tc1 = GetTickCount()/1000+last_t;
      else {
         tc1=TimeCurrent();
         last_t=tc1-GetTickCount()/1000;
         lastTF=tf;
      }
      tc1=tc1-tc1%PerSec;
   }
   if(t>=tc1) return double(tc1-t)/PerSec;
   int ib=iBarShift(_Symbol,tf,t);
   if (tf<PERIOD_W1) return(ib-double(t%PerSec)/PerSec);
   else return(ib-double(t-iTime(_Symbol,tf,ib))/PerSec);
}
вот такие пирожки. 
Именно поэтому я вообще не использую объекты, как анахронизм. 
Возможно для любителей объектов следует из моей библиотеки iCanvas создать другую библиотеу без канваса. Канвас по сути - это лишь дополнительная фича в этой библиотеке. 
Важно пояснить, что такое моя библиотека iCanvas. 
Это большой жирный костыль на фатальный симантический баг MQ, который MQ никак не хотят исправлять, и скорей всего не исправят. Почему фатальный? Да потому, что если бы они его исправили, то произошло ровно то, что произошло, когда компания Google создала движок V8, произведя браузерную революцию. Именно по причине этого бага МТ5 никак не может выстрелить в мире трейдинга, а плетется где-то на переферии.
 
Nikolai Semko #:
Поэтому мне пришлось написать свою функцию double wBarShift, для которой нужно держать массив времени открытия баров в актуальном состоянии:

возможно эту функцию можно сильно упростить без использования массива времени открытия баров. Просто когда я ее создавал, у меня было главное требование - высочайшая производительность, выше чему у стандартной iBarShift, так как ее вызов при работе с Canvas и при привязки канваса с чарту, происходит чрезвычайно часто. Поэтому держать массив времени открытия баров и поддерживать его в актуальном состоянии было оправдано. 
Поэтому можете попробовать создать функцю-аналог iBarShift, но которая возвращает дробные и отрицательные бары (будущие быры). Вместо массива времени использовать внутри стандартную функцию iBarShift.
Но в текущей реализации wBarShift намного быстрее стандартного iBarShift. Примерно в 3-4 раза. На моем процессоре 20 наносекунд против 70 наносекунд.

 
Vladimir Pastushak #:

Правильно ли я перебрал функцию ?

Накидал советник для экспериментов.

Но результаты все равно не сходятся.

В подтверждение моих слов я заменил в твоем коде iBarShift на мой wBarShift и изменил int на double 
и все стало работать корректно.
но тебе не надо вешать мою библу если тебе не нужен канвас(слишком жрет много ресурсов, т.к. с каждым товым баром обновляется большая таблица параметров чарта.
Поэтому тебе нужно написать свой iiBarShift
Файлы:
TestLine.mq5  12 kb
 
Nikolai Semko #:
В подтверждение моих слов я заменил в твоем коде iBarShift на мой wBarShift и изменил int на double 
и все стало работать корректно.
но тебе не надо вешать мою библу если тебе не нужен канвас(слишком жрет много ресурсов, т.к. с каждым товым баром обновляется большая таблица параметров чарта.
Поэтому тебе нужно написать свой iiBarShift

Честно сказать я не совсем понимаю, есть бары у них есть номера. Мне нужно знать значения на 0 текущем баре. Без заглядывания за пределы 0 бара в отрицательную сторону.

У нас есть номера баров, есть цены, почему функция не верно считает ?

Для меня загадка, наверно я уже стал стар..

 
Vladimir Pastushak #:

Честно сказать я не совсем понимаю, есть бары у них есть номера. Мне нужно знать значения на 0 текущем баре. Без заглядывания за пределы 0 бара в отрицательную сторону.

У нас есть номера баров, есть цены, почему функция не верно считает ?

Для меня загадка, наверно я уже стал стар..

Ошибки не будет если ширина бара 1 пиксель и правая точка линии левее нулевого бара
Но если правая граница линии правее нулевого бара, то для правильного расчёта нужно дополнительное смещение, так как iBarShift будет возвращать -1