forked from amrali/CBitBuffer
		
	
		
			
				
	
	
		
			195 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			MQL5
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			MQL5
		
	
	
	
	
	
//+------------------------------------------------------------------+
 | 
						|
//|                                               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);
 | 
						|
     }
 | 
						|
  };
 |