//+------------------------------------------------------------------+ //| TicksShortLite.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.03" #property description "Standalone / lightweight version (no #include dependencies)" #property description "Ideal for embedding in small projects or performance tests" //+------------------------------------------------------------------+ //| 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 | //| - ZIP compression via SaveZipped() | //+------------------------------------------------------------------+ class TICKS_SHORT { private: static long PriceToInteger(const double price, const double power) { return (long)(price * power + 0.5); } //============================================================== // ZigZag + VLQ utilities //============================================================== static ulong ZigZagEncode(const long n) { return (ulong)((n << 1) ^ (n >> 63)); } static long ZigZagDecode(const ulong n) { return (long)((n >> 1) ^ -(long)(n & 1)); } static void WriteVLQ(uchar &buf[], int &pos, ulong v) { while(v >= 0x80) { buf[pos++] = (uchar)((v & 0x7F) | 0x80); v >>= 7; } buf[pos++] = (uchar)v; } static ulong ReadVLQ(const uchar &buf[], int &pos) { ulong v = 0; int shift = 0; while(pos < ArraySize(buf)) { uchar b = buf[pos++]; v |= ((ulong)(b & 0x7F)) << shift; if((b & 0x80) == 0) break; shift += 7; } return v; } public: //============================================================== // Compress ticks into uchar buffer //============================================================== static int Compress(const MqlTick &ticks[], uchar &buf[], const int iDigits = -1) { const int nticks = ArraySize(ticks); if (nticks == 0) { ArrayFree(buf); return 0; } const int digits = (iDigits < 0) ? _Digits : MathMin(iDigits, 7); const double power = MathPow(10, digits); // Pre-allocate 7 bytes per tick (rough upper bound) if (ArrayResize(buf, nticks * 7) == -1) { Print("ERROR: Failed to resize buffer in Compress."); return -1; } int pos = 0; WriteVLQ(buf, pos, nticks); WriteVLQ(buf, pos, digits); long prev_time = 0, prev_bid = 0, prev_ask = 0; for (int i = 0; i < nticks; i++) { const MqlTick tick = ticks[i]; long t = tick.time_msc; long b = PriceToInteger(tick.bid, power); long a = PriceToInteger(tick.ask, power); WriteVLQ(buf, pos, ZigZagEncode(t - prev_time)); // ZigZag + VLQ encode each delta WriteVLQ(buf, pos, ZigZagEncode(b - prev_bid)); WriteVLQ(buf, pos, ZigZagEncode(a - prev_ask)); prev_time = t; // Update previous prev_bid = b; prev_ask = a; if (pos + 8 >= ArraySize(buf)) { ArrayResize(buf, ArraySize(buf) * 2); } } return ArrayResize(buf, pos); // Number of bytes written } //============================================================== // Decompress uchar buffer back to ticks //============================================================== static int Decompress(const uchar &buf[], MqlTick &out_ticks[]) { const int nbuf = ArraySize(buf); if (nbuf == 0) { ArrayFree(out_ticks); return 0; } int pos = 0; const int totalCompressedTicks = (int)ReadVLQ(buf, pos); const int digits = (int)ReadVLQ(buf, pos); 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 += ZigZagDecode(ReadVLQ(buf, pos)); // Delta reconstruction bid += ZigZagDecode(ReadVLQ(buf, pos)); ask += ZigZagDecode(ReadVLQ(buf, pos)); 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) { uchar compressed[]; int count = Compress(ticks, compressed, iDigits); if (count <= 0) return false; // Error in compression return FileSave(fileName, compressed, (common ? FILE_COMMON : 0)); } //============================================================== // Compress + ZIP save //============================================================== static bool SaveZipped(const string fileName, const MqlTick &ticks[], const bool common = false, const int iDigits = -1) { uchar compressed[]; int count = Compress(ticks, compressed, iDigits); if (count <= 0) return false; // Error in compression uchar zipped[], key[]; if (!CryptEncode(CRYPT_ARCH_ZIP, compressed, key, zipped)) { Print("ERROR: ZIP compression failed in SaveZipped()"); return false; } return FileSave(fileName, zipped, (common ? FILE_COMMON : 0)); } //============================================================== // Load and decompress (auto-detect ZIP) //============================================================== static int Load(const string fileName, MqlTick &ticks[], const bool common = false) { uchar compressed[]; int countLoaded = (int)FileLoad(fileName, compressed, (common ? FILE_COMMON : 0)); if (countLoaded <= 0) { ArrayFree(ticks); return countLoaded; } uchar unzipped[], key[]; if (CryptDecode(CRYPT_ARCH_ZIP, compressed, key, unzipped)) return Decompress(unzipped, ticks); // ZIP detected else return Decompress(compressed, ticks); // Plain data } //============================================================== // 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 uchar &Compressed[]) { MqlTick Ticks2[]; int decompressedCount = Decompress(Compressed, Ticks2); if (decompressedCount == -1) return false; // Decompression error return IsEqual(Ticks, Ticks2); } };