//+------------------------------------------------------------------+ //| TicksShort.mqh | //| Copyright © 2025, Amr Ali | //| https://www.mql5.com/en/users/amrali | //+------------------------------------------------------------------+ #property copyright "Copyright © 2025, Amr Ali" #property link "https://www.mql5.com/en/users/amrali" #property version "1.02" #property description "Implementation of the 'TicksShort' library by 'fxsaber' utilizing VLQ encoding." #property description "Source: https://www.mql5.com/ru/code/61126" //+------------------------------------------------------------------+ //| Class: TICKS_SHORT | //| Description: Delta + VLQ Compression for MqlTick[] | //| Features: | //| - Delta encoding of time, bid, and ask prices | //| - ZigZag transform to handle signed values | //| - VLQ variable-length integer encoding | //| - Optimized for speed and minimal allocations | //+------------------------------------------------------------------+ #include "..\CBitBuffer.mqh" class TICKS_SHORT { private: static long PriceToInteger(const double price, const double power) { //return (long)MathRound(price * power); return (long)(price * power + 0.5); } public: //+---------------------------------------------------------------+ //| Compress ticks into ulong buffer | //+---------------------------------------------------------------+ static int Compress(const MqlTick &ticks[], ulong &compressed[], const int iDigits = -1) { const int nticks = ArraySize(ticks); if (nticks == 0) { ArrayFree(compressed); return 0; } const int digits = (iDigits < 0) ? _Digits : MathMin(iDigits, 7); const double power = MathPow(10, digits); CBitBuffer buffer(nticks * 7); // Pre-allocate 7 bytes per tick (rough upper bound) buffer.WriteInt(nticks); buffer.WriteVarInt(digits); long prev_time = 0, prev_bid = 0, prev_ask = 0; for (int i = 0; i < nticks; i++) { const MqlTick t = ticks[i]; buffer.WriteVarLong(t.time_msc - prev_time); // ZigZag + VLQ encode each delta buffer.WriteVarLong(PriceToInteger(t.bid, power) - prev_bid); buffer.WriteVarLong(PriceToInteger(t.ask, power) - prev_ask); if (buffer.GetLastError() != BIT_BUFFER_NO_ERROR) { PrintFormat("ERROR: Caught buffer error in Compress: %s", buffer.GetLastErrorString()); return -1; // Error in compression } prev_time = t.time_msc; // Update previous prev_bid = PriceToInteger(t.bid, power); prev_ask = PriceToInteger(t.ask, power); } if (!buffer.GetFinalizedBuffer(compressed)) // Export to compressed[] return -1; return ArraySize(compressed); // Number of ULongs written } //+---------------------------------------------------------------+ //| Decompress ulong buffer back to ticks | //+---------------------------------------------------------------+ static int Decompress(const ulong &compressed[], MqlTick &out_ticks[]) { const int size = ArraySize(compressed); if (size == 0) { ArrayFree(out_ticks); return 0; } CBitBuffer buffer; if(!buffer.SetRawBuffer(compressed)) // Import from compressed[] return -1; const int totalCompressedTicks = buffer.ReadInt(); const int digits = buffer.ReadVarInt(); const double point = MathPow(10, -digits); if (ArrayResize(out_ticks, totalCompressedTicks) == -1) { Print("ERROR: Failed to resize ticks array in Decompress."); return -1; } long time = 0, bid = 0, ask = 0; for (int i = 0; i < totalCompressedTicks; i++) { time += buffer.ReadVarLong(); // Delta reconstruction bid += buffer.ReadVarLong(); ask += buffer.ReadVarLong(); if (buffer.GetLastError() != BIT_BUFFER_NO_ERROR) { PrintFormat("ERROR: Caught buffer error in Decompress: %s", buffer.GetLastErrorString()); return -1; // Error in decompression } MqlTick tick = {}; tick.time_msc = time; tick.bid = NormalizeDouble(bid * point, digits); tick.ask = NormalizeDouble(ask * point, digits); tick.time = (datetime)(time / 1000); out_ticks[i] = tick; // Append reconstructed tick } return ArraySize(out_ticks); // Number of MqlTicks written } //+---------------------------------------------------------------+ //| Compress ticks and save to file | //+---------------------------------------------------------------+ static bool Save(const string fileName, const MqlTick &ticks[], const bool common = false, const int iDigits = -1) { ulong compressed[]; int count = Compress(ticks, compressed, iDigits); if (count < 0) return false; // Error in compression return FileSave(fileName, compressed, (common ? FILE_COMMON : 0)); } //+---------------------------------------------------------------+ //| Load compressed ticks from file and decompress | //+---------------------------------------------------------------+ static int Load(const string fileName, MqlTick &ticks[], const bool common = false) { ulong compressed[]; int countLoaded = (int)FileLoad(fileName, compressed, (common ? FILE_COMMON : 0)); if (countLoaded <= 0) { ArrayFree(ticks); return countLoaded; } ArrayResize(compressed, countLoaded); int decompressedCount = Decompress(compressed, ticks); return(decompressedCount); } //+---------------------------------------------------------------+ //| Equality check functions | //+---------------------------------------------------------------+ static bool IsEqual(const MqlTick &Tick1, const MqlTick &Tick2) { return((Tick1.bid == Tick2.bid) && (Tick1.ask == Tick2.ask) && (Tick1.time_msc == Tick2.time_msc)); } static 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); } static bool IsEqual(const MqlTick &Ticks[], const ulong &Compressed[]) { MqlTick Ticks2[]; int decompressedCount = Decompress(Compressed, Ticks2); if (decompressedCount == -1) return false; // Decompression error return IsEqual(Ticks, Ticks2); } };