Как лучше организовать файл кэша пользовательских индикаторов?

 

Есть "тяжёлые" индикаторы. По крайней мере при включении которых в советник, оптимизация, да и просто тест идёт крайне медленно. Вот я и подумал что, возможно, стоит сначала создать файл кэша таких индикаторов и при оптимизации уже брать их значения из кэша, а не рассчитывать их на лету...

Только не уверен, что организовал этот кэш оптимально. Может подскажет кто-нибудь, как будет барче?

//+------------------------------------------------------------------+
//|                                             make_buff_custom.mq5 |
//|                                     Copyright 2018, Tabolin S.N. |
//|                           https://www.mql5.com/ru/users/vip.avos |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, Tabolin S.N."
#property link      "https://www.mql5.com/ru/users/vip.avos"
#property version   "1.00"
//----------------------------------------------------------------------------------------------
#include <Trade\AccountInfo.mqh>             // доступ к свойствам текущего открытого торгового счета
#include <Trade\Trade.mqh>                   // подключаем библиотеку для совершения торговых операций
#include <Trade\PositionInfo.mqh>            // подключаем библиотеку для получения информации о позициях
#include <Trade\SymbolInfo.mqh>              // доступ к свойствам текущего символа
#include <ChartObjects\ChartObject.mqh>      // обеспечивает возможность упрощенного доступа к функциям API MQL5
#include <lib_cisnewbar.mqh>

CAccountInfo               my_Account;       // доступ к свойствам текущего открытого торгового счета
CTrade                     my_Trade;         // структура для выполнения торговых операций
CPositionInfo              my_Position;      // структура для получения информации о позициях
CSymbolInfo                my_Symbol;        // доступ к свойствам текущего символа
CisNewBar                  current_chart;    // экземпляр класса CisNewBar: текущий график
//----------------------------------------------------------------------------------------------
input    int      period_ICHIMOKU_T       = 9;           // период ICHIMOKU_T (9-3-18)
input    int      period_ICHIMOKU_K       = 27;          // период ICHIMOKU_K (27-27-125)
input    int      period_nas_ATR          = 16;          // период nas_ATR (16-4-52)
input    int      period_nas_COOF         = 1;           // период nas_COOF (1-1-4)
input    int      period_FrAMA            = 10;          // период FrAMA (10-2-20)
//+------------------------------------------------------------------+
int               handle_NAS;                            // переменная для хранения хэндла индикатора NAS3
double            buf_level_dn[];                        // массив для хранения значений индикатора NAS_D
double            buf_level_up[];                        // массив для хранения значений индикатора NAS_U

int               handle_ICHIMOKU;                       // переменная для хранения хэндла индикатора ICHIMOKU
double            buffer_ICHIMOKU_K[];                   // массив для хранения значений индикатора ICHIMOKU_K
double            buffer_ICHIMOKU_T[];                   // массив для хранения значений индикатора ICHIMOKU_T

int               handle_FrAMA;                          // переменная для хранения хэндла индикатора FrAMA
double            buffer_FrAMA[];                        // массив для хранения значений индикатора FrAMA
//*********
int               new_bars;                              // определитель нового бара
bool              write_ICHIMOKU;                        // записывать файл ICHIMOKU
bool              write_NAS;                             // записывать файл NAS
bool              write_FrAMA;                           // записывать файл FrAMA
int               filehandle;
string            dir;                                   // пака записи
string            filename_ICHIMOKU;                     // файл ICHIMOKU
string            filename_NAS;                          // файл NAS
string            filename_FrAMA;                        // файл FrAMA
//*********
MqlRates          rates[];                               // Структура для хранения информации о ценах, объемах и спреде
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   dir                  = my_Account.Company();
   StringReplace(dir," ","_");
   filename_FrAMA       = dir+"\\"+Symbol()+"_FrAMA_"+IntegerToString(period_FrAMA)+"_"+IntegerToString(PeriodSeconds())+".bin";
   if(FileIsExist(filename_FrAMA,FILE_COMMON))     write_FrAMA    = false;
   else write_FrAMA     = true;
   filename_ICHIMOKU    = dir+"\\"+Symbol()+"_ICHIMOKU_"+IntegerToString(period_ICHIMOKU_T)+"_"+IntegerToString(period_ICHIMOKU_K)+"_"+IntegerToString(PeriodSeconds())+".bin";
   if(FileIsExist(filename_ICHIMOKU,FILE_COMMON))  write_ICHIMOKU = false;
   else write_ICHIMOKU  = true;
   filename_NAS         = dir+"\\"+Symbol()+"_NAS_"+IntegerToString(period_nas_ATR)+"_"+IntegerToString(period_nas_COOF)+"_"+IntegerToString(PeriodSeconds())+".bin";
   if(FileIsExist(filename_NAS,FILE_COMMON))       write_NAS      = false;
   else write_NAS       = true;
//+--------
   ArraySetAsSeries(rates,                true);
   ArraySetAsSeries(buffer_ICHIMOKU_K,    true);
   ArraySetAsSeries(buffer_ICHIMOKU_T,    true);
   ArraySetAsSeries(buffer_FrAMA,         true);
   ArraySetAsSeries(buf_level_dn,         true);
   ArraySetAsSeries(buf_level_up,         true);
//+--------
   if(write_NAS)
   {
      handle_NAS = iCustom(Symbol(),0,"nas3",period_nas_ATR,period_nas_COOF);
      if(handle_NAS==INVALID_HANDLE)                                       // проверяем наличие хендла индикатора
      {
         Comment("Не удалось получить хендл индикатора handle_NAS");       // если хендл не получен, то выводим сообщение в лог об ошибке
         Print("Не удалось получить хендл индикатора handle_NAS");
         return(INIT_FAILED);                                              // завершаем работу с ошибкой
      }
   }
   
   if(write_ICHIMOKU)
   {
      handle_ICHIMOKU = iCustom(Symbol(),0,"Ichimoku_m",period_ICHIMOKU_T,period_ICHIMOKU_K,52);
      if(handle_ICHIMOKU==INVALID_HANDLE)                                  // проверяем наличие хендла индикатора
      {
         Comment("Не удалось получить хендл индикатора handle_ICHIMOKU");  // если хендл не получен, то выводим сообщение в лог об ошибке
         Print("Не удалось получить хендл индикатора handle_ICHIMOKU");
         return(INIT_FAILED);                                              // завершаем работу с ошибкой
      }
   }
   
   if(write_FrAMA)
   {
      handle_FrAMA = iCustom(Symbol(),0,"FrAMA_m",period_FrAMA,0,PRICE_CLOSE);
      if(handle_FrAMA==INVALID_HANDLE)                                     // проверяем наличие хендла индикатора
      {
         Comment("Не удалось получить хендл индикатора handle_FrAMA");     // если хендл не получен, то выводим сообщение в лог об ошибке
         Print("Не удалось получить хендл индикатора handle_FrAMA");
         return(INIT_FAILED);                                              // завершаем работу с ошибкой
      }
   }
   
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   IndicatorRelease(handle_FrAMA);                                   // удаляем хэндл индикатора и освобождаем память занимаемую им
   IndicatorRelease(handle_ICHIMOKU);                                // удаляем хэндл индикатора и освобождаем память занимаемую им
   IndicatorRelease(handle_NAS);                                     // удаляем хэндл индикатора и освобождаем память занимаемую им

   int j = ChartIndicatorsTotal(0,0);
   for(int i = j-1; i >= 0; i--) ChartIndicatorDelete(0,0,ChartIndicatorName(0,0,i)); // удаляем индикатор с графика
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   new_bars    = current_chart.isNewBar();   // проверить появление нового бара
   if(new_bars > 0 && copyBuffers())
   {
      if(write_ICHIMOKU)
      {
         filehandle  = FileOpen(filename_ICHIMOKU,FILE_READ|FILE_WRITE|FILE_BIN|FILE_COMMON);
         if(filehandle != INVALID_HANDLE)
         {
            FileSeek(filehandle,0,SEEK_END);
            if(FileWriteLong(filehandle,(ulong)rates[0].time) == 0)  Print("Ошибка записи файла === _Ichimoku_m_9_108_rates[0].time");
            if(FileWriteArray(filehandle,buffer_ICHIMOKU_K) == 0)    Print("Ошибка записи файла === _Ichimoku_m_9_108_buffer_ICHIMOKU_K");
            if(FileWriteArray(filehandle,buffer_ICHIMOKU_T) == 0)    Print("Ошибка записи файла === _Ichimoku_m_9_108_buffer_ICHIMOKU_T");
            FileClose(filehandle);
         }
      }
      
      if(write_NAS)
      {
         filehandle  = FileOpen(filename_NAS,FILE_READ|FILE_WRITE|FILE_BIN|FILE_COMMON);
         if(filehandle != INVALID_HANDLE)
         {
            FileSeek(filehandle,0,SEEK_END);
            if(FileWriteLong(filehandle,(ulong)rates[0].time) == 0)  Print("Ошибка записи файла === _nas3_20_3_rates[0].time");
            if(FileWriteArray(filehandle,buf_level_dn) == 0)         Print("Ошибка записи файла === _nas3_20_3_buf_level_dn");
            if(FileWriteArray(filehandle,buf_level_up) == 0)         Print("Ошибка записи файла === _nas3_20_3_buf_level_up");
            FileClose(filehandle);
         }
      }
      if(write_FrAMA)
      {
         filehandle  = FileOpen(filename_FrAMA,FILE_READ|FILE_WRITE|FILE_BIN|FILE_COMMON);
         if(filehandle != INVALID_HANDLE)
         {
            FileSeek(filehandle,0,SEEK_END);
            if(FileWriteLong(filehandle,(ulong)rates[0].time) == 0)  Print("Ошибка записи файла === _FrAMA_m_14_rates[0].time");
            if(FileWriteArray(filehandle,buffer_FrAMA) == 0)         Print("Ошибка записи файла === _FrAMA_m_14_buffer_FrAMA");
            FileClose(filehandle);
         }
      }
   }
}
//+------------------------------------------------------------------+
bool copyBuffers()
{
   ArrayFree(rates);
   ArrayFree(buffer_ICHIMOKU_K);
   ArrayFree(buffer_ICHIMOKU_T);
   ArrayFree(buffer_FrAMA);
   ArrayFree(buf_level_dn);
   ArrayFree(buf_level_up);

   if(CopyRates(Symbol(),0,1,1,rates) < 0) return(false);
   
   if(write_NAS)
   {
      if(CopyBuffer(handle_NAS,1,1,1,buf_level_dn) < 0 ) // копируем данные из индикаторного массива в массив buf_level_dn
      {                                                                                   // если не скопировалось
         Comment("Не удалось скопировать данные из индикаторного буфера buf_level_dn");   // то выводим сообщение об ошибке
         return(false);                                                                   // и выходим из функции
      }
      for(int i = 0; i < ArraySize(buf_level_dn); i++) if(buf_level_dn[i] > 500) buf_level_dn[i] = NULL;
      
      if(CopyBuffer(handle_NAS,0,1,1,buf_level_up) < 0 ) // копируем данные из индикаторного массива в массив buf_level_up
      {                                                                                   // если не скопировалось
         Comment("Не удалось скопировать данные из индикаторного буфера buf_level_up");   // то выводим сообщение об ошибке
         return(false);                                                                   // и выходим из функции
      }
      for(int i = 0; i < ArraySize(buf_level_up); i++) if(buf_level_up[i] > 500) buf_level_up[i] = NULL;
   }
   
   if(write_FrAMA)
   {
      if(CopyBuffer(handle_FrAMA,0,1,1,buffer_FrAMA) < 0 ) // копируем данные из индикаторного массива в массив buffer_FrAMA
      {                                                                                   // если не скопировалось
         Comment("Не удалось скопировать данные из индикаторного буфера buffer_FrAMA");   // то выводим сообщение об ошибке
         return(false);                                                                   // и выходим из функции
      }
   }
   
   if(write_ICHIMOKU)
   {
      if(CopyBuffer(handle_ICHIMOKU,0,1,1,buffer_ICHIMOKU_T) < 0 ) // копируем данные из индикаторного массива в массив buffer_ICHIMOKU_T
      {                                                                                         // если не скопировалось
         Comment("Не удалось скопировать данные из индикаторного буфера buffer_ICHIMOKU_T");    // то выводим сообщение об ошибке
         return(false);                                                                         // и выходим из функции
      }
      
      if(CopyBuffer(handle_ICHIMOKU,1,1,1,buffer_ICHIMOKU_K) < 0 ) // копируем данные из индикаторного массива в массив buffer_ICHIMOKU_K
      {                                                                                         // если не скопировалось
         Comment("Не удалось скопировать данные из индикаторного буфера buffer_ICHIMOKU_K");    // то выводим сообщение об ошибке
         return(false);                                                                         // и выходим из функции
      }
   }
   
   return(true);
}
//+------------------------------------------------------------------+
Файлы:
 

Что-то идёт не так....

Если количество баров (значений индикатора) одинаково, то почему размер файлов разный?


 
Писать значения в оффлайн график(hst формат), за основу взять решения periodconverter/renko/синтетические символы. Пишите в OHLCV расчетные значения индикаторов которые вам нужны. В mql4 можно в один бар записать несколько(на одно время подряд несколько баров с этим временем - ), а в mql5 так уже нельзя сделать.
 
Мне вот интересно. С чего вдруг пришла такая мысль, что прочитать с файла будет быстрее, чем просчитать 1 раз? Нынче процессоры уже не те, что были раньше. Сейчас рассчёты не требуют много времени.
 
Viktar Dzemikhau:
Мне вот интересно. С чего вдруг пришла такая мысль, что прочитать с файла будет быстрее, чем просчитать 1 раз? Нынче процессоры уже не те, что были раньше. Сейчас рассчёты не требуют много времени.

Из практики. Есть действительно тяжелые индикаторы и их комбинации.

 
Andrey Khatimlianskii:

Из практики. Есть действительно тяжелые индикаторы и их комбинации.

Совершенно верно.

 
А зачем Вы на каждом OnTick() открываете хендл файла? Достаточно в OnInit() достать из bin массив, объявленный на глобальном уровне и потом перебирать его. И наоборот, в DeInit() записывать готовый массив в файл. 
 
Sergey Savinkin:
А зачем Вы на каждом OnTick() открываете хендл файла? Достаточно в OnInit() достать из bin массив, объявленный на глобальном уровне и потом перебирать его. И наоборот, в DeInit() записывать готовый массив в файл. 

В принципе, да. Но тогда под каждый файл нужен свой filehandle 

 

Как бы это сделал я.

  Перед запуском оптимизации запускается оптимизация специального советника. Его цель - запись в файл хэндла индикатора (один советник - один индикатор). Период - тот, на котором будет делаться оптимизация или больший. Можно прогнать его один раз на большом периоде и не перезапускать, если не нужны свежие данные. В советнике запускается хэндл с расчетом индикатора (тем самым, долгим и тяжелым). Формируются файлы с названием "Название индикатора_символ_таймфрейм.bin". На каждом символе нужно прогнать оптимизацию столько раз, сколько нужно таймфреймов.

OnInit() советника считывает существующий файл в структуру (Время начала бара; Параметр1; Параметр2 ;...ПараметрN, Значение). В процессе прогона массив структуры дополняется новыми элементами с текущими параметрами.

OnDeinit() сортирует структуру по дате (как сортировать - вопрос технический и легко решаемый) и записывает обратно в файл.

Другой вариант - советник изначально делает новый массив структуры, а перед деинсталляцией склеивает то, что в файле с тем, что сделано. Например, так

Все, кэш создан. Рабочий советник при запуске проверяет наличие файла. Если он есть, то копирует его в процедуре OnInit() в массив и в дальнейшем работает с массивом.

Есть но - тестирование в облаке. Но оно решается через #property tester_file.

З.Ы. Исправил опечатку.

 
Sergey Savinkin:

Как бы это сделал я.

Спасибо. Надо так и сделать. Тем более, что мой вариант, при считывании файла, тоже достаточно долгий...

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