CBitBuffer/TicksShort_VLQ/TicksShortLite.mqh

217 lines
16 KiB
MQL5
Raw Permalink Normal View History

2025-10-08 02:04:09 +03:00
<EFBFBD><EFBFBD>//+------------------------------------------------------------------+
//| TicksShortLite.mqh |
//| Copyright <EFBFBD> 2025, Amr Ali |
//| https://www.mql5.com/en/users/amrali |
//+------------------------------------------------------------------+
#property copyright "Copyright <00> 2025, Amr Ali"
#property link "https://www.mql5.com/en/users/amrali"
2025-10-09 02:57:25 +03:00
#property version "1.03"
2025-10-08 02:04:09 +03:00
#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 |
2025-10-09 02:57:25 +03:00
//| - ZIP compression via SaveZipped() |
2025-10-08 02:04:09 +03:00
//+------------------------------------------------------------------+
class TICKS_SHORT
{
private:
static long PriceToInteger(const double price, const double power)
2025-10-08 23:35:20 +03:00
{
2025-10-08 02:04:09 +03:00
return (long)(price * power + 0.5);
2025-10-08 23:35:20 +03:00
}
2025-10-08 02:04:09 +03:00
2025-10-08 23:35:20 +03:00
//==============================================================
// ZigZag + VLQ utilities
//==============================================================
2025-10-08 02:04:09 +03:00
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)); }
2025-10-08 23:35:20 +03:00
static void WriteVLQ(uchar &buf[], int &pos, ulong v)
{
while(v >= 0x80)
{
buf[pos++] = (uchar)((v & 0x7F) | 0x80);
2025-10-08 02:04:09 +03:00
v >>= 7;
2025-10-08 23:35:20 +03:00
}
buf[pos++] = (uchar)v;
}
2025-10-08 02:04:09 +03:00
2025-10-08 23:35:20 +03:00
static ulong ReadVLQ(const uchar &buf[], int &pos)
{
2025-10-08 02:04:09 +03:00
ulong v = 0;
2025-10-08 23:35:20 +03:00
int shift = 0;
while(pos < ArraySize(buf))
{
uchar b = buf[pos++];
v |= ((ulong)(b & 0x7F)) << shift;
if((b & 0x80) == 0)
break;
2025-10-08 02:04:09 +03:00
shift += 7;
2025-10-08 23:35:20 +03:00
}
2025-10-08 02:04:09 +03:00
return v;
2025-10-08 23:35:20 +03:00
}
2025-10-08 02:04:09 +03:00
public:
2025-10-08 23:35:20 +03:00
//==============================================================
// Compress ticks into uchar buffer
//==============================================================
2025-10-08 02:04:09 +03:00
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);
2025-10-08 07:25:58 +03:00
// 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;
}
2025-10-08 02:04:09 +03:00
int pos = 0;
2025-10-08 23:35:20 +03:00
WriteVLQ(buf, pos, nticks);
WriteVLQ(buf, pos, digits);
2025-10-08 02:04:09 +03:00
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);
2025-10-08 23:35:20 +03:00
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));
2025-10-08 02:04:09 +03:00
prev_time = t; // Update previous
prev_bid = b;
prev_ask = a;
2025-10-08 18:05:58 +03:00
if (pos + 8 >= ArraySize(buf)) { ArrayResize(buf, ArraySize(buf) * 2); }
2025-10-08 02:04:09 +03:00
}
return ArrayResize(buf, pos); // Number of bytes written
}
2025-10-08 23:35:20 +03:00
//==============================================================
// Decompress uchar buffer back to ticks
//==============================================================
2025-10-08 02:04:09 +03:00
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;
2025-10-08 23:35:20 +03:00
const int totalCompressedTicks = (int)ReadVLQ(buf, pos);
const int digits = (int)ReadVLQ(buf, pos);
2025-10-08 02:04:09 +03:00
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++)
{
2025-10-08 23:35:20 +03:00
time += ZigZagDecode(ReadVLQ(buf, pos)); // Delta reconstruction
bid += ZigZagDecode(ReadVLQ(buf, pos));
ask += ZigZagDecode(ReadVLQ(buf, pos));
2025-10-08 02:04:09 +03:00
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
}
2025-10-08 23:35:20 +03:00
//==============================================================
// Compress ticks and save to file
//==============================================================
2025-10-08 02:04:09 +03:00
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);
2025-10-09 02:57:25 +03:00
if (count <= 0) return false; // Error in compression
2025-10-08 02:04:09 +03:00
return FileSave(fileName, compressed, (common ? FILE_COMMON : 0));
}
2025-10-08 23:35:20 +03:00
//==============================================================
2025-10-09 02:57:25 +03:00
// 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)
2025-10-08 23:35:20 +03:00
//==============================================================
2025-10-08 02:04:09 +03:00
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; }
2025-10-09 02:57:25 +03:00
uchar unzipped[], key[];
if (CryptDecode(CRYPT_ARCH_ZIP, compressed, key, unzipped))
return Decompress(unzipped, ticks); // ZIP detected
else
return Decompress(compressed, ticks); // Plain data
2025-10-08 02:04:09 +03:00
}
2025-10-08 23:35:20 +03:00
//==============================================================
// Equality check functions
//==============================================================
2025-10-08 02:04:09 +03:00
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);
}
};