//+------------------------------------------------------------------+ //| 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.02" #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 | //+------------------------------------------------------------------+ class TICKS_SHORT { private: static long PriceToInteger(const double price, const double power) { //return (long)MathRound(price * power); return (long)(price * power + 0.5); } // Handle signed values 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)); } // VLQ variable-length integer encoding static void VLQWrite(uchar &buf[], int &pos, ulong v) { do { uchar b = (uchar)(v & 0x7F); v >>= 7; if (v > 0) b |= 0x80; // Set MSB if more bytes follow buf[pos++] = b; } while(v > 0); } static ulong VLQRead(const uchar &buf[], int &pos) { ulong v = 0; long shift = 0; uchar b; do { if (pos >= ArraySize(buf)) { Print("VLQRead Error."); return 0; } b = buf[pos++]; v |= (ulong)(b & 0x7F) << shift; if (shift >= 64) { Print("VLQ sequence malformed."); return 0; } shift += 7; } while ((b & 0x80) != 0); // Continue if MSB is set 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; VLQWrite(buf, pos, nticks); VLQWrite(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); VLQWrite(buf, pos, ZigZagEncode(t - prev_time)); // ZigZag + VLQ encode each delta VLQWrite(buf, pos, ZigZagEncode(b - prev_bid)); VLQWrite(buf, pos, ZigZagEncode(a - prev_ask)); prev_time = t; // Update previous prev_bid = b; prev_ask = a; } 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)VLQRead(buf, pos); const int digits = (int)VLQRead(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(VLQRead(buf, pos)); // Delta reconstruction bid += ZigZagDecode(VLQRead(buf, pos)); ask += ZigZagDecode(VLQRead(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)); } //+---------------------------------------------------------------+ //| Load compressed ticks from file and decompress | //+---------------------------------------------------------------+ 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; } 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 uchar &Compressed[]) { MqlTick Ticks2[]; int decompressedCount = Decompress(Compressed, Ticks2); if (decompressedCount == -1) return false; // Decompression error return IsEqual(Ticks, Ticks2); } };