169 lines
14 KiB
MQL5
169 lines
14 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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);
|
|
}
|
|
};
|