TKZ format, version 1
TKZ stores the tick stream (time_msc, bid, ask) losslessly: decoding yields
exactly the same 64-bit integers and the same IEEE-754 bit patterns that were
encoded, in the same order, including duplicates, ticks that go back in time
and arbitrary double values (NaN, infinities, -0.0, denormals,
non-normalised prices).
The reference implementation is MQL5/Include/Tkz/TkzCodec.mqh; where this
document and the code differ, the code is normative. All integers are
little-endian.
1. Archive file (.tkz)
| offset | size | field |
|---|---|---|
| 0 | 4 | magic "TKZF" |
| 4 | 2 | file format version = 1 |
| 6 | 2 | header size = 64 (chunks start here) |
| 8 | 32 | symbol name, UTF-8, zero padded (informational) |
| 40 | 4 | symbol digits as int32, informational (-1 = unknown) |
| 44 | 20 | reserved, zero |
The header is followed by chunks stored back to back. A reader builds its index by walking the chunk headers (each one states its payload size), so no separate index is needed and appending chunks to an existing file is trivial.
2. Chunk
A chunk is a self-contained block of ticks: it is decoded without any other chunk, so chunks can be read in any order (random access by day/week/month).
| offset | size | field |
|---|---|---|
| 0 | 4 | magic "TKZ1" |
| 4 | 1 | format version = 1 |
| 5 | 1 | model version = 1 |
| 6 | 1 | table_bits: log2 of the counter table size (16..26) |
| 7 | 1 | digits d (0..12): prices are integers in units of 10^-d |
| 8 | 1 | modulus M (1..16): decimal sub-grid modulus (1 = off) |
| 9 | 1 | flags: bit 0 = chunk contains escaped (irregular) prices |
| 10 | 2 | reserved, zero |
| 12 | 4 | tick count N |
| 16 | 4 | payload size in bytes |
| 20 | 4 | checksum of the decoded ticks (see 2.4) |
| 24 | 8 | t_base: time_msc of the first tick |
| 32 | 8 | time_msc of the last tick |
| 40 | 8 | time step gt >= 1 |
| 48 | 8 | price step g >= 1 |
| 56 | 8 | B_base: first regular bid as an integer (units of 10^-d) |
| 64 | 8 | A_base: first regular ask as an integer |
| 72 | 8 | minimum time_msc in the chunk |
| 80 | 8 | maximum time_msc in the chunk |
| 88 | payload | arithmetic-coded tick stream |
Decoders must reject unknown format or model versions.
2.1 Prices as integers
digitsis the smallest d for which every price p satisfiesbits((double)round(p * 10^d) / 10^d) == bits(p)with|p * 10^d| <= 4e15. If no d in 0..12 makes all prices exact, the d with the fewest failures is used. Prices that fail this test are irregular: they are stored as raw 64-bit patterns (escapes) and do not move the integer price state.10^dis computed by repeated multiplication by 10.0, so it is exact.- Integer bid states start at
B_base(ask:A_base). The price stepgis the GCD of all differences between consecutive regular values of both sides (1 if none). The model works onX = (B - (B_base mod g)) / g(floored mod), which is exact, so tick sizes like 0.25 or 5 points cost nothing. - The decoder reconstructs a regular price as
(double)(X * g + off) / 10^d, which is the correctly rounded value and therefore bit-identical.
2.2 Time
gt is the GCD of all |t[i] - t[i-1]| (1 if all are equal), e.g. 1000 for
second-resolution history. Each tick codes dt = (t[i] - t[i-1]) / gt
(the first tick uses t_base, so its dt is 0). Negative and huge values are
legal and go through the raw escape described below.
2.3 Decimal sub-grid
Many feeds quote on a coarser grid with a constant markup, e.g. XAUUSD with 3
digits where almost every price ends in 5. If the entropy of X mod 10 over
the chunk is below 2 bits (and N >= 256), M = 10 is used: every price state is
split into q = floor(X / M) and residue r = X mod M, and moves are coded as
a new residue plus a move of q. Otherwise M = 1 (no split).
2.4 Checksum
h = 0; for every tick, with T = time_msc, B/A = bit patterns of bid/ask:
h = H(h ^ lo32(T), hi32(T)), then the same for B and A, where H is the
32-bit mixing function H2 of the reference code. The decoder verifies it, so
corruption and implementation mismatches are detected.
3. Tick coding
Every tick is coded as a sequence of binary decisions, each predicted by the context-mixing model (section 4) and arithmetic coded. Decisions are grouped in segments of 1-4 bits that form a small binary tree (node 1 = root); one hash-table block of 16 counters serves one segment.
- Time delta (
dt, magnitude group 0): bit lengthkofdtas a 4-bit valuemin(k, 15); 15 escapes to a second nibblemin(k - 15, 15); 15 again escapes to the raw 64-bit value as 16 nibbles (negative values always take this path). Then thek - 1bits below the leading one, MSB first, in nibbles; the first three nibbles use the full prefix as context, later ones its low 12 bits. - Tick type: tree of
bid changed,ask changedand, if both changed,both moved by the same amount. - Escapes (only if flag bit 0): for each side that did not change, a flag; a flagged side is followed by its raw 64-bit pattern (16 nibbles).
- Bid move (if changed): with M > 1 the new residue (4 bits); then the
coarse move
dq: when the residue changed,dq == 0is possible and anonzerodecision precedes the sign; the magnitude|dq| - 1is coded like the time delta (group 1). - Ask move (if changed and not equal to the bid move): new residue
(M > 1); if the bid did not change, the coarse ask move (group 2), otherwise
the change of the coarse spread
dqa - dqb(group 3), which is much more predictable than the ask move itself. - State update: last two dt values and bit lengths, last three tick types, last two coarse moves per side, last spread change, EMAs of the dt bit length (rates 1/8 and 1/64), EMA of the bid move size, EMA of the coarse spread.
4. Model
- Counters: 32-bit
p22 << 10 | n(22-bit probability, 10-bit count), updatep += (target - p) * 2 / (2n + 3),nsaturates at 255. - Hash table:
2^table_bitscounters in blocks of 16; slot 0 holds a 32-bit check, slots 1-15 the tree nodes of a segment. Block lookup is 2-way associative; on a miss the block whose node 1 has the smaller count is replaced. - Contexts: 4-10 per field, built from the coding state (previous dt and
its bit length, EMAs, previous tick types, last moves of both sides, the
current dt bucket, spread relative to its EMA and in absolute units,
residues, hour of the previous tick, ...). See
CodeTick()for the exact hashes. - Mixing: inputs are
stretch(p)of each context plus a bias. Mixer 1 selects its weight set by the decision (class * 16 + node, 5376 sets), mixer 2 by a hash of the decision and a field-specific context (65536 sets), a final 2-input mixer per decision class combines both. Learning rates: 8/16384 for the first layer, 6/16384 for the final mixer. - APM / SSE: two interpolating APMs (33 buckets over the stretch domain,
rate 1/128), one per decision, one per hashed decision context. The final
probability is
(2 * p_mix + p_apm1 + p_apm2) / 4, clamped to [1, 4095]. - Arithmetic coder: 32-bit carry-less binary coder,
xmid = x1 + ((x2 - x1) * p) >> 12(64-bit product); on bit 1x2 = xmid, elsex1 = xmid + 1; leading equal bytes are shifted out. The encoder flushes the 4 bytes ofx1; the decoder reads zeros past the end.
All model arithmetic is integer-only, so every platform produces the same
bytes. The self-test (TkzSelfTest) checks this against a golden hash.