Библиотеки: MathTicker - генератор тиков в математическом режиме - страница 3

 
Forester #:

OPT файлы вроде есть после одиночной оптимизации

Если заменить символ или интервал тестирования, то новый opt-файл не появится, а будет перезаписан старый.

Правильно, когда изменение входных данных создает соответствующий opt-файл.


Ну и если загрузили opt-файл в Тестер, то из него хорошо бы иметь возможность запускать сразу одиночные проходы.

 
fxsaber #:

Разница, как здесь.

за счет того, что котировки не сжаты, код по чтению тиков из файла работает быстрее раза в полтора-два.

Но все же причина, скорее всего, не в сжатии тиков. Вы не тратите ресурсы на проброс тика в виртуалку и получение его оттуда через SymbolInfoTick. Грубо говоря, вы не делаете на каждом тике два присваивания MqlTick и проверку работы с ордерами (хоть их и ноль).


Добавил сжатие тиковых данных. 2 уровня:
1) закодировал сжатие через дельты цен и времени.
2) дополнительно можно применить сжатие ZIP. Вариант с полными тиками в 1,5 раза компактнее чем сумма .tcs за тот же год, а AskBid в 3,5 раза.

Вариант AskBid стал сравним с вашим по скорости. Так что сжатие тиков добавило времени.

 
Вот думаю, что если так компактно получилось - то можно сохранить всю историю за 5-6 лет и через инпуты указывать интервал тестирования. Ну и дать имена в виде названий инструментов. Подумаю...
 
Forester #:

Добавил сжатие тиковых данных.

Вариант AskBid стал сравним с вашим по скорости.

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

#include <Forester\MathTicker.mqh> // https://www.mql5.com/ru/code/61126
void Strategy( const MqlTick& ) {}

input datetime inFrom = D'2024.12.01';

bool IsEqual( const MqlTick &Tick1, const MqlTick &Tick2 )
{
  return((Tick1.bid == Tick2.bid) && (Tick1.ask == Tick2.ask) && (Tick1.time_msc == Tick2.time_msc));
}

bool IsEqual( const MqlTick &Ticks1[], const MqlTick &Ticks2[] )
{
  const int Size = ::ArraySize(Ticks1);

  bool Res = (::ArraySize(Ticks2) == Size);

  for (int i = 0; Res && (i < Size); i++)
    Res = IsEqual(Ticks1[i], Ticks2[i]);

  return(Res);
}
  
// Возвращает размер массива в байтах.
template <typename T>
ulong GetSize( const T &Array[] ) { return((ulong)sizeof(T) * ArraySize(Array)); }

template <typename T1, typename T2>
double Criterion( const T1 &Decompression[], const T2 &Compression[], const ulong Interval )
{
  const double Performance = (double)ArraySize(Decompression) / Interval;

  return(Performance * ((double)GetSize(Decompression) / GetSize(Compression)));
}

#define TICK_SHORT uchar

void OnStart()
{
  MqlTick Ticks[]; // Для исходных тиков.

  if (CopyTicksRange(_Symbol, Ticks, COPY_TICKS_ALL,(ulong)inFrom * 1000) > 0)
  {
    TICK_SHORT Ticks2[]; // Для сжатых тиков.
    TickCompressor Compressor2;
    int Amount = ArraySize(Ticks);
    
    ulong Interval = GetMicrosecondCount();
//    TICKS_SHORT::Compress(Ticks, Ticks2); // Сжали.
    Compressor2.Compress(Ticks, Ticks2, Amount); // Сжали.
    Interval = GetMicrosecondCount() - Interval;
    const double Performance = (double)ArraySize(Ticks) / Interval;

    // https://www.mql5.com/ru/forum/490384#comment_57497700
    long n1 = ArraySize(Ticks) * (long)sizeof(MqlTick);
    long n2 = ArraySize(Ticks2) * (long)sizeof(TICK_SHORT);
    PrintFormat("Compressed %I64i bytes into %I64i bytes ==> %.2f%%",n1,n2,(double)n2/n1*100);

    Print("Compress performance: " + DoubleToString(Performance * sizeof(MqlTick) / 1.04858, 0) + " MB/s");
    Print("Compress performance: " + DoubleToString(Performance, 1) + " Ticks (millions)/sec.");
    Print("Compress performance criterion: " + DoubleToString(Criterion(Ticks, Ticks2, Interval), 1));

    MqlTick Ticks3[]; // Для разжатых тиков.
    TickCompressor Compressor3;
    int Amount2 = ArraySize(Ticks2);

    ulong Interval2 = GetMicrosecondCount();
//    TICKS_SHORT::Decompress(Ticks2, Ticks3); // Разжали.
    Compressor3.DeCompress(Ticks2, Ticks3, Amount2); // Разжали.

    Interval2 = GetMicrosecondCount() - Interval2;
    const double Performance2 = (double)ArraySize(Ticks3) / Interval2;

    Print("Decompress performance: " + DoubleToString(Performance2 * sizeof(MqlTick) / 1.04858, 0) + " MB/s");
    Print("Decompress performance: " + DoubleToString(Performance2, 1) + " Ticks (millions)/sec.");
    Print("Decompress performance criterion: " + DoubleToString(Criterion(Ticks3, Ticks2, Interval2), 1));

    Print("Correct = " + (string)IsEqual(Ticks, Ticks3)); // Сравнили.
  }
}

Не смог разобраться, почему не сжимается, а на разжатии вылет за пределы массива.

 
Forester #:
Вот думаю, что если так компактно получилось - то можно сохранить всю историю за 5-6 лет и через инпуты указывать интервал тестирования. Ну и дать имена в виде названий инструментов. Подумаю...
Сегодня приводил показатели сжатия.
В среднем 1.9 байта на тик.
fxsaber: Discussion
fxsaber: Discussion
  • t.me
16 млн тиков. Компрессия: 59.8 MB. Почти столько же tkc-формат - 59.6 MB. Доп. компрессия: 28.9 MB. В среднем 1.9 байта на тик. Грубо говоря, при сохранении таких показателей компрессии торрент будет (по количеству байтов) занимать в два раза больше, чем количество содержащихся тиков: в каждых 100 Гб торрента будет содержаться ~50 млрд тиков.
 
Forester #:

Вариант AskBid стал сравним с вашим по скорости. Так что сжатие тиков добавило времени.

Вы сравниваете теплое с мягким. Написал же сразу причину замедления. Она не в сжатии.
 
fxsaber #:

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

Не смог разобраться, почему не сжимается, а на разжатии вылет за пределы массива.

У меня используется статический массив
MqlTick Ticks[1000];

При заполнении сжимается этот блок, потом следующие 1000 и т.д. В конце остаток <1000 сжимает.

 
Forester #:

У меня используется статический массив
MqlTick Ticks[1000];

При заполнении сжимается этот блок, потом следующие 1000 и т.д. В конце остаток <1000 сжимает.

Читал исходник, конечно.
int Compress(MqlTick& t[], uchar &m[], int &ticks){//сжимает по 1000 тиков за раз или можно изменить размер t[]

Могли бы сделать этот исходник рабочим? Без него невозможно понять корректность алгоритма сжатия/разжатия и его характеристики.

 
fxsaber #:
Читал исходник, конечно.

Могли бы сделать этот исходник рабочим? Без него невозможно понять корректность алгоритма сжатия/разжатия и его характеристики.

попробую
 
fxsaber #:

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

Переделал код для более быстрой работы с большими массивами. Ранее использовался статический массив для 1000 тиков. Сделал динамическим, и чтение с любого места, а не с 0. Аналогично при сохранении распакованных тиков сделал запись тиков в любое место массива.
Для работы с сохранением тиков в файл все это не нужно, запись и чтение данных по прежнему идет блоками от 0 позиции в массиве. Большие массивы не использую, т.к. если в него поместить тики за несколько лет, то это может занять несколько Гб памяти + память потребляется экспертом, а при оптимизации на каждый агент не видел более 2Гб, видимо лишнее скидывается в кэш на диске. + Ресайз массивов медленная операция. И еще одна причина - то, что ZIP архивация работает с данными не более 1,5Гб, если больше - то будут сбои.

Но для вашего теста сделал эти изменения.

Кстати, ранее размер блоков перебирал до 1000. С вашим скриптом перебирал до 2млн. Оказалось, что быстрее всего с вашим скриптом распаковка  идет при int ticks_per_block=600000;.
А себе для работы с файлами подобрал 30 000 вместо 1000.

Проверьте разные

int method=1;1...6

для Ask и Bid 1 и 4 варианты. 4й с ZIP.

Сбор тиков в большой массив (для сравнения с исходными тиками) перенес из цикла для оценки производительности в отдельный, из за описаных выше причин. Работать с тиками можно из малых блоков без предварительной склейки их в один массив.

#include <Forester\MathTicker.mqh> // https://www.mql5.com/ru/code/61126
#include <fxsaber\TicksShort\TicksShort.mqh> // https://www.mql5.com/ru/code/61126

// метод сжатия
int method=1;//1...6 BidAsk_=1, BidAskVolume_=2, All_=3, BidAsk_Zipped=4, BidAskVolume_Zipped=5, All_Zipped=6

input datetime inFrom = D'2025.01.01';


void Strategy( const MqlTick& ) {}

// Возвращает размер массива в байтах.
template <typename T>
ulong GetSize( const T &Array[] ) { return((ulong)sizeof(T) * ArraySize(Array)); }

template <typename T1, typename T2>
double Criterion( const T1 &Decompression[], const T2 &Compression[], const ulong Interval )
{
  const double Performance = (double)ArraySize(Decompression) / Interval;

  return(Performance * ((double)GetSize(Decompression) / GetSize(Compression)));
}

#define TICK_SHORT uchar

void OnStart()
{
  MqlTick Ticks[]; // Для исходных тиков.

  if (CopyTicksRange(_Symbol, Ticks, COPY_TICKS_ALL,(ulong)inFrom * 1000) > 0)
  {
    TICK_SHORT Ticks2[]; // Для сжатых тиков.
    TickCompressor Compressor2;
    int Amount = ArraySize(Ticks);
    
    TICK_SHORT tmp[]; // Для сжатых тиков.

    
    double VolumeStep_=SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
    Compressor2.Start(method,_Point,VolumeStep_,_Digits);
    Print("Ticks: ",ArraySize(Ticks));
    ulong Interval = GetMicrosecondCount();
//    TICKS_SHORT::Compress(Ticks, Ticks2); // Сжали.

    int ticks=0,src_start=0;//
    int ZIPpos=0;//счетчик сжатых байтов
    int ticks_per_block=600000;
    for(int i=0;i<Amount;i++){
      if(ticks==ticks_per_block){ int l=Compressor2.Compress(Ticks,tmp,src_start,ticks); ZIPpos+=ArrayCopy(Ticks2,tmp,ZIPpos);src_start+=ticks; ticks=0;} 
      ticks++;
    }
    Compressor2.Compress(Ticks,tmp,src_start,ticks); ZIPpos+=ArrayCopy(Ticks2,tmp,ZIPpos);
    Print("Compressed size: ",ZIPpos);
    
    Interval = GetMicrosecondCount() - Interval;
    const double Performance = (double)ArraySize(Ticks) / Interval;

    // https://www.mql5.com/ru/forum/490384#comment_57497700
    long n1 = ArraySize(Ticks) * (long)sizeof(MqlTick);
    long n2 = ArraySize(Ticks2) * (long)sizeof(TICK_SHORT);
    PrintFormat("Compressed %I64i bytes into %I64i bytes ==> %.2f%%",n1,n2,(double)n2/n1*100);

    Print("Compress performance: " + DoubleToString(Performance * sizeof(MqlTick) / 1.04858, 0) + " MB/s");
    Print("Compress performance: " + DoubleToString(Performance, 1) + " Ticks (millions)/sec.");
    Print("Compress performance criterion: " + DoubleToString(Criterion(Ticks, Ticks2, Interval), 1));

    MqlTick Ticks3[]; // Для разжатых тиков.
    TickCompressor Compressor3;
    Compressor3.Start(method,_Point,VolumeStep_,_Digits);
    ulong Interval2 = GetMicrosecondCount();
//    TICKS_SHORT::Decompress(Ticks2, Ticks3); // Разжали.

    ArrayResize(Ticks3,ticks_per_block); //изменить размер массива для чтения поблочно
    int total_ticks=0;
    int nextSize=Compressor3.ArrToInt2(Ticks2,0);//размер 1го блока
    ZIPpos=4;//считано 4 байта - сместить адрес
    while (true){
       //uint s = FileReadArray(h, Ticks2, 0, nextSize); //чтение блока из файла
       uint s = ArrayCopy(tmp,Ticks2,0,ZIPpos,nextSize); //скопировать новый блок в tmp размером nextSize
       if(s==0){break;} 
       ZIPpos+=nextSize;
       ticks=Compressor3.DeCompress(tmp,Ticks3,nextSize,0); //распаковать блок и перезаписать в Ticks3
       total_ticks+=ticks;
       //for (int j = 0; j < ticks; j++){ Strategy(Ticks3[j]);}//стратегия
    };
    Interval2 = GetMicrosecondCount() - Interval2;
    ArrayResize(Ticks3,total_ticks);
    const double Performance2 = (double)ArraySize(Ticks3) / Interval2;

    Print("Decompress performance: " + DoubleToString(Performance2 * sizeof(MqlTick) / 1.04858, 0) + " MB/s");
    Print("Decompress performance: " + DoubleToString(Performance2, 1) + " Ticks (millions)/sec.");
    Print("Decompress performance criterion: " + DoubleToString(Criterion(Ticks3, Ticks2, Interval2), 1));
    
    //сравнить тики отдельнo, чтобы не ухудшать оценку сборкой большого массива для сравнения
    
    Compressor3.Start(method,_Point,VolumeStep_,_Digits);
    nextSize=Compressor3.ArrToInt2(Ticks2,0);//размер 1го блока
    ZIPpos=4;//считано 4 байта - сместить адрес
    int pos2=0;
    int dst_start=0;
    ArrayResize(Ticks3,Amount);//размер по числу исходных тиков
    while (true){
       //uint s = FileReadArray(h, Ticks2, 0, nextSize); //чтение блока из файла
       uint s = ArrayCopy(tmp,Ticks2,0,ZIPpos,nextSize); //скопировать новый блок в tmp размером nextSize
       if(s==0){break;} 
       ZIPpos+=nextSize;
       //медленнее  ArrayResize(Ticks3,dst_start+ticks_per_block,10000000); //изменить размер большого массива - работает медленнее, чем перезапись малого блока
       ticks=Compressor3.DeCompress(tmp,Ticks3,nextSize,dst_start);//распаковать блок не в начало массива, а в большой массив со всеми тиками
       dst_start=ticks;
    };
    ArrayResize(Ticks3,dst_start);
    
    Print("Correct = " + (string)TICKS_SHORT::IsEqual(Ticks, Ticks3)); // Сравнили.
  }
}