Библиотеки: TicksShort

 

TicksShort:

Короткий формат хранения тиков.

Автор: fxsaber

 
А зачем время оставлено в двух полях? Достаточно только time_msc, ИМХО.
 
Stanislav Korotky #:
А зачем время оставлено в двух полях? Достаточно только time_msc, ИМХО.

Это мое косноязычие. Имел в виду, что эта информация будет доступна после разжатия. Конечно, сжимается только bid/ask/time_msc.

 
fxsaber #:

Это мое косноязычие. Имел в виду, что эта информация будет доступна после разжатия. Конечно, сжимается только bid/ask/time_msc.

Понятно. Проблема только в лишней подсветке для поля time в описании (в первом блоке кода). Так что язычие - нормальное.

 

1. Excellent compression ratio 10:1 (6 GB ticks.bin can be compressed into 660 MB file). 

2. Speed of decompression is very fast.

 

Bug found:

#property script_show_inputs

input datetime inFrom = D'2025.07.01';

input string   inSymb = "EURUSD";

#include "TicksShort.mqh"

void OnStart()
{
  MqlTick Ticks[];

  if (CopyTicksRange(inSymb, Ticks, COPY_TICKS_ALL,(ulong)inFrom * 1000) > 0)
  {
    TICK_SHORT Compressed[];

    TICKS_SHORT::Compress(Ticks, Compressed);

    Print("Correct = " + (string)TICKS_SHORT::IsEqual(Ticks, Compressed));
  }
}

// Launching on different charts:
// (EURUSD,H1)  Correct = true
// (USDJPY,H1)  Correct = false
// (BTCUSD,D1)  Correct = false

Bug fix "TicksShort.mqh":

//  static int Compress( const MqlTick &TicksIn[], TICK_SHORT &TicksOut[], const int iDigits = INT_MAX )
  static int Compress( const MqlTick &TicksIn[], TICK_SHORT &TicksOut[], const int iDigits = -1 )
  {
   // ...
  }
 
amrali #:
Bug fix "TicksShort.mqh":
I think the author meant to input INT_MIN but accidentally typed INT_MAX instead.
 
amrali #:

Bug found:

Это было сделано специально. Когда записываешь в файл, то это обычно большой объем данных, поэтому почти гарантировано будет правильно вычислен Digits, поэтому там iDigits = -1.

А вот с массивами далеко не так. Массив может быть и из одного только элемента. Поэтому важно понимать, что iDigits-параметр нужно задавать почти во всех случаях, кроме текущего символа.


Эти функции работы с файлами и массивами являются просто дополнением основного функционала. Поскольку сжимать тики - это нужно единицам пользователей, то предпочел просто указать ALT+M в описании.

 

My experimental version with clear code comments and optimized performance.

Uses delta encoding of ticks + zigzag encoding of small negative deltas for more efficient bit-packing.

TicksShort_Performance (EURUSD,H1)      Compressed 1411131480 bytes into 141113316 bytes ==> 10.00%
TicksShort_Performance (EURUSD,H1)      Compress performance: 14004 MB/s
TicksShort_Performance (EURUSD,H1)      Compress performance: 244.7 Ticks (millions)/sec.
TicksShort_Performance (EURUSD,H1)      Compress performance criterion: 2447.3
TicksShort_Performance (EURUSD,H1)      Decompress performance: 5820 MB/s
TicksShort_Performance (EURUSD,H1)      Decompress performance: 101.7 Ticks (millions)/sec.
TicksShort_Performance (EURUSD,H1)      Decompress performance criterion: 1017.1
TicksShort_Performance (EURUSD,H1)      Correct = true
 
amrali #:

My experimental version with clear code comments and optimized performance.

Uses delta encoding of ticks + zigzag encoding of small negative deltas for more efficient bit-packing.

Ваш вариант на 20% быстрее на декомпрессии. И это очень хорошо!


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


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


Ниже ваш вариант.

  static void UnpackDeltaTick(const TICK_SHORT &tick, uint &dBid, uint &dAsk, uint &dTime)
  {
    // Decode delta tick, 6 bytes
    dBid = tick.UShort0 & 0x0FFF;                          // 12 bits
    dAsk = ((tick.UShort0 >> 12) |                         // 12 bits (4 + 8)
           ((tick.UInt1 & 0xFF) << 4));

    dTime = tick.UInt1 >> 9;                               // 23 bits
  }

      const bool isDelta = (ticks[i].UInt1 & C_DELTA_FLAG);

      if (isDelta)
      {
        uint dBid = 0, dAsk = 0;
        uint dTime = 0;
        UnpackDeltaTick(ticks[i], dBid, dAsk, dTime);  // Bit-unpacking
        bid += ZigzagDecode(dBid);                     // ZigZag decoding & delta reconstruction
        ask += ZigzagDecode(dAsk);
        time_msc += dTime;
      }

      result[index].bid = NormalizeDouble(bid * point, digits);  // Price conversion to double
      result[index].ask = NormalizeDouble(ask * point, digits);
      result[index].time_msc = (long)time_msc;
      result[index].time = (datetime)(time_msc / 1000);


Мой вариант.

  bool Load( int &ShiftBid, int &ShiftAsk, uint &ShiftTime ) const
  {
    const bool Res = (this.Struct0.UInt1 & FLAG);

    if (Res)
    {
      ShiftBid = (this.Struct0.UShort0 & 0x0FFF) - SHIFT;
      ShiftAsk = this.Struct1.Short1 >> 4;

      ShiftTime = this.Struct0.UInt1 >> 9;
    }

    return(Res);
  }

      if ((this.FlagLoad  = TickIn.Load(ShiftBid, ShiftAsk, ShiftTime)))
      {
        TickOut.bid = ::NormalizeDouble((this.PriceBid += ShiftBid) * this.Pow, 7/* this.digits*/);
        TickOut.ask = ::NormalizeDouble((this.PriceAsk += ShiftAsk) * this.Pow, 7/* this.digits*/);

        TickOut.time = (datetime)((TickOut.time_msc = (long)(this.Time_Msc += ShiftTime)) / 1000);
      }


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


Предполагаю, что ускорение достигается за счет оптимизации со стороны компиляции. У меня идет заполнение массива не напрямую, а через внутреннюю функцию. Так специально задумано.

 
fxsaber # :

Your version is 20% faster on decompression. And that's very good!


For my task, I need fast decompression, so I only look at this indicator.


However, I don't see any algorithmic reason to be faster.


Below is your option.


My option.


I have only noted the places that distinguish both variants. Your variant seems to have more calculations. Moreover, it seems that for dAsk you can skip ZigZag and get a signed value right away, as is done in my variant.


I assume that the acceleration is achieved through optimization on the compilation side. I fill the array not directly, but through an internal function. This is specially intended.

I enjoyed re-writing the compressor as a mental exercise to learn something new about different available compression algorithms for time series data.