from pathlib import Path code = r'''#property strict #property script_show_inputs #property version "1.20" #property description "Fast ObjectGetValueByTime replacement with AutoRecentWeighted" #property description "Exact + NativeLike + AutoRecentWeighted" #property description "Demo/test version with detailed Russian comments" //+------------------------------------------------------------------+ //| FastObjectGetValueByTimeAutoRecentWeighted_RU.mq5 | //| | //| Назначение: | //| Быстрая практическая замена ObjectGetValueByTime() для | //| линейных объектов с двумя точками: | //| - OBJ_TREND | //| - OBJ_ARROWED_LINE | //| | //| Идея решения: | //| 1) Exact: семантически точная геометрия по дробным | //| координатам баров. | //| 2) NativeLike: тот же быстрый движок, но время запроса | //| приводится к сетке открытий баров, что на | //| многих графиках ближе к native-поведению | //| ObjectGetValueByTime(). | //| 3) AutoRecentWeighted: автоматически выбирает лучший режим | //| (Exact или NativeLike) с главным приоритетом | //| на ошибке именно в текущем query-time. | //| | //| Почему код быстрый: | //| - вместо постоянных iTime()/iBarShift() на каждый расчёт | //| здесь кэшируется массив времени через CopyTime(); | //| - поиск нужного бара делается бинарным поиском; | //| - один и тот же кэш многократно используется повторно. | //| | //| Что делает тестовая версия скрипта: | //| - создаёт 2 демонстрационные линии на графике; | //| - считает значение native / Exact / NativeLike / Auto; | //| - печатает результаты в лог для сравнения. | //+------------------------------------------------------------------+ //--- создавать ли демонстрационные объекты автоматически input bool InpCreateDemoObjects = true; //--- имена демонстрационных объектов input string InpHistoryObjectName = "FAST_OGVBT_ARW_HISTORY"; input string InpFutureObjectName = "FAST_OGVBT_ARW_FUTURE"; //--- нормализовать ли цену к тик-сайзу инструмента input bool InpNormalizePrice = true; //--- печатать ли подробности автокалибровки input bool InpPrintCalibration = true; //--- параметры построения demo-history объекта input int InpHistoryLeftBars = 120; input int InpHistoryRightBars = 20; //--- параметры построения demo-future объекта input int InpFutureLeftBars = 60; input int InpFutureRightBarsAhead = 20; //+------------------------------------------------------------------+ //| Режимы работы быстрого движка | //+------------------------------------------------------------------+ enum ENUM_FAST_OGVBT_MODE { FAST_OGVBT_EXACT = 0, // точная геометрия FAST_OGVBT_NATIVE_LIKE = 1, // приближение к native-поведению FAST_OGVBT_AUTO_RECENT_WEIGHTED = 2 // автоматический выбор режима }; //+------------------------------------------------------------------+ //| Кэш временного ряда | //| | //| Храним: | //| symbol - символ, для которого кэш построен | //| tf - таймфрейм | //| zero_open - время открытия нулевого бара | //| times[] - массив времён баров | //| count - сколько элементов реально в кэше | //+------------------------------------------------------------------+ struct SFastTimeCache { string symbol; ENUM_TIMEFRAMES tf; datetime zero_open; datetime times[]; int count; }; //+------------------------------------------------------------------+ //| Кэш выбора режима автокалибровки | //| | //| key - сигнатура объекта + bucket времени запроса | //| mode - какой режим был выбран: Exact или NativeLike | //+------------------------------------------------------------------+ struct SCalibrationCache { string key; int mode; }; //--- глобальные кэши static SFastTimeCache g_time_cache; static SCalibrationCache g_calibration_cache[]; //+------------------------------------------------------------------+ //| Преобразование ENUM_TIMEFRAMES в читаемую строку | //+------------------------------------------------------------------+ string FastTfToString(const ENUM_TIMEFRAMES tf) { switch(tf) { case PERIOD_M1: return "M1"; case PERIOD_M2: return "M2"; case PERIOD_M3: return "M3"; case PERIOD_M4: return "M4"; case PERIOD_M5: return "M5"; case PERIOD_M6: return "M6"; case PERIOD_M10: return "M10"; case PERIOD_M12: return "M12"; case PERIOD_M15: return "M15"; case PERIOD_M20: return "M20"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H2: return "H2"; case PERIOD_H3: return "H3"; case PERIOD_H4: return "H4"; case PERIOD_H6: return "H6"; case PERIOD_H8: return "H8"; case PERIOD_H12: return "H12"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN1"; } return IntegerToString((int)tf); } //+------------------------------------------------------------------+ //| Форматирование времени для лога | //+------------------------------------------------------------------+ string FastTimeToStr(const datetime t) { return TimeToString(t, TIME_DATE|TIME_MINUTES|TIME_SECONDS); } //+------------------------------------------------------------------+ //| Форматирование цены для лога | //+------------------------------------------------------------------+ string FastDoubleToStr(const double v) { return DoubleToString(v, _Digits); } //+------------------------------------------------------------------+ //| Красивый разделитель в логе | //+------------------------------------------------------------------+ void PrintSeparator(const string title) { Print("============================================================"); Print(title); Print("============================================================"); } //+------------------------------------------------------------------+ //| Нормализация цены к тик-сайзу инструмента | //| | //| Это важно, потому что native ObjectGetValueByTime() часто | //| фактически возвращает цену, которая лежит на сетке допустимых | //| цен инструмента. | //+------------------------------------------------------------------+ double NormalizePriceToSymbol(const double price, const string symbol = "") { string sym = (symbol == "") ? _Symbol : symbol; double tick_size = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE); int digits = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS); if(tick_size <= 0.0) return NormalizeDouble(price, digits); return NormalizeDouble(MathRound(price / tick_size) * tick_size, digits); } //+------------------------------------------------------------------+ //| Безопасное получение тик-сайза | //| | //| Используется как epsilon в логике выбора режима. | //+------------------------------------------------------------------+ double GetTickSizeSafe(const string symbol = "") { string sym = (symbol == "") ? _Symbol : symbol; double tick = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE); if(tick > 0.0) return tick; if(_Point > 0.0) return _Point; return 1e-8; } //+------------------------------------------------------------------+ //| Время открытия месяца для произвольного datetime | //| | //| Для MN1 нельзя полагаться на "фиксированное число секунд", | //| потому что месяцы имеют разную длину. | //+------------------------------------------------------------------+ datetime MonthOpen(const datetime t) { MqlDateTime dt; TimeToStruct(t, dt); dt.day = 1; dt.hour = 0; dt.min = 0; dt.sec = 0; return StructToTime(dt); } //+------------------------------------------------------------------+ //| Добавление N месяцев к времени открытия месяца | //+------------------------------------------------------------------+ datetime AddMonthsToOpen(const datetime open_time, const int months) { MqlDateTime dt; TimeToStruct(open_time, dt); int total_months = dt.year * 12 + (dt.mon - 1) + months; dt.year = total_months / 12; dt.mon = total_months % 12 + 1; dt.day = 1; dt.hour = 0; dt.min = 0; dt.sec = 0; return StructToTime(dt); } //+------------------------------------------------------------------+ //| Строим сигнатуру объекта | //| | //| Сюда включаем все параметры, которые меняют геометрию объекта: | //| символ, TF, тип объекта, времена и цены обеих точек, ray-флаги. | //| | //| Если объект изменился, то ключ тоже изменится, и старая | //| калибровка больше не будет использоваться. | //+------------------------------------------------------------------+ string BuildObjectSignatureKey(const string symbol, const ENUM_TIMEFRAMES tf, const string name, const datetime time0, const double price0, const datetime time1, const double price1, const bool ray_left, const bool ray_right, const ENUM_OBJECT type) { return symbol + "|" + IntegerToString((int)tf) + "|" + name + "|" + IntegerToString((int)type) + "|" + IntegerToString((int)time0) + "|" + DoubleToString(price0, 8) + "|" + IntegerToString((int)time1) + "|" + DoubleToString(price1, 8) + "|" + (ray_left ? "1" : "0") + "|" + (ray_right ? "1" : "0"); } //+------------------------------------------------------------------+ //| Формируем окончательный ключ калибровки | //| | //| Помимо сигнатуры объекта добавляем bucket времени запроса. | //| Это нужно, чтобы режим мог кэшироваться не "навсегда", а | //| относительно текущего положения запроса во времени. | //+------------------------------------------------------------------+ string BuildCalibrationKey(const string object_signature, const datetime query_bucket) { return object_signature + "|QB=" + IntegerToString((int)query_bucket); } //+------------------------------------------------------------------+ //| Поиск записи кэша калибровки | //+------------------------------------------------------------------+ int FindCalibrationCacheIndex(const string key) { int n = ArraySize(g_calibration_cache); for(int i = 0; i < n; i++) { if(g_calibration_cache[i].key == key) return i; } return -1; } //+------------------------------------------------------------------+ //| Сохранение выбранного режима в кэш | //+------------------------------------------------------------------+ void SaveCalibrationMode(const string key, const int mode) { int idx = FindCalibrationCacheIndex(key); if(idx >= 0) { g_calibration_cache[idx].mode = mode; return; } int n = ArraySize(g_calibration_cache); ArrayResize(g_calibration_cache, n + 1); g_calibration_cache[n].key = key; g_calibration_cache[n].mode = mode; } //+------------------------------------------------------------------+ //| Загрузка режима из кэша | //+------------------------------------------------------------------+ bool LoadCalibrationMode(const string key, int &mode) { int idx = FindCalibrationCacheIndex(key); if(idx < 0) return false; mode = g_calibration_cache[idx].mode; return true; } //+------------------------------------------------------------------+ //| Проверка: разрешено ли спрашивать объект в момент времени t | //| | //| Учитываем ray_left / ray_right. | //+------------------------------------------------------------------+ bool IsObjectTimeAllowed(const datetime t, const datetime left_time, const datetime right_time, const bool ray_left, const bool ray_right) { if(t < left_time && !ray_left) return false; if(t > right_time && !ray_right) return false; return true; } //+------------------------------------------------------------------+ //| Добавление sample-точки для калибровки | //| | //| В массивы sample_times[] и sample_weights[] добавляем только | //| уникальные времена. | //+------------------------------------------------------------------+ bool AddWeightedSample(datetime times[], double weights[], int &count, const datetime t, const double w) { if(count >= ArraySize(times) || count >= ArraySize(weights)) return false; for(int i = 0; i < count; i++) { if(times[i] == t) return true; } times[count] = t; weights[count] = w; count++; return true; } //+------------------------------------------------------------------+ //| Сброс кэша времени | //+------------------------------------------------------------------+ void ResetFastTimeCache() { g_time_cache.symbol = ""; g_time_cache.tf = PERIOD_CURRENT; g_time_cache.zero_open = 0; g_time_cache.count = 0; ArrayFree(g_time_cache.times); } //+------------------------------------------------------------------+ //| Подготовка / обновление временного кэша | //| | //| oldest_needed_time - самое старое время, которое нам понадобится | //| для вычисления координат. | //| | //| Здесь один раз читается массив времени через CopyTime(). | //| Потом все расчёты работают уже по локальному кэшу. | //+------------------------------------------------------------------+ bool EnsureFastTimeCache(const string symbol, const ENUM_TIMEFRAMES tf, const datetime oldest_needed_time) { datetime zero_open = iTime(symbol, tf, 0); if(zero_open <= 0) return false; bool reset_needed = (g_time_cache.symbol != symbol || g_time_cache.tf != tf); if(reset_needed) ResetFastTimeCache(); g_time_cache.symbol = symbol; g_time_cache.tf = tf; int required_bars = 256; //--- если нам нужен более старый участок истории, //--- оцениваем, сколько баров надо загрузить if(oldest_needed_time < zero_open) { int ib = iBarShift(symbol, tf, oldest_needed_time, false); if(ib > 0) required_bars = ib + 8; } if(required_bars < 256) required_bars = 256; bool rebuild = false; if(g_time_cache.count == 0) rebuild = true; if(g_time_cache.zero_open != zero_open) rebuild = true; if(g_time_cache.count < required_bars) rebuild = true; if(!rebuild) return true; ArrayFree(g_time_cache.times); ArrayResize(g_time_cache.times, required_bars); ArraySetAsSeries(g_time_cache.times, true); int copied = CopyTime(symbol, tf, 0, required_bars, g_time_cache.times); if(copied <= 0) return false; if(copied < required_bars) ArrayResize(g_time_cache.times, copied); g_time_cache.count = copied; g_time_cache.zero_open = zero_open; return (g_time_cache.count > 1); } //+------------------------------------------------------------------+ //| Поиск индекса бара, время которого является floor для t | //| | //| Иными словами: | //| ищем такой бар, открытие которого <= t, | //| и следующий более "новый" бар уже > t. | //| | //| Здесь используется бинарный поиск по кэшу времени. | //+------------------------------------------------------------------+ bool FindFloorBarIndex(const datetime t, int &index_out) { if(g_time_cache.count <= 0) return false; //--- если время правее нулевого бара, то floor-индекс = 0 if(t >= g_time_cache.times[0]) { index_out = 0; return true; } int last = g_time_cache.count - 1; //--- если время левее самого старого бара в кэше, считаем что данных нет if(t < g_time_cache.times[last]) return false; int lo = 0; int hi = last; while(lo <= hi) { int mid = (lo + hi) / 2; datetime tm = g_time_cache.times[mid]; if(t == tm) { index_out = mid; return true; } if(t > tm) hi = mid - 1; else lo = mid + 1; } if(lo < 0 || lo > last) return false; index_out = lo; return true; } //+------------------------------------------------------------------+ //| Floor-время для будущего участка | //| | //| Если запрос времени правее нулевого бара, надо понять, к какому | //| "условному открытию бара" его привязать. | //| | //| Для MN1 используется отдельная календарная логика по месяцам. | //+------------------------------------------------------------------+ bool FutureFloorOpen(const datetime current_open, const datetime t, const ENUM_TIMEFRAMES tf, datetime &out_open) { //--- отдельная логика для месячного ТФ if(tf == PERIOD_MN1) { datetime cur_month = MonthOpen(current_open); datetime x = MonthOpen(t); out_open = (x < cur_month) ? cur_month : x; return true; } int sec = PeriodSeconds(tf); if(sec <= 0) return false; out_open = (t < current_open) ? current_open : (datetime)(current_open + ((t - current_open) / sec) * sec); return true; } //+------------------------------------------------------------------+ //| Приведение времени к сетке открытий баров | //| | //| Это основной вспомогательный кирпич для NativeLike режима. | //+------------------------------------------------------------------+ bool FloorToChartOpen(const datetime t, datetime &out_floor_time) { if(g_time_cache.count <= 0) return false; datetime zero_open = g_time_cache.times[0]; //--- будущее относительно нулевого бара if(t >= zero_open) return FutureFloorOpen(zero_open, t, g_time_cache.tf, out_floor_time); //--- исторический участок int idx = -1; if(!FindFloorBarIndex(t, idx)) return false; out_floor_time = g_time_cache.times[idx]; return true; } //+------------------------------------------------------------------+ //| Дробная бар-координата для будущего времени | //| | //| Координаты считаются в логике: | //| бар 0 -> координата 0 | //| правее -> отрицательные координаты | //| левее -> положительные координаты | //| | //| Это удобно, потому что будущие точки можно продолжать линейно. | //+------------------------------------------------------------------+ bool FutureBarCoord(const datetime current_open, const datetime t, const ENUM_TIMEFRAMES tf, double &coord) { //--- месячный ТФ считаем отдельно, по календарным месяцам if(tf == PERIOD_MN1) { datetime cur_month_open = MonthOpen(current_open); datetime t_month_open = MonthOpen(t); int months = 0; if(t_month_open >= cur_month_open) { MqlDateTime a, b; TimeToStruct(cur_month_open, a); TimeToStruct(t_month_open, b); months = (b.year - a.year) * 12 + (b.mon - a.mon); } datetime next_month_open = AddMonthsToOpen(t_month_open, 1); double denom = double(next_month_open - t_month_open); double frac = 0.0; if(denom > 0.0) frac = double(t - t_month_open) / denom; coord = -(double)months - frac; return true; } //--- для обычных ТФ можно использовать фиксированную длину бара в секундах int sec = PeriodSeconds(tf); if(sec <= 0) return false; coord = -(double)(t - current_open) / (double)sec; return true; } //+------------------------------------------------------------------+ //| Точная дробная координата бара для произвольного времени | //| | //| Это ядро режима Exact. | //| | //| Логика: | //| - если время в будущем -> считаем FutureBarCoord() | //| - если время точно совпало с bar-open -> координата = индекс | //| - иначе интерполируем дробную позицию между соседними барами | //+------------------------------------------------------------------+ bool FastBarCoordExact(const datetime t, double &coord_out) { if(g_time_cache.count <= 0) return false; datetime zero_open = g_time_cache.times[0]; //--- точно нулевой бар if(t == zero_open) { coord_out = 0.0; return true; } //--- будущее if(t > zero_open) return FutureBarCoord(zero_open, t, g_time_cache.tf, coord_out); //--- история int idx = -1; if(!FindFloorBarIndex(t, idx)) return false; datetime older = g_time_cache.times[idx]; //--- попали точно в открытие бара if(t == older) { coord_out = (double)idx; return true; } //--- специальный случай около нулевого бара if(idx == 0) { coord_out = 0.0; return true; } datetime newer = g_time_cache.times[idx - 1]; double denom = double(newer - older); if(denom <= 0.0) { coord_out = (double)idx; return true; } //--- дробная координата между соседними барами coord_out = idx - (double)(t - older) / denom; return true; } //+------------------------------------------------------------------+ //| Чтение 2-точечного линейного объекта с графика | //| | //| Поддерживаются только OBJ_TREND и OBJ_ARROWED_LINE. | //+------------------------------------------------------------------+ bool ReadStraightObject2Points(const long chart_id, const string name, ENUM_OBJECT &type, datetime &time0, double &price0, datetime &time1, double &price1, bool &ray_left, bool &ray_right) { if(ObjectFind(chart_id, name) < 0) return false; type = (ENUM_OBJECT)ObjectGetInteger(chart_id, name, OBJPROP_TYPE); if(type != OBJ_TREND && type != OBJ_ARROWED_LINE) return false; time0 = (datetime)ObjectGetInteger(chart_id, name, OBJPROP_TIME, 0); price0 = ObjectGetDouble(chart_id, name, OBJPROP_PRICE, 0); time1 = (datetime)ObjectGetInteger(chart_id, name, OBJPROP_TIME, 1); price1 = ObjectGetDouble(chart_id, name, OBJPROP_PRICE, 1); ray_left = (bool)ObjectGetInteger(chart_id, name, OBJPROP_RAY_LEFT); ray_right = (bool)ObjectGetInteger(chart_id, name, OBJPROP_RAY_RIGHT); return true; } //+------------------------------------------------------------------+ //| Безопасный вызов native ObjectGetValueByTime | //| | //| Возвращаем bool и отдельно код ошибки. | //+------------------------------------------------------------------+ bool NativeObjectGetValueByTimeEx(const string name, const datetime query_time, double &out_price, int &out_error) { ResetLastError(); out_price = ObjectGetValueByTime(0, name, query_time, 0); out_error = GetLastError(); return (out_error == 0); } //+------------------------------------------------------------------+ //| Основной быстрый движок | //| | //| mode = EXACT или NATIVE_LIKE | //| | //| Общая идея: | //| 1) читаем объект; | //| 2) проверяем ray-ограничения; | //| 3) готовим временной кэш; | //| 4) переводим точки и query_time в бар-координаты; | //| 5) считаем цену по линейной интерполяции. | //+------------------------------------------------------------------+ bool FastObjectGetValueByTimeMode(const long chart_id, const string name, const datetime query_time, double &out_price, const ENUM_FAST_OGVBT_MODE mode, const bool normalize_price = true, const bool floor_anchor_times = false, const string symbol_override = "") { ENUM_OBJECT type; datetime time0, time1; double price0, price1; bool ray_left, ray_right; if(!ReadStraightObject2Points(chart_id, name, type, time0, price0, time1, price1, ray_left, ray_right)) return false; datetime left_time = (time0 < time1) ? time0 : time1; datetime right_time = (time0 < time1) ? time1 : time0; //--- если запрос вне допустимого диапазона и лучи не разрешают, //--- возвращаем false if(query_time < left_time && !ray_left) return false; if(query_time > right_time && !ray_right) return false; string symbol = (symbol_override == "") ? _Symbol : symbol_override; ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)_Period; //--- определяем, какой самый старый момент нам понадобится datetime oldest_needed = time0; if(time1 < oldest_needed) oldest_needed = time1; if(query_time < oldest_needed) oldest_needed = query_time; if(!EnsureFastTimeCache(symbol, tf, oldest_needed)) return false; datetime query_eff = query_time; datetime time0_eff = time0; datetime time1_eff = time1; //--- для NativeLike приводим только время запроса к bar-open сетке if(mode == FAST_OGVBT_NATIVE_LIKE) { if(!FloorToChartOpen(query_time, query_eff)) return false; } //--- дополнительная опция: при желании можно тоже floor-ить anchor time //--- обычно она не нужна, поэтому по умолчанию false if(floor_anchor_times) { if(!FloorToChartOpen(time0, time0_eff)) return false; if(!FloorToChartOpen(time1, time1_eff)) return false; } double bar0 = 0.0; double bar1 = 0.0; double bar2 = 0.0; if(!FastBarCoordExact(time0_eff, bar0)) return false; if(!FastBarCoordExact(time1_eff, bar1)) return false; if(!FastBarCoordExact(query_eff, bar2)) return false; //--- вертикальный / вырожденный случай: обе точки в одной bar-координате if(bar0 == bar1) return false; //--- линейная интерполяция / экстраполяция по координатам баров out_price = price0 + (price1 - price0) * (bar2 - bar0) / (bar1 - bar0); if(normalize_price) out_price = NormalizePriceToSymbol(out_price, symbol); return true; } //+------------------------------------------------------------------+ //| Удобная обёртка Exact | //+------------------------------------------------------------------+ bool FastObjectGetValueByTimeExact(const long chart_id, const string name, const datetime query_time, double &out_price, const bool normalize_price = true, const bool floor_anchor_times = false, const string symbol_override = "") { return FastObjectGetValueByTimeMode(chart_id, name, query_time, out_price, FAST_OGVBT_EXACT, normalize_price, floor_anchor_times, symbol_override); } //+------------------------------------------------------------------+ //| Удобная обёртка NativeLike | //+------------------------------------------------------------------+ bool FastObjectGetValueByTimeNativeLike(const long chart_id, const string name, const datetime query_time, double &out_price, const bool normalize_price = true, const bool floor_anchor_times = false, const string symbol_override = "") { return FastObjectGetValueByTimeMode(chart_id, name, query_time, out_price, FAST_OGVBT_NATIVE_LIKE, normalize_price, floor_anchor_times, symbol_override); } //+------------------------------------------------------------------+ //| Автоматический выбор режима с приоритетом query-time | //| | //| Как выбирается режим: | //| 1) создаются локальные sample-точки вокруг query_time; | //| 2) для каждой точки считается native / Exact / NativeLike; | //| 3) главная метрика - ошибка именно в query_time; | //| 4) если по query_time разница мала, используется | //| weighted-score по локальным sample-точкам; | //| 5) выбранный режим кэшируется. | //| | //| Это принципиально лучше "простого среднего по истории", | //| потому что нам важнее точность именно в текущем запросе. | //+------------------------------------------------------------------+ int ChooseAutoRecentWeightedMode(const long chart_id, const string name, const string symbol, const ENUM_TIMEFRAMES tf, const datetime time0, const double price0, const datetime time1, const double price1, const bool ray_left, const bool ray_right, const ENUM_OBJECT type, const datetime query_time) { datetime left_time = (time0 < time1) ? time0 : time1; datetime right_time = (time0 < time1) ? time1 : time0; datetime oldest_needed = time0; if(time1 < oldest_needed) oldest_needed = time1; if(query_time < oldest_needed) oldest_needed = query_time; if(!EnsureFastTimeCache(symbol, tf, oldest_needed)) return FAST_OGVBT_NATIVE_LIKE; //--- bucket времени запроса нужен для кэширования калибровки datetime query_bucket = query_time; if(!FloorToChartOpen(query_time, query_bucket)) query_bucket = query_time; string object_signature = BuildObjectSignatureKey(symbol, tf, name, time0, price0, time1, price1, ray_left, ray_right, type); string key = BuildCalibrationKey(object_signature, query_bucket); //--- если для этого объекта и этого участка времени уже был выбран режим, //--- используем его повторно int cached_mode = FAST_OGVBT_NATIVE_LIKE; if(LoadCalibrationMode(key, cached_mode)) return cached_mode; datetime sample_times[16]; double sample_weights[16]; int sample_count = 0; //--- САМЫЙ ВАЖНЫЙ SAMPLE: //--- текущее query_time получает максимальный вес AddWeightedSample(sample_times, sample_weights, sample_count, query_time, 100.0); //--- bucket/query-floor тоже полезен как "локальная опорная точка" if(query_bucket != query_time) AddWeightedSample(sample_times, sample_weights, sample_count, query_bucket, 30.0); //--- локальные соседние точки вокруг запроса if(tf == PERIOD_MN1) { //--- для MN1 вместо секунд используем календарную логику datetime cur_open = query_bucket; datetime next_open = AddMonthsToOpen(MonthOpen(cur_open), 1); datetime prev_open = AddMonthsToOpen(MonthOpen(cur_open), -1); datetime mid_month = (datetime)(cur_open + (next_open - cur_open) / 2); if(IsObjectTimeAllowed(mid_month, left_time, right_time, ray_left, ray_right)) AddWeightedSample(sample_times, sample_weights, sample_count, mid_month, 18.0); if(IsObjectTimeAllowed(prev_open, left_time, right_time, ray_left, ray_right)) AddWeightedSample(sample_times, sample_weights, sample_count, prev_open, 10.0); if(IsObjectTimeAllowed(next_open, left_time, right_time, ray_left, ray_right)) AddWeightedSample(sample_times, sample_weights, sample_count, next_open, 10.0); } else { int sec = PeriodSeconds(tf); if(sec > 0) { datetime q_minus_half = (datetime)(query_time - sec / 2); datetime q_plus_half = (datetime)(query_time + sec / 2); datetime q_minus_full = (datetime)(query_time - sec); datetime q_plus_full = (datetime)(query_time + sec); if(IsObjectTimeAllowed(q_minus_half, left_time, right_time, ray_left, ray_right)) AddWeightedSample(sample_times, sample_weights, sample_count, q_minus_half, 22.0); if(IsObjectTimeAllowed(q_plus_half, left_time, right_time, ray_left, ray_right)) AddWeightedSample(sample_times, sample_weights, sample_count, q_plus_half, 22.0); if(IsObjectTimeAllowed(q_minus_full, left_time, right_time, ray_left, ray_right)) AddWeightedSample(sample_times, sample_weights, sample_count, q_minus_full, 10.0); if(IsObjectTimeAllowed(q_plus_full, left_time, right_time, ray_left, ray_right)) AddWeightedSample(sample_times, sample_weights, sample_count, q_plus_full, 10.0); } } //--- дальние / глобальные точки дают только очень небольшой вес: //--- они нужны как tie-break, но не должны ломать выбор режима datetime midpoint = (datetime)(left_time + (right_time - left_time) / 2); if(IsObjectTimeAllowed(midpoint, left_time, right_time, ray_left, ray_right)) AddWeightedSample(sample_times, sample_weights, sample_count, midpoint, 2.0); AddWeightedSample(sample_times, sample_weights, sample_count, time0, 0.5); AddWeightedSample(sample_times, sample_weights, sample_count, time1, 0.5); double weighted_exact = 0.0; double weighted_floor = 0.0; int valid_exact = 0; int valid_floor = 0; double query_err_exact = 1e100; double query_err_floor = 1e100; bool have_query_exact = false; bool have_query_floor = false; //--- считаем ошибки обоих режимов на sample-точках for(int i = 0; i < sample_count; i++) { datetime q = sample_times[i]; double w = sample_weights[i]; double native_value = 0.0; int native_error = 0; if(!NativeObjectGetValueByTimeEx(name, q, native_value, native_error)) continue; double exact_value = 0.0; if(FastObjectGetValueByTimeExact(chart_id, name, q, exact_value, InpNormalizePrice)) { double e = MathAbs(native_value - exact_value); weighted_exact += w * e; valid_exact++; if(q == query_time) { query_err_exact = e; have_query_exact = true; } } double floor_value = 0.0; if(FastObjectGetValueByTimeNativeLike(chart_id, name, q, floor_value, InpNormalizePrice)) { double e = MathAbs(native_value - floor_value); weighted_floor += w * e; valid_floor++; if(q == query_time) { query_err_floor = e; have_query_floor = true; } } } int chosen = FAST_OGVBT_NATIVE_LIKE; string reason = "weighted_fallback"; //--- epsilon нужен, чтобы не дёргаться из-за микроскопических различий double eps = GetTickSizeSafe(symbol) * 0.25; //--- ОСНОВНОЕ ПРАВИЛО: //--- если в текущем query_time один режим явно лучше, выбираем его if(have_query_exact && have_query_floor) { if(query_err_exact + eps < query_err_floor) { chosen = FAST_OGVBT_EXACT; reason = "query_priority_exact"; } else if(query_err_floor + eps < query_err_exact) { chosen = FAST_OGVBT_NATIVE_LIKE; reason = "query_priority_native_like"; } else { //--- если в query-time они почти равны, используем weighted-score if(valid_exact <= 0 && valid_floor <= 0) { chosen = FAST_OGVBT_NATIVE_LIKE; reason = "no_valid_samples_default"; } else if(valid_exact <= 0) { chosen = FAST_OGVBT_NATIVE_LIKE; reason = "exact_invalid"; } else if(valid_floor <= 0) { chosen = FAST_OGVBT_EXACT; reason = "native_like_invalid"; } else { chosen = (weighted_exact + eps < weighted_floor) ? FAST_OGVBT_EXACT : FAST_OGVBT_NATIVE_LIKE; reason = (chosen == FAST_OGVBT_EXACT) ? "weighted_exact" : "weighted_native_like"; } } } else if(have_query_exact && !have_query_floor) { chosen = FAST_OGVBT_EXACT; reason = "query_only_exact"; } else if(!have_query_exact && have_query_floor) { chosen = FAST_OGVBT_NATIVE_LIKE; reason = "query_only_native_like"; } else { //--- если query-time не удалось оценить ни в одном режиме, //--- падаем обратно на weighted-логику if(valid_exact <= 0 && valid_floor <= 0) { chosen = FAST_OGVBT_NATIVE_LIKE; reason = "no_valid_samples_default"; } else if(valid_exact <= 0) { chosen = FAST_OGVBT_NATIVE_LIKE; reason = "exact_invalid"; } else if(valid_floor <= 0) { chosen = FAST_OGVBT_EXACT; reason = "native_like_invalid"; } else { chosen = (weighted_exact + eps < weighted_floor) ? FAST_OGVBT_EXACT : FAST_OGVBT_NATIVE_LIKE; reason = (chosen == FAST_OGVBT_EXACT) ? "weighted_exact" : "weighted_native_like"; } } SaveCalibrationMode(key, chosen); if(InpPrintCalibration) { Print("AutoRecentWeighted | Object=", name, " | Symbol=", symbol, " | TF=", FastTfToString(tf), " | Samples=", IntegerToString(sample_count), " | ValidExact=", IntegerToString(valid_exact), " | ValidNativeLike=", IntegerToString(valid_floor), " | QueryErrExact=", (have_query_exact ? FastDoubleToStr(query_err_exact) : "n/a"), " | QueryErrNativeLike=", (have_query_floor ? FastDoubleToStr(query_err_floor) : "n/a"), " | WeightedExact=", DoubleToString(weighted_exact, _Digits + 4), " | WeightedNativeLike=", DoubleToString(weighted_floor, _Digits + 4), " | Chosen=", (chosen == FAST_OGVBT_EXACT ? "Exact" : "NativeLike"), " | Reason=", reason); } return chosen; } //+------------------------------------------------------------------+ //| Автоматическая обёртка | //+------------------------------------------------------------------+ bool FastObjectGetValueByTimeAutoRecentWeighted(const long chart_id, const string name, const datetime query_time, double &out_price, const bool normalize_price = true, const bool floor_anchor_times = false, const string symbol_override = "") { ENUM_OBJECT type; datetime time0, time1; double price0, price1; bool ray_left, ray_right; if(!ReadStraightObject2Points(chart_id, name, type, time0, price0, time1, price1, ray_left, ray_right)) return false; string symbol = (symbol_override == "") ? _Symbol : symbol_override; ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)_Period; int mode = ChooseAutoRecentWeightedMode(chart_id, name, symbol, tf, time0, price0, time1, price1, ray_left, ray_right, type, query_time); if(mode == FAST_OGVBT_EXACT) return FastObjectGetValueByTimeExact(chart_id, name, query_time, out_price, normalize_price, floor_anchor_times, symbol); return FastObjectGetValueByTimeNativeLike(chart_id, name, query_time, out_price, normalize_price, floor_anchor_times, symbol); } //+------------------------------------------------------------------+ //| Удобный алиас, если хочется короткое имя | //+------------------------------------------------------------------+ bool FastObjectGetValueByTimeAuto(const long chart_id, const string name, const datetime query_time, double &out_price, const bool normalize_price = true, const bool floor_anchor_times = false, const string symbol_override = "") { return FastObjectGetValueByTimeAutoRecentWeighted(chart_id, name, query_time, out_price, normalize_price, floor_anchor_times, symbol_override); } //+------------------------------------------------------------------+ //| Создание или перемещение трендовой линии | //| | //| Эта функция используется только в demo/test части скрипта. | //+------------------------------------------------------------------+ bool CreateOrMoveTrend(const string name, const datetime time0, const double price0, const datetime time1, const double price1, const color clr, const bool ray_left, const bool ray_right, const string text) { if(ObjectFind(0, name) < 0) { if(!ObjectCreate(0, name, OBJ_TREND, 0, time0, price0, time1, price1)) return false; } else { if(!ObjectMove(0, name, 0, time0, price0)) return false; if(!ObjectMove(0, name, 1, time1, price1)) return false; } ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_WIDTH, 2); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, name, OBJPROP_RAY_LEFT, ray_left); ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, ray_right); ObjectSetInteger(0, name, OBJPROP_BACK, false); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, true); ObjectSetInteger(0, name, OBJPROP_SELECTED, false); ObjectSetString(0, name, OBJPROP_TEXT, text); return true; } //+------------------------------------------------------------------+ //| Подбор безопасных demo-параметров под текущий график | //| | //| На маленькой истории нельзя брать слишком далёкие индексы баров. | //| На MN1 специально берём меньшие значения. | //+------------------------------------------------------------------+ bool SelectAdaptiveDemoBars(int &history_left, int &history_right, int &future_left, int &future_ahead) { int total = Bars(_Symbol, _Period); if(total < 12) return false; history_left = InpHistoryLeftBars; history_right = InpHistoryRightBars; future_left = InpFutureLeftBars; future_ahead = InpFutureRightBarsAhead; if(_Period == PERIOD_MN1) { history_left = 6; history_right = 2; future_left = 4; future_ahead = 2; } if(history_left > total - 4) history_left = total - 4; if(history_left < 3) history_left = 3; if(history_right >= history_left) history_right = history_left - 2; if(history_right < 1) history_right = 1; if(future_left > total - 4) future_left = total - 4; if(future_left < 2) future_left = 2; if(future_ahead < 1) future_ahead = 1; return true; } //+------------------------------------------------------------------+ //| Подготовка demo-history объекта | //+------------------------------------------------------------------+ bool PrepareHistoryObject() { int left_bars = 0, right_bars = 0, future_left = 0, future_ahead = 0; if(!SelectAdaptiveDemoBars(left_bars, right_bars, future_left, future_ahead)) return false; datetime t0 = iTime(_Symbol, _Period, left_bars); datetime t1 = iTime(_Symbol, _Period, right_bars); if(t0 <= 0 || t1 <= 0) return false; double p0 = iLow(_Symbol, _Period, left_bars) - 10 * _Point; double p1 = iHigh(_Symbol, _Period, right_bars) + 10 * _Point; return CreateOrMoveTrend(InpHistoryObjectName, t0, p0, t1, p1, clrDodgerBlue, false, true, "ARW history demo"); } //+------------------------------------------------------------------+ //| Подготовка demo-future объекта | //+------------------------------------------------------------------+ bool PrepareFutureObject() { int left_bars = 0, right_bars = 0, future_left = 0, future_ahead = 0; if(!SelectAdaptiveDemoBars(left_bars, right_bars, future_left, future_ahead)) return false; datetime t0 = iTime(_Symbol, _Period, future_left); datetime t1 = 0; //--- правая точка в будущем if(_Period == PERIOD_MN1) t1 = AddMonthsToOpen(MonthOpen(iTime(_Symbol, _Period, 0)), future_ahead); else { int sec = PeriodSeconds(_Period); if(sec <= 0) return false; t1 = (datetime)(iTime(_Symbol, _Period, 0) + sec * future_ahead); } if(t0 <= 0 || t1 <= 0) return false; double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(bid <= 0.0) bid = iClose(_Symbol, _Period, 0); double p0 = iLow(_Symbol, _Period, future_left) - 20 * _Point; double p1 = bid + 80 * _Point; return CreateOrMoveTrend(InpFutureObjectName, t0, p0, t1, p1, clrTomato, false, false, "ARW future demo"); } //+------------------------------------------------------------------+ //| Печать сравнения для одного объекта | //+------------------------------------------------------------------+ void PrintComparisonForObject(const string obj_name, const datetime t) { double native_value = 0.0; double exact_value = 0.0; double floor_value = 0.0; double auto_value = 0.0; int native_error = 0; bool ok_native = NativeObjectGetValueByTimeEx(obj_name, t, native_value, native_error); bool ok_exact = FastObjectGetValueByTimeExact(0, obj_name, t, exact_value, InpNormalizePrice); bool ok_floor = FastObjectGetValueByTimeNativeLike(0, obj_name, t, floor_value, InpNormalizePrice); bool ok_auto = FastObjectGetValueByTimeAutoRecentWeighted(0, obj_name, t, auto_value, InpNormalizePrice); Print("Object=", obj_name, " | Time=", FastTimeToStr(t), " | Native=", ok_native ? FastDoubleToStr(native_value) : ("FAILED err=" + IntegerToString(native_error)), " | Exact=", ok_exact ? FastDoubleToStr(exact_value) : "FAILED", " | NativeLike=", ok_floor ? FastDoubleToStr(floor_value) : "FAILED", " | AutoRecentWeighted=", ok_auto ? FastDoubleToStr(auto_value) : "FAILED", " | |N-Exact|=", (ok_native && ok_exact) ? FastDoubleToStr(MathAbs(native_value - exact_value)) : "n/a", " | |N-NativeLike|=", (ok_native && ok_floor) ? FastDoubleToStr(MathAbs(native_value - floor_value)) : "n/a", " | |N-AutoRecentWeighted|=", (ok_native && ok_auto) ? FastDoubleToStr(MathAbs(native_value - auto_value)) : "n/a"); } //+------------------------------------------------------------------+ //| Точка входа скрипта | //+------------------------------------------------------------------+ void OnStart() { PrintSeparator("FastObjectGetValueByTimeAutoRecentWeighted_WithTest_RU"); Print("Symbol=", _Symbol, " | TF=", FastTfToString((ENUM_TIMEFRAMES)_Period), " | Digits=", IntegerToString(_Digits), " | Point=", DoubleToString(_Point, _Digits)); Print("Modes: Exact, NativeLike, AutoRecentWeighted"); Print("AutoRecentWeighted gives strongest priority to the actual query time."); Print("Supports: OBJ_TREND, OBJ_ARROWED_LINE"); Print("============================================================"); bool ok_hist = true; bool ok_fut = true; //--- создаём demo-объекты, если включено if(InpCreateDemoObjects) { ok_hist = PrepareHistoryObject(); ok_fut = PrepareFutureObject(); ChartRedraw(); Print("PrepareHistoryObject=", (ok_hist ? "true" : "false"), " | PrepareFutureObject=", (ok_fut ? "true" : "false")); } //--- сравнение делаем в текущем времени datetime t = TimeCurrent(); if(ObjectFind(0, InpHistoryObjectName) >= 0) PrintComparisonForObject(InpHistoryObjectName, t); else Print("History demo object not found: ", InpHistoryObjectName); if(ObjectFind(0, InpFutureObjectName) >= 0) PrintComparisonForObject(InpFutureObjectName, t); else Print("Future demo object not found: ", InpFutureObjectName); PrintSeparator("Finished"); } ''' path = Path('/mnt/data/FastObjectGetValueByTimeAutoRecentWeighted_WithTest_RU.mq5') path.write_text(code, encoding='utf-8') print(f"Created: {path}") print(f"Lines: {len(code.splitlines())}")