2026-11-05 13:25:46 +03:00
//+------------------------------------------------------------------+
//| RenkoDeepSeek.mq5 |
//| Renko с сигналами пробоя последних подтверждённых вершин/впадин. |
//| История восстанавливается по тикам; текущие тики обрабатываются |
//| без смены периода графика. Цикл начинается в 03:00 по Москве. |
//| |
//| ЦЕЛЕВАЯ СПЕЦИФИКАЦИЯ ПРИВЯЗОК RENKO (обязательна для реализации): |
//| 1. Ценовой Anchor фиксирован: 1.00000 для всех запусков, графиков|
//| и экземпляров индикатора. Сетка уровней: 1.00000 + N*Brick, |
//| где N — целое число, Brick — размер кирпича в цене. |
//| 2. Anchor не устанавливается по первому тику, цене открытия |
//| графика, текущей цене или моменту подключения индикатора. |
//| 3. Начало/конец временного окна истории не меняют ценовую сетку. |
//| Расширение истории и перезапуск должны давать те же уровни |
//| Renko при одинаковой последовательности исходных тиков. |
//| 4. Время тиков обрабатывается по единой шкале UTC; граница |
//| суточного сигнального цикла — 03:00 Europe/Moscow (UTC+3). |
//| Граница цикла сбрасывает сигнальные экстремумы, но не Anchor, |
//| цену последнего закрытия или направление Renko. |
//| 5. Временная история определяется доступными историческими |
//| тиками и заданным окном загрузки, а не временем открытия |
//| графика/подключения индикатора. Историческое смещение времени |
//| сервера брокера должно учитываться для каждого периода; |
//| неизвестное смещение/неполная история явно помечаются. |
//| 6. Если 1.00000 или шаг Brick несовместимы с tick size символа, |
//| не сдвигать Anchor молча и не строить уровни вне сетки тиков: |
//| явно сообщить о несовместимости и остановить построение. |
//| 7. Хранится и отображается не более InpMaxHistoryBricks |
//| последних кирпичей (по умолчанию 400), независимо от Brick. |
//| 8. Каждый экземпляр работает в отдельном подокне. Скрытый маркер |
//| сохраняет идентичность подокна при изменении его номера; |
//| ChartID отделяет копии шаблона на разных графиках. Два |
//| экземпляра в одном подокне не запускаются. |
//| |
//| ВАЖНО: комментарий фиксирует целевое поведение; он сам по себе |
//| не означает, что текущая реализация уже соответствует пунктам. |
//| |
//| Бюджет пересбора срезовый: прерванный по времени пересбор |
//| продолжается с того же чанка. Хранение ограничено количеством; |
//| фактический вывод дополнительно ограничен rates_total. |
//| При появлении нового кирпича новые элементы идут перед старыми. |
//+------------------------------------------------------------------+
# property indicator_separate_window
# property indicator_plots 1
# property indicator_buffers 5
# property indicator_type1 DRAW_COLOR_CANDLES
# property indicator_color1 clrLimeGreen , clrTomato
# property indicator_width1 1
# property indicator_label1 " RenkoDeepSeek — кирпичи "
2026-11-05 19:12:31 +03:00
# property version " 1.45 "
2026-11-05 13:25:46 +03:00
# property description " Renko по тикам: пробой swing-уровней, раннее предупреждение, структурный SL. "
2026-11-05 19:12:31 +03:00
# property description " Цикл с 03:00 МСК; история восстанавливается короткими шагами. "
2026-11-05 13:25:46 +03:00
//+------------------------------------------------------------------+
//| КРАТКОЕ ИНЖЕНЕРНОЕ ОПИСАНИЕ ТЕКУЩЕЙ РЕАЛИЗАЦИИ |
//| |
//| • Источник цены — Bid. Renko строится от фиксированного Anchor |
//| 1.00000; несовместимость Anchor/шага с tick size блокирует |
//| расчёт, а не исправляется скрытым смещением. Разворот — 2 |
//| кирпича. |
//| • InpMaxHistoryBricks ограничивает кольцевой буфер последних |
//| кирпичей (по умолчанию 400). Это лимит количества, не дней: |
//| внутреннее окно истории расширяется, пока цель не достигнута |
//| либо не исчерпана доступная история. 400 — верхняя цель, а не |
//| гарантия при недостатке тиков/ценового движения. |
2026-11-05 19:12:31 +03:00
//| • История запрашивается минутными чанками; каждый шаг ограничен|
2026-11-05 13:25:46 +03:00
//| бюджетом времени; checkpoint сохраняет окно и позицию чанка, |
//| чтобы следующий вызов продолжил пересборку, а не начал заново. |
//| • Граница суточного цикла по умолчанию 03:00 МСК сбрасывает |
//| swing/сигнальные состояния, но сохраняет Renko-якорь, последнее|
//| закрытие и направление последовательности. |
//| • Изоляция экземпляров: каждому подокну соответствует скрытый |
//| пустой маркер; ключи настроек/кэша используют ChartID и ID |
//| маркера, а не изменяемый номер подокна. Два экземпляра |
//| RenkoDeepSeek в одном подокне запрещены. Маркер сохраняется|
//| при снятии индикатора для повторного подключения. |
//| • Пробой подтверждается закрытием кирпича за swing-уровнем. |
//| Раннее предупреждение рассчитывается по текущему Bid; |
//| структурный SL — информационный. Торговые ордера не размещаются.|
//| |
//| ИЗВЕСТНЫЕ ГРАНИЦЫ: нет независимой Renko-оси; вывод ограничен |
//| rates_total. При смене ChartID настройки/кэш могут потребовать |
//| повторного создания; старый кэш после перехода на маркерную |
//| идентичность не мигрируется и строится заново. |
//| Исторические серверные UTC-смещения не восстановлены по периодам;|
//| используется текущее смещение. Частичная история подавляет живые |
//| оповещения. Описание отражает статический код, не runtime-тест. |
//+------------------------------------------------------------------+
//--- Настройки индикатора: размер кирпича, история и цвета.
input int InpBrickPoints = 10 ; // Размер кирпича Renko в пунктах (значение по умолчанию: 600)
input int InpCycleStartHour = 3 ; // Час начала суточного цикла по Москве, 0–23
input int InpCycleStartMinute = 0 ; // Минута начала суточного цикла по Москве, 0–59
input string InpInstanceId = " " ; // Устаревший параметр для миграции прежних настроек; вручную задавать не требуется
input int InpMaxHistoryBricks = 400 ; // Максимум последних кирпичей в истории; 1–200000, независимо от размера кирпича
input color InpUpColor = clrLimeGreen ; // Цвет восходящих кирпичей
input color InpDownColor = clrTomato ; // Цвет нисходящих кирпичей
input color InpButtonActiveColor = clrForestGreen ; // Цвет активных кнопок
input color InpButtonInactiveColor = clrFireBrick ; // Цвет неактивных кнопок
input bool InpBreakoutAlerts = false ; // Стартовый мастер-флаг: включить все категории оповещений
input bool InpBreakoutHighAlerts = false ; // Оповещать о пробое подтверждённых вершин
input bool InpBreakoutLowAlerts = false ; // Оповещать о пробое подтверждённых впадин
input bool InpBrickReversalAlerts = false ; // Оповещать о развороте направления кирпичей
input bool InpPreBreakoutBuyAlerts = true ; // Раннее предупреждение BUY у вершины
input bool InpPreBreakoutSellAlerts = true ; // Раннее предупреждение SELL у впадины
input double InpPreBreakoutDistanceBricks = 0.5 ; // Порог предупреждения в долях кирпича
input int InpPreBreakoutDistancePoints = 0 ; // Дополнительный порог в пунктах; 0 — отключён
input int InpStopLossBufferPoints = 10 ; // Буфер структурного SL в пунктах
input bool InpPreBreakoutPushNotifications = false ; // Push для ранних сигналов
//--- Входы добавлены в конец списка, чтобы не смещать существующие параметры в .set-файлах.
input bool InpPushNotifications = true ; // Push для оповещений о пробоях и разворотах
input bool InpPreferSavedBrickSize = true ; // Устаревший параметр совместимости; сохранённый размер всегда имеет приоритет
input bool InpPopupAlerts = true ; // Показывать модальное окно Alert (выключите при плотных сигналах)
//--- Внутренние ограничения восстановления истории; пользователю менять их не требуется.
const int INITIAL_HISTORY_WINDOW_DAYS = 1 ; // Начальное внутреннее окно запроса; расширяется до лимита кирпичей
const int PANEL_UPDATE_INTERVAL_MS = 200 ; // Троттлинг панели: не чаще 5 обновлений в секунду
const long MOSCOW_UTC_OFFSET_SECONDS = 3 * 3600 ; // Москва не использует сезонный перевод часов
const double RENKO_PRICE_ANCHOR = 1.00000 ; // Фиксированный ценовой Anchor Renko
// Почанковая загрузка тиков: один запрос на сутки стабильно упирается
// в тайм-аут 4403, поэтому история читается короткими интервалами.
2026-11-05 19:12:31 +03:00
const int HISTORY_CHUNK_SECONDS = 60 ; // размер чанка: 1 минута; ограничивает объём одного запроса
2026-11-05 13:25:46 +03:00
const int HISTORY_CHUNK_RETRIES = 1 ; // одна повторная попытка; после ошибки фиксируем разрыв и продолжаем историю
const int HISTORY_PROBE_SECONDS = 15 * 60 ; // пробный запрос свежих тиков
const int TICK_BASE_PROBE_SECONDS = 3600 ; // пробный запрос у самой старой границы
2026-11-05 19:12:31 +03:00
const ulong HISTORY_BUILD_BUDGET_MS = 200 ; // мягкий бюджет одного шага восстановления
const int HISTORY_RESUME_DELAY_MS = 1000 ; // восстановление продолжается отдельными шагами таймера
2026-11-05 13:25:46 +03:00
// Пустой ответ CopyTicksRange нельзя считать подтверждённым пропуском:
// он означает и «сделок не было», и «участок базы ещё не выгружен».
const long EMPTY_RANGE_CONFIRM_MS = 3600 * 1000 ; // доверяем пустому ответу только для давних участков
const long TICK_LIVE_ALERT_WINDOW_MS = 5000 ; // оповещения разрешены только по «живым» тикам
const int MAX_HISTORY_BRICKS_ALLOWED = 200000 ; // верхний технический предел настраиваемой истории
//--- Структура данных одного кирпича Renko.
struct RenkoBrick
{
datetime time ;
double open ;
double high ;
double low ;
double close ;
int direction ;
} ;
//--- Состояние расчёта, параметры экземпляра и буферы отображения.
int g_current_brick_points = 10 ;
double g_brick_size = 0.0 ;
double g_price_step = 0.0 ; // только подтверждённый SYMBOL_TRADE_TICK_SIZE
double g_last_close = 0.0 ;
double g_last_bid = 0.0 ;
datetime g_cycle_start = 0 ;
int g_direction = 0 ; // 1 — вверх, -1 — вниз, 0 — направление не определено
long g_last_tick_msc = 0 ;
int g_ticks_processed_at_last_msc = 0 ;
datetime g_last_tick_copy_warning = 0 ;
bool g_have_anchor = false ;
bool g_anchor_init_warned = false ;
bool g_price_grid_valid = false ;
bool g_history_initialized = false ;
bool g_cache_loaded = false ;
bool g_cache_dirty = false ; // кэш требует периодического сохранения
bool g_cache_timer_enabled = false ;
bool g_settings_initialized = false ;
bool g_instance_identity_ready = false ;
ulong g_last_history_attempt_ms = 0 ;
int g_history_retry_count = 0 ;
bool g_alert_enabled = false ;
bool g_breakout_high_enabled = false ;
bool g_breakout_low_enabled = false ;
bool g_brick_reversal_enabled = false ;
bool g_pre_buy_enabled = true ;
bool g_pre_sell_enabled = true ;
string g_object_prefix = " " ;
int g_control_y_offset = 0 ;
string g_instance_id = " " ;
string g_instance_marker_id = " " ;
string g_instance_marker_name = " " ;
string g_indicator_short_name = " " ;
int g_instance_window = -1 ;
string g_alert_button_name = " " ;
string g_breakout_high_button_name = " " ;
string g_breakout_low_button_name = " " ;
string g_brick_reversal_button_name = " " ;
string g_pre_buy_button_name = " " ;
string g_pre_sell_button_name = " " ;
string g_brick_edit_name = " " ;
string g_brick_apply_name = " " ;
double g_open_buffer [ ] ;
double g_high_buffer [ ] ;
double g_low_buffer [ ] ;
double g_close_buffer [ ] ;
double g_color_buffer [ ] ;
RenkoBrick g_bricks [ ] ;
int g_brick_count = 0 ;
int g_brick_start = 0 ; // физический индекс самого старого кирпича в кольцевом буфере
int g_last_rendered_count = 0 ;
int g_last_rendered_rates_total = -1 ;
bool g_series_dirty = true ; // серия изменилась — нужна перерисовка буферов
double g_last_swing_high = 0.0 ; // Последняя подтверждённая вершина Renko
double g_last_swing_low = 0.0 ; // Последняя подтверждённая впадина Renko
bool g_swing_high_breakout_alerted = false ; // событие пробоя зафиксировано (для панели)
bool g_swing_low_breakout_alerted = false ;
bool g_swing_high_alert_sent = false ; // оповещение по этому событию уже отправлено
bool g_swing_low_alert_sent = false ;
bool g_pre_buy_alerted = false , g_pre_sell_alerted = false ;
bool g_pre_buy_cancelled = false , g_pre_sell_cancelled = false ;
string g_pre_status_label_name = " " , g_pre_detail_label_name = " " , g_pre_sl_label_name = " " ;
//--- Служебное состояние.
long g_server_to_utc = 0 ; // смещение серверного времени относительно UTC
bool g_replaying = false ; // признак восстановления истории: без UI и журнала
ulong g_last_panel_update_ms = 0 ; // троттлинг обновления панели
bool g_history_partial = false ; // история загружена не полностью
ulong g_last_gap_fill_ms = 0 ;
bool g_rebuild_running = false ; // защита от вложенного пересбора истории
bool g_step_warned = false ; // предупреждение о сетке цены уже выдано
datetime g_rebuild_resume_from = 0 ; // пересбор продолжается с этого чанка
int g_rebuild_resume_window_days = 0 ; // размер окна, к которому относится продолжение
bool g_history_needs_more = false ; // пересбор прерван по бюджету и требует продолжения
bool g_rebuild_partial_accum = false ; // в текущем проходе были незагруженные чанки
long g_diag_valid_bid_ticks = 0 ;
double g_diag_min_bid = 0.0 ;
double g_diag_max_bid = 0.0 ;
double g_diag_first_bid = 0.0 ;
double g_diag_last_bid = 0.0 ;
//--- Снимок серии: неудачный пересбор не должен стирать показанные кирпичи.
RenkoBrick g_snap_bricks [ ] ;
int g_snap_count = 0 ;
datetime g_snap_cycle_start = 0 ;
int g_snap_direction = 0 ;
long g_snap_last_tick_msc = 0 ;
int g_snap_ticks_at_msc = 0 ;
bool g_snap_have_anchor = false ;
bool g_snap_history_initialized = false ;
bool g_snap_partial = false ;
datetime g_snap_rebuild_resume_from = 0 ;
int g_snap_rebuild_resume_window_days = 0 ;
bool g_snap_history_needs_more = false ;
bool g_snap_rebuild_partial_accum = false ;
double g_snap_last_close = 0.0 ;
double g_snap_last_bid = 0.0 ;
double g_snap_swing_high = 0.0 ;
double g_snap_swing_low = 0.0 ;
bool g_snap_high_alerted = false ;
bool g_snap_low_alerted = false ;
bool g_snap_high_sent = false ;
bool g_snap_low_sent = false ;
bool g_snap_pre_buy = false ;
bool g_snap_pre_sell = false ;
bool g_snap_buy_cancelled = false ;
bool g_snap_sell_cancelled = false ;
bool g_snap_valid = false ;
int BrickPhysicalIndex ( const int logical_index )
{
if ( InpMaxHistoryBricks < = 0 )
return logical_index ;
return ( g_brick_start + logical_index ) % InpMaxHistoryBricks ;
}
// Нулевая серия может быть корректным состоянием: Anchor и курсор уже есть,
// но цена ещё не прошла расстояние одного кирпича. В таком случае можно
// продолжать с живых тиков, не повторяя одну и ту же полную историю.
bool HasUsableRenkoState ( )
{
if ( g_brick_count > 0 )
return true ;
return g_have_anchor & & g_cycle_start > 0 & & g_last_tick_msc > 0 & &
g_last_close > 0.0 & & g_last_bid > 0.0 & & ! g_history_partial ;
}
// Получает самую раннюю известную дату истории символа. Это граница
// доступности данных, а не пользовательский лимит истории.
bool GetEarliestAvailableHistoryDate ( datetime & first_date )
{
long value = 0 ;
ResetLastError ( ) ;
if ( SeriesInfoInteger ( _Symbol , PERIOD_CURRENT , SERIES_SERVER_FIRSTDATE , value ) & & value > 0 )
{
first_date = ( datetime ) value ;
return true ;
}
const int server_error = GetLastError ( ) ;
value = 0 ;
ResetLastError ( ) ;
if ( SeriesInfoInteger ( _Symbol , PERIOD_CURRENT , SERIES_TERMINAL_FIRSTDATE , value ) & & value > 0 )
{
first_date = ( datetime ) value ;
return true ;
}
PrintFormat ( " RenkoDeepSeek: не удалось определить самую раннюю доступную дату истории символа (ошибки SeriesInfoInteger: server=%d, terminal=%d). Расширение истории заблокировано до получения границы данных. " ,
server_error , GetLastError ( ) ) ;
return false ;
}
// Максимальное окно — только техническая граница реально доступной истории.
// Целевой объём задаётся исключительно InpMaxHistoryBricks, а не днями.
int MaximumHistoryWindowDaysAvailable ( const datetime cycle_start_server )
{
datetime first_date = 0 ;
if ( ! GetEarliestAvailableHistoryDate ( first_date ) )
return 0 ;
if ( first_date > cycle_start_server )
return 1 ;
const long span_seconds = ( long ) cycle_start_server - ( long ) first_date ;
long days = ( span_seconds + 86399 ) / 86400 + 1 ;
if ( days < 1 )
days = 1 ;
if ( days > 2147483647 )
days = 2147483647 ;
return ( int ) days ;
}
// Пересчитывает смещение серверного времени относительно UTC.
// TimeTradeServer() не зависит от наличия свежих тиков, поэтому на выходных
// и на только что открытом графике смещение не «уплывает» на часы.
void RefreshServerOffset ( )
{
const datetime trade_server = TimeTradeServer ( ) ;
const datetime server_now = ( trade_server > 0 ? trade_server : TimeCurrent ( ) ) ;
if ( server_now < = 0 )
return ; // нет соединения: сохраняем последнее известное смещение
g_server_to_utc = ( long ) ( TimeGMT ( ) - server_now ) ;
}
// Переводит время тика сервера в московское «настенное» время (UTC+3).
datetime ServerTimeToMoscow ( datetime server_time )
{
return ( datetime ) ( ( long ) server_time + g_server_to_utc + MOSCOW_UTC_OFFSET_SECONDS ) ;
}
// Обратное преобразование: значение из CycleStart() → метка времени сервера.
datetime MoscowToServerTime ( datetime moscow_time )
{
return ( datetime ) ( ( long ) moscow_time - MOSCOW_UTC_OFFSET_SECONDS - g_server_to_utc ) ;
}
// Возвращает границу суточного цикла по московскому календарю.
datetime CycleStart ( datetime server_time )
{
datetime moscow_time = ServerTimeToMoscow ( server_time ) ;
MqlDateTime dt ;
if ( ! TimeToStruct ( moscow_time , dt ) )
return 0 ;
if ( dt . hour < InpCycleStartHour | |
( dt . hour = = InpCycleStartHour & & dt . min < InpCycleStartMinute ) )
moscow_time - = 86400 ;
if ( ! TimeToStruct ( moscow_time , dt ) )
return 0 ;
dt . hour = InpCycleStartHour ;
dt . min = InpCycleStartMinute ;
dt . sec = 0 ;
return StructToTime ( dt ) ;
}
// Убирает только двоичную погрешность уже проверенной tick grid.
// Размер кирпича и Anchor эта функция не корректирует: несовместимость блокируется отдельно.
double SnapToTick ( const double price )
{
if ( g_price_step < = 0.0 | | ! MathIsValidNumber ( g_price_step ) )
return 0.0 ;
return NormalizeDouble ( MathRound ( price / g_price_step ) * g_price_step , _Digits ) ;
}
// Проверяет, что фиксированный Anchor и размер кирпича принадлежат сетке тиков.
bool IsRenkoGridCompatible ( const int brick_points , const bool report )
{
if ( g_price_step < = 0.0 | | ! MathIsValidNumber ( g_price_step ) | |
_Point < = 0.0 | | brick_points < 1 )
{
if ( report )
Print ( " RenkoDeepSeek: невозможно проверить сетку Renko: неверный tick size, _Point или размер кирпича. " ) ;
return false ;
}
const double anchor_ticks = RENKO_PRICE_ANCHOR / g_price_step ;
const double brick_size = ( double ) brick_points * _Point ;
const double brick_ticks = brick_size / g_price_step ;
const double anchor_tolerance = 1e-6 ;
const double brick_tolerance = 1e-6 ;
if ( MathAbs ( anchor_ticks - MathRound ( anchor_ticks ) ) > anchor_tolerance )
{
if ( report )
PrintFormat ( " RenkoDeepSeek: фиксированный Anchor %.5f несовместим с tick size %.10f символа %s; построение остановлено. " ,
RENKO_PRICE_ANCHOR , g_price_step , _Symbol ) ;
return false ;
}
if ( brick_ticks < 1.0 | | MathAbs ( brick_ticks - MathRound ( brick_ticks ) ) > brick_tolerance )
{
if ( report )
PrintFormat ( " RenkoDeepSeek: Brick %.10f (%d пунктов) несовместим с tick size %.10f символа %s; выберите размер, кратный шагу тика. " ,
brick_size , brick_points , g_price_step , _Symbol ) ;
return false ;
}
return true ;
}
// Первое закрытие привязывается к сетке от Anchor через floor.
// Цены переводятся в целые tick units, чтобы не накапливать двоичную погрешность.
bool CalculateInitialCloseFromBid ( const double bid , double & initial_close )
{
initial_close = 0.0 ;
if ( ! MathIsValidNumber ( bid ) | | bid < = 0.0 | |
! MathIsValidNumber ( g_price_step ) | | g_price_step < = 0.0 | |
! MathIsValidNumber ( g_brick_size ) | | g_brick_size < = 0.0 )
return false ;
const double max_exact_ticks = 9007199254740991.0 ;
const double bid_ticks_value = bid / g_price_step ;
const double anchor_ticks_value = RENKO_PRICE_ANCHOR / g_price_step ;
const double brick_ticks_value = g_brick_size / g_price_step ;
if ( ! MathIsValidNumber ( bid_ticks_value ) | |
! MathIsValidNumber ( anchor_ticks_value ) | |
! MathIsValidNumber ( brick_ticks_value ) | |
MathAbs ( bid_ticks_value ) > max_exact_ticks | |
MathAbs ( anchor_ticks_value ) > max_exact_ticks | |
brick_ticks_value < 1.0 | |
brick_ticks_value > max_exact_ticks )
return false ;
const long bid_ticks = ( long ) MathRound ( bid_ticks_value ) ;
const long anchor_ticks = ( long ) MathRound ( anchor_ticks_value ) ;
const long brick_ticks = ( long ) MathRound ( brick_ticks_value ) ;
if ( brick_ticks < 1 | |
MathAbs ( bid - ( double ) bid_ticks * g_price_step ) > g_price_step * 1e-6 )
return false ;
const long delta_ticks = bid_ticks - anchor_ticks ;
const double brick_count = MathFloor ( ( double ) delta_ticks / ( double ) brick_ticks ) ;
const double close_ticks_value = ( double ) anchor_ticks +
brick_count * ( double ) brick_ticks ;
if ( ! MathIsValidNumber ( close_ticks_value ) | |
MathAbs ( close_ticks_value ) > max_exact_ticks )
return false ;
const long close_ticks = ( long ) close_ticks_value ;
initial_close = NormalizeDouble ( ( double ) close_ticks * g_price_step , _Digits ) ;
const double tolerance = g_price_step * 1e-6 ;
if ( ! MathIsValidNumber ( initial_close ) | |
initial_close > bid + tolerance | |
bid - initial_close > = g_brick_size + tolerance )
{
initial_close = 0.0 ;
return false ;
}
return true ;
}
// Шаг одного кирпича от текущего закрытия.
bool BrickStep ( const bool up , const double from_close , double & to_close )
{
const double raw = from_close + ( up ? g_brick_size : - g_brick_size ) ;
double snapped = SnapToTick ( raw ) ;
if ( up ? ( snapped < = from_close ) : ( snapped > = from_close ) )
return false ;
to_close = snapped ;
return true ;
}
//--- Полный сброс серии при первичной инициализации/восстановлении.
void ResetCycle ( datetime cycle_start )
{
g_cycle_start = cycle_start ;
g_last_close = 0.0 ;
g_last_bid = 0.0 ;
g_direction = 0 ;
g_have_anchor = false ;
g_anchor_init_warned = false ;
g_last_swing_high = 0.0 ;
g_last_swing_low = 0.0 ;
g_swing_high_breakout_alerted = false ;
g_swing_low_breakout_alerted = false ;
g_swing_high_alert_sent = false ;
g_swing_low_alert_sent = false ;
g_pre_buy_alerted = false ; g_pre_sell_alerted = false ;
g_pre_buy_cancelled = false ; g_pre_sell_cancelled = false ;
}
//--- Новые торговые сутки сбрасывают сигнальные экстремумы, но не Renko-цену.
// Это позволяет строить единую историческую последовательность через границы суток.
void ResetCycleSignals ( const datetime cycle_start )
{
g_cycle_start = cycle_start ;
g_last_swing_high = 0.0 ;
g_last_swing_low = 0.0 ;
g_swing_high_breakout_alerted = false ;
g_swing_low_breakout_alerted = false ;
g_swing_high_alert_sent = false ;
g_swing_low_alert_sent = false ;
g_pre_buy_alerted = false ;
g_pre_sell_alerted = false ;
g_pre_buy_cancelled = false ;
g_pre_sell_cancelled = false ;
}
//--- Короткое имя индикатора с указанием, что время цикла задано по Москве.
string BuildShortName ( )
{
return StringFormat ( " RenkoDeepSeek %d пунктов / цикл %02d:%02d МСК [%s|W%d] " ,
g_current_brick_points , InpCycleStartHour ,
InpCycleStartMinute , g_instance_id , g_instance_window ) ;
}
//--- Добавление построенного кирпича во внутреннюю последовательность.
bool DrawBrick ( datetime tick_time , double open_price , double close_price , int direction )
{
// Буфер заранее выделен в OnInit; кольцевая запись исключает сдвиг
// до 200 000 элементов при каждом новом кирпиче.
bool confirmed_high = false ;
bool confirmed_low = false ;
double swing_high = 0.0 ;
double swing_low = 0.0 ;
if ( g_brick_count > 0 )
{
const RenkoBrick previous = g_bricks [ BrickPhysicalIndex ( g_brick_count - 1 ) ] ;
if ( previous . direction > 0 & & direction < 0 )
{
confirmed_high = true ;
swing_high = previous . high ;
}
else if ( previous . direction < 0 & & direction > 0 )
{
confirmed_low = true ;
swing_low = previous . low ;
}
}
// Кирпич приводится к сетке цены инструмента: иначе значения в буферах,
// уровнях и кэше расходились бы на ~1e-16 и сравнение цен было недетерминированным.
const double brick_open = SnapToTick ( open_price ) ;
const double brick_close = SnapToTick ( close_price ) ;
int physical_index = 0 ;
if ( g_brick_count < InpMaxHistoryBricks )
physical_index = BrickPhysicalIndex ( g_brick_count ) ;
else
{
physical_index = g_brick_start ;
g_brick_start = ( g_brick_start + 1 ) % InpMaxHistoryBricks ;
}
g_bricks [ physical_index ] . time = tick_time ;
g_bricks [ physical_index ] . open = brick_open ;
g_bricks [ physical_index ] . high = MathMax ( brick_open , brick_close ) ;
g_bricks [ physical_index ] . low = MathMin ( brick_open , brick_close ) ;
g_bricks [ physical_index ] . close = brick_close ;
g_bricks [ physical_index ] . direction = direction ;
if ( g_brick_count < InpMaxHistoryBricks )
+ + g_brick_count ;
g_series_dirty = true ;
g_cache_dirty = true ;
if ( confirmed_high )
{
g_last_swing_high = swing_high ;
g_swing_high_breakout_alerted = false ;
g_swing_high_alert_sent = false ;
g_pre_buy_alerted = false ; g_pre_buy_cancelled = false ;
}
else if ( confirmed_low )
{
g_last_swing_low = swing_low ;
g_swing_low_breakout_alerted = false ;
g_swing_low_alert_sent = false ;
g_pre_sell_alerted = false ; g_pre_sell_cancelled = false ;
}
return true ;
}
//--- Перенос последовательности Renko в буферы индикатора для отображения.
// force=false — перерисовка выполняется только при изменении серии или rates_total.
void RenderBricks ( const int rates_total , const bool force )
{
if ( ! force & & ! g_series_dirty & & rates_total = = g_last_rendered_rates_total )
return ;
int count = g_brick_count ;
if ( count > rates_total )
count = rates_total ;
int clear_count = ( g_last_rendered_rates_total ! = rates_total
? rates_total
: ( g_last_rendered_count > count ? g_last_rendered_count : count ) ) ;
if ( clear_count > rates_total )
clear_count = rates_total ;
for ( int i = 0 ; i < clear_count ; + + i )
{
g_open_buffer [ i ] = EMPTY_VALUE ;
g_high_buffer [ i ] = EMPTY_VALUE ;
g_low_buffer [ i ] = EMPTY_VALUE ;
g_close_buffer [ i ] = EMPTY_VALUE ;
g_color_buffer [ i ] = 0.0 ;
}
// Самый новый кирпич — в буфер с индексом 0, более старые — следом.
for ( int i = 0 ; i < count ; + + i )
{
const int source = BrickPhysicalIndex ( g_brick_count - 1 - i ) ;
g_open_buffer [ i ] = g_bricks [ source ] . open ;
g_high_buffer [ i ] = g_bricks [ source ] . high ;
g_low_buffer [ i ] = g_bricks [ source ] . low ;
g_close_buffer [ i ] = g_bricks [ source ] . close ;
g_color_buffer [ i ] = ( g_bricks [ source ] . direction > 0 ? 0.0 : 1.0 ) ;
}
g_last_rendered_count = count ;
g_last_rendered_rates_total = rates_total ;
g_series_dirty = false ;
}
//--- Вывод локального и push-оповещения одним и тем же сообщением.
void NotifyUser ( const string message , const bool send_push )
{
Print ( message ) ;
if ( InpPopupAlerts )
Alert ( message ) ;
if ( ! send_push )
return ;
ResetLastError ( ) ;
if ( ! SendNotification ( message ) )
Print ( " RenkoDeepSeek: SendNotification не отправлено; код ошибки= " , GetLastError ( ) ,
" . Проверьте настройки уведомлений MetaTrader 5. " ) ;
}
//--- Подбор ширины кнопки по длине отображаемого текста.
void SetButtonTextFit ( const string button_name , const string button_text , const int font_size )
{
if ( button_name = = " " | | ObjectFind ( 0 , button_name ) < 0 )
return ;
ObjectSetInteger ( 0 , button_name , OBJPROP_FONTSIZE , font_size ) ;
ObjectSetString ( 0 , button_name , OBJPROP_TEXT , button_text ) ;
const int text_length = StringLen ( button_text ) ;
const int button_width = ( int ) MathCeil ( ( double ) text_length * font_size * 0.72 ) + 18 ;
ObjectSetInteger ( 0 , button_name , OBJPROP_XSIZE , button_width ) ;
}
// Ограничивает подпись доступной шириной графика, чтобы длинные строки
// не выходили за правую границу и не накладывались на другие элементы.
string FitPrePanelText ( const string text )
{
const long chart_width = ChartGetInteger ( 0 , CHART_WIDTH_IN_PIXELS , 0 ) ;
int max_chars = ( int ) ( ( chart_width - 20 ) / 6 ) ;
if ( max_chars < 12 )
max_chars = 12 ;
if ( StringLen ( text ) < = max_chars )
return text ;
if ( max_chars < = 3 )
return StringSubstr ( text , 0 , max_chars ) ;
return StringSubstr ( text , 0 , max_chars - 3 ) + " ... " ;
}
bool CreatePrePanelLabel ( const string name , const int window , const int y )
{
if ( ObjectFind ( 0 , name ) < 0 & & ! ObjectCreate ( 0 , name , OBJ_LABEL , window , 0 , 0 ) ) return false ;
ObjectSetInteger ( 0 , name , OBJPROP_CORNER , CORNER_LEFT_UPPER ) ;
ObjectSetInteger ( 0 , name , OBJPROP_ANCHOR , ANCHOR_LEFT_UPPER ) ;
ObjectSetInteger ( 0 , name , OBJPROP_XDISTANCE , 8 ) ; ObjectSetInteger ( 0 , name , OBJPROP_YDISTANCE , y ) ;
ObjectSetInteger ( 0 , name , OBJPROP_FONTSIZE , 8 ) ; ObjectSetInteger ( 0 , name , OBJPROP_COLOR , clrSilver ) ;
ObjectSetInteger ( 0 , name , OBJPROP_SELECTABLE , false ) ; ObjectSetInteger ( 0 , name , OBJPROP_HIDDEN , true ) ;
ObjectSetString ( 0 , name , OBJPROP_TEXT , " " ) ; return true ;
}
// Расстояние от уровня входа до структурного SL в пунктах.
string SlDistanceText ( const double level , const double sl , const bool valid )
{
if ( ! valid )
return " — " ;
return DoubleToString ( MathAbs ( level - sl ) / _Point , 1 ) ;
}
// force=true — обновить немедленно (кнопки, создание панели);
// force=false — с троттлингом (Bid меняется на каждом тике).
void UpdatePreBreakoutPanel ( const double bid , const bool force )
{
if ( g_pre_status_label_name = = " " | | ObjectFind ( 0 , g_pre_status_label_name ) < 0 | |
g_pre_detail_label_name = = " " | | ObjectFind ( 0 , g_pre_detail_label_name ) < 0 | |
g_pre_sl_label_name = = " " | | ObjectFind ( 0 , g_pre_sl_label_name ) < 0 )
return ;
if ( ! force )
{
const ulong now_ms = GetTickCount64 ( ) ;
if ( g_last_panel_update_ms ! = 0 & &
now_ms - g_last_panel_update_ms < ( ulong ) PANEL_UPDATE_INTERVAL_MS )
return ;
g_last_panel_update_ms = now_ms ;
}
const string bid_text = DoubleToString ( bid , _Digits ) ;
const double buy = ( g_last_swing_high > 0.0 ? NormalizeDouble ( g_last_swing_high + g_brick_size , _Digits ) : 0.0 ) ;
const double sell = ( g_last_swing_low > 0.0 ? NormalizeDouble ( g_last_swing_low - g_brick_size , _Digits ) : 0.0 ) ;
const double bsl = ( g_last_swing_low > 0.0 ? NormalizeDouble ( g_last_swing_low - InpStopLossBufferPoints * _Point , _Digits ) : 0.0 ) ;
const double ssl = ( g_last_swing_high > 0.0 ? NormalizeDouble ( g_last_swing_high + InpStopLossBufferPoints * _Point , _Digits ) : 0.0 ) ;
const double bd = ( buy > 0.0 ? MathAbs ( buy - bid ) / _Point : 0.0 ) ;
const double sd = ( sell > 0.0 ? MathAbs ( bid - sell ) / _Point : 0.0 ) ;
const double threshold = MathMax ( g_brick_size * InpPreBreakoutDistanceBricks ,
InpPreBreakoutDistancePoints * _Point ) ;
const bool bv = ( buy > 0.0 & & g_last_swing_low > 0.0 & & bsl < buy ) ;
const bool sv = ( sell > 0.0 & & g_last_swing_high > 0.0 & & ssl > sell ) ;
const bool bn = ( g_pre_buy_enabled & & ! g_swing_high_breakout_alerted & & g_direction > 0 & &
buy > bid & & bd * _Point < = threshold ) ;
const bool sn = ( g_pre_sell_enabled & & ! g_swing_low_breakout_alerted & & g_direction < 0 & &
sell < bid & & sd * _Point < = threshold ) ;
string status = " Ожидание подхода к уровню " ;
string detail = _Symbol + " | Bid= " + bid_text ;
string slinfo = " SL: нет активного сигнала " ;
color status_color = clrSilver ;
if ( g_swing_high_breakout_alerted & & buy > 0.0 )
{
status = " ПРОБОЙ вершины произошёл " ;
status_color = clrLimeGreen ;
detail = StringFormat ( " %s | Bid=%s | уровень BUY=%s " , _Symbol , bid_text ,
DoubleToString ( buy , _Digits ) ) ;
slinfo = ( bv ? StringFormat ( " SL BUY=%s | от уровня входа %.1f пунктов " ,
DoubleToString ( bsl , _Digits ) , MathAbs ( buy - bsl ) / _Point )
: " SL BUY: недостаточно swing-данных " ) ;
}
else if ( g_swing_low_breakout_alerted & & sell > 0.0 )
{
status = " ПРОБОЙ впадины произошёл " ;
status_color = clrLimeGreen ;
detail = StringFormat ( " %s | Bid=%s | уровень SELL=%s " , _Symbol , bid_text ,
DoubleToString ( sell , _Digits ) ) ;
slinfo = ( sv ? StringFormat ( " SL SELL=%s | от уровня входа %.1f пунктов " ,
DoubleToString ( ssl , _Digits ) , MathAbs ( ssl - sell ) / _Point )
: " SL SELL: недостаточно swing-данных " ) ;
}
else if ( bn )
{
status = " BUY: цена приближается к пробою вершины " ;
status_color = clrDarkOrange ;
detail = StringFormat ( " %s | Bid=%s | уровень=%s | до уровня %.1f пунктов " ,
_Symbol , bid_text , DoubleToString ( buy , _Digits ) , bd ) ;
slinfo = ( bv ? StringFormat ( " Предлагаемый SL BUY=%s | риск %.1f пунктов " ,
DoubleToString ( bsl , _Digits ) , MathAbs ( buy - bsl ) / _Point )
: " SL BUY: нет корректной подтверждённой впадины " ) ;
}
else if ( sn )
{
status = " SELL: цена приближается к пробою впадины " ;
status_color = clrDarkOrange ;
detail = StringFormat ( " %s | Bid=%s | уровень=%s | до уровня %.1f пунктов " ,
_Symbol , bid_text , DoubleToString ( sell , _Digits ) , sd ) ;
slinfo = ( sv ? StringFormat ( " Предлагаемый SL SELL=%s | риск %.1f пунктов " ,
DoubleToString ( ssl , _Digits ) , MathAbs ( ssl - sell ) / _Point )
: " SL SELL: нет корректной подтверждённой вершины " ) ;
}
else if ( g_pre_buy_cancelled | | g_pre_sell_cancelled )
{
status = " Предварительный сигнал отменён: цена отошла " ;
status_color = clrTomato ;
}
else if ( buy < = 0.0 & & sell < = 0.0 )
{
status = " Ожидание: нет подтверждённых swing-уровней " ;
}
else
{
detail = StringFormat ( " %s | Bid=%s | BUY=%s (%.1f п.) | SELL=%s (%.1f п.) " , _Symbol , bid_text ,
( buy > 0.0 ? DoubleToString ( buy , _Digits ) : " нет " ) , bd ,
( sell > 0.0 ? DoubleToString ( sell , _Digits ) : " нет " ) , sd ) ;
slinfo = " SL: противоположный подтверждённый swing + буфер " ;
}
ObjectSetString ( 0 , g_pre_status_label_name , OBJPROP_TEXT , FitPrePanelText ( status ) ) ;
ObjectSetInteger ( 0 , g_pre_status_label_name , OBJPROP_COLOR , status_color ) ;
ObjectSetString ( 0 , g_pre_detail_label_name , OBJPROP_TEXT , FitPrePanelText ( detail ) ) ;
ObjectSetString ( 0 , g_pre_sl_label_name , OBJPROP_TEXT , FitPrePanelText ( slinfo ) ) ;
}
void EvaluatePreBreakoutSignals ( const double bid , const bool enable_alert )
{
if ( bid < = 0.0 | | g_brick_size < = 0.0 )
return ;
const bool push = ( InpPreBreakoutPushNotifications & & InpPushNotifications ) ;
const double threshold = MathMax ( g_brick_size * InpPreBreakoutDistanceBricks ,
InpPreBreakoutDistancePoints * _Point ) ;
const double rearm = threshold + g_brick_size * 0.25 ;
if ( g_last_swing_high > 0.0 )
{
const double level = NormalizeDouble ( g_last_swing_high + g_brick_size , _Digits ) ;
const double dist = ( level - bid ) / _Point ;
if ( g_pre_buy_alerted & & bid < level & & dist * _Point > rearm )
{
g_pre_buy_alerted = false ;
g_pre_buy_cancelled = true ;
}
const bool near = ( g_pre_buy_enabled & & ! g_swing_high_breakout_alerted & & g_direction > 0 & &
bid < level & & dist > = 0.0 & & dist * _Point < = threshold ) ;
if ( near )
{
g_pre_buy_cancelled = false ;
if ( enable_alert & & g_alert_enabled & & ! g_pre_buy_alerted )
{
const double sl = ( g_last_swing_low > 0.0
? NormalizeDouble ( g_last_swing_low - InpStopLossBufferPoints * _Point , _Digits )
: 0.0 ) ;
const bool valid = ( sl > 0.0 & & sl < level ) ;
g_pre_buy_alerted = true ;
NotifyUser ( StringFormat ( " RenkoDeepSeek: ПРЕДУПРЕЖДЕНИЕ ДО ПРОБОЯ; BUY; %s; Bid=%s; уровень=%s; расстояние=%.1f пунктов; SL=%s; SL от уровня входа=%s пунктов. Пробой не подтверждён, вход ручной. " ,
_Symbol , DoubleToString ( bid , _Digits ) ,
DoubleToString ( level , _Digits ) , dist ,
( valid ? DoubleToString ( sl , _Digits ) : " НЕТ ДАННЫХ " ) ,
SlDistanceText ( level , sl , valid ) ) , push ) ;
}
}
}
else
{
g_pre_buy_alerted = false ;
g_pre_buy_cancelled = false ;
}
if ( g_last_swing_low > 0.0 )
{
const double level = NormalizeDouble ( g_last_swing_low - g_brick_size , _Digits ) ;
const double dist = ( bid - level ) / _Point ;
if ( g_pre_sell_alerted & & bid > level & & dist * _Point > rearm )
{
g_pre_sell_alerted = false ;
g_pre_sell_cancelled = true ;
}
const bool near = ( g_pre_sell_enabled & & ! g_swing_low_breakout_alerted & & g_direction < 0 & &
bid > level & & dist > = 0.0 & & dist * _Point < = threshold ) ;
if ( near )
{
g_pre_sell_cancelled = false ;
if ( enable_alert & & g_alert_enabled & & ! g_pre_sell_alerted )
{
const double sl = ( g_last_swing_high > 0.0
? NormalizeDouble ( g_last_swing_high + InpStopLossBufferPoints * _Point , _Digits )
: 0.0 ) ;
const bool valid = ( sl > level ) ;
g_pre_sell_alerted = true ;
NotifyUser ( StringFormat ( " RenkoDeepSeek: ПРЕДУПРЕЖДЕНИЕ ДО ПРОБОЯ; SELL; %s; Bid=%s; уровень=%s; расстояние=%.1f пунктов; SL=%s; SL от уровня входа=%s пунктов. Пробой не подтверждён, вход ручной. " ,
_Symbol , DoubleToString ( bid , _Digits ) ,
DoubleToString ( level , _Digits ) , dist ,
( valid ? DoubleToString ( sl , _Digits ) : " НЕТ ДАННЫХ " ) ,
SlDistanceText ( level , sl , valid ) ) , push ) ;
}
}
}
else
{
g_pre_sell_alerted = false ;
g_pre_sell_cancelled = false ;
}
// Панель не трогаем во время восстановления истории.
if ( ! g_replaying )
UpdatePreBreakoutPanel ( bid , false ) ;
}
//--- Пробой подтверждается только закрытием уже созданного кирпича за Swing-уровнем.
void EvaluateConfirmedBreakout ( const double brick_close , const bool enable_alert )
{
if ( ! MathIsValidNumber ( brick_close ) )
return ;
if ( g_last_swing_high > 0.0 & &
! g_swing_high_breakout_alerted & &
brick_close > g_last_swing_high )
{
g_swing_high_breakout_alerted = true ;
if ( ! g_replaying )
Print ( " RenkoDeepSeek: подтверждённый пробой Swing High; вершина= " ,
DoubleToString ( g_last_swing_high , _Digits ) , " ; закрытие кирпича= " ,
DoubleToString ( brick_close , _Digits ) , " . " ) ;
if ( enable_alert & & g_alert_enabled & & g_breakout_high_enabled & &
! g_swing_high_alert_sent )
{
g_swing_high_alert_sent = true ;
NotifyUser ( StringFormat ( " RenkoDeepSeek: ПОДТВЕРЖДЁННЫЙ ПРОБОЙ SWING HIGH %s; Swing High=%s; закрытие Renko-кирпича=%s; размер=%d пунктов. " ,
_Symbol , DoubleToString ( g_last_swing_high , _Digits ) ,
DoubleToString ( brick_close , _Digits ) ,
g_current_brick_points ) ,
InpPushNotifications ) ;
}
}
if ( g_last_swing_low > 0.0 & &
! g_swing_low_breakout_alerted & &
brick_close < g_last_swing_low )
{
g_swing_low_breakout_alerted = true ;
if ( ! g_replaying )
Print ( " RenkoDeepSeek: подтверждённый пробой Swing Low; впадина= " ,
DoubleToString ( g_last_swing_low , _Digits ) , " ; закрытие кирпича= " ,
DoubleToString ( brick_close , _Digits ) , " . " ) ;
if ( enable_alert & & g_alert_enabled & & g_breakout_low_enabled & &
! g_swing_low_alert_sent )
{
g_swing_low_alert_sent = true ;
NotifyUser ( StringFormat ( " RenkoDeepSeek: ПОДТВЕРЖДЁННЫЙ ПРОБОЙ SWING LOW %s; Swing Low=%s; закрытие Renko-кирпича=%s; размер=%d пунктов. " ,
_Symbol , DoubleToString ( g_last_swing_low , _Digits ) ,
DoubleToString ( brick_close , _Digits ) ,
g_current_brick_points ) ,
InpPushNotifications ) ;
}
}
}
//--- Обработка Bid: построение кирпичей и подтверждение пробоя закрытым кирпичом.
void ProcessBid ( datetime tick_time , double bid , bool enable_alert )
{
if ( bid < = 0.0 | | g_brick_size < = 0.0 )
return ;
// Пропуски в исторических диапазонах искажают swing-структуру: живые
// оповещения подавляются, пока полная пересборка не подтвердит структуру.
if ( g_history_partial )
enable_alert = false ;
const datetime tick_cycle = CycleStart ( tick_time ) ;
if ( tick_cycle = = 0 )
return ;
if ( tick_cycle ! = g_cycle_start )
{
if ( ! g_replaying & & g_cycle_start ! = 0 )
Print ( " RenkoDeepSeek: начало нового суточного цикла; граница МСК= " ,
TimeToString ( MoscowToServerTime ( tick_cycle ) , TIME_DATE | TIME_MINUTES ) ,
" по времени сервера; сигнальные экстремумы сброшены, Renko-последовательность продолжается. " ) ;
if ( g_cycle_start ! = 0 & & g_have_anchor )
{
// Не сбрасываем last_close/direction/anchor: иначе при крупном кирпиче
// каждые сутки цена заново становится якорем и история может остаться пустой.
ResetCycleSignals ( tick_cycle ) ;
g_last_bid = bid ; // не проверяем пробой сигнала через ночной разрыв
}
else
ResetCycle ( tick_cycle ) ;
}
// Первый тик после полной инициализации задаёт якорь; на границе суток
// существующий Renko-якорь сохраняется, чтобы история оставалась непрерывной.
if ( ! g_have_anchor )
{
if ( ! CalculateInitialCloseFromBid ( bid , g_last_close ) )
{
if ( ! g_anchor_init_warned )
{
Print ( " RenkoDeepSeek: первый Bid не удалось корректно привязать к ценовой сетке Anchor/tick size; инициализация Renko отложена. " ) ;
g_anchor_init_warned = true ;
}
return ;
}
g_anchor_init_warned = false ;
g_last_bid = bid ;
g_have_anchor = true ;
if ( ! g_replaying )
UpdatePreBreakoutPanel ( bid , false ) ;
}
int safety = 0 ;
bool changed = true ;
bool step_failed = false ;
double next_close = 0.0 ;
double brick_open = 0.0 ;
while ( changed & & safety + + < 10000 )
{
changed = false ;
if ( g_direction = = 0 )
{
if ( bid > = g_last_close + g_brick_size )
{
if ( ! BrickStep ( true , g_last_close , next_close ) )
{
step_failed = true ;
break ;
}
if ( ! DrawBrick ( tick_time , g_last_close , next_close , 1 ) )
return ;
g_last_close = next_close ;
g_direction = 1 ;
EvaluateConfirmedBreakout ( g_last_close , enable_alert ) ;
changed = true ;
}
else if ( bid < = g_last_close - g_brick_size )
{
if ( ! BrickStep ( false , g_last_close , next_close ) )
{
step_failed = true ;
break ;
}
if ( ! DrawBrick ( tick_time , g_last_close , next_close , -1 ) )
return ;
g_last_close = next_close ;
g_direction = -1 ;
EvaluateConfirmedBreakout ( g_last_close , enable_alert ) ;
changed = true ;
}
}
else if ( g_direction > 0 )
{
if ( bid > = g_last_close + g_brick_size )
{
if ( ! BrickStep ( true , g_last_close , next_close ) )
{
step_failed = true ;
break ;
}
if ( ! DrawBrick ( tick_time , g_last_close , next_close , 1 ) )
return ;
g_last_close = next_close ;
EvaluateConfirmedBreakout ( g_last_close , enable_alert ) ;
changed = true ;
}
else if ( bid < = g_last_close - 2.0 * g_brick_size )
{
if ( ! BrickStep ( false , g_last_close , brick_open ) | |
! BrickStep ( false , brick_open , next_close ) )
{
step_failed = true ;
break ;
}
if ( ! DrawBrick ( tick_time , brick_open , next_close , -1 ) )
return ;
g_last_close = next_close ;
g_direction = -1 ;
EvaluateConfirmedBreakout ( g_last_close , enable_alert ) ;
if ( enable_alert & & g_alert_enabled & & g_brick_reversal_enabled )
NotifyUser ( StringFormat ( " RenkoDeepSeek: РАЗВОРОТ КИРПИЧЕЙ ВНИЗ; размер %d пунктов; %s " ,
g_current_brick_points , _Symbol ) , InpPushNotifications ) ;
changed = true ;
}
}
else
{
if ( bid < = g_last_close - g_brick_size )
{
if ( ! BrickStep ( false , g_last_close , next_close ) )
{
step_failed = true ;
break ;
}
if ( ! DrawBrick ( tick_time , g_last_close , next_close , -1 ) )
return ;
g_last_close = next_close ;
EvaluateConfirmedBreakout ( g_last_close , enable_alert ) ;
changed = true ;
}
else if ( bid > = g_last_close + 2.0 * g_brick_size )
{
if ( ! BrickStep ( true , g_last_close , brick_open ) | |
! BrickStep ( true , brick_open , next_close ) )
{
step_failed = true ;
break ;
}
if ( ! DrawBrick ( tick_time , brick_open , next_close , 1 ) )
return ;
g_last_close = next_close ;
g_direction = 1 ;
EvaluateConfirmedBreakout ( g_last_close , enable_alert ) ;
if ( enable_alert & & g_alert_enabled & & g_brick_reversal_enabled )
NotifyUser ( StringFormat ( " RenkoDeepSeek: РАЗВОРОТ КИРПИЧЕЙ ВВЕРХ; размер %d пунктов; %s " ,
g_current_brick_points , _Symbol ) , InpPushNotifications ) ;
changed = true ;
}
}
}
if ( step_failed & & ! g_step_warned )
{
g_step_warned = true ;
Print ( " RenkoDeepSeek: шаг кирпича не меняет цену на сетке инструмента (tick size= " ,
DoubleToString ( SymbolInfoDouble ( _Symbol , SYMBOL_TRADE_TICK_SIZE ) , _Digits ) ,
" ); построение кирпичей приостановлено. Увеличьте InpBrickPoints. " ) ;
}
if ( changed )
Print ( " RenkoDeepSeek: достигнут предел 10000 кирпичей за один тик; остаток движения этим тиком не обработан и будет учтён при следующем поступлении цены. " ) ;
g_last_bid = bid ;
EvaluatePreBreakoutSignals ( bid , enable_alert ) ;
}
//--- Сохранение согласованного состояния серии перед пересбором.
bool SnapshotSequence ( )
{
// Существующий снимок остаётся действительным, пока новый не скопирован
// полностью. Отказ ArrayResize не должен уничтожать резерв для отката.
const int count = g_brick_count ;
if ( count > 0 & & ArrayResize ( g_snap_bricks , count , 256 ) ! = count )
{
Print ( " RenkoDeepSeek: не удалось создать новый снимок серии; предыдущий резервный снимок сохранён. " ) ;
return false ;
}
for ( int i = 0 ; i < count ; + + i )
g_snap_bricks [ i ] = g_bricks [ BrickPhysicalIndex ( i ) ] ;
g_snap_count = count ;
g_snap_cycle_start = g_cycle_start ;
g_snap_direction = g_direction ;
g_snap_last_tick_msc = g_last_tick_msc ;
g_snap_ticks_at_msc = g_ticks_processed_at_last_msc ;
g_snap_have_anchor = g_have_anchor ;
g_snap_history_initialized = g_history_initialized ;
g_snap_partial = g_history_partial ;
g_snap_rebuild_resume_from = g_rebuild_resume_from ;
g_snap_rebuild_resume_window_days = g_rebuild_resume_window_days ;
g_snap_history_needs_more = g_history_needs_more ;
g_snap_rebuild_partial_accum = g_rebuild_partial_accum ;
g_snap_last_close = g_last_close ;
g_snap_last_bid = g_last_bid ;
g_snap_swing_high = g_last_swing_high ;
g_snap_swing_low = g_last_swing_low ;
g_snap_high_alerted = g_swing_high_breakout_alerted ;
g_snap_low_alerted = g_swing_low_breakout_alerted ;
g_snap_high_sent = g_swing_high_alert_sent ;
g_snap_low_sent = g_swing_low_alert_sent ;
g_snap_pre_buy = g_pre_buy_alerted ;
g_snap_pre_sell = g_pre_sell_alerted ;
g_snap_buy_cancelled = g_pre_buy_cancelled ;
g_snap_sell_cancelled = g_pre_sell_cancelled ;
g_snap_valid = true ;
return true ;
}
//--- Возврат к сохранённому состоянию после неудачного пересбора.
void RestoreSequence ( )
{
if ( ! g_snap_valid )
return ;
const int count = g_snap_count ;
if ( ArraySize ( g_bricks ) ! = InpMaxHistoryBricks & &
ArrayResize ( g_bricks , InpMaxHistoryBricks , 256 ) ! = InpMaxHistoryBricks )
{
Print ( " RenkoDeepSeek: не удалось подготовить кольцевой буфер для восстановления снимка; резервный снимок сохранён для повторной попытки. " ) ;
return ;
}
g_brick_start = 0 ;
for ( int i = 0 ; i < count ; + + i )
g_bricks [ i ] = g_snap_bricks [ i ] ;
g_brick_count = count ;
g_cycle_start = g_snap_cycle_start ;
g_direction = g_snap_direction ;
g_last_tick_msc = g_snap_last_tick_msc ;
g_ticks_processed_at_last_msc = g_snap_ticks_at_msc ;
g_have_anchor = g_snap_have_anchor ;
g_history_initialized = g_snap_history_initialized ;
g_history_partial = g_snap_partial ;
g_rebuild_resume_from = g_snap_rebuild_resume_from ;
g_rebuild_resume_window_days = g_snap_rebuild_resume_window_days ;
g_history_needs_more = g_snap_history_needs_more ;
g_rebuild_partial_accum = g_snap_rebuild_partial_accum ;
g_last_close = g_snap_last_close ;
g_last_bid = g_snap_last_bid ;
g_last_swing_high = g_snap_swing_high ;
g_last_swing_low = g_snap_swing_low ;
g_swing_high_breakout_alerted = g_snap_high_alerted ;
g_swing_low_breakout_alerted = g_snap_low_alerted ;
g_swing_high_alert_sent = g_snap_high_sent ;
g_swing_low_alert_sent = g_snap_low_sent ;
g_pre_buy_alerted = g_snap_pre_buy ;
g_pre_sell_alerted = g_snap_pre_sell ;
g_pre_buy_cancelled = g_snap_buy_cancelled ;
g_pre_sell_cancelled = g_snap_sell_cancelled ;
g_series_dirty = true ;
g_snap_valid = false ;
Print ( " RenkoDeepSeek: пересбор истории не удался; восстановлена предыдущая серия кирпичей ( " ,
g_brick_count , " ). " ) ;
}
//--- Восстановление истории тиков короткими чанками.
// Причина: один запрос на сутки упирается в тайм-аут 4403 и возвращает 0 тиков.
// Пересбор идёт срезами по бюджету времени и продолжается с того же чанка.
bool RebuildHistoryWindow ( const int requested_window_days )
{
if ( ! IsRenkoGridCompatible ( g_current_brick_points , true ) )
return false ;
RefreshServerOffset ( ) ;
const datetime now = TimeCurrent ( ) ;
const datetime current_cycle = CycleStart ( now ) ;
if ( current_cycle = = 0 )
return false ;
const datetime cycle_start_server = MoscowToServerTime ( current_cycle ) ;
datetime earliest_history = 0 ;
if ( ! GetEarliestAvailableHistoryDate ( earliest_history ) )
return false ;
int window_days = requested_window_days ;
if ( window_days < 1 )
window_days = 1 ;
const int maximum_window_days = MaximumHistoryWindowDaysAvailable ( cycle_start_server ) ;
if ( maximum_window_days < = 0 )
return false ;
if ( window_days > maximum_window_days )
window_days = maximum_window_days ;
datetime range_from = cycle_start_server - ( datetime ) ( ( window_days - 1 ) * 86400 ) ;
if ( range_from < earliest_history )
range_from = earliest_history ;
const datetime range_to = now + 1 ;
if ( range_from > = range_to )
{
PrintFormat ( " RenkoDeepSeek: начало доступной истории %s находится за текущей границей запроса; история пока недоступна. " ,
TimeToString ( earliest_history , TIME_DATE | TIME_MINUTES ) ) ;
return false ;
}
2026-11-05 19:12:31 +03:00
const bool resuming = ( g_rebuild_resume_from ! = 0 & & g_rebuild_resume_window_days = = window_days ) ;
2026-11-05 13:25:46 +03:00
// Один дешёвый запрос по свежему участку отделяет «база тиков ещё не готова»
// от «старая часть недоступна». Проверка идёт до сброса состояния, поэтому
// неудачная попытка не трогает уже показанную серию кирпичей.
2026-11-05 19:12:31 +03:00
if ( ! resuming )
2026-11-05 13:25:46 +03:00
{
MqlTick probe [ ] ;
const datetime probe_from = ( now > HISTORY_PROBE_SECONDS ? now - HISTORY_PROBE_SECONDS
: now - 60 ) ;
ResetLastError ( ) ;
2026-11-05 19:12:31 +03:00
int probe_copied = CopyTicksRange ( _Symbol , probe , COPY_TICKS_INFO ,
( ulong ) probe_from * 1000 ,
( ulong ) ( now + 1 ) * 1000 ) ;
int probe_error = GetLastError ( ) ;
if ( probe_copied = = 0 )
{
MqlTick probe_fallback [ ] ;
ResetLastError ( ) ;
const int fallback_count = CopyTicks ( _Symbol , probe_fallback , COPY_TICKS_INFO ,
( ulong ) probe_from * 1000 , 1 ) ;
const int fallback_error = GetLastError ( ) ;
if ( fallback_count > 0 )
{
probe_copied = fallback_count ;
probe_error = fallback_error ;
}
else if ( fallback_error ! = 0 )
probe_error = fallback_error ;
}
2026-11-05 13:25:46 +03:00
if ( probe_error = = 4403 & & probe_copied < = 0 )
{
PrintFormat ( " RenkoDeepSeek: база тиков ещё не готова (код=%d; доступных тиков=%d на последних %d минутах); восстановление будет повторено позже. " ,
probe_error , probe_copied , HISTORY_PROBE_SECONDS / 60 ) ;
return false ;
}
if ( probe_error = = 4403 & & probe_copied > 0 )
PrintFormat ( " RenkoDeepSeek: предварительная проверка получила %d частичных тиков (4403); пересбор продолжится по чанкам. " ,
probe_copied ) ;
else if ( probe_copied < 0 & & probe_error ! = 4401 )
{
PrintFormat ( " RenkoDeepSeek: база тиков ещё не готова (код=%d на последних %d минутах); восстановление будет повторено позже. " ,
probe_error , HISTORY_PROBE_SECONDS / 60 ) ;
return false ;
}
// 0 тиков без ошибки — нормально: ночь или выходные. Продолжаем.
}
// Проверка у самой старой границы носит информационный характер:
// обрывать из-за неё пересбор нельзя, чанки сами сообщат об ошибках.
2026-11-05 19:12:31 +03:00
if ( ! resuming )
2026-11-05 13:25:46 +03:00
{
datetime old_probe_to = range_from + ( datetime ) TICK_BASE_PROBE_SECONDS ;
if ( old_probe_to > range_to )
old_probe_to = range_to ;
if ( old_probe_to > range_from )
{
MqlTick old_probe [ ] ;
ResetLastError ( ) ;
2026-11-05 19:12:31 +03:00
int old_copied = CopyTicksRange ( _Symbol , old_probe , COPY_TICKS_INFO ,
( ulong ) range_from * 1000 ,
( ulong ) old_probe_to * 1000 - 1 ) ;
int old_error = GetLastError ( ) ;
if ( old_copied = = 0 )
{
MqlTick old_fallback [ ] ;
ResetLastError ( ) ;
const int fallback_count = CopyTicks ( _Symbol , old_fallback , COPY_TICKS_INFO ,
( ulong ) range_from * 1000 , 1 ) ;
const int fallback_error = GetLastError ( ) ;
if ( fallback_count > 0 )
{
old_copied = fallback_count ;
old_error = fallback_error ;
}
else if ( fallback_error ! = 0 )
old_error = fallback_error ;
}
2026-11-05 13:25:46 +03:00
if ( old_copied < 0 & & old_error ! = 4401 )
PrintFormat ( " RenkoDeepSeek: самый старый участок истории тиков пока недоступен (код=%d); пересбор продолжается по чанкам. " ,
old_error ) ;
}
}
// Продолжение прерванного по бюджету пересбора: состояние НЕ очищаем,
// иначе каждый срез начинал бы окно заново и история никогда не росла бы.
if ( ! resuming )
{
g_brick_count = 0 ;
g_brick_start = 0 ;
g_series_dirty = true ;
ResetCycle ( 0 ) ;
g_last_tick_msc = 0 ;
g_ticks_processed_at_last_msc = 0 ;
g_last_bid = 0.0 ;
g_rebuild_partial_accum = false ;
g_diag_valid_bid_ticks = 0 ;
g_diag_min_bid = 0.0 ;
g_diag_max_bid = 0.0 ;
g_diag_first_bid = 0.0 ;
g_diag_last_bid = 0.0 ;
g_history_needs_more = false ;
g_rebuild_resume_from = range_from ;
g_rebuild_resume_window_days = window_days ;
g_cache_dirty = true ;
}
const ulong build_started_ms = GetTickCount64 ( ) ;
int total_chunks = 0 ;
int failed_chunks = 0 ;
int total_ticks_copied = 0 ;
bool interrupted = false ;
bool incomplete = false ;
datetime chunk_from = ( resuming ? g_rebuild_resume_from : range_from ) ;
while ( chunk_from < range_to )
{
if ( IsStopped ( ) )
{
interrupted = true ;
break ;
}
// Бюджет времени защищает интерфейс: один срез не должен заметно
// замораживать график. Остаток продолжается следующим вызовом.
if ( GetTickCount64 ( ) - build_started_ms > HISTORY_BUILD_BUDGET_MS )
{
incomplete = true ;
break ;
}
datetime chunk_to = chunk_from + HISTORY_CHUNK_SECONDS ;
if ( chunk_to > range_to )
chunk_to = range_to ;
if ( chunk_to < = chunk_from )
break ;
MqlTick ticks [ ] ;
int copied = -1 ;
int copy_error = 0 ;
for ( int attempt = 0 ; attempt < = HISTORY_CHUNK_RETRIES ; + + attempt )
{
ResetLastError ( ) ;
copied = CopyTicksRange ( _Symbol , ticks , COPY_TICKS_INFO ,
( ulong ) chunk_from * 1000 ,
( ulong ) chunk_to * 1000 - 1 ) ;
copy_error = GetLastError ( ) ;
// 4403 может сопровождаться частичным результатом: сохраняем его,
// а повтор диапазона выполняем следующим срезом, не затирая массив.
if ( copy_error = = 4403 | | copied > = 0 )
break ;
}
+ + total_chunks ;
2026-11-05 19:12:31 +03:00
// Нулевой CopyTicksRange не трактуем сразу как пустой интервал:
// проверяем тот же диапазон альтернативным API CopyTicks.
if ( copied = = 0 )
{
MqlTick fallback_ticks [ ] ;
ResetLastError ( ) ;
const long fallback_from_msc = MathMax ( ( long ) chunk_from * 1000 , g_last_tick_msc ) ;
const int fallback_copied = CopyTicks ( _Symbol , fallback_ticks , COPY_TICKS_INFO ,
( ulong ) fallback_from_msc , 1000 ) ;
const int fallback_error = GetLastError ( ) ;
int in_range = 0 ;
for ( int k = 0 ; k < fallback_copied ; + + k )
{
if ( fallback_ticks [ k ] . time_msc > = ( long ) chunk_to * 1000 )
break ;
+ + in_range ;
}
if ( in_range > 0 )
{
if ( ArrayResize ( ticks , in_range ) ! = in_range )
{
copied = -1 ;
copy_error = 4004 ;
}
else
{
for ( int k = 0 ; k < in_range ; + + k )
ticks [ k ] = fallback_ticks [ k ] ;
copied = in_range ;
// Полный fallback-пакет может быть усечён лимитом count.
if ( fallback_copied > = 1000 & & in_range = = fallback_copied )
copy_error = 4403 ;
else
copy_error = fallback_error ;
}
}
else if ( fallback_copied > 0 )
{
// Первый доступный тик уже за правой границей: альтернативный
// запрос подтвердил отсутствие тиков внутри этого чанка.
copied = 0 ;
copy_error = 0 ;
}
else
{
copied = 0 ;
if ( fallback_error = = 0 & &
( long ) chunk_to * 1000 + EMPTY_RANGE_CONFIRM_MS < ( long ) now * 1000 )
{
// Оба API вернули пустой ответ без ошибки для давно прошедшего
// интервала: применяем ограниченное по времени подтверждение.
copy_error = 0 ;
}
else
{
// Для недавнего диапазона ноль от обоих API не доказывает пустоту.
copy_error = ( fallback_error = = 0 ? 4403 : fallback_error ) ;
}
}
}
2026-11-05 13:25:46 +03:00
if ( copy_error = = 4403 & & copied < = 0 )
{
+ + failed_chunks ;
incomplete = true ;
if ( failed_chunks < = 3 )
PrintFormat ( " RenkoDeepSeek: чанк пока не загружен (4403; тиков=%d); диапазон %s — %s сохранён для повтора. " ,
copied ,
TimeToString ( chunk_from , TIME_DATE | TIME_MINUTES ) ,
TimeToString ( chunk_to , TIME_DATE | TIME_MINUTES ) ) ;
// Не перескакиваем диапазон и не сбрасываем Renko-состояние:
// следующий срез повторит тот же чанк после синхронизации.
break ;
}
else if ( copied < 0 )
{
// Ошибка API не подтверждает отсутствие тиков. Не перескакиваем чанк,
// не сбрасываем последовательность и повторяем тот же диапазон позже.
+ + failed_chunks ;
incomplete = true ;
PrintFormat ( " RenkoDeepSeek: CopyTicksRange код=%d; %s — %s; чанк оставлен checkpoint для повторной загрузки. " ,
copy_error ,
TimeToString ( chunk_from , TIME_DATE | TIME_MINUTES ) ,
TimeToString ( chunk_to , TIME_DATE | TIME_MINUTES ) ) ;
break ;
}
else if ( copied = = 0 & & copy_error ! = 0 )
{
// Нулевой результат вместе с ошибкой не подтверждает пустой диапазон.
// Оставляем тот же чанк в checkpoint и повторяем после синхронизации.
+ + failed_chunks ;
incomplete = true ;
PrintFormat ( " RenkoDeepSeek: CopyTicksRange вернул 0 тиков с ошибкой %d для %s — %s; диапазон оставлен checkpoint для повтора. " ,
copy_error ,
TimeToString ( chunk_from , TIME_DATE | TIME_MINUTES ) ,
TimeToString ( chunk_to , TIME_DATE | TIME_MINUTES ) ) ;
break ;
}
else if ( copied = = 0 & & copy_error = = 0 )
{
// Успешный пустой ответ — подтверждённый пустой интервал (например,
// ночь/выходной). Закрываем чанк, иначе восстановление застрянет
// навсегда на первом интервале без тиков.
PrintFormat ( " RenkoDeepSeek: подтверждённо пустой интервал %s — %s; чанк закрыт без продвижения Renko-цены. " ,
TimeToString ( chunk_from , TIME_DATE | TIME_MINUTES ) ,
TimeToString ( chunk_to , TIME_DATE | TIME_MINUTES ) ) ;
}
else
{
total_ticks_copied + = copied ;
// Повторный запрос частично загруженного чанка возвращает его с начала.
// Пропускаем уже обработанный префикс по паре (time_msc, ordinal),
// иначе после 4403 одни и те же тики повторно попадут в Renko-последовательность.
const long resume_tick_msc = g_last_tick_msc ;
const int resume_ticks_at_msc = g_ticks_processed_at_last_msc ;
int seen_resume_msc = 0 ;
for ( int i = 0 ; i < copied ; + + i )
{
if ( ( i & 0xFFF ) = = 0 & & IsStopped ( ) )
{
interrupted = true ;
break ;
}
// Не обрабатываем повторно префикс чанка, уже прошедший до checkpoint.
if ( ticks [ i ] . time_msc < resume_tick_msc )
continue ;
if ( ticks [ i ] . time_msc = = resume_tick_msc )
{
+ + seen_resume_msc ;
if ( seen_resume_msc < = resume_ticks_at_msc )
continue ;
}
// Курсор считает все записи тиков, включая записи без корректного Bid.
if ( ticks [ i ] . time_msc > g_last_tick_msc )
{
g_last_tick_msc = ticks [ i ] . time_msc ;
g_ticks_processed_at_last_msc = 1 ;
}
else if ( ticks [ i ] . time_msc = = g_last_tick_msc )
{
+ + g_ticks_processed_at_last_msc ;
}
g_cache_dirty = true ;
if ( ticks [ i ] . bid < = 0.0 | | ! MathIsValidNumber ( ticks [ i ] . bid ) )
continue ;
+ + g_diag_valid_bid_ticks ;
if ( g_diag_first_bid < = 0.0 )
g_diag_first_bid = ticks [ i ] . bid ;
g_diag_last_bid = ticks [ i ] . bid ;
if ( g_diag_min_bid < = 0.0 | | ticks [ i ] . bid < g_diag_min_bid )
g_diag_min_bid = ticks [ i ] . bid ;
if ( g_diag_max_bid < = 0.0 | | ticks [ i ] . bid > g_diag_max_bid )
g_diag_max_bid = ticks [ i ] . bid ;
ProcessBid ( ( datetime ) ticks [ i ] . time , ticks [ i ] . bid , false ) ;
g_last_bid = ticks [ i ] . bid ;
}
if ( interrupted )
break ;
if ( copy_error = = 4403 )
{
+ + failed_chunks ;
incomplete = true ;
if ( failed_chunks < = 3 )
PrintFormat ( " RenkoDeepSeek: обработано %d частичных тиков чанка (4403); диапазон %s — %s останется checkpoint для повтора. " ,
copied ,
TimeToString ( chunk_from , TIME_DATE | TIME_MINUTES ) ,
TimeToString ( chunk_to , TIME_DATE | TIME_MINUTES ) ) ;
// Курсор тиков уже запоминает обработанные записи, включая ordinal
// внутри одинакового time_msc. Повторяем этот же чанк, не теряя хвост.
break ;
}
}
// Полный ответ без ошибки закрывает ранее частично полученный чанк.
// Не переносим временную неполноту в итоговый статус всей истории.
g_rebuild_partial_accum = false ;
chunk_from = chunk_to ;
g_rebuild_resume_from = chunk_from ;
}
if ( failed_chunks > 0 | | incomplete )
g_rebuild_partial_accum = true ;
g_cache_dirty = true ;
if ( interrupted )
{
g_brick_count = 0 ;
g_brick_start = 0 ;
g_series_dirty = true ;
ResetCycle ( 0 ) ;
g_last_tick_msc = 0 ;
g_ticks_processed_at_last_msc = 0 ;
g_last_bid = 0.0 ;
g_rebuild_resume_from = 0 ;
g_rebuild_resume_window_days = 0 ;
g_rebuild_partial_accum = false ;
g_history_needs_more = false ;
Print ( " RenkoDeepSeek: восстановление истории прервано; частичные кирпичи сброшены. " ) ;
return false ;
}
if ( total_ticks_copied = = 0 & & g_brick_count = = 0 & & ! resuming )
{
g_rebuild_resume_from = 0 ;
g_rebuild_resume_window_days = 0 ;
g_rebuild_partial_accum = false ;
Print ( " RenkoDeepSeek: за выбранный период история тиков не получена; восстановление будет повторено позже. " ) ;
return false ;
}
// Работа не закончена — сохраняем точку продолжения. Следующий вызов
// продолжит с этого чанка, а не начнёт окно заново.
if ( chunk_from < range_to )
{
g_rebuild_resume_from = chunk_from ;
g_rebuild_resume_window_days = window_days ;
g_history_needs_more = true ;
g_cache_dirty = true ;
g_history_partial = true ;
g_series_dirty = true ;
PrintFormat ( " RenkoDeepSeek: пересбор истории продолжается (чанк %s): внутреннее окно=%d дней; чанков=%d; не загружено=%d; кирпичей=%d/%d. " ,
TimeToString ( chunk_from , TIME_DATE | TIME_MINUTES ) , window_days , total_chunks ,
failed_chunks , g_brick_count , InpMaxHistoryBricks ) ;
return true ;
}
// Полный проход по окну завершён.
g_rebuild_resume_from = 0 ;
g_rebuild_resume_window_days = 0 ;
g_history_needs_more = false ;
g_history_partial = g_rebuild_partial_accum ;
if ( ! g_history_partial )
{
2026-11-05 19:12:31 +03:00
}
2026-11-05 13:25:46 +03:00
g_series_dirty = true ;
PrintFormat ( " RenkoDeepSeek: история готова: внутреннее окно=%d дней; чанков=%d; не загружено=%d; тиков=%d; Bid-тиков=%I64d; кирпичей=%d/%d; размер=%d пунктов (brick=%.10f); _Point=%.10f; tick_size=%.10f; Bid min/max=%s/%s; диапазон=%.10f; первый/последний Bid=%s/%s; якорь=%s; граница цикла (сервер)=%s%s " ,
window_days , total_chunks , failed_chunks , total_ticks_copied , g_diag_valid_bid_ticks ,
g_brick_count , InpMaxHistoryBricks , g_current_brick_points , g_brick_size , _Point ,
SymbolInfoDouble ( _Symbol , SYMBOL_TRADE_TICK_SIZE ) ,
( g_diag_min_bid > 0.0 ? DoubleToString ( g_diag_min_bid , _Digits ) : " нет " ) ,
( g_diag_max_bid > 0.0 ? DoubleToString ( g_diag_max_bid , _Digits ) : " нет " ) ,
( g_diag_max_bid > 0.0 & & g_diag_min_bid > 0.0 ? g_diag_max_bid - g_diag_min_bid : 0.0 ) ,
( g_diag_first_bid > 0.0 ? DoubleToString ( g_diag_first_bid , _Digits ) : " нет " ) ,
( g_diag_last_bid > 0.0 ? DoubleToString ( g_diag_last_bid , _Digits ) : " нет " ) ,
( g_have_anchor ? DoubleToString ( g_last_close , _Digits ) : " не установлен " ) ,
TimeToString ( cycle_start_server , TIME_DATE | TIME_MINUTES ) ,
( g_history_partial ? " ; ЧАСТИЧНО — будет дозагрузка " : " " ) ) ;
if ( g_brick_count = = 0 )
{
const double bid_range = ( g_diag_max_bid > 0.0 & & g_diag_min_bid > 0.0
? g_diag_max_bid - g_diag_min_bid : 0.0 ) ;
if ( g_diag_valid_bid_ticks = = 0 )
Print ( " RenkoDeepSeek: ноль кирпичей: в истории нет корректных Bid. " ) ;
else if ( bid_range + _Point * 0.1 < g_brick_size )
PrintFormat ( " RenkoDeepSeek: диапазон Bid %.10f меньше размера кирпича %.10f; движения недостаточно для текущего размера. " ,
bid_range , g_brick_size ) ;
else
Print ( " RenkoDeepSeek: диапазон Bid достаточен, но кирпичи не созданы; проверьте последовательность тиков/состояние цикла. " ) ;
}
return true ;
}
//--- Расширение истории с сохранением уже показанной серии при неудаче.
bool RebuildHistory ( )
{
if ( g_rebuild_running )
return false ;
g_rebuild_running = true ;
g_last_history_attempt_ms = GetTickCount64 ( ) ;
if ( ! SnapshotSequence ( ) )
{
g_rebuild_running = false ;
2026-11-05 19:12:31 +03:00
Print ( " RenkoDeepSeek: шаг восстановления отменён — резервный снимок не создан; текущая серия и checkpoint сохранены. " ) ;
2026-11-05 13:25:46 +03:00
return false ;
}
const bool previous_replaying = g_replaying ;
g_replaying = true ;
RefreshServerOffset ( ) ;
const datetime current_cycle = CycleStart ( TimeCurrent ( ) ) ;
if ( current_cycle = = 0 )
{
g_replaying = previous_replaying ;
g_rebuild_running = false ;
RestoreSequence ( ) ;
return false ;
}
2026-11-05 19:12:31 +03:00
2026-11-05 13:25:46 +03:00
const datetime cycle_start_server = MoscowToServerTime ( current_cycle ) ;
const int maximum_window_days = MaximumHistoryWindowDaysAvailable ( cycle_start_server ) ;
if ( maximum_window_days < = 0 )
{
g_replaying = previous_replaying ;
g_rebuild_running = false ;
RestoreSequence ( ) ;
return false ;
}
2026-11-05 19:12:31 +03:00
// За один вызов обрабатывается только одно окно. Расширение истории
// выполняется следующим событием таймера, а не циклом while(true).
int window_days = ( g_rebuild_resume_window_days > 0
2026-11-05 13:25:46 +03:00
? g_rebuild_resume_window_days
: INITIAL_HISTORY_WINDOW_DAYS ) ;
if ( window_days > maximum_window_days )
window_days = maximum_window_days ;
2026-11-05 19:12:31 +03:00
const bool result = RebuildHistoryWindow ( window_days ) ;
if ( ! result )
2026-11-05 13:25:46 +03:00
{
2026-11-05 19:12:31 +03:00
g_replaying = previous_replaying ;
g_rebuild_running = false ;
RestoreSequence ( ) ;
return false ;
}
2026-11-05 13:25:46 +03:00
2026-11-05 19:12:31 +03:00
g_series_dirty = true ;
2026-11-05 13:25:46 +03:00
2026-11-05 19:12:31 +03:00
// Следующий вызов сначала снимет checkpoint текущей серии. Второй
// полный снимок здесь не нужен: он удваивал стоимость каждого шага.
2026-11-05 13:25:46 +03:00
2026-11-05 19:12:31 +03:00
if ( ! g_history_needs_more & & ! g_history_partial & &
g_brick_count < InpMaxHistoryBricks & & window_days < maximum_window_days )
{
int next_window_days = ( window_days > maximum_window_days / 2
? maximum_window_days
: window_days * 2 ) ;
if ( next_window_days < = window_days )
next_window_days = maximum_window_days ;
2026-11-05 13:25:46 +03:00
2026-11-05 19:12:31 +03:00
g_rebuild_resume_from = 0 ;
g_rebuild_resume_window_days = next_window_days ;
g_history_needs_more = true ;
g_cache_dirty = true ;
2026-11-05 13:25:46 +03:00
2026-11-05 19:12:31 +03:00
PrintFormat ( " RenkoDeepSeek: шаг окна завершён (%d/%d кирпичей); расширение до %d дней запланировано на следующий шаг. " ,
g_brick_count , InpMaxHistoryBricks , next_window_days ) ;
2026-11-05 13:25:46 +03:00
}
g_replaying = previous_replaying ;
g_rebuild_running = false ;
if ( g_brick_count < = 0 & & ! g_history_needs_more )
2026-11-05 19:12:31 +03:00
Print ( " RenkoDeepSeek: проход истории завершён без кирпичей; проверены доступные тики и Anchor. " ) ;
if ( g_brick_count < InpMaxHistoryBricks & & ! g_history_needs_more & &
! g_history_partial & & window_days > = maximum_window_days )
2026-11-05 13:25:46 +03:00
{
datetime earliest_history = 0 ;
GetEarliestAvailableHistoryDate ( earliest_history ) ;
2026-11-05 19:12:31 +03:00
PrintFormat ( " RenkoDeepSeek: достигнута самая ранняя доступная дата %s; построено %d из %d кирпичей. " ,
2026-11-05 13:25:46 +03:00
TimeToString ( earliest_history , TIME_DATE | TIME_MINUTES ) ,
2026-11-05 19:12:31 +03:00
g_brick_count , InpMaxHistoryBricks ) ;
2026-11-05 13:25:46 +03:00
}
return true ;
}
//--- Единое оформление переключателей.
void SetToggleButtonStyle ( const string button_name , const bool enabled )
{
if ( button_name = = " " | | ObjectFind ( 0 , button_name ) < 0 )
return ;
const color button_color = enabled ? InpButtonActiveColor : InpButtonInactiveColor ;
ResetLastError ( ) ;
bool ok = true ;
if ( ! ObjectSetInteger ( 0 , button_name , OBJPROP_BGCOLOR , button_color ) )
ok = false ;
if ( ! ObjectSetInteger ( 0 , button_name , OBJPROP_COLOR , clrWhite ) )
ok = false ;
if ( ! ObjectSetInteger ( 0 , button_name , OBJPROP_BORDER_COLOR , button_color ) )
ok = false ;
if ( ! ObjectSetInteger ( 0 , button_name , OBJPROP_STATE , false ) )
ok = false ;
if ( ! ok )
Print ( " RenkoDeepSeek: не удалось обновить оформление кнопки " , button_name ,
" ; код ошибки= " , GetLastError ( ) ) ;
ChartRedraw ( 0 ) ;
}
//--- Состояние разворота отображается текстом, а не только цветом.
void UpdateBrickReversalButton ( )
{
if ( g_brick_reversal_button_name = = " " | |
ObjectFind ( 0 , g_brick_reversal_button_name ) < 0 )
return ;
const string state_text = g_brick_reversal_enabled ? " ВКЛ " : " ВЫКЛ " ;
SetButtonTextFit ( g_brick_reversal_button_name ,
" Разворот кирпичей: " + state_text , 8 ) ;
SetToggleButtonStyle ( g_brick_reversal_button_name , g_brick_reversal_enabled ) ;
}
// Обновляет подписи и состояние независимых переключателей предварительных сигналов.
void UpdatePreBreakoutToggleButtons ( )
{
if ( g_pre_buy_button_name ! = " " & & ObjectFind ( 0 , g_pre_buy_button_name ) > = 0 )
{
SetButtonTextFit ( g_pre_buy_button_name , g_pre_buy_enabled ? " Ранний BUY: ВКЛ " : " Ранний BUY: ВЫКЛ " , 8 ) ;
SetToggleButtonStyle ( g_pre_buy_button_name , g_pre_buy_enabled ) ;
}
if ( g_pre_sell_button_name ! = " " & & ObjectFind ( 0 , g_pre_sell_button_name ) > = 0 )
{
SetButtonTextFit ( g_pre_sell_button_name , g_pre_sell_enabled ? " Ранний SELL: ВКЛ " : " Ранний SELL: ВЫКЛ " , 8 ) ;
SetToggleButtonStyle ( g_pre_sell_button_name , g_pre_sell_enabled ) ;
}
}
//--- Количество включённых категорий определяет состояние главной кнопки.
int EnabledAlertCategoryCount ( )
{
int count = 0 ;
if ( g_breakout_high_enabled ) + + count ;
if ( g_breakout_low_enabled ) + + count ;
if ( g_brick_reversal_enabled ) + + count ;
if ( g_pre_buy_enabled ) + + count ;
if ( g_pre_sell_enabled ) + + count ;
return count ;
}
// Мастер-флаг включён, если включена хотя бы одна категория.
void SyncAlertMasterState ( )
{
g_alert_enabled = ( EnabledAlertCategoryCount ( ) > 0 ) ;
}
//--- Три состояния главной кнопки: ВЫКЛ, ЧАСТИЧНО, ВКЛ.
void UpdateAlertMasterButton ( )
{
if ( g_alert_button_name = = " " | | ObjectFind ( 0 , g_alert_button_name ) < 0 )
return ;
const int enabled_count = EnabledAlertCategoryCount ( ) ;
const string label = ( enabled_count = = 0 ? " ОПОВЕЩЕНИЯ: ВЫКЛ " :
enabled_count = = 5 ? " ОПОВЕЩЕНИЯ: ВКЛ " :
" ОПОВЕЩЕНИЯ: ЧАСТИЧНО " ) ;
SetButtonTextFit ( g_alert_button_name , label , 9 ) ;
ObjectSetInteger ( 0 , g_alert_button_name , OBJPROP_XSIZE , 180 ) ;
if ( enabled_count = = 0 | | enabled_count = = 5 )
SetToggleButtonStyle ( g_alert_button_name , enabled_count = = 5 ) ;
else
{
ResetLastError ( ) ;
bool ok = true ;
if ( ! ObjectSetInteger ( 0 , g_alert_button_name , OBJPROP_BGCOLOR , clrDarkOrange ) ) ok = false ;
if ( ! ObjectSetInteger ( 0 , g_alert_button_name , OBJPROP_COLOR , clrWhite ) ) ok = false ;
if ( ! ObjectSetInteger ( 0 , g_alert_button_name , OBJPROP_BORDER_COLOR , clrDarkOrange ) ) ok = false ;
if ( ! ObjectSetInteger ( 0 , g_alert_button_name , OBJPROP_STATE , false ) ) ok = false ;
if ( ! ok )
Print ( " RenkoDeepSeek: не удалось обновить оформление частичного состояния главной кнопки; код ошибки= " , GetLastError ( ) ) ;
ChartRedraw ( 0 ) ;
}
}
//--- Применение входных настроек к состояниям категорий.
// InpBreakoutAlerts — стартовый мастер-флаг: если он включён, включаются все
// категории; если выключен, действуют индивидуальные входы.
void ApplyAlertInputs ( )
{
g_breakout_high_enabled = InpBreakoutHighAlerts | | InpBreakoutAlerts ;
g_breakout_low_enabled = InpBreakoutLowAlerts | | InpBreakoutAlerts ;
g_brick_reversal_enabled = InpBrickReversalAlerts | | InpBreakoutAlerts ;
g_pre_buy_enabled = InpPreBreakoutBuyAlerts | | InpBreakoutAlerts ;
g_pre_sell_enabled = InpPreBreakoutSellAlerts | | InpBreakoutAlerts ;
SyncAlertMasterState ( ) ;
}
//--- Синхронизация всех подписей и цветов с сохранёнными состояниями.
void RefreshAllAlertButtons ( )
{
UpdateAlertMasterButton ( ) ;
if ( g_breakout_high_button_name ! = " " & & ObjectFind ( 0 , g_breakout_high_button_name ) > = 0 )
{
SetButtonTextFit ( g_breakout_high_button_name , " Пробой вершин " , 8 ) ;
SetToggleButtonStyle ( g_breakout_high_button_name , g_breakout_high_enabled ) ;
}
if ( g_breakout_low_button_name ! = " " & & ObjectFind ( 0 , g_breakout_low_button_name ) > = 0 )
{
SetButtonTextFit ( g_breakout_low_button_name , " Пробой впадин " , 8 ) ;
SetToggleButtonStyle ( g_breakout_low_button_name , g_breakout_low_enabled ) ;
}
UpdateBrickReversalButton ( ) ;
UpdatePreBreakoutToggleButtons ( ) ;
}
//--- Создание и проверка элементов управления текущего экземпляра.
bool CreateControlObjects ( )
{
int subwindow = ChartWindowFind ( ) ;
if ( subwindow < 0 & & g_indicator_short_name ! = " " )
subwindow = ChartWindowFind ( 0 , g_indicator_short_name ) ;
if ( subwindow < 0 )
{
ResetLastError ( ) ;
Print ( " RenkoDeepSeek: окно текущего экземпляра индикатора пока не найдено; элементы управления будут созданы при следующей попытке. Имя= " ,
g_indicator_short_name , " ; ошибка= " , GetLastError ( ) ) ;
return false ;
}
g_alert_button_name = g_object_prefix + " .AlertToggle " ;
g_breakout_high_button_name = g_object_prefix + " .BreakoutHighToggle " ;
g_breakout_low_button_name = g_object_prefix + " .BreakoutLowToggle " ;
g_brick_reversal_button_name = g_object_prefix + " .BrickReversalToggle " ;
g_pre_buy_button_name = g_object_prefix + " .PreBuyToggle " ;
g_pre_sell_button_name = g_object_prefix + " .PreSellToggle " ;
g_pre_status_label_name = g_object_prefix + " .PreStatus " ; g_pre_detail_label_name = g_object_prefix + " .PreDetails " ; g_pre_sl_label_name = g_object_prefix + " .PreSL " ;
g_brick_edit_name = g_object_prefix + " .BrickSizeEdit " ;
g_brick_apply_name = g_object_prefix + " .BrickSizeApply " ;
SyncAlertMasterState ( ) ;
if ( ObjectFind ( 0 , g_alert_button_name ) < 0 )
{
ResetLastError ( ) ;
if ( ! ObjectCreate ( 0 , g_alert_button_name , OBJ_BUTTON , subwindow , 0 , 0 ) )
{
Print ( " RenkoDeepSeek: не удалось создать кнопку оповещений; код ошибки= " , GetLastError ( ) ) ;
g_alert_button_name = " " ;
g_breakout_high_button_name = " " ;
g_breakout_low_button_name = " " ;
g_brick_edit_name = " " ;
g_brick_apply_name = " " ;
return false ;
}
}
ObjectSetInteger ( 0 , g_alert_button_name , OBJPROP_CORNER , CORNER_LEFT_UPPER ) ;
ObjectSetInteger ( 0 , g_alert_button_name , OBJPROP_XDISTANCE , 8 ) ;
ObjectSetInteger ( 0 , g_alert_button_name , OBJPROP_YDISTANCE , 35 + g_control_y_offset ) ;
ObjectSetInteger ( 0 , g_alert_button_name , OBJPROP_YSIZE , 24 ) ;
ObjectSetInteger ( 0 , g_alert_button_name , OBJPROP_FONTSIZE , 9 ) ;
ObjectSetInteger ( 0 , g_alert_button_name , OBJPROP_STATE , false ) ;
UpdateAlertMasterButton ( ) ;
if ( ObjectFind ( 0 , g_breakout_high_button_name ) < 0 & &
! ObjectCreate ( 0 , g_breakout_high_button_name , OBJ_BUTTON , subwindow , 0 , 0 ) )
{
Print ( " RenkoDeepSeek: не удалось создать кнопку пробоя вершин; код ошибки= " , GetLastError ( ) ) ;
g_breakout_high_button_name = " " ;
return false ;
}
ObjectSetInteger ( 0 , g_breakout_high_button_name , OBJPROP_CORNER , CORNER_LEFT_UPPER ) ;
ObjectSetInteger ( 0 , g_breakout_high_button_name , OBJPROP_XDISTANCE , 8 ) ;
ObjectSetInteger ( 0 , g_breakout_high_button_name , OBJPROP_YDISTANCE , 63 + g_control_y_offset ) ;
ObjectSetInteger ( 0 , g_breakout_high_button_name , OBJPROP_YSIZE , 24 ) ;
ObjectSetInteger ( 0 , g_breakout_high_button_name , OBJPROP_FONTSIZE , 8 ) ;
SetButtonTextFit ( g_breakout_high_button_name , " Пробой вершин " , 8 ) ;
ObjectSetInteger ( 0 , g_breakout_high_button_name , OBJPROP_STATE , false ) ;
SetToggleButtonStyle ( g_breakout_high_button_name , g_breakout_high_enabled ) ;
if ( ObjectFind ( 0 , g_breakout_low_button_name ) < 0 & &
! ObjectCreate ( 0 , g_breakout_low_button_name , OBJ_BUTTON , subwindow , 0 , 0 ) )
{
Print ( " RenkoDeepSeek: не удалось создать кнопку пробоя впадин; код ошибки= " , GetLastError ( ) ) ;
g_breakout_low_button_name = " " ;
return false ;
}
ObjectSetInteger ( 0 , g_breakout_low_button_name , OBJPROP_CORNER , CORNER_LEFT_UPPER ) ;
ObjectSetInteger ( 0 , g_breakout_low_button_name , OBJPROP_XDISTANCE , 8 ) ;
ObjectSetInteger ( 0 , g_breakout_low_button_name , OBJPROP_YDISTANCE , 91 + g_control_y_offset ) ;
ObjectSetInteger ( 0 , g_breakout_low_button_name , OBJPROP_YSIZE , 24 ) ;
ObjectSetInteger ( 0 , g_breakout_low_button_name , OBJPROP_FONTSIZE , 8 ) ;
SetButtonTextFit ( g_breakout_low_button_name , " Пробой впадин " , 8 ) ;
ObjectSetInteger ( 0 , g_breakout_low_button_name , OBJPROP_STATE , false ) ;
SetToggleButtonStyle ( g_breakout_low_button_name , g_breakout_low_enabled ) ;
if ( ObjectFind ( 0 , g_brick_reversal_button_name ) < 0 & &
! ObjectCreate ( 0 , g_brick_reversal_button_name , OBJ_BUTTON , subwindow , 0 , 0 ) )
{
Print ( " RenkoDeepSeek: не удалось создать кнопку разворота кирпичей; код ошибки= " , GetLastError ( ) ) ;
g_brick_reversal_button_name = " " ;
return false ;
}
ObjectSetInteger ( 0 , g_brick_reversal_button_name , OBJPROP_CORNER , CORNER_LEFT_UPPER ) ;
ObjectSetInteger ( 0 , g_brick_reversal_button_name , OBJPROP_XDISTANCE , 8 ) ;
ObjectSetInteger ( 0 , g_brick_reversal_button_name , OBJPROP_YDISTANCE , 119 + g_control_y_offset ) ;
ObjectSetInteger ( 0 , g_brick_reversal_button_name , OBJPROP_YSIZE , 24 ) ;
ObjectSetInteger ( 0 , g_brick_reversal_button_name , OBJPROP_FONTSIZE , 8 ) ;
ObjectSetInteger ( 0 , g_brick_reversal_button_name , OBJPROP_STATE , false ) ;
UpdateBrickReversalButton ( ) ;
if ( ObjectFind ( 0 , g_pre_buy_button_name ) < 0 & & ! ObjectCreate ( 0 , g_pre_buy_button_name , OBJ_BUTTON , subwindow , 0 , 0 ) )
{
Print ( " RenkoDeepSeek: не удалось создать кнопку раннего BUY; код ошибки= " , GetLastError ( ) ) ;
g_pre_buy_button_name = " " ;
return false ;
}
ObjectSetInteger ( 0 , g_pre_buy_button_name , OBJPROP_CORNER , CORNER_LEFT_UPPER ) ;
ObjectSetInteger ( 0 , g_pre_buy_button_name , OBJPROP_XDISTANCE , 8 ) ;
ObjectSetInteger ( 0 , g_pre_buy_button_name , OBJPROP_YDISTANCE , 147 + g_control_y_offset ) ;
ObjectSetInteger ( 0 , g_pre_buy_button_name , OBJPROP_XSIZE , 136 ) ;
ObjectSetInteger ( 0 , g_pre_buy_button_name , OBJPROP_YSIZE , 24 ) ;
ObjectSetInteger ( 0 , g_pre_buy_button_name , OBJPROP_FONTSIZE , 8 ) ;
if ( ObjectFind ( 0 , g_pre_sell_button_name ) < 0 & & ! ObjectCreate ( 0 , g_pre_sell_button_name , OBJ_BUTTON , subwindow , 0 , 0 ) )
{
Print ( " RenkoDeepSeek: не удалось создать кнопку раннего SELL; код ошибки= " , GetLastError ( ) ) ;
g_pre_sell_button_name = " " ;
return false ;
}
ObjectSetInteger ( 0 , g_pre_sell_button_name , OBJPROP_CORNER , CORNER_LEFT_UPPER ) ;
ObjectSetInteger ( 0 , g_pre_sell_button_name , OBJPROP_XDISTANCE , 150 ) ;
ObjectSetInteger ( 0 , g_pre_sell_button_name , OBJPROP_YDISTANCE , 147 + g_control_y_offset ) ;
ObjectSetInteger ( 0 , g_pre_sell_button_name , OBJPROP_XSIZE , 136 ) ;
ObjectSetInteger ( 0 , g_pre_sell_button_name , OBJPROP_YSIZE , 24 ) ;
ObjectSetInteger ( 0 , g_pre_sell_button_name , OBJPROP_FONTSIZE , 8 ) ;
UpdatePreBreakoutToggleButtons ( ) ;
if ( ! CreatePrePanelLabel ( g_pre_status_label_name , subwindow , 175 ) | | ! CreatePrePanelLabel ( g_pre_detail_label_name , subwindow , 193 ) | | ! CreatePrePanelLabel ( g_pre_sl_label_name , subwindow , 211 ) )
Print ( " RenkoDeepSeek: панель ранних сигналов будет повторно создана при следующем обновлении. " ) ;
UpdatePreBreakoutPanel ( g_last_bid , true ) ;
if ( ObjectFind ( 0 , g_brick_edit_name ) < 0 )
{
ResetLastError ( ) ;
if ( ! ObjectCreate ( 0 , g_brick_edit_name , OBJ_EDIT , subwindow , 0 , 0 ) )
{
Print ( " RenkoDeepSeek: не удалось создать поле ввода размера кирпича; код ошибки= " , GetLastError ( ) ) ;
g_alert_button_name = " " ;
g_breakout_high_button_name = " " ;
g_breakout_low_button_name = " " ;
g_brick_reversal_button_name = " " ;
g_brick_edit_name = " " ;
g_brick_apply_name = " " ;
return false ;
}
}
ObjectSetInteger ( 0 , g_brick_edit_name , OBJPROP_CORNER , CORNER_LEFT_UPPER ) ;
ObjectSetInteger ( 0 , g_brick_edit_name , OBJPROP_XDISTANCE , 192 ) ;
ObjectSetInteger ( 0 , g_brick_edit_name , OBJPROP_YDISTANCE , 35 + g_control_y_offset ) ;
ObjectSetInteger ( 0 , g_brick_edit_name , OBJPROP_XSIZE , 72 ) ;
ObjectSetInteger ( 0 , g_brick_edit_name , OBJPROP_YSIZE , 24 ) ;
ObjectSetInteger ( 0 , g_brick_edit_name , OBJPROP_FONTSIZE , 9 ) ;
ObjectSetInteger ( 0 , g_brick_edit_name , OBJPROP_ALIGN , ALIGN_CENTER ) ;
ObjectSetInteger ( 0 , g_brick_edit_name , OBJPROP_READONLY , false ) ;
ObjectSetInteger ( 0 , g_brick_edit_name , OBJPROP_COLOR , clrBlack ) ;
ObjectSetInteger ( 0 , g_brick_edit_name , OBJPROP_BGCOLOR , clrWhite ) ;
ObjectSetInteger ( 0 , g_brick_edit_name , OBJPROP_BORDER_COLOR , clrDimGray ) ;
ObjectSetString ( 0 , g_brick_edit_name , OBJPROP_TEXT , IntegerToString ( g_current_brick_points ) ) ;
if ( ObjectFind ( 0 , g_brick_apply_name ) < 0 )
{
ResetLastError ( ) ;
if ( ! ObjectCreate ( 0 , g_brick_apply_name , OBJ_BUTTON , subwindow , 0 , 0 ) )
{
Print ( " RenkoDeepSeek: не удалось создать кнопку применения размера; код ошибки= " , GetLastError ( ) ) ;
g_alert_button_name = " " ;
g_breakout_high_button_name = " " ;
g_breakout_low_button_name = " " ;
g_brick_reversal_button_name = " " ;
g_brick_edit_name = " " ;
g_brick_apply_name = " " ;
return false ;
}
}
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_CORNER , CORNER_LEFT_UPPER ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_XDISTANCE , 272 ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_YDISTANCE , 35 + g_control_y_offset ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_YSIZE , 24 ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_FONTSIZE , 9 ) ;
SetButtonTextFit ( g_brick_apply_name , " Применить " , 9 ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_STATE , false ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_BGCOLOR , clrSteelBlue ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_COLOR , clrWhite ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_BORDER_COLOR , clrMidnightBlue ) ;
ChartRedraw ( 0 ) ;
return true ;
}
//--- Проверка введённого размера кирпича и перестроение истории.
bool ApplyBrickSizeFromEdit ( )
{
if ( g_brick_edit_name = = " " )
return false ;
string entered = ObjectGetString ( 0 , g_brick_edit_name , OBJPROP_TEXT ) ;
StringTrimLeft ( entered ) ;
StringTrimRight ( entered ) ;
const long parsed = StringToInteger ( entered ) ;
if ( entered = = " " | | parsed < 1 | | parsed > 100000000 )
{
NotifyUser ( " RenkoDeepSeek: укажите целое число пунктов от 1 до 100000000. " , false ) ;
ObjectSetString ( 0 , g_brick_edit_name , OBJPROP_TEXT , IntegerToString ( g_current_brick_points ) ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_STATE , false ) ;
return false ;
}
for ( int i = 0 ; i < StringLen ( entered ) ; + + i )
{
const ushort ch = StringGetCharacter ( entered , i ) ;
if ( ch < ' 0 ' | | ch > ' 9 ' )
{
NotifyUser ( " RenkoDeepSeek: размер кирпича должен быть целым числом пунктов. " , false ) ;
ObjectSetString ( 0 , g_brick_edit_name , OBJPROP_TEXT , IntegerToString ( g_current_brick_points ) ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_STATE , false ) ;
return false ;
}
}
const int requested_points = ( int ) parsed ;
if ( requested_points = = g_current_brick_points )
{
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_STATE , false ) ;
return true ;
}
if ( ! IsRenkoGridCompatible ( requested_points , true ) )
{
NotifyUser ( " RenkoDeepSeek: размер кирпича несовместим с SYMBOL_TRADE_TICK_SIZE/Anchor; размер не изменён. " , false ) ;
ObjectSetString ( 0 , g_brick_edit_name , OBJPROP_TEXT , IntegerToString ( g_current_brick_points ) ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_STATE , false ) ;
return false ;
}
const int previous_points = g_current_brick_points ;
const double previous_size = g_brick_size ;
g_current_brick_points = requested_points ;
g_brick_size = ( double ) g_current_brick_points * _Point ;
g_cache_loaded = false ;
g_history_initialized = false ;
g_step_warned = false ;
g_rebuild_resume_from = 0 ;
g_rebuild_resume_window_days = 0 ;
g_history_needs_more = false ;
g_last_tick_msc = 0 ;
g_ticks_processed_at_last_msc = 0 ;
IndicatorSetString ( INDICATOR_SHORTNAME , BuildShortName ( ) ) ;
if ( ! RebuildHistory ( ) )
{
// Откат к прежней геометрии: серия уже восстановлена из снимка,
// поэтому повторная пересборка здесь не нужна и только блокирует UI.
g_current_brick_points = previous_points ;
g_brick_size = previous_size ;
g_last_history_attempt_ms = 0 ;
g_step_warned = false ;
g_rebuild_resume_from = 0 ;
g_rebuild_resume_window_days = 0 ;
g_history_needs_more = false ;
IndicatorSetString ( INDICATOR_SHORTNAME , BuildShortName ( ) ) ;
g_history_initialized = HasUsableRenkoState ( ) ;
ObjectSetString ( 0 , g_brick_edit_name , OBJPROP_TEXT , IntegerToString ( g_current_brick_points ) ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_STATE , false ) ;
RenderBricks ( ArraySize ( g_open_buffer ) , true ) ;
ChartRedraw ( 0 ) ;
NotifyUser ( " RenkoDeepSeek: не удалось перестроить историю для нового размера кирпича; размер не изменён. " , false ) ;
return false ;
}
2026-11-05 19:12:31 +03:00
g_history_initialized = ( HasUsableRenkoState ( ) & &
! g_history_needs_more & &
! g_history_partial ) ;
2026-11-05 13:25:46 +03:00
RenderBricks ( ArraySize ( g_open_buffer ) , true ) ;
ObjectSetString ( 0 , g_brick_edit_name , OBJPROP_TEXT , IntegerToString ( g_current_brick_points ) ) ;
ObjectSetInteger ( 0 , g_brick_apply_name , OBJPROP_STATE , false ) ;
ChartRedraw ( 0 ) ;
if ( ! SaveBrickPoints ( g_current_brick_points ) )
2026-11-05 19:12:31 +03:00
NotifyUser ( " RenkoDeepSeek: размер применён, восстановление истории продолжается; сохранить настройку на диск не удалось. " , false ) ;
else if ( g_history_initialized )
2026-11-05 13:25:46 +03:00
Print ( " RenkoDeepSeek: размер кирпича изменён на " , g_current_brick_points ,
" пунктов; история перестроена. " ) ;
2026-11-05 19:12:31 +03:00
else
Print ( " RenkoDeepSeek: размер кирпича изменён на " , g_current_brick_points ,
" пунктов; пошаговое восстановление истории продолжается. " ) ;
2026-11-05 13:25:46 +03:00
return true ;
}
//--- Устойчивая идентификация файла настроек.
uint HashText ( const string value )
{
uint hash = ( uint ) 2166136261 ;
for ( int i = 0 ; i < StringLen ( value ) ; + + i )
{
hash ^ = ( uint ) StringGetCharacter ( value , i ) ;
hash * = 16777619 ;
}
return hash ;
}
// Маркер привязан к подокну и сохраняет идентичность при изменении номера окна.
// ChartID дополнительно изолирует одинаковые маркеры на разных графиках.
const string INSTANCE_MARKER_PREFIX = " RGB.Instance. " ;
int CountInstanceMarkersInWindow ( const int window ,
string & marker_name ,
string & marker_id )
{
marker_name = " " ;
marker_id = " " ;
int count = 0 ;
const int total = ObjectsTotal ( 0 , window , -1 ) ;
for ( int i = 0 ; i < total ; + + i )
{
const string name = ObjectName ( 0 , i , window , -1 ) ;
if ( StringFind ( name , INSTANCE_MARKER_PREFIX ) ! = 0 )
continue ;
+ + count ;
marker_name = name ;
marker_id = StringSubstr ( name , StringLen ( INSTANCE_MARKER_PREFIX ) ) ;
}
return count ;
}
// Маркер — пустая метка без цвета: она не отображается и скрыта из обычного списка объектов.
// Объект намеренно не удаляется в OnDeinit: он хранит идентичность подокна между перезапусками.
bool CreateInstanceMarker ( const int window ,
const string marker_id ,
string & marker_name )
{
marker_name = INSTANCE_MARKER_PREFIX + marker_id ;
if ( StringLen ( marker_name ) > 63 | | ObjectFind ( 0 , marker_name ) > = 0 )
return false ;
ResetLastError ( ) ;
if ( ! ObjectCreate ( 0 , marker_name , OBJ_LABEL , window , 0 , 0 ) )
{
PrintFormat ( " RenkoDeepSeek: не удалось создать маркер идентичности подокна %d; код ошибки=%d. " ,
window , GetLastError ( ) ) ;
return false ;
}
bool ok = true ;
const bool set_hidden = ObjectSetInteger ( 0 , marker_name , OBJPROP_HIDDEN , true ) ;
const bool set_selectable = ObjectSetInteger ( 0 , marker_name , OBJPROP_SELECTABLE , false ) ;
const bool set_selected = ObjectSetInteger ( 0 , marker_name , OBJPROP_SELECTED , false ) ;
const bool set_color = ObjectSetInteger ( 0 , marker_name , OBJPROP_COLOR , clrNONE ) ;
const bool set_back = ObjectSetInteger ( 0 , marker_name , OBJPROP_BACK , true ) ;
const bool set_corner = ObjectSetInteger ( 0 , marker_name , OBJPROP_CORNER , CORNER_LEFT_UPPER ) ;
const bool set_xdistance = ObjectSetInteger ( 0 , marker_name , OBJPROP_XDISTANCE , 0 ) ;
const bool set_ydistance = ObjectSetInteger ( 0 , marker_name , OBJPROP_YDISTANCE , 0 ) ;
const bool set_fontsize = ObjectSetInteger ( 0 , marker_name , OBJPROP_FONTSIZE , 1 ) ;
const bool set_text = ObjectSetString ( 0 , marker_name , OBJPROP_TEXT , " " ) ;
ok = set_hidden & & set_selectable & & set_selected & & set_color & & set_back & &
set_corner & & set_xdistance & & set_ydistance & & set_fontsize & & set_text ;
const int actual_window = ObjectFind ( 0 , marker_name ) ;
const long actual_type = ObjectGetInteger ( 0 , marker_name , OBJPROP_TYPE ) ;
const long actual_hidden = ObjectGetInteger ( 0 , marker_name , OBJPROP_HIDDEN ) ;
const long actual_selectable = ObjectGetInteger ( 0 , marker_name , OBJPROP_SELECTABLE ) ;
const long actual_selected = ObjectGetInteger ( 0 , marker_name , OBJPROP_SELECTED ) ;
const long actual_color = ObjectGetInteger ( 0 , marker_name , OBJPROP_COLOR ) ;
const long actual_back = ObjectGetInteger ( 0 , marker_name , OBJPROP_BACK ) ;
const string actual_text = ObjectGetString ( 0 , marker_name , OBJPROP_TEXT ) ;
if ( ! ok | | actual_window ! = window | | actual_type ! = OBJ_LABEL | |
actual_hidden ! = true | | actual_selectable ! = false | |
actual_selected ! = false | | ( uint ) actual_color ! = ( uint ) clrNONE | |
actual_back ! = true | | actual_text ! = " " )
{
const int error = GetLastError ( ) ;
PrintFormat ( " RenkoDeepSeek: проверка маркера подокна %d не пройдена; "
" set[hidden=%d selectable=%d selected=%d color=%d back=%d corner=%d x=%d y=%d font=%d text=%d]; "
" read[window=%d type=%d hidden=%I64d selectable=%I64d selected=%I64d color=%I64d back=%I64d text_len=%d]; error=%d. " ,
window ,
set_hidden , set_selectable , set_selected , set_color , set_back ,
set_corner , set_xdistance , set_ydistance , set_fontsize , set_text ,
actual_window , ( int ) actual_type , actual_hidden , actual_selectable ,
actual_selected , actual_color , actual_back , StringLen ( actual_text ) , error ) ;
ObjectDelete ( 0 , marker_name ) ;
marker_name = " " ;
return false ;
}
return true ;
}
string MakePersistentInstanceId ( )
{
const string base_id = IntegerToString ( ( long ) GetTickCount64 ( ) ) + " _ " +
IntegerToString ( ( long ) GetMicrosecondCount ( ) ) + " _ " +
IntegerToString ( ObjectsTotal ( 0 , -1 , -1 ) ) ;
string candidate = base_id ;
int suffix = 1 ;
while ( ObjectFind ( 0 , INSTANCE_MARKER_PREFIX + candidate ) > = 0 )
{
candidate = base_id + " _ " + IntegerToString ( suffix ) ;
+ + suffix ;
}
return candidate ;
}
bool InitializeInstanceMarker ( const int window )
{
string marker_name = " " ;
string marker_id = " " ;
const int marker_count = CountInstanceMarkersInWindow ( window , marker_name , marker_id ) ;
if ( marker_count > 1 )
{
PrintFormat ( " RenkoDeepSeek: в подокне %d найдено несколько маркеров идентичности; запуск остановлен во избежание смешивания настроек. " ,
window ) ;
return false ;
}
if ( marker_count = = 1 )
{
2026-11-05 19:12:31 +03:00
// Сохраняем прежний ID (и тем самым доступ к прежним настройкам/кэшу),
// но восстанавливаем свойства маркера после изменения шаблона графика
// или сброса свойств графических объектов терминалом.
if ( marker_id = = " " | |
StringFind ( marker_name , INSTANCE_MARKER_PREFIX ) ! = 0 | |
ObjectFind ( 0 , marker_name ) ! = window | |
ObjectGetInteger ( 0 , marker_name , OBJPROP_TYPE ) ! = OBJ_LABEL )
2026-11-05 13:25:46 +03:00
{
2026-11-05 19:12:31 +03:00
PrintFormat ( " RenkoDeepSeek: маркер подокна %d нельзя безопасно восстановить: id_empty=%d name_prefix_ok=%d object_window=%d object_type=%I64d; запуск остановлен. " ,
window , marker_id = = " " ,
StringFind ( marker_name , INSTANCE_MARKER_PREFIX ) = = 0 ,
ObjectFind ( 0 , marker_name ) ,
ObjectGetInteger ( 0 , marker_name , OBJPROP_TYPE ) ) ;
2026-11-05 13:25:46 +03:00
return false ;
}
2026-11-05 19:12:31 +03:00
ResetLastError ( ) ;
bool repaired = true ;
repaired = ObjectSetInteger ( 0 , marker_name , OBJPROP_HIDDEN , true ) & & repaired ;
repaired = ObjectSetInteger ( 0 , marker_name , OBJPROP_SELECTABLE , false ) & & repaired ;
repaired = ObjectSetInteger ( 0 , marker_name , OBJPROP_SELECTED , false ) & & repaired ;
repaired = ObjectSetInteger ( 0 , marker_name , OBJPROP_COLOR , clrNONE ) & & repaired ;
repaired = ObjectSetInteger ( 0 , marker_name , OBJPROP_BACK , true ) & & repaired ;
repaired = ObjectSetInteger ( 0 , marker_name , OBJPROP_CORNER , CORNER_LEFT_UPPER ) & & repaired ;
repaired = ObjectSetInteger ( 0 , marker_name , OBJPROP_XDISTANCE , 0 ) & & repaired ;
repaired = ObjectSetInteger ( 0 , marker_name , OBJPROP_YDISTANCE , 0 ) & & repaired ;
repaired = ObjectSetInteger ( 0 , marker_name , OBJPROP_FONTSIZE , 1 ) & & repaired ;
repaired = ObjectSetString ( 0 , marker_name , OBJPROP_TEXT , " " ) & & repaired ;
const int repair_error = GetLastError ( ) ;
const bool verified =
ObjectFind ( 0 , marker_name ) = = window & &
ObjectGetInteger ( 0 , marker_name , OBJPROP_TYPE ) = = OBJ_LABEL & &
ObjectGetInteger ( 0 , marker_name , OBJPROP_HIDDEN ) = = true & &
ObjectGetInteger ( 0 , marker_name , OBJPROP_SELECTABLE ) = = false & &
ObjectGetInteger ( 0 , marker_name , OBJPROP_SELECTED ) = = false & &
( uint ) ObjectGetInteger ( 0 , marker_name , OBJPROP_COLOR ) = = ( uint ) clrNONE & &
ObjectGetInteger ( 0 , marker_name , OBJPROP_BACK ) = = true & &
ObjectGetString ( 0 , marker_name , OBJPROP_TEXT ) = = " " ;
if ( ! repaired | | ! verified )
{
PrintFormat ( " RenkoDeepSeek: восстановление маркера подокна %d не подтверждено; setters_ok=%d verified=%d error=%d; запуск остановлен. " ,
window , repaired , verified , repair_error ) ;
return false ;
}
2026-11-05 13:25:46 +03:00
g_instance_marker_name = marker_name ;
g_instance_marker_id = marker_id ;
2026-11-05 19:12:31 +03:00
PrintFormat ( " RenkoDeepSeek: свойства существующего маркера подокна %d восстановлены; ID экземпляра и пространство настроек/кэша сохранены. " ,
window ) ;
2026-11-05 13:25:46 +03:00
return true ;
}
marker_id = MakePersistentInstanceId ( ) ;
if ( ! CreateInstanceMarker ( window , marker_id , marker_name ) )
{
PrintFormat ( " RenkoDeepSeek: не удалось создать уникальный маркер идентичности для подокна %d; запуск остановлен. " ,
window ) ;
return false ;
}
g_instance_marker_name = marker_name ;
g_instance_marker_id = marker_id ;
return true ;
}
// Не допускаем два экземпляра RenkoDeepSeek в одном подокне.
// Остальные типы индикаторов могут совместно работать в этом подокне.
bool HasOtherRenkoInstanceInWindow ( const int window )
{
const string own_marker = " [ " + g_instance_id ;
const int total = ChartIndicatorsTotal ( 0 , window ) ;
for ( int i = 0 ; i < total ; + + i )
{
const string short_name = ChartIndicatorName ( 0 , window , i ) ;
if ( StringFind ( short_name , " RenkoDeepSeek " ) ! = 0 )
continue ;
if ( StringFind ( short_name , own_marker ) > = 0 )
continue ;
return true ;
}
return false ;
}
// Идентичность файлов и кэша не зависит от изменяемого номера подокна.
string InstanceIdentityKey ( )
{
if ( g_instance_marker_id = = " " )
return " " ;
return " CH: " + IntegerToString ( ( long ) ChartID ( ) ) +
" :INSTANCE: " + g_instance_marker_id ;
}
// Пространство настроек разделяется по серверу и номеру торгового счёта.
string SettingsAccountNamespace ( )
{
string server = AccountInfoString ( ACCOUNT_SERVER ) ;
StringReplace ( server , " / " , " _ " ) ;
StringReplace ( server , " \\ " , " _ " ) ;
StringReplace ( server , " : " , " _ " ) ;
StringReplace ( server , " " , " _ " ) ;
const long login = AccountInfoInteger ( ACCOUNT_LOGIN ) ;
if ( login < = 0 | | server = = " " )
return " UNBOUND_ " + IntegerToString ( ( long ) ChartID ( ) ) ;
return IntegerToString ( login ) + " _ " + server ;
}
// Один общий lock не позволяет нескольким счетам импортировать одни и те же
// старые файлы, в которых ещё не было сведений о счёте.
string LegacySettingsMigrationLockFile ( )
{
return StringFormat ( " RenkoDeepSeekLegacySettings_%u_%u.lock " ,
HashText ( _Symbol ) , HashText ( InstanceIdentityKey ( ) ) ) ;
}
void CopyLegacySettingsFile ( const string source_file ,
const string target_file ,
const string description )
{
if ( FileIsExist ( target_file , FILE_COMMON ) | |
! FileIsExist ( source_file , FILE_COMMON ) )
return ;
ResetLastError ( ) ;
if ( FileCopy ( source_file , FILE_COMMON , target_file , FILE_COMMON ) )
Print ( " RenkoDeepSeek: перенесены прежние настройки ( " , description ,
" ) в пространство текущего экземпляра: " , target_file ) ;
else
Print ( " RenkoDeepSeek: не удалось перенести прежние настройки ( " , description ,
" ); источник= " , source_file , " ; код ошибки= " , GetLastError ( ) ) ;
}
// Старые файлы не содержат account identity. Первый счёт, который
// захватит эксклюзивный lock, становится единственным получателем миграции.
// Остальные счета не импортируют эти общие legacy-настройки.
void MigrateLegacyInstanceSettings ( )
{
const string current_owner = SettingsAccountNamespace ( ) ;
if ( StringFind ( current_owner , " UNBOUND_ " ) = = 0 )
{
Print ( " RenkoDeepSeek: счёт/сервер ещё не определён; миграция общих старых настроек отложена до появления account identity. " ) ;
return ;
}
const string lock_file = LegacySettingsMigrationLockFile ( ) ;
ResetLastError ( ) ;
const int lock_handle = FileOpen ( lock_file , FILE_READ | FILE_WRITE | FILE_TXT | FILE_COMMON ) ;
if ( lock_handle = = INVALID_HANDLE )
{
Print ( " RenkoDeepSeek: не удалось получить lock миграции старых настроек; импорт пропущен во избежание смешивания счетов. Код ошибки= " , GetLastError ( ) ) ;
return ;
}
string claimed_owner = FileReadString ( lock_handle ) ;
StringTrimLeft ( claimed_owner ) ;
StringTrimRight ( claimed_owner ) ;
if ( claimed_owner ! = " " & & claimed_owner ! = current_owner )
{
FileClose ( lock_handle ) ;
Print ( " RenkoDeepSeek: старые настройки уже закреплены за другим счётом; импорт в текущий счёт запрещён. " ) ;
return ;
}
if ( claimed_owner = = " " )
{
FileSeek ( lock_handle , 0 , SEEK_SET ) ;
if ( FileWriteString ( lock_handle , current_owner + " \n " ) < = 0 )
{
FileClose ( lock_handle ) ;
Print ( " RenkoDeepSeek: не удалось зафиксировать владельца миграции старых настроек; импорт пропущен. " ) ;
return ;
}
FileFlush ( lock_handle ) ;
}
string stable_instance_id = InpInstanceId ;
StringTrimLeft ( stable_instance_id ) ;
StringTrimRight ( stable_instance_id ) ;
string legacy_window_identity = " CH: " + IntegerToString ( ( long ) ChartID ( ) ) +
" :W " + IntegerToString ( g_instance_window ) ;
if ( stable_instance_id ! = " " )
legacy_window_identity + = " :ID: " + stable_instance_id ;
CopyLegacySettingsFile (
StringFormat ( " RenkoDeepSeek_%u_%u.txt " ,
HashText ( _Symbol ) , HashText ( legacy_window_identity ) ) ,
BrickSizeSettingsFile ( ) ,
" размер кирпича " ) ;
CopyLegacySettingsFile (
StringFormat ( " RenkoDeepSeekButtons_%u_%u.txt " ,
HashText ( _Symbol ) , HashText ( legacy_window_identity ) ) ,
ButtonSettingsFile ( ) ,
" состояния кнопок " ) ;
// Поддержка самой старой схемы, где явный ID был единственным ключом.
if ( stable_instance_id ! = " " )
{
const string legacy_identity = " ID: " + stable_instance_id ;
CopyLegacySettingsFile (
StringFormat ( " RenkoDeepSeek_%u_%u.txt " ,
HashText ( _Symbol ) , HashText ( legacy_identity ) ) ,
BrickSizeSettingsFile ( ) ,
" размер кирпича по прежнему явному ID " ) ;
CopyLegacySettingsFile (
StringFormat ( " RenkoDeepSeekButtons_%u_%u.txt " ,
HashText ( _Symbol ) , HashText ( legacy_identity ) ) ,
ButtonSettingsFile ( ) ,
" состояния кнопок по прежнему явному ID " ) ;
}
FileClose ( lock_handle ) ;
}
string BrickSizeSettingsFile ( )
{
return StringFormat ( " RenkoDeepSeek_%s_%u_%u.txt " ,
SettingsAccountNamespace ( ) ,
HashText ( _Symbol ) ,
HashText ( InstanceIdentityKey ( ) ) ) ;
}
// Возвращает сохранённый размер или входное значение, если сохранения ещё нет.
int LoadSavedBrickPoints ( const int fallback_points )
{
// Сохранённый размер всегда имеет приоритет; параметр InpPreferSavedBrickSize
// оставлен только для совместимости со старыми наборами входных параметров.
const string file_name = BrickSizeSettingsFile ( ) ;
ResetLastError ( ) ;
const int handle = FileOpen ( file_name , FILE_READ | FILE_TXT | FILE_COMMON | FILE_SHARE_READ | FILE_SHARE_WRITE ) ;
if ( handle = = INVALID_HANDLE )
return fallback_points ;
string value = FileReadString ( handle ) ;
FileClose ( handle ) ;
StringTrimLeft ( value ) ;
StringTrimRight ( value ) ;
const long parsed = StringToInteger ( value ) ;
if ( value = = " " | | parsed < 1 | | parsed > 100000000 )
{
Print ( " RenkoDeepSeek: сохранённый размер кирпича недействителен; используется InpBrickPoints= " , fallback_points ,
" . Файл= " , file_name ) ;
return fallback_points ;
}
for ( int i = 0 ; i < StringLen ( value ) ; + + i )
{
const ushort ch = StringGetCharacter ( value , i ) ;
if ( ch < ' 0 ' | | ch > ' 9 ' )
{
Print ( " RenkoDeepSeek: сохранённый размер кирпича содержит недопустимые символы; используется InpBrickPoints= " , fallback_points ,
" . Файл= " , file_name ) ;
return fallback_points ;
}
}
if ( ( int ) parsed ! = fallback_points )
Print ( " RenkoDeepSeek: применён сохранённый размер кирпича " , ( int ) parsed ,
" пунктов вместо InpBrickPoints= " , fallback_points ,
" ; InpBrickPoints используется только при отсутствии или повреждении сохранённого значения. ID экземпляра= " ,
g_instance_id , " . " ) ;
return ( int ) parsed ;
}
// Сохраняет настройку в Common\Files с проверкой повторным чтением.
bool SaveBrickPoints ( const int points )
{
const string file_name = BrickSizeSettingsFile ( ) ;
ResetLastError ( ) ;
const int handle = FileOpen ( file_name , FILE_WRITE | FILE_TXT | FILE_COMMON | FILE_SHARE_READ | FILE_SHARE_WRITE ) ;
if ( handle = = INVALID_HANDLE )
{
Print ( " RenkoDeepSeek: не удалось открыть файл сохранения размера кирпича; файл= " ,
file_name , " ; код ошибки= " , GetLastError ( ) ) ;
return false ;
}
FileWriteString ( handle , IntegerToString ( points ) ) ;
FileFlush ( handle ) ;
FileClose ( handle ) ;
ResetLastError ( ) ;
const int verify_handle = FileOpen ( file_name , FILE_READ | FILE_TXT | FILE_COMMON | FILE_SHARE_READ | FILE_SHARE_WRITE ) ;
if ( verify_handle = = INVALID_HANDLE )
{
Print ( " RenkoDeepSeek: не удалось проверить сохранённый размер кирпича; файл= " ,
file_name , " ; код ошибки= " , GetLastError ( ) ) ;
return false ;
}
string saved_value = FileReadString ( verify_handle ) ;
FileClose ( verify_handle ) ;
StringTrimLeft ( saved_value ) ;
StringTrimRight ( saved_value ) ;
if ( saved_value ! = IntegerToString ( points ) )
{
Print ( " RenkoDeepSeek: проверка сохранения размера кирпича не пройдена; ожидалось= " ,
points , " ; прочитано= " , saved_value , " ; файл= " , file_name ) ;
return false ;
}
return true ;
}
//--- Пропуск пустых строк в конце файла.
// В FILE_TXT запись с завершающим переводом строки может оставить «пустой»
// токен, поэтому строгий FileIsEnding ложно объявлял бы файл повреждённым.
bool ConsumeTrailingEmptyLines ( const int handle )
{
for ( int guard = 0 ; guard < 64 & & ! FileIsEnding ( handle ) ; + + guard )
{
string value = FileReadString ( handle ) ;
StringTrimLeft ( value ) ;
StringTrimRight ( value ) ;
if ( value ! = " " )
return false ;
}
return true ;
}
//--- Файл состояний переключателей на каждый экземпляр.
string ButtonSettingsFile ( )
{
return StringFormat ( " RenkoDeepSeekButtons_%s_%u_%u.txt " ,
SettingsAccountNamespace ( ) ,
HashText ( _Symbol ) ,
HashText ( InstanceIdentityKey ( ) ) ) ;
}
// Старые файлы с четырьмя состояниями совместимы.
void LoadButtonSettings ( )
{
const string file_name = ButtonSettingsFile ( ) ;
ResetLastError ( ) ;
const int handle = FileOpen ( file_name , FILE_READ | FILE_TXT | FILE_COMMON | FILE_SHARE_READ | FILE_SHARE_WRITE ) ;
if ( handle = = INVALID_HANDLE )
return ;
string values [ 6 ] ;
for ( int i = 0 ; i < 6 ; + + i )
values [ i ] = " " ;
int read_count = 0 ;
bool corrupted = false ;
for ( int guard = 0 ; guard < 64 & & read_count < 6 & & ! FileIsEnding ( handle ) ; + + guard )
{
string value = FileReadString ( handle ) ;
StringTrimLeft ( value ) ;
StringTrimRight ( value ) ;
if ( value = = " " )
continue ; // завершающий перевод строки — не ошибка формата
if ( value ! = " 0 " & & value ! = " 1 " )
{
corrupted = true ; // лишнее содержимое: файл не наш или повреждён
break ;
}
values [ read_count ] = value ;
+ + read_count ;
}
if ( ! corrupted )
corrupted = ! ConsumeTrailingEmptyLines ( handle ) ;
FileClose ( handle ) ;
if ( read_count < 4 | | corrupted )
{
Print ( " RenkoDeepSeek: файл состояний кнопок повреждён или имеет неизвестный формат; используются входные настройки. Файл= " , file_name ) ;
return ;
}
g_breakout_high_enabled = ( values [ 1 ] = = " 1 " ) ;
g_breakout_low_enabled = ( values [ 2 ] = = " 1 " ) ;
g_brick_reversal_enabled = ( values [ 3 ] = = " 1 " ) ;
if ( read_count > = 5 ) g_pre_buy_enabled = ( values [ 4 ] = = " 1 " ) ;
if ( read_count > = 6 ) g_pre_sell_enabled = ( values [ 5 ] = = " 1 " ) ;
// Мастер-флаг — производное состояние.
SyncAlertMasterState ( ) ;
Print ( " RenkoDeepSeek: состояния кнопок восстановлены; ID экземпляра= " , g_instance_id , " . " ) ;
}
// Сохраняет все переключатели одним файлом после каждого нажатия.
bool SaveButtonSettings ( )
{
const string file_name = ButtonSettingsFile ( ) ;
ResetLastError ( ) ;
const int handle = FileOpen ( file_name , FILE_WRITE | FILE_TXT | FILE_COMMON | FILE_SHARE_READ | FILE_SHARE_WRITE ) ;
if ( handle = = INVALID_HANDLE )
{
Print ( " RenkoDeepSeek: не удалось сохранить состояния кнопок; файл= " ,
file_name , " ; код ошибки= " , GetLastError ( ) ) ;
return false ;
}
FileWriteString ( handle ,
( g_alert_enabled ? " 1 " : " 0 " ) + " \n " +
( g_breakout_high_enabled ? " 1 " : " 0 " ) + " \n " +
( g_breakout_low_enabled ? " 1 " : " 0 " ) + " \n " +
( g_brick_reversal_enabled ? " 1 " : " 0 " ) + " \n " +
( g_pre_buy_enabled ? " 1 " : " 0 " ) + " \n " +
( g_pre_sell_enabled ? " 1 " : " 0 " ) + " \n " ) ;
FileFlush ( handle ) ;
FileClose ( handle ) ;
ResetLastError ( ) ;
const int verify_handle = FileOpen ( file_name , FILE_READ | FILE_TXT | FILE_COMMON | FILE_SHARE_READ | FILE_SHARE_WRITE ) ;
if ( verify_handle = = INVALID_HANDLE )
{
Print ( " RenkoDeepSeek: не удалось проверить файл состояний кнопок; файл= " ,
file_name , " ; код ошибки= " , GetLastError ( ) ) ;
return false ;
}
string expected [ 6 ] ;
expected [ 0 ] = ( g_alert_enabled ? " 1 " : " 0 " ) ;
expected [ 1 ] = ( g_breakout_high_enabled ? " 1 " : " 0 " ) ;
expected [ 2 ] = ( g_breakout_low_enabled ? " 1 " : " 0 " ) ;
expected [ 3 ] = ( g_brick_reversal_enabled ? " 1 " : " 0 " ) ;
expected [ 4 ] = ( g_pre_buy_enabled ? " 1 " : " 0 " ) ;
expected [ 5 ] = ( g_pre_sell_enabled ? " 1 " : " 0 " ) ;
bool verified = true ;
for ( int i = 0 ; i < 6 ; + + i )
{
if ( FileIsEnding ( verify_handle ) | | FileReadString ( verify_handle ) ! = expected [ i ] )
{
verified = false ;
break ;
}
}
FileClose ( verify_handle ) ;
if ( ! verified )
Print ( " RenkoDeepSeek: проверка сохранения состояний кнопок не пройдена; файл= " , file_name ) ;
return verified ;
}
//--- Идентификатор кэша включает экземпляр, сервер и параметры Renko.
string RenkoCacheKey ( )
{
// Старые кэши с Anchor от первого Bid несовместимы с новой ценовой сеткой.
return StringFormat ( " v8|anchor=%.5f|anchor_init=floor_first_bid|%s|%I64d|%s|%s|%d|%.10f|%.10f|%d|%d|%d|%d " ,
RENKO_PRICE_ANCHOR ,
AccountInfoString ( ACCOUNT_SERVER ) ,
AccountInfoInteger ( ACCOUNT_LOGIN ) ,
_Symbol , InstanceIdentityKey ( ) , _Digits , _Point , g_price_step ,
g_current_brick_points , InpCycleStartHour , InpCycleStartMinute ,
InpMaxHistoryBricks ) ;
}
string RenkoCacheFileName ( )
{
string server = AccountInfoString ( ACCOUNT_SERVER ) ;
StringReplace ( server , " / " , " _ " ) ;
StringReplace ( server , " \\ " , " _ " ) ;
StringReplace ( server , " : " , " _ " ) ;
StringReplace ( server , " " , " _ " ) ;
string symbol = _Symbol ;
StringReplace ( symbol , " / " , " _ " ) ;
StringReplace ( symbol , " \\ " , " _ " ) ;
StringReplace ( symbol , " : " , " _ " ) ;
return StringFormat ( " RenkoDeepSeek_%I64d_%s_%s_%u_%d_%d_%d_%d.cache " ,
AccountInfoInteger ( ACCOUNT_LOGIN ) , server , symbol ,
HashText ( InstanceIdentityKey ( ) ) ,
g_current_brick_points , InpCycleStartHour , InpCycleStartMinute ,
InpMaxHistoryBricks ) ;
}
bool CacheWriteLine ( const int handle , const string value , string & checksum_payload )
{
const string line = value + " \n " ;
if ( FileWriteString ( handle , line ) < = 0 )
return false ;
checksum_payload + = line ;
return true ;
}
string CacheReadLine ( const int handle , string & checksum_payload )
{
const string value = FileReadString ( handle ) ;
checksum_payload + = value + " \n " ;
return value ;
}
//--- Сохраняем согласованный снимок состояния, включая контрольную сумму.
bool SaveRenkoCache ( )
{
// Пустая серия допустима только как checkpoint незавершённой загрузки.
// Разрешаем сохранять его до появления первого кирпича:
// иначе перезапуск во время самого начала загрузки потеряет позицию чанка.
2026-11-05 19:12:31 +03:00
if ( ( ! g_history_initialized & & g_rebuild_resume_from < = 0 & & ! g_history_needs_more ) | |
2026-11-05 13:25:46 +03:00
( g_brick_count < = 0 & & g_rebuild_resume_from < = 0 & &
( ! g_have_anchor | | g_last_tick_msc < = 0 ) ) )
return false ;
const string file_name = RenkoCacheFileName ( ) ;
const string temp_name = file_name + " .tmp " ;
ResetLastError ( ) ;
const int handle = FileOpen ( temp_name , FILE_WRITE | FILE_TXT | FILE_COMMON | FILE_SHARE_READ | FILE_SHARE_WRITE ) ;
if ( handle = = INVALID_HANDLE )
{
Print ( " RenkoDeepSeek: не удалось открыть временный кэш; код= " , GetLastError ( ) ) ;
return false ;
}
bool ok = true ;
string checksum_payload = " " ;
ok = ok & & CacheWriteLine ( handle , " RenkoDeepSeekCacheV7 " , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , RenkoCacheKey ( ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , IntegerToString ( g_brick_count ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , IntegerToString ( ( long ) g_cycle_start ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , IntegerToString ( g_direction ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , IntegerToString ( g_last_tick_msc ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , IntegerToString ( g_ticks_processed_at_last_msc ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , g_have_anchor ? " 1 " : " 0 " , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , g_history_partial ? " 1 " : " 0 " , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , IntegerToString ( ( long ) g_rebuild_resume_from ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , IntegerToString ( g_rebuild_resume_window_days ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , g_rebuild_partial_accum ? " 1 " : " 0 " , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , DoubleToString ( g_last_close , _Digits ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , DoubleToString ( g_last_bid , _Digits ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , DoubleToString ( g_last_swing_high , _Digits ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , DoubleToString ( g_last_swing_low , _Digits ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , g_swing_high_breakout_alerted ? " 1 " : " 0 " , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , g_swing_low_breakout_alerted ? " 1 " : " 0 " , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , g_swing_high_alert_sent ? " 1 " : " 0 " , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , g_swing_low_alert_sent ? " 1 " : " 0 " , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , g_pre_buy_alerted ? " 1 " : " 0 " , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , g_pre_sell_alerted ? " 1 " : " 0 " , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , g_pre_buy_cancelled ? " 1 " : " 0 " , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , g_pre_sell_cancelled ? " 1 " : " 0 " , checksum_payload ) ;
for ( int i = 0 ; ok & & i < g_brick_count ; + + i )
{
const int physical_index = BrickPhysicalIndex ( i ) ;
ok = CacheWriteLine ( handle , IntegerToString ( ( long ) g_bricks [ physical_index ] . time ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , DoubleToString ( g_bricks [ physical_index ] . open , _Digits ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , DoubleToString ( g_bricks [ physical_index ] . high , _Digits ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , DoubleToString ( g_bricks [ physical_index ] . low , _Digits ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , DoubleToString ( g_bricks [ physical_index ] . close , _Digits ) , checksum_payload ) ;
ok = ok & & CacheWriteLine ( handle , IntegerToString ( g_bricks [ physical_index ] . direction ) , checksum_payload ) ;
}
ok = ok & & FileWriteString ( handle ,
IntegerToString ( ( long ) HashText ( checksum_payload ) ) + " \n " ) > 0 ;
FileFlush ( handle ) ;
FileClose ( handle ) ;
if ( ! ok )
{
FileDelete ( temp_name , FILE_COMMON ) ;
Print ( " RenkoDeepSeek: запись кэша завершилась неполностью; исходный кэш сохранён. " ) ;
return false ;
}
ResetLastError ( ) ;
if ( ! FileMove ( temp_name , FILE_COMMON , file_name , FILE_COMMON | FILE_REWRITE ) )
{
const int error = GetLastError ( ) ;
FileDelete ( temp_name , FILE_COMMON ) ;
Print ( " RenkoDeepSeek: не удалось заменить кэш; код= " , error ) ;
return false ;
}
g_cache_dirty = false ;
return true ;
}
//--- Загружаем только полный снимок с точным совпадением конфигурации.
bool LoadRenkoCache ( )
{
const string file_name = RenkoCacheFileName ( ) ;
ResetLastError ( ) ;
const int handle = FileOpen ( file_name , FILE_READ | FILE_TXT | FILE_COMMON | FILE_SHARE_READ | FILE_SHARE_WRITE ) ;
if ( handle = = INVALID_HANDLE )
return false ;
bool ok = true ;
string checksum_payload = " " ;
if ( CacheReadLine ( handle , checksum_payload ) ! = " RenkoDeepSeekCacheV7 " )
ok = false ;
if ( ok & & CacheReadLine ( handle , checksum_payload ) ! = RenkoCacheKey ( ) )
ok = false ;
int count = -1 ;
if ( ok )
count = ( int ) StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) ;
// Нулевая серия допустима только для сохранённого checkpoint незавершённой загрузки;
// остальные пустые снимки отклоняются проверкой состояния ниже.
if ( count < 0 | | count > InpMaxHistoryBricks )
ok = false ;
datetime cycle_start = 0 ;
int direction = 0 ;
long last_tick_msc = 0 ;
int ticks_at_msc = 0 ;
bool have_anchor = false ;
bool history_partial = false ;
datetime rebuild_resume_from = 0 ;
int rebuild_resume_window_days = 0 ;
bool rebuild_partial_accum = false ;
double last_close = 0.0 , last_bid = 0.0 , swing_high = 0.0 , swing_low = 0.0 ;
bool high_alerted = false , low_alerted = false ;
bool high_sent = false , low_sent = false ;
bool pre_buy = false , pre_sell = false , buy_cancelled = false , sell_cancelled = false ;
if ( ok ) cycle_start = ( datetime ) StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) ;
if ( ok ) direction = ( int ) StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) ;
if ( ok ) last_tick_msc = ( long ) StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) ;
if ( ok ) ticks_at_msc = ( int ) StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) ;
if ( ok ) have_anchor = ( StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) = = 1 ) ;
if ( ok ) history_partial = ( StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) = = 1 ) ;
if ( ok ) rebuild_resume_from = ( datetime ) StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) ;
if ( ok ) rebuild_resume_window_days = ( int ) StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) ;
if ( ok ) rebuild_partial_accum = ( StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) = = 1 ) ;
if ( ok ) last_close = StringToDouble ( CacheReadLine ( handle , checksum_payload ) ) ;
if ( ok ) last_bid = StringToDouble ( CacheReadLine ( handle , checksum_payload ) ) ;
if ( ok ) swing_high = StringToDouble ( CacheReadLine ( handle , checksum_payload ) ) ;
if ( ok ) swing_low = StringToDouble ( CacheReadLine ( handle , checksum_payload ) ) ;
if ( ok ) high_alerted = ( StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) = = 1 ) ;
if ( ok ) low_alerted = ( StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) = = 1 ) ;
if ( ok ) high_sent = ( StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) = = 1 ) ;
if ( ok ) low_sent = ( StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) = = 1 ) ;
if ( ok ) pre_buy = ( StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) = = 1 ) ;
if ( ok ) pre_sell = ( StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) = = 1 ) ;
if ( ok ) buy_cancelled = ( StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) = = 1 ) ;
if ( ok ) sell_cancelled = ( StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) = = 1 ) ;
const bool has_bricks = ( count > 0 ) ;
if ( ok & & ( direction < -1 | | direction > 1 | | ticks_at_msc < 0 | |
last_tick_msc < 0 | | ( ( has_bricks | | have_anchor ) & & last_tick_msc < = 0 ) | |
( has_bricks & & ( cycle_start < = 0 | | ! have_anchor | |
last_close < = 0.0 | | last_bid < = 0.0 ) ) | |
( have_anchor & & ( cycle_start < = 0 | | last_close < = 0.0 | | last_bid < = 0.0 ) ) | |
( ! have_anchor & & ( last_close ! = 0.0 | | last_bid ! = 0.0 ) ) | |
! MathIsValidNumber ( last_close ) | | ! MathIsValidNumber ( last_bid ) | |
! MathIsValidNumber ( swing_high ) | | ! MathIsValidNumber ( swing_low ) | |
( swing_high > 0.0 & & swing_low > 0.0 & & swing_high < swing_low ) | |
rebuild_resume_from < 0 | | rebuild_resume_window_days < 0 | |
( ( rebuild_resume_from = = 0 ) ! = ( rebuild_resume_window_days = = 0 ) ) | |
( history_partial ! = ( rebuild_resume_from > 0 ) ) | |
( history_partial ! = rebuild_partial_accum ) | |
( ! has_bricks & & rebuild_resume_from < = 0 & & ! have_anchor ) ) )
ok = false ;
if ( ok & & ArraySize ( g_bricks ) ! = InpMaxHistoryBricks & &
ArrayResize ( g_bricks , InpMaxHistoryBricks , 256 ) ! = InpMaxHistoryBricks )
ok = false ;
for ( int i = 0 ; ok & & i < count ; + + i )
{
g_bricks [ i ] . time = ( datetime ) StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) ;
g_bricks [ i ] . open = StringToDouble ( CacheReadLine ( handle , checksum_payload ) ) ;
g_bricks [ i ] . high = StringToDouble ( CacheReadLine ( handle , checksum_payload ) ) ;
g_bricks [ i ] . low = StringToDouble ( CacheReadLine ( handle , checksum_payload ) ) ;
g_bricks [ i ] . close = StringToDouble ( CacheReadLine ( handle , checksum_payload ) ) ;
g_bricks [ i ] . direction = ( int ) StringToInteger ( CacheReadLine ( handle , checksum_payload ) ) ;
if ( g_bricks [ i ] . time < = 0 | | g_bricks [ i ] . direction = = 0 | |
g_bricks [ i ] . direction < -1 | | g_bricks [ i ] . direction > 1 | |
g_bricks [ i ] . open < = 0.0 | | g_bricks [ i ] . high < = 0.0 | |
g_bricks [ i ] . low < = 0.0 | | g_bricks [ i ] . close < = 0.0 | |
! MathIsValidNumber ( g_bricks [ i ] . open ) | |
! MathIsValidNumber ( g_bricks [ i ] . high ) | |
! MathIsValidNumber ( g_bricks [ i ] . low ) | |
! MathIsValidNumber ( g_bricks [ i ] . close ) | |
g_bricks [ i ] . high < MathMax ( g_bricks [ i ] . open , g_bricks [ i ] . close ) | |
g_bricks [ i ] . low > MathMin ( g_bricks [ i ] . open , g_bricks [ i ] . close ) | |
( i > 0 & & g_bricks [ i ] . time < g_bricks [ i -1 ] . time ) )
ok = false ;
}
const string checksum_value = FileReadString ( handle ) ;
if ( ok & & ( checksum_value = = " " | |
( long ) StringToInteger ( checksum_value ) ! = ( long ) HashText ( checksum_payload ) ) )
ok = false ;
if ( ok & & ! ConsumeTrailingEmptyLines ( handle ) )
ok = false ;
FileClose ( handle ) ;
if ( ! ok )
{
g_brick_count = 0 ;
g_brick_start = 0 ;
g_series_dirty = true ;
Print ( " RenkoDeepSeek: кэш отсутствует, повреждён или не соответствует настройкам; будет восстановлена история тиков. " ) ;
return false ;
}
g_brick_count = count ;
g_brick_start = 0 ;
g_cycle_start = cycle_start ;
g_direction = direction ;
g_last_tick_msc = last_tick_msc ;
g_ticks_processed_at_last_msc = ticks_at_msc ;
g_have_anchor = have_anchor ;
g_history_partial = history_partial ;
g_rebuild_resume_from = rebuild_resume_from ;
g_rebuild_resume_window_days = rebuild_resume_window_days ;
g_rebuild_partial_accum = rebuild_partial_accum ;
2026-11-05 19:12:31 +03:00
g_history_needs_more = ( rebuild_resume_from > 0 | | rebuild_resume_window_days > 0 ) ;
2026-11-05 13:25:46 +03:00
g_last_close = last_close ;
g_last_bid = last_bid ;
g_last_swing_high = swing_high ;
g_last_swing_low = swing_low ;
g_swing_high_breakout_alerted = high_alerted ;
g_swing_low_breakout_alerted = low_alerted ;
g_swing_high_alert_sent = high_sent ;
g_swing_low_alert_sent = low_sent ;
g_pre_buy_alerted = pre_buy ;
g_pre_sell_alerted = pre_sell ;
g_pre_buy_cancelled = buy_cancelled ;
g_pre_sell_cancelled = sell_cancelled ;
g_cache_loaded = true ;
g_series_dirty = true ;
Print ( " RenkoDeepSeek: восстановлен кэш Renko; кирпичей= " , g_brick_count ,
" ; курсор тиков= " , g_last_tick_msc , " . " ) ;
return true ;
}
// Проверяет, занят ли runtime-ID другим индикатором на этом графике.
bool IsRuntimeInstanceIdInUse ( const string candidate )
{
const string marker = " [ " + candidate ;
const int windows_total = ( int ) ChartGetInteger ( 0 , CHART_WINDOWS_TOTAL ) ;
for ( int window = 0 ; window < windows_total ; + + window )
{
const int indicators_total = ChartIndicatorsTotal ( 0 , window ) ;
for ( int index = 0 ; index < indicators_total ; + + index )
{
const string short_name = ChartIndicatorName ( 0 , window , index ) ;
if ( StringFind ( short_name , marker ) > = 0 )
return true ;
}
}
return false ;
}
// Создаёт уникальный runtime-ID до поиска подокна текущего индикатора.
string MakeUniqueRuntimeInstanceId ( string requested_id )
{
if ( requested_id = = " " )
{
requested_id = " Auto " +
IntegerToString ( ( long ) GetTickCount64 ( ) ) + " _ " +
IntegerToString ( ( long ) GetMicrosecondCount ( ) ) + " _ " +
IntegerToString ( ObjectsTotal ( 0 , -1 , -1 ) ) ;
}
const string base_id = requested_id ;
int suffix = 1 ;
while ( IsRuntimeInstanceIdInUse ( requested_id ) )
{
requested_id = base_id + " _ " + IntegerToString ( suffix ) ;
+ + suffix ;
}
return requested_id ;
}
// Определяет подокно только после присвоения уникального короткого имени.
int ResolveOwnIndicatorWindow ( )
{
int window = ChartWindowFind ( ) ;
if ( window > = 0 )
return window ;
const int windows_total = ( int ) ChartGetInteger ( 0 , CHART_WINDOWS_TOTAL ) ;
for ( int candidate = 0 ; candidate < windows_total ; + + candidate )
{
const int indicators_total = ChartIndicatorsTotal ( 0 , candidate ) ;
for ( int index = 0 ; index < indicators_total ; + + index )
{
if ( ChartIndicatorName ( 0 , candidate , index ) = = g_indicator_short_name )
return candidate ;
}
}
return -1 ;
}
//--- Инициализация буферов, параметров и элементов управления.
int OnInit ( )
{
// Runtime-ID всегда генерируется автоматически; InpInstanceId используется
// только для переноса файлов, созданных прежними версиями индикатора.
string instance_id = MakeUniqueRuntimeInstanceId ( " " ) ;
g_instance_id = instance_id ;
g_indicator_short_name = " RenkoDeepSeek initializing [ " + g_instance_id + " ] " ;
IndicatorSetString ( INDICATOR_SHORTNAME , g_indicator_short_name ) ;
g_instance_window = ResolveOwnIndicatorWindow ( ) ;
if ( g_instance_window < = 0 )
{
Print ( " RenkoDeepSeek: не удалось однозначно определить отдельное подокно текущего экземпляра; запуск остановлен. " ) ;
return INIT_FAILED ;
}
if ( HasOtherRenkoInstanceInWindow ( g_instance_window ) )
{
PrintFormat ( " RenkoDeepSeek: в подокне %d уже работает другой экземпляр RenkoDeepSeek. Второй экземпляр в том же подокне запрещён; настройки и кэш не загружаются. " ,
g_instance_window ) ;
return INIT_FAILED ;
}
if ( ! InitializeInstanceMarker ( g_instance_window ) )
return INIT_FAILED ;
g_instance_identity_ready = true ;
if ( ! InpPreferSavedBrickSize )
Print ( " RenkoDeepSeek: параметр InpPreferSavedBrickSize устарел и игнорируется; сохранённый размер кирпича всегда имеет приоритет. " ) ;
if ( InpBrickPoints < 1 | | InpMaxHistoryBricks < 1 | |
InpMaxHistoryBricks > MAX_HISTORY_BRICKS_ALLOWED | |
InpCycleStartHour < 0 | | InpCycleStartHour > 23 | |
InpCycleStartMinute < 0 | | InpCycleStartMinute > 59 | |
InpPreBreakoutDistanceBricks < = 0.0 | | InpPreBreakoutDistanceBricks > 5.0 | |
InpPreBreakoutDistancePoints < 0 | | InpStopLossBufferPoints < 0 )
{
PrintFormat ( " RenkoDeepSeek: проверьте размер кирпича, лимит истории (1–%d кирпичей), время цикла, порог раннего сигнала (>0 и <=5 кирпичей; пункты >=0) и буфер SL (>=0). " , MAX_HISTORY_BRICKS_ALLOWED ) ;
return INIT_PARAMETERS_INCORRECT ;
}
if ( ArrayResize ( g_bricks , InpMaxHistoryBricks , 256 ) ! = InpMaxHistoryBricks )
{
Print ( " RenkoDeepSeek: не удалось выделить кольцевой буфер истории на " , InpMaxHistoryBricks , " кирпичей; код ошибки= " , GetLastError ( ) ) ;
return INIT_FAILED ;
}
g_brick_start = 0 ;
if ( ! SetIndexBuffer ( 0 , g_open_buffer , INDICATOR_DATA ) | |
! SetIndexBuffer ( 1 , g_high_buffer , INDICATOR_DATA ) | |
! SetIndexBuffer ( 2 , g_low_buffer , INDICATOR_DATA ) | |
! SetIndexBuffer ( 3 , g_close_buffer , INDICATOR_DATA ) | |
! SetIndexBuffer ( 4 , g_color_buffer , INDICATOR_COLOR_INDEX ) )
return INIT_FAILED ;
ArraySetAsSeries ( g_open_buffer , true ) ;
ArraySetAsSeries ( g_high_buffer , true ) ;
ArraySetAsSeries ( g_low_buffer , true ) ;
ArraySetAsSeries ( g_close_buffer , true ) ;
ArraySetAsSeries ( g_color_buffer , true ) ;
PlotIndexSetDouble ( 0 , PLOT_EMPTY_VALUE , EMPTY_VALUE ) ;
PlotIndexSetInteger ( 0 , PLOT_SHOW_DATA , false ) ;
PlotIndexSetInteger ( 0 , PLOT_COLOR_INDEXES , 2 ) ;
PlotIndexSetInteger ( 0 , PLOT_LINE_COLOR , 0 , InpUpColor ) ;
PlotIndexSetInteger ( 0 , PLOT_LINE_COLOR , 1 , InpDownColor ) ;
IndicatorSetInteger ( INDICATOR_DIGITS , _Digits ) ;
RefreshServerOffset ( ) ;
double tick_size = 0.0 ;
ResetLastError ( ) ;
if ( ! SymbolInfoDouble ( _Symbol , SYMBOL_TRADE_TICK_SIZE , tick_size ) | |
! MathIsValidNumber ( tick_size ) | | tick_size < = 0.0 )
{
PrintFormat ( " RenkoDeepSeek: не удалось получить корректный SYMBOL_TRADE_TICK_SIZE для %s; автоматическая подмена на _Point запрещена. Код ошибки=%d. " ,
_Symbol , GetLastError ( ) ) ;
return INIT_FAILED ;
}
g_price_step = tick_size ;
MigrateLegacyInstanceSettings ( ) ;
g_current_brick_points = LoadSavedBrickPoints ( InpBrickPoints ) ;
if ( ! IsRenkoGridCompatible ( g_current_brick_points , true ) )
{
PrintFormat ( " RenkoDeepSeek: эффективный размер кирпича %d пунктов несовместим с Anchor/tick grid; запуск отменён. Автоматический переход на InpBrickPoints или округление запрещены. " ,
g_current_brick_points ) ;
return INIT_PARAMETERS_INCORRECT ;
}
g_price_grid_valid = true ;
// InpBreakoutAlerts — стартовый мастер-флаг; файл настроек имеет приоритет.
ApplyAlertInputs ( ) ;
LoadButtonSettings ( ) ;
SyncAlertMasterState ( ) ;
g_settings_initialized = true ;
g_brick_size = ( double ) g_current_brick_points * _Point ;
g_cache_loaded = LoadRenkoCache ( ) ;
2026-11-05 19:12:31 +03:00
g_history_initialized = ( g_cache_loaded & & HasUsableRenkoState ( ) & &
! g_history_needs_more & & ! g_history_partial ) ;
2026-11-05 13:25:46 +03:00
g_indicator_short_name = BuildShortName ( ) ;
IndicatorSetString ( INDICATOR_SHORTNAME , g_indicator_short_name ) ;
g_control_y_offset = 0 ;
const int objects_before_create = ObjectsTotal ( 0 , -1 , -1 ) ;
g_object_prefix = " RGB. " + IntegerToString ( ( long ) ChartID ( ) ) + " . " +
IntegerToString ( g_instance_window ) + " . " +
IntegerToString ( ( long ) HashText ( InstanceIdentityKey ( ) ) ) + " . " +
IntegerToString ( ( long ) GetMicrosecondCount ( ) ) + " . " +
IntegerToString ( objects_before_create ) + " . " ;
CreateControlObjects ( ) ;
ChartRedraw ( 0 ) ;
2026-11-05 19:12:31 +03:00
if ( EventSetTimer ( 1 ) )
2026-11-05 13:25:46 +03:00
g_cache_timer_enabled = true ;
else
Print ( " RenkoDeepSeek: не удалось включить периодическое сохранение Renko-кэша; останется сохранение при деинициализации. Код ошибки= " , GetLastError ( ) ) ;
return INIT_SUCCEEDED ;
}
2026-11-05 19:12:31 +03:00
//--- Пошаговое восстановление истории и периодическое сохранение кэша.
2026-11-05 13:25:46 +03:00
void OnTimer ( )
{
2026-11-05 19:12:31 +03:00
if ( ! g_instance_identity_ready | | ! g_settings_initialized )
2026-11-05 13:25:46 +03:00
return ;
2026-11-05 19:12:31 +03:00
const bool need_history = ( ! g_history_initialized | | g_history_needs_more ) ;
if ( need_history )
{
int retry_delay = 30 ;
for ( int retry_step = 0 ; retry_step < g_history_retry_count & & retry_delay < 900 ; + + retry_step )
retry_delay = ( int ) MathMin ( retry_delay * 2 , 900 ) ;
const ulong wait_ms = ( g_history_needs_more ? ( ulong ) HISTORY_RESUME_DELAY_MS
: ( ulong ) retry_delay * 1000 ) ;
if ( g_last_history_attempt_ms = = 0 | |
GetTickCount64 ( ) - g_last_history_attempt_ms > = wait_ms )
{
if ( RebuildHistory ( ) )
{
g_history_retry_count = 0 ;
// Не разрешаем live catch-up, пока не закончены все исторические
// окна и не закрыт checkpoint текущего чанка.
g_history_initialized = ( HasUsableRenkoState ( ) & &
! g_history_needs_more & &
! g_history_partial ) ;
}
else if ( g_history_retry_count < 10 )
+ + g_history_retry_count ;
if ( ArraySize ( g_open_buffer ) > 0 )
{
RenderBricks ( ArraySize ( g_open_buffer ) , false ) ;
ChartRedraw ( 0 ) ;
}
}
}
if ( g_cache_dirty & &
( g_history_initialized | | g_rebuild_resume_from > 0 | | g_history_needs_more ) )
SaveRenkoCache ( ) ;
2026-11-05 13:25:46 +03:00
}
//--- Удаление элементов управления текущего экземпляра.
void OnDeinit ( const int reason )
{
if ( g_cache_timer_enabled )
{
EventKillTimer ( ) ;
g_cache_timer_enabled = false ;
}
const int resolved_window = ChartWindowFind ( ) ;
const int marker_window = ( g_instance_marker_name = = " " ? -1 : ObjectFind ( 0 , g_instance_marker_name ) ) ;
if ( resolved_window > 0 & & marker_window = = resolved_window )
g_instance_window = resolved_window ;
else if ( marker_window ! = g_instance_window )
{
g_instance_identity_ready = false ;
Print ( " RenkoDeepSeek: маркер экземпляра больше не соответствует подокну; сохранение настроек и кэша пропущено во избежание записи в чужое пространство. " ) ;
}
if ( g_instance_identity_ready & & g_settings_initialized & & g_instance_window > 0 )
{
if ( g_current_brick_points > = 1 & & ! SaveBrickPoints ( g_current_brick_points ) )
Print ( " RenkoDeepSeek: не удалось повторно сохранить размер кирпича при деинициализации. " ) ;
if ( ! SaveButtonSettings ( ) )
Print ( " RenkoDeepSeek: не удалось повторно сохранить состояния кнопок при деинициализации. " ) ;
}
else if ( g_settings_initialized & & ! g_instance_identity_ready )
Print ( " RenkoDeepSeek: настройки не сохранены при деинициализации — идентичность экземпляра не подтверждена. " ) ;
else if ( g_settings_initialized )
Print ( " RenkoDeepSeek: настройки не сохранены при деинициализации — подокно экземпляра не удалось определить. " ) ;
if ( g_instance_identity_ready & &
2026-11-05 19:12:31 +03:00
( g_history_initialized | | g_rebuild_resume_from > 0 | | g_history_needs_more ) & &
2026-11-05 13:25:46 +03:00
! SaveRenkoCache ( ) )
Print ( " RenkoDeepSeek: снимок состояния не сохранён; при следующем запуске может потребоваться восстановление истории. " ) ;
if ( g_object_prefix ! = " " )
{
for ( int i = ObjectsTotal ( 0 , -1 , -1 ) - 1 ; i > = 0 ; - - i )
{
const string object_name = ObjectName ( 0 , i , -1 , -1 ) ;
if ( StringFind ( object_name , g_object_prefix ) = = 0 )
ObjectDelete ( 0 , object_name ) ;
}
}
}
//--- Обработка нажатий кнопок индикатора.
void OnChartEvent ( const int id ,
const long & lparam ,
const double & dparam ,
const string & sparam )
{
if ( id ! = CHARTEVENT_OBJECT_CLICK )
return ;
const int current_window = ChartWindowFind ( ) ;
if ( current_window < = 0 | | ObjectFind ( 0 , g_instance_marker_name ) ! = current_window )
{
g_instance_identity_ready = false ;
g_price_grid_valid = false ;
Print ( " RenkoDeepSeek: идентичность подокна не подтверждена; действие кнопки отклонено. " ) ;
return ;
}
if ( current_window ! = g_instance_window & & HasOtherRenkoInstanceInWindow ( current_window ) )
{
g_instance_identity_ready = false ;
g_price_grid_valid = false ;
Print ( " RenkoDeepSeek: обнаружен конфликт экземпляров в подокне; действие кнопки отклонено. " ) ;
return ;
}
g_instance_window = current_window ;
if ( sparam = = g_brick_apply_name )
{
ApplyBrickSizeFromEdit ( ) ;
return ;
}
bool handled = true ;
if ( sparam = = g_breakout_high_button_name )
g_breakout_high_enabled = ! g_breakout_high_enabled ;
else if ( sparam = = g_breakout_low_button_name )
g_breakout_low_enabled = ! g_breakout_low_enabled ;
else if ( sparam = = g_brick_reversal_button_name )
g_brick_reversal_enabled = ! g_brick_reversal_enabled ;
else if ( sparam = = g_pre_buy_button_name )
g_pre_buy_enabled = ! g_pre_buy_enabled ;
else if ( sparam = = g_pre_sell_button_name )
g_pre_sell_enabled = ! g_pre_sell_enabled ;
else if ( sparam = = g_alert_button_name )
{
// Любая активная категория означает, что нажатие выключает все;
// полностью выключенное состояние переключается сразу во включённое.
const bool target = ! g_alert_enabled ;
g_breakout_high_enabled = target ;
g_breakout_low_enabled = target ;
g_brick_reversal_enabled = target ;
g_pre_buy_enabled = target ;
g_pre_sell_enabled = target ;
}
else
handled = false ;
if ( ! handled )
return ;
SyncAlertMasterState ( ) ;
RefreshAllAlertButtons ( ) ;
UpdatePreBreakoutPanel ( g_last_bid , true ) ;
SaveButtonSettings ( ) ;
ChartRedraw ( 0 ) ;
}
//--- Обновление истории и обработка новых рыночных тиков.
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 ( rates_total < = 0 | | ! g_price_grid_valid | | ! g_instance_identity_ready )
return 0 ;
const int current_window = ChartWindowFind ( ) ;
if ( current_window < = 0 )
{
g_instance_identity_ready = false ;
g_price_grid_valid = false ;
Print ( " RenkoDeepSeek: не удалось подтвердить отдельное подокно экземпляра; расчёт остановлен. " ) ;
return 0 ;
}
if ( current_window > 0 & & ObjectFind ( 0 , g_instance_marker_name ) ! = current_window )
{
g_instance_identity_ready = false ;
g_price_grid_valid = false ;
Print ( " RenkoDeepSeek: маркер идентичности отсутствует в собственном подокне; расчёт остановлен. " ) ;
return 0 ;
}
if ( current_window > 0 & & current_window ! = g_instance_window )
{
if ( HasOtherRenkoInstanceInWindow ( current_window ) )
{
g_instance_identity_ready = false ;
g_price_grid_valid = false ;
Print ( " RenkoDeepSeek: после изменения структуры графика обнаружен конфликт экземпляров; расчёт остановлен. " ) ;
return 0 ;
}
g_instance_window = current_window ;
}
RefreshServerOffset ( ) ;
if ( g_alert_button_name = = " " | | ObjectFind ( 0 , g_alert_button_name ) < 0 | |
g_breakout_high_button_name = = " " | | ObjectFind ( 0 , g_breakout_high_button_name ) < 0 | |
g_breakout_low_button_name = = " " | | ObjectFind ( 0 , g_breakout_low_button_name ) < 0 | |
g_brick_reversal_button_name = = " " | | ObjectFind ( 0 , g_brick_reversal_button_name ) < 0 | |
g_pre_buy_button_name = = " " | | ObjectFind ( 0 , g_pre_buy_button_name ) < 0 | |
g_pre_sell_button_name = = " " | | ObjectFind ( 0 , g_pre_sell_button_name ) < 0 | |
g_pre_status_label_name = = " " | | ObjectFind ( 0 , g_pre_status_label_name ) < 0 | |
g_pre_detail_label_name = = " " | | ObjectFind ( 0 , g_pre_detail_label_name ) < 0 | |
g_pre_sl_label_name = = " " | | ObjectFind ( 0 , g_pre_sl_label_name ) < 0 | |
g_brick_edit_name = = " " | | ObjectFind ( 0 , g_brick_edit_name ) < 0 | |
g_brick_apply_name = = " " | | ObjectFind ( 0 , g_brick_apply_name ) < 0 )
{
CreateControlObjects ( ) ;
ChartRedraw ( 0 ) ;
}
if ( g_history_initialized )
{
MqlTick latest_tick ;
if ( SymbolInfoTick ( _Symbol , latest_tick ) & & latest_tick . bid > 0.0 )
{
if ( g_last_tick_msc = = 0 )
{
ProcessBid ( ( datetime ) latest_tick . time , latest_tick . bid , true ) ;
g_last_tick_msc = latest_tick . time_msc ;
g_ticks_processed_at_last_msc = 1 ;
g_last_bid = latest_tick . bid ;
g_cache_dirty = true ;
}
else if ( latest_tick . time_msc > = g_last_tick_msc )
{
// Догоняем сервер короткими интервалами: один большой запрос
// (например, после выходных) упирается в тайм-аут 4403.
int catchup_guard = 0 ;
2026-11-05 19:12:31 +03:00
while ( catchup_guard + + < 1 & & g_last_tick_msc < latest_tick . time_msc )
2026-11-05 13:25:46 +03:00
{
const long cursor_before = g_last_tick_msc ;
long request_to = cursor_before + ( long ) HISTORY_CHUNK_SECONDS * 1000 ;
if ( request_to > latest_tick . time_msc )
request_to = latest_tick . time_msc ;
MqlTick new_ticks [ ] ;
ResetLastError ( ) ;
2026-11-05 19:12:31 +03:00
int copied = CopyTicksRange ( _Symbol , new_ticks , COPY_TICKS_INFO ,
2026-11-05 13:25:46 +03:00
( ulong ) cursor_before , ( ulong ) request_to ) ;
2026-11-05 19:12:31 +03:00
int copy_error = GetLastError ( ) ;
if ( copied = = 0 )
{
MqlTick fallback_ticks [ ] ;
ResetLastError ( ) ;
const int fallback_copied = CopyTicks ( _Symbol , fallback_ticks , COPY_TICKS_INFO ,
( ulong ) cursor_before , 1000 ) ;
const int fallback_error = GetLastError ( ) ;
int in_range = 0 ;
for ( int k = 0 ; k < fallback_copied ; + + k )
{
if ( fallback_ticks [ k ] . time_msc > request_to )
break ;
+ + in_range ;
}
if ( in_range > 0 )
{
if ( ArrayResize ( new_ticks , in_range ) ! = in_range )
{
copied = -1 ;
copy_error = 4004 ;
}
else
{
for ( int k = 0 ; k < in_range ; + + k )
new_ticks [ k ] = fallback_ticks [ k ] ;
copied = in_range ;
copy_error = ( fallback_copied > = 1000 & & in_range = = fallback_copied
? 4403 : fallback_error ) ;
}
}
else if ( fallback_copied > 0 )
{
copied = 0 ;
copy_error = 0 ;
}
else
{
copied = 0 ;
if ( fallback_error = = 0 & &
request_to + EMPTY_RANGE_CONFIRM_MS < latest_tick . time_msc )
copy_error = 0 ;
else
copy_error = ( fallback_error = = 0 ? 4403 : fallback_error ) ;
}
}
2026-11-05 13:25:46 +03:00
if ( copied < 0 & & copy_error ! = 4403 )
{
Print ( " RenkoDeepSeek: догон тиков приостановлен: CopyTicksRange не подтвердил диапазон после курсора (код= " ,
copy_error , " ). Текущая серия и курсор сохранены; будет повтор того же диапазона без полной пересборки. " ) ;
break ;
}
if ( copy_error = = 4403 & & copied < = 0 )
{
if ( g_last_tick_copy_warning = = 0 | | TimeCurrent ( ) - g_last_tick_copy_warning > = 10 )
{
Print ( " RenkoDeepSeek: догон тиков приостановлен (4403; доступных тиков= " ,
copied , " ) на курсоре " , cursor_before , " ; повтор при следующем тике. " ) ;
g_last_tick_copy_warning = TimeCurrent ( ) ;
}
break ;
}
if ( copied = = 0 & & copy_error = = 0 )
{
// Пустой ответ не доказывает, что участок выгружен. Курсор
// сдвигаем только по давно прошедшему интервалу: иначе можно
// навсегда потерять реальные тики (и сделанные по ним сигналы).
if ( request_to + EMPTY_RANGE_CONFIRM_MS < latest_tick . time_msc )
{
g_last_tick_msc = request_to ;
g_ticks_processed_at_last_msc = 0 ;
g_cache_dirty = true ;
}
else
break ;
}
if ( copied > 0 )
{
int seen_at_cursor_msc = 0 ;
2026-11-05 19:12:31 +03:00
int processed_this_calculate = 0 ;
2026-11-05 13:25:46 +03:00
for ( int i = 0 ; i < copied ; + + i )
{
const long tick_msc = new_ticks [ i ] . time_msc ;
if ( tick_msc < g_last_tick_msc )
continue ;
if ( tick_msc = = g_last_tick_msc )
{
+ + seen_at_cursor_msc ;
if ( seen_at_cursor_msc < = g_ticks_processed_at_last_msc )
continue ;
}
2026-11-05 19:12:31 +03:00
if ( processed_this_calculate > = 500 )
break ;
+ + processed_this_calculate ;
2026-11-05 13:25:46 +03:00
if ( new_ticks [ i ] . bid < = 0.0 )
{
if ( tick_msc > g_last_tick_msc )
{
g_last_tick_msc = tick_msc ;
g_ticks_processed_at_last_msc = 1 ;
}
else if ( tick_msc = = g_last_tick_msc )
+ + g_ticks_processed_at_last_msc ;
g_cache_dirty = true ;
continue ;
}
// Оповещения разрешены только по «живым» тикам: догон
// не должен выдавать алерты по событиям, которые уже прошли.
const bool live_tick = ( latest_tick . time_msc - tick_msc ) < = TICK_LIVE_ALERT_WINDOW_MS ;
ProcessBid ( ( datetime ) new_ticks [ i ] . time , new_ticks [ i ] . bid , live_tick ) ;
if ( tick_msc > g_last_tick_msc )
{
g_last_tick_msc = tick_msc ;
g_ticks_processed_at_last_msc = 1 ;
}
else if ( tick_msc = = g_last_tick_msc )
+ + g_ticks_processed_at_last_msc ;
g_last_bid = new_ticks [ i ] . bid ;
g_cache_dirty = true ;
}
}
if ( copy_error = = 4403 )
{
if ( g_last_tick_copy_warning = = 0 | | TimeCurrent ( ) - g_last_tick_copy_warning > = 10 )
{
Print ( " RenkoDeepSeek: обработаны доступные тики при тайм-ауте 4403; догон продолжится при следующем тике. " ) ;
g_last_tick_copy_warning = TimeCurrent ( ) ;
}
// Не запрашиваем следующий интервал: текущий ответ неполон.
// Следующий OnCalculate повторит запрос с обновлённого курсора.
break ;
}
// Курсор не сдвинулся — дальше крутиться бессмысленно.
if ( g_last_tick_msc < = cursor_before )
break ;
}
}
else
{
// Серверное время временно отстало от сохранённого курсора.
// Не обнуляем пригодную серию и не запускаем полную пересборку:
// ждём, пока поток тиков достигнет сохранённой временной позиции.
if ( g_last_tick_copy_warning = = 0 | | TimeCurrent ( ) - g_last_tick_copy_warning > = 60 )
{
Print ( " RenkoDeepSeek: курсор тиков ( " , g_last_tick_msc ,
" мс) впереди времени последнего тика ( " , latest_tick . time_msc ,
" мс); обработка приостановлена без сброса кэша и серии. " ) ;
g_last_tick_copy_warning = TimeCurrent ( ) ;
}
}
}
}
if ( g_last_bid > 0.0 ) UpdatePreBreakoutPanel ( g_last_bid , false ) ;
RenderBricks ( rates_total , false ) ;
return rates_total ;
}
//+------------------------------------------------------------------+