Особенности языка mql5, тонкости и приёмы работы - страница 334

 
amrali #:
Вы не можете открыть сделку в пятницу, 03.04.2026, так как рынок закрыт (Страстная пятница перед Пасхальным воскресеньем).

Иногда могу открыть/закрыть.


Считаю, что 5/6 результатов ниже правильные.

void OnStart()
{
  const datetime OpenTime1 = D'2026.04.02 11:00';
  const datetime CloseTime1 = D'2026.04.03 02:00';    

  Print(HOLIDAY::GetDealLength(OpenTime1, CloseTime1)); // 54000
  Print(HOLIDAY::GetDealLength_Cached(OpenTime1, CloseTime1)); // 54000

  const datetime OpenTime2 = D'2026.04.02 11:00';
  const datetime CloseTime2 = D'2026.04.06 02:00';    

  Print(HOLIDAY::GetDealLength(OpenTime2, CloseTime2)); // 54000
  Print(HOLIDAY::GetDealLength_Cached(OpenTime2, CloseTime2)); // 54000

  const datetime OpenTime3 = D'2026.04.03 11:00';
  const datetime CloseTime3 = D'2026.04.06 02:00';    

  Print(HOLIDAY::GetDealLength(OpenTime3, CloseTime3)); // 54000
  Print(HOLIDAY::GetDealLength_Cached(OpenTime3, CloseTime3)); // -32400
}
 
fxsaber #:

Я считаю, что 5 из 6 приведенных ниже результатов верны.


Исправление:

  static int GetDealLength_Cached(
    datetime openTime,
    datetime closeTime)
  {
    int totalSeconds =
      (int)(closeTime - openTime);

    int a = DayIndex(openTime + DAY - 1 - Epoch);
    int b = DayIndex(closeTime - Epoch);

    int holidays = Prefix[b] - Prefix[a];

    return totalSeconds - holidays * DAY;
  }

Результат:

void OnStart()
{
  const datetime OpenTime3 = D'2026.04.03 11:00';
  const datetime CloseTime3 = D'2026.04.06 02:00';    

  Print(HOLIDAY::GetDealLength(OpenTime3, CloseTime3)); // 54 000
  Print(HOLIDAY::GetDealLength_Cached(OpenTime3, CloseTime3)); // 54 000
}
Файлы:
Holiday4.mq5  16 kb
 
amrali #:

Исправление:

Да, теперь совпадает.


Я взял реальную задачу, когда в конце бэктеста нужно посчитать длительность всех позиций. Сохранил Open/Close-время позиций в массив. И прогнал на нем следующий скрипт.

void OnStart()
{
  int Times[];
  const int Size = (int)FileLoad("Times.bin", Times); // https://c.mql5.com/3/490/Times.zip
  
  Print(Size >> 1); // 324865 positions.

  // Loop 1: Benchmark non-cached
  ulong sum1 = 0;
  ulong t1 = GetMicrosecondCount();
  
  for (int i = 0; i < Size; i++)
  {
    const datetime OpenTime = Times[i++];
    const datetime CloseTime = Times[i];
    
    sum1 += HOLIDAY::GetDealLength(OpenTime, CloseTime);
  }

  // GetDealLength: 582 microsec (sum = 187295981)
  PrintFormat("GetDealLength: %llu microsec (sum = %llu)", GetMicrosecondCount() - t1, sum1);

  // Loop 2: Benchmark cached
  ulong sum2 = 0;
  ulong t2 = GetMicrosecondCount();
  for (int i = 0; i < Size; i++)
  {
    const datetime OpenTime = Times[i++];
    const datetime CloseTime = Times[i];
    
    sum2 += HOLIDAY::GetDealLength_Cached(OpenTime, CloseTime);
  }

  // GetDealLength_Cached: 1046 microsec (sum = 187295981)
  PrintFormat("GetDealLength_Cached: %llu microsec (sum = %llu)", GetMicrosecondCount() - t2, sum2);

  Print("CORRECT: ", sum1 == sum2); // true
}

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


ЗЫ Благодаря Вам обнаружил у себя ошибку.

#include <fxsaber\BestInterval\Holiday.mqh>

void OnStart()
{
  const datetime OpenTime1 = D'2026.04.03 01:00';
  const datetime CloseTime1 = D'2026.04.06 02:00';    

  Print(HOLIDAY::GetDealLength(OpenTime1, CloseTime1)); // 3600

  const datetime OpenTime2 = D'2026.04.03 03:00';
  const datetime CloseTime2 = D'2026.04.06 02:00';    

  Print(HOLIDAY::GetDealLength(OpenTime2, CloseTime2)); // 82800
}
Еще раз спасибо.
Файлы:
Times.zip  888 kb
 
fxsaber #:

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


Прилагаемый файл «Time.bin» содержит 324 865 позиций, из которых 324 425 (99,87 %) имеют короткую продолжительность < 24 часа (не охватывают выходные и праздничные дни).

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

 
amrali #:

Прилагаемый файл «Time.bin» содержит 324 865 позиций, из которых 324 425 (99,87 %) имеют короткую продолжительность < 24 часа (не охватывают выходные и праздничные дни).

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

Сделал так.
    const datetime OpenTime = Times[i++];
    const datetime CloseTime = Times[i] + 86400;


Результат.

GetDealLength: 2746 microsec (sum = 28188499181)
GetDealLength_Cached: 2575 microsec (sum = 28254595181)


Чем больше это слагаемое, тем быстрее вариант с кэшем.

 
fxsaber #:

P.S. Благодаря вам я обнаружил ошибку в своём собственном коде.

Ещё раз спасибо.

  static int GetDealLength( const datetime &OpenTime, const datetime &CloseTime )
  {
    int Length = (int)(CloseTime - OpenTime);

    //if (Length > DAY)
    //  for (datetime Time = CloseTime - DAY; Time > OpenTime; Time -= DAY)
    //    if (HOLIDAY::Is(Time))
    //      Длина -= ДЕНЬ;

    if (Length > DAY)
    {
      const datetime EndOfOpenDay = OpenTime - OpenTime % DAY + (DAY - 1);

      for (datetime Time = CloseTime - DAY; Time > EndOfOpenDay; Time -= DAY)
        if (HOLIDAY::Is(Time))
          Length -= DAY;
    }

    return(Length);
  }

Тест:

void OnStart()
{
  const datetime OpenTime1 = D'2026.04.03 01:00';
  const datetime CloseTime1 = D'2026.04.06 02:00';

  Print(HOLIDAY::GetDealLength(OpenTime1, CloseTime1)); // 90000

  const datetime OpenTime2 = D'2026.04.03 03:00';
  const datetime CloseTime2 = D'2026.04.06 02:00';

  Print(HOLIDAY::GetDealLength(OpenTime2, CloseTime2)); // 82800
}
 

Добрый день,

К сожалению, при проведении симуляции даты и времени я обнаружил, что ни одна из более быстрых замен для StructToTime() и TimeToStruct(), опубликованных ранее здесь и здесь, не работала корректно для лет после 2100 года.

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

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

Альтернатива функции StructToTime() на языке MQL:

datetime StructToTimeFast(const MqlDateTime& st)
  {
   const uint j = st.mon < 3;
   const uint y = st.year - j;
   const uint m = j ? st.mon + 12 : st.mon;
   const uint f = (979 * m - 2918) / 32;  // количество дней до начала месяца
   const uint n = st.day + f + 365*y + (y/4) - (y/100) + (y/400) - 306 - 719163;
   return (datetime)n*86400 LL + st.hour*3600 + st.min*60 + st.sec;
  }

Замена функции TimeToStruct() в MQL:

bool TimeToStructFast(datetime time, MqlDateTime& dt_struct)
  {
   uint  n = (uint)(time / 86400 ULL)          ;  // День Unix
   uint  N = n + 12699422                     ;  // Расчетный календарный день
   uint  a = 4 * N + 3                        ;  // N_1
   uint  c = a / 146097                       ;  //  век
   uint  e = a % 146097 / 4                   ;  //  [0, 36524] — день века
   uint  b = 4 * e + 3                        ;  // N_2
   uint  z = b / 1461                         ;  //  [0, 99]  - год века
   uint  h = b % 1461 / 4                     ;  //  [0, 365] — количество дней с 1 марта
   uint  d = 2141 * h + 197913                ;  // N_3
   uint  M = d / 65536                        ;  //  [3, 14]  — Месяц
   uint  D = d % 65536 / 2141                 ;  //  [0, 30]  — День
   uint  Y = 100 * c + z                      ;  // Год
   uint  J = h >= 306                         ;  // янв-фев
   uint  l = z ? (z % 4 == 0) : (c % 4 == 0)  ;  // isLeap

   uint sec = (uint)(time - n * 86400 ULL);
   dt_struct.year = (int)(Y - 32800 + J);
   dt_struct.mon  = (int)(J ? M - 12 : M);
   dt_struct.day  = (int)(D + 1);
   dt_struct.hour = (int)(sec / 3600);
   dt_struct.min  = (int)((sec / 60) % 60);
   dt_struct.sec  = (int)(sec % 60);
   dt_struct.day_of_week = (int)((n + 4) % 7);
   dt_struct.day_of_year = (int)(h + (J ? -306 : 59 + l));

   return (true);
  }

Тесты производительности:

StructToTimeFast (EURUSD,H1)    Compiler Version: 5836, AVX2 + FMA3
StructToTimeFast (EURUSD,H1)    13 th Gen Intel Core i7-13700 KF, AVX2 + FMA3
StructToTimeFast (EURUSD,H1)    30.27 ns, checksum = 323450257960585376  /// Функция StructToTime() в MQL
StructToTimeFast (EURUSD,H1)     1.95 ns, checksum = 323450257960585376   // StructToTimeFast
TimeToStructFast (EURUSD,H1)    Compiler Version: 5836, AVX2 + FMA3
TimeToStructFast (EURUSD,H1)    13 th Gen Intel Core i7-13700 KF, AVX2 + FMA3
TimeToStructFast (EURUSD,H1)    1970.01.01 01:02:44 - 2999.12.31 23:23:34
TimeToStructFast (EURUSD,H1)    21.34 ns, checksum = 6246603919299185333  /// Функция TimeToStruct() в MQL
TimeToStructFast (EURUSD,H1)     2.79 ns, checksum = 6246603919299185333   // TimeToStructFast
Benchmark results are unreliable due to compiler optimizations and testing conditions.
Benchmark results are unreliable due to compiler optimizations and testing conditions.
  • 2024.11.29
  • www.mql5.com
The discussion revolves around the performance of a function in different compiler versions and optimization settings. It highlights the variability in results due to compiler optimizations, the unreliability of certain benchmarks, and the impact of code modifications on performance. The author notes that the function's behavior is influenced by factors like the compiler version, optimization levels, and the presence of specific code elements, making it difficult to draw definitive conclusions. The function is considered unreliable and may be removed from benchmarks due to its inconsistent performance across different environments.
Файлы:
 
amrali #:
не работала корректно для лет после 2100 года

Я извиняюсь, но сейчас 2026, а вот к 2100 скорее всего о терминале МТ5 не останется в интернете даже воспоминаний.

Зачем всё это?

 
Vitaly Muzichenko #:

В чём смысл всего этого?

Это алгоритмическое решение проблемы обработки чередующихся последовательностей, нарушаемых нерегулярными событиями, такими как високосные годы и столетние годы. Помимо обеспечения корректного преобразования дат для всех лет, данный подход демонстрирует общий метод, который можно повторно использовать для решения аналогичных задач, связанных с в остальном регулярными последовательностями, содержащими периодические исключения.

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

Высокопроизводительные функции работы со временем, действующие для всего диапазона дат и времени MQL5 (с 1 января 1970 года по 31 декабря 3000 года):

(На основе алгоритма преобразования календарей Нери–Шнайдера: здесь)

// вернуть 1..31
int TimeDay(const datetime t)
  {
   uint dayOfCentury = ((((uint)(t / 86400 ULL) + 719162 + 306) << 2) | 3 U) % 146097 U;
   uint daySinceMarch1 = (dayOfCentury | 3 U) % 1461 U / 4;
   uint monthDayPacked = daySinceMarch1 * 2141 + 197913;
   return (int)(monthDayPacked & 0xFFFF) / 2141 + 1;
  }

// вернуть 1..12
int TimeMonth(const datetime t)
  {
   uint dayOfCentury = ((((uint)(t / 86400 ULL) + 719162 + 306) << 2) | 3 U) % 146097 U;
   uint daySinceMarch1 = (dayOfCentury | 3 U) % 1461 U / 4;
   uint monthDayPacked = daySinceMarch1 * 2141 + 197913;
   return (int)(monthDayPacked >> 16) - (daySinceMarch1 >= 306 ? 12 : 0);
  }

// вернуть год (например, 2019)
int TimeYear(const datetime t)
  {
   uint days = ((((uint)(t / 86400 ULL) + 719162) << 2) | 3 U);
   uint century = days / 146097 U;
   uint year = ((days % 146097 U) | 3 U) / 1461 U;
   return (int)(100 * century + year) + 1;
  }

// возвращает (0 — воскресенье, 1 — понедельник, …, 6 — суббота)
int TimeDayOfWeek(const datetime t)
  {
   return (int)(t / 86400 ULL + THURSDAY) % 7;
  }

// возвращает 0...365
int TimeDayOfYear(const datetime t)
  {
   return (int)(((((((uint)(t / 86400 ULL) + 719162) << 2) | 3 U) % 146097 U) | 3 U) % 1461 U) / 4;
  }

// возвращает 0...23
int TimeHour(const datetime t)
  {
   return (int)((t / 3600 ULL) % 24 U);
  }

// возвращает 0...59
int TimeMinute(const datetime t)
  {
   return (int)((t / 60 ULL) % 60 ULL);
  }

// возвращает 0...59
int TimeSeconds(const datetime t)
  {
   return (int)(t % 60 ULL);
  }

Прилагаемый файл скрипта содержит исходные коды и полные проверки.