# TKZ: tick history compression for MetaTrader 5 TKZ is an MQL5 library and a set of scripts that store MetaTrader 5 tick history, meaning `time_msc`, `bid` and `ask`, in as few bytes as possible. Compression is lossless and the round trip is bit-exact. It uses a context-mixing compressor built for the structure of quote streams: * integer price mapping with automatic digits, tick size and decimal sub-grid detection; * a binary decomposition of time deltas and price moves; * about ten adaptive contexts per decision; * a two-layer logistic mixer, two SSE stages and an arithmetic coder. On real tick data it takes **1.37–2.2 bytes per tick**, 11–17× smaller than 24-byte `(time_msc, bid, ask)` records and 27–44× smaller than `MqlTick`. That is 16–26 % smaller than `xz -9e` applied to already delta-encoded data. ## Results Bytes per tick, whole file as one chunk. The baselines are the best of `xz -9e` and `zstd -19 --long`, run on raw records and on delta-encoded columns (`tests/data/baselines.py`). | Data set | Ticks | Raw records | xz, raw | xz, deltas + varint columns | **TKZ** | vs best baseline | |---|---:|---:|---:|---:|---:|---:| | Dukascopy EURUSD, Nov 2023 | 1 938 713 | 24 | 3.077 | 1.743 | **1.460** | −16 % | | Dukascopy XAUUSD, Sep 2023 | 2 391 711 | 24 | 2.956 | 1.687 | **1.369** | −19 % | | Broker XAUUSD feed, May 2026 | 687 853 | 24 | 3.908 | 1.905 | **1.445** | −24 % | | US500 CFD (Tickstory), Feb 2025 | 199 851 | 24 | 4.931 | 2.993 | **2.201** | −26 % | In forex, about 70 % of the output is the tick timestamps. Bid and ask together cost about 3.3 bits per tick on EURUSD. Effect of the chunk size (all four sets combined): | Ticks per chunk | 20 000 | 100 000 (≈ a day of EURUSD) | 500 000 (≈ a week) | whole month | |---|---:|---:|---:|---:| | bytes/tick | 1.485 | 1.459 | 1.448 | 1.445 | Speed of the C++ build of the same code (bounds-checked arrays, one core): encoding and decoding are symmetric at about 250 000 ticks/s. MQL5 is usually somewhat slower than optimised C++. `TkzSelfTest` and `TkzExport` print the real speed in your terminal. Memory: about 25 MB of model tables per codec instance (a writer with verification uses two), plus about 40 bytes per tick of the chunk being encoded. ## Installation Copy the `MQL5` folder of this repository into the terminal data folder (MetaEditor: *File → Open Data Folder*), then compile the scripts in `MQL5/Scripts/Tkz/` in MetaEditor. ``` MQL5/Include/Tkz/TkzCodec.mqh codec: one chunk <-> ticks MQL5/Include/Tkz/TkzArchive.mqh .tkz files: CTkzWriter, CTkzReader MQL5/Include/Tkz/TkzSelfTest.mqh self-test used by the script and the C++ tests MQL5/Scripts/Tkz/TkzSelfTest.mq5 run this first MQL5/Scripts/Tkz/TkzExport.mq5 history -> .tkz MQL5/Scripts/Tkz/TkzVerify.mq5 .tkz -> compare with history, measure decoding MQL5/Scripts/Tkz/TkzToCustom.mq5 .tkz -> custom symbol (charts, Strategy Tester) ``` ## Scripts 1. **TkzSelfTest**: synthetic edge cases (NaN/Inf/-0.0, negative time steps, huge gaps, tick grids, negative prices, corruption), a byte-exact *golden* comparison with the reference implementation, a round trip of the latest ticks of the chart symbol, and archive I/O. Every line must say `PASS`. 2. **TkzExport**: inputs are the symbol, the date range, the chunk period (day/week/month), the tick selection (`COPY_TICKS_ALL` or `COPY_TICKS_INFO`), the file name, the common folder and the verification switch. Ticks are fetched day by day with `CopyTicksRange`. Each chunk is compressed and, with verification on, decoded again and compared before it is written. The script prints the size, bytes/tick, the compression ratios and the speed. 3. **TkzVerify**: decodes every chunk, compares it with `CopyTicksRange` over the same time span and reports the decoding speed. 4. **TkzToCustom**: creates a custom symbol (by default `.tkz`, properties copied from the original symbol) and loads the archive with `CustomTicksReplace`. The files are written to `MQL5/Files` (or the common `Files` folder). ## API ```mql5 #include // --- one chunk in memory CTkzCodec codec; uchar blob[]; int size = codec.EncodeTicks(ticks, 0, ArraySize(ticks), blob); // appends a chunk MqlTick restored[]; int n = codec.DecodeTicks(blob, 0, restored); // -1 on error // also: Encode()/Decode() with parallel arrays long time_msc[], double bid[], ask[] // --- archive files CTkzWriter w; w.SetVerify(true); // optional: decode and compare each chunk w.Open("EURUSD.tkz", "EURUSD", 5, false); // append=true adds chunks to an existing file w.WriteTicks(ticks, 0, ArraySize(ticks)); // one call = one chunk w.Close(); CTkzReader r; r.Open("EURUSD.tkz", false); r.ReadRange(from_msc, to_msc, ticks); // decodes only the chunks that overlap r.ReadChunk(i, ticks); r.ReadAll(ticks); r.GetChunkInfo(i, info); ``` Decoded `MqlTick`s have `time`, `time_msc`, `bid` and `ask` set, plus `TICK_FLAG_BID`/`TICK_FLAG_ASK` where the value changed. `last` and volumes are not stored and are zero. ## Format [docs/FORMAT.md](docs/FORMAT.md) describes the file and chunk layout, preprocessing, binarisation and model. In short: * every chunk is independent: an 88-byte header plus the payload, with a checksum of the decoded ticks; * the archive is a 64-byte file header followed by chunks; * all model arithmetic is integer-only, so archives are portable between machines and builds. ## Testing without MetaTrader The MQL5 sources are translated to C++ by `tests/mql2cpp.py` and compiled against `tests/mql5_shim.h`. The shim is a small emulation of the MQL5 runtime: arrays are bounds-checked, memory from `ArrayResize` is poisoned, and terminal functions run over a tick file. This is the exact code that runs in the terminal: * `test_codec`: synthetic edge cases, bit-exact round trips of real data, determinism, archives (append, range queries), 3000 fuzzed chunks (rejected or decoded identically, never a crash), and the `TkzSelfTest.mqh` suite; * `test_scripts`: runs `TkzExport` (day/week/month chunks, checked against independent calendar periods), `TkzVerify`, `TkzToCustom` and `TkzSelfTest` end to end on an emulated terminal. ```sh tests/data/fetch_samples.sh # optional: download the benchmark samples (~36 MB) CXX="c++" tests/run_tests.sh tests/build/ticks/*.ticks ``` Any C++17 compiler works (`CXX="zig c++"` also works). The golden check of `TkzSelfTest` encodes three deterministic chunks and compares the bytes with this reference build. The chunks include negative prices, crossed quotes, escapes, a decimal sub-grid, and time and price steps. A `PASS` in MetaTrader means the terminal produces exactly the same archives.