// Сравнение размера истории пользовательских символов MT5 при двух способах записи M1:
// BULK: отдельный новый символ для каждого количества M1 (30, 60, 90...) и один CustomRatesReplace
// SEQ: один символ, в который те же M1 записываются последовательно по одному бару
// На каждом этапе обе истории содержат одинаковое количество одинаковых свечей
// Размер YYYY.hcc читается с диска
#property strict
#property script_show_inputs

input int InpTestMinutes = 3;          // Длительность теста (минуты)

#define ORIGIN_SYMBOL "EURUSD"         // Символ-донор

#define INTERVAL_MS 1000               // Пауза между последовательными записями — 1 секунда
#define SIZE_STEP 30                   // Контроль размера HCC через каждые 30 записей

// WinAPI для чтения физического размера файла HCC на диске
#import "kernel32.dll"
int GetFileAttributesExW(string file_name, int info_level, uchar &file_info[]);
#import

// Выполняем A/B: для каждого этапа BULK записывает все N баров за один вызов, SEQ — по одному
void OnStart()
  {
   Print("Terminal build: ", (int)TerminalInfoInteger(TERMINAL_BUILD));

   if(InpTestMinutes <= 0)                             // Продолжительность теста должна быть положительной
      return;

   const int synthetic_bar_count = InpTestMinutes * 60; // Кол-во искусственных M1: одна последовательная запись в секунду
   PrintFormat("Test duration: %d min; M1 bars: %d; size step: %d M1",
               InpTestMinutes, synthetic_bar_count, SIZE_STEP);

   MqlDateTime start = {};                            // Создаём структуру даты и обнуляем все её поля
   MqlDateTime now;                                   // Создаём переменную для текущей даты и времени по отдельным полям
   TimeToStruct(TimeLocal(), now);                    // Получаем текущую дату и время компьютера
   start.year = now.year - 1;                         // Предыдущий календарный год
   start.mon = 6;
   start.day = 1;                                     // Начиная с 1 июня 00:00
   datetime first = StructToTime(start);              // Время первой искусственной M1-свечи

   MqlRates bars[];                                   // Создаём массив всех искусственных M1-свечей теста
   ArrayResize(bars, synthetic_bar_count);

// Создаём заданное количество M1-свечей, по одной на каждую минуту
   for(int i = 0; i < synthetic_bar_count; i++)
     {
      ZeroMemory(bars[i]);                            // Создаём свечу
      bars[i].time = first + i * 60;                  // Последовательные минуты без пропусков
      bars[i].open = 1.10000;                         // Фиксированные искусственные цены для всех M1
      bars[i].high = 1.10010;
      bars[i].low = 1.09990;
      bars[i].close = 1.10005;
      bars[i].tick_volume = 10;                       // Фиксированные искусственные объёмы и спред
      bars[i].real_volume = 10;
      bars[i].spread = 2;
     }

   string id = MakeRunId();                           // Уникальный идентификатор текущего запуска теста
   string seq = "TestHCCSeq_" + id;                   // Один SEQ-символ на все 180 последовательных записей
   if(!PrepareSymbol(seq))                            // Создаём SEQ-символ до начала записи
      return;

   string seq_path = HccPath(seq, start.year);        // Путь к годовому HCC-файлу SEQ
   Print("SEQ symbol: ", seq);
   Print("SEQ HCC: ", seq_path);

   ulong last_bulk_bytes = 0;                         // Размер последнего BULK-файла, записанного одним вызовом
   ulong last_seq_bytes = 0;                          // Размер SEQ-файла после такого же количества M1
   bool sizes_ok = true;                              // Результат всех измерений размера на диске

// SEQ записывает все M1 по одному; после каждых 30 M1 создаём отдельный BULK с тем же набором свечей
   for(int i = 0; i < synthetic_bar_count && !IsStopped(); i++) // Выполняем отдельный Replace для каждой M1
     {
      MqlRates one[1];                                                 // Массив для единственной свечи
      one[0] = bars[i];                                                // Копируем очередную свечу из исходного массива

      ResetLastError();
      int rc = CustomRatesReplace(seq, one[0].time, one[0].time, one); // Записываем в SEQ одну M1-свечу
      if(rc != 1)                                                      // Проверяем успешную запись одного бара
        {
         Print("SEQ write failed at #", i + 1, ": rc=", rc, " error=", GetLastError());
         return;
        }

      int count = i + 1;                                             // Количество M1, уже записанных в SEQ
      if(count % SIZE_STEP == 0 || count == synthetic_bar_count)     // Контроль каждые 30 M1 и в конце теста
        {
         string bulk = StringFormat("HCCBulk_%d_%s", count, id);     // Новый  BULK-символ для N свечей
         if(!PrepareSymbol(bulk))                                    // Каждый BULK создаётся с пустой историей
            return;

         MqlRates bulk_bars[];                                       // Отдельный массив из первых N исходных свечей
         ArrayResize(bulk_bars, count);
         for(int j = 0; j < count; j++)                              // Копируем исходные свечи с первой по N-ю
            bulk_bars[j] = bars[j];

         ResetLastError();
         int bulk_rc = CustomRatesReplace(bulk, first, bulk_bars[count - 1].time, bulk_bars); // Все N M1 за один вызов
         if(bulk_rc != count)                                                                 // Проверяем, что записаны все N свечей
           {
            Print("BULK write failed at ", count, " M1: rc=", bulk_rc, " error=", GetLastError());
            return;
           }

         string bulk_path = HccPath(bulk, start.year); // Путь к годовому HCC нового BULK-символа
         Print("BULK symbol for ", count, " M1: ", bulk);
         Print("BULK HCC: ", bulk_path);

         Sleep(250);                                  // Ждём перед измерением размеров обоих файлов
         ulong bulk_bytes = 0;                        // Размер HCC нового BULK с N свечами
         ulong seq_bytes = 0;                         // Размер HCC SEQ с теми же N свечами
         bool bulk_ok = PrintSize(StringFormat("BULK after 1 call (%d M1)", count), bulk_path, bulk_bytes);
         bool seq_ok = PrintSize(StringFormat("SEQ after %d calls (%d M1)", count, count), seq_path, seq_bytes);

         // Проверяем на каждом этапе не только количество, но и все поля записанных свечей
         if(!CheckHistory(bulk, bulk_bars) || !CheckHistory(seq, bulk_bars))
           {
            Print("RESULT: FAIL — сохранённые M1 не совпадают с исходными, этап ", count);
            return;
           }

         if(!bulk_ok || !seq_ok || bulk_bytes == 0)    // Если размер хотя бы одного файла недоступен, не считаем отношение
            sizes_ok = false;
         else
            PrintFormat("POINT: M1=%d, BULK=%I64u bytes (1 call), SEQ=%I64u bytes (%d calls), SEQ/BULK=%.3fx",
                        count, bulk_bytes, seq_bytes, count, (double)seq_bytes / (double)bulk_bytes);

         last_bulk_bytes = bulk_bytes;                 // Запоминаем измерения последнего этапа
         last_seq_bytes = seq_bytes;
        }

      if(count < synthetic_bar_count)                 // Если впереди остались свечи для записи
         Sleep(INTERVAL_MS);                          // Ждём одну секунду перед следующим SEQ-вызовом
     }

   if(IsStopped())                                    // Не сообщаем об успешном тесте, если скрипт был остановлен
      return;

   if(!sizes_ok || last_bulk_bytes == 0)              // Проверяем, что оба размера доступны на всех этапах
     {
      Print("RESULT: M1 PASS, HCC size unavailable (разрешите импорт DLL)");
      return;
     }

   PrintFormat("RESULT: BULK=%I64u bytes (1 call, %d M1), SEQ=%I64u bytes (%d one-bar calls), SEQ/BULK=%.3fx, M1=EQUAL",
               last_bulk_bytes, synthetic_bar_count, last_seq_bytes, synthetic_bar_count,
               (double)last_seq_bytes / (double)last_bulk_bytes);
   Print("Тестовые символы сохранены для проверки; исходные котировки не изменялись.");
  }

// Уникальная часть имён, чтобы не смешивать историю с прошлыми запусками
string MakeRunId()
  {
   MqlDateTime t;
   TimeToStruct(TimeLocal(), t);
   return StringFormat("%02d%02d%02d_%02d%02d%02d_%04I64u",
                       t.year % 100, t.mon, t.day, t.hour, t.min, t.sec,
                       GetTickCount64() % 10000);
  }

// Создаём новый custom-symbol на основе EURUSD с торговыми сессиями 24/7
bool PrepareSymbol(const string name)
  {
   if(SymbolInfoInteger(name, SYMBOL_EXIST))                // Старый символ с таким именем не используем
     {
      Print(name, ": symbol name collision; test stopped");
      return false;
     }

   ResetLastError();
   if(!CustomSymbolCreate(name, "HCCForum", ORIGIN_SYMBOL)) // Создаём на основе обычного EURUSD
     {
      Print(name, ": CustomSymbolCreate failed: ", GetLastError());
      return false;
     }

   for(int d = 0; d < 7; d++)                               // Сессии 24/7
     {
      CustomSymbolSetSessionTrade(name, (ENUM_DAY_OF_WEEK)d, 0, 0, 86400);
      CustomSymbolSetSessionQuote(name, (ENUM_DAY_OF_WEEK)d, 0, 0, 86400);
     }

   ResetLastError();
   if(!SymbolSelect(name, true))                             // Добавляем символ в Market Watch
     {
      Print(name, ": SymbolSelect failed: ", GetLastError());
      return false;
     }
   return true;
  }

// Путь к годовому файлу M1-истории пользовательского символа
string HccPath(const string name, const int year)
  {
   return TerminalInfoString(TERMINAL_DATA_PATH) +
          "\\bases\\Custom\\history\\" + name + "\\" + IntegerToString(year) + ".hcc";
  }

// Читаем физический размер HCC через GetFileAttributesExW
bool HccSize(const string file_path, ulong &size)
  {
   uchar info[];                   // Создаём массив байтов для получения информации о файле через Windows API

   if(ArrayResize(info, 36) != 36) // Выделяем 36 байт для структуры WIN32_FILE_ATTRIBUTE_DATA и проверяем успешность операции
      return false;
   ArrayInitialize(info, 0);       // Заполняем весь массив нулями перед обращением к Windows API
   size = 0;                       // Обнуляем переменную, в которую будет записан размер HCC-файла

   if(GetFileAttributesExW(file_path, 0, info) == 0)        // Запрашиваем у Windows информацию о файле по указанному пути
      return false;

   ulong high = (ulong)info[28] | ((ulong)info[29] << 8) |  // Собираем старшие 32 бита размера файла из четырёх байтов
                ((ulong)info[30] << 16) | ((ulong)info[31] << 24);

   ulong low  = (ulong)info[32] | ((ulong)info[33] << 8) |  // Собираем младшие 32 бита размера файла из следующих четырёх байтов
                ((ulong)info[34] << 16) | ((ulong)info[35] << 24);

   size = (high << 32) | low;                               // Объединяем старшую и младшую части в полный 64-битный размер файла в байтах
   return true;
  }

// Выводим размер HCC; если файл недоступен, не выдаём это за нулевой размер
bool PrintSize(const string label, const string path, ulong &size)
  {
   if(!HccSize(path, size)) // Проверяем, удалось ли получить размер HCC-файла по указанному пути
     {
      Print(label, ": HCC size unavailable: ", path);
      return false;
     }
   PrintFormat("%s: HCC = %I64u bytes", label, size);
   return true;
  }

// Проверяем, что все поля каждой M1-свечи после записи совпали с исходными
bool CheckHistory(const string name, const MqlRates &expected[])
  {
   MqlRates actual[];                                       // Создаём массив для чтения M1-свечей, сохранённых в тестовом символе
   ArraySetAsSeries(actual, false);                         // Устанавливаем порядок свечей в массиве: от старых к новым
   int count = ArraySize(expected);                         // Получаем количество искусственных M1-свечей, которые должны находиться в истории
   int got = CopyRates(name, PERIOD_M1, expected[0].time,   // Запрашиваем сохранённые M1-свечи, начиная со времени первой исходной свечи
                       expected[count - 1].time, actual);   // Заканчиваем временем последней исходной свечи и сохраняем результат в actual

   if(got != count)                                                // Если количество полученных свечей не совпадает с ожидаемым
     {
      PrintFormat("%s: expected %d M1, got %d", name, count, got); // Выводим имя символа, ожидаемое и фактическое количество свечей
      return false;                                                // Прекращаем проверку, если количество свечей не совпадает
     }

   for(int i = 0; i < count; i++)                            // Поочерёдно сравниваем каждую сохранённую свечу с соответствующей исходной
     {
      if(actual[i].time        != expected[i].time        || // Проверяем совпадение времени открытия свечи
         actual[i].open        != expected[i].open        || // Проверяем совпадение цены открытия
         actual[i].high        != expected[i].high        || // Проверяем совпадение максимальной цены
         actual[i].low         != expected[i].low         || // Проверяем совпадение минимальной цены
         actual[i].close       != expected[i].close       || // Проверяем совпадение цены закрытия
         actual[i].tick_volume != expected[i].tick_volume || // Проверяем совпадение тикового объёма
         actual[i].real_volume != expected[i].real_volume || // Проверяем совпадение реального объёма
         actual[i].spread      != expected[i].spread)        // Проверяем совпадение спреда
        {
         PrintFormat("%s: M1 mismatch at index %d", name, i); // Выводим имя символа и номер свечи, в которой обнаружено отличие
         return false;                                        // Прекращаем проверку, поскольку содержимое истории отличается
        }
     }

   PrintFormat("%s: %d M1, all fields match input", name, count); // Сообщаем, что все сохранённые свечи полностью совпадают с исходными
   return true;                                                   // Возвращаем успешный результат проверки истории
  }
//+------------------------------------------------------------------+
