| # 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 `lib/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
|
|
|
| * `digits` is the smallest d for which every price p satisfies
|
| `bits((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^d` is computed by repeated multiplication by 10.0, so it is exact.
|
| * Integer bid states start at `B_base` (ask: `A_base`). The price step `g` is the
|
| GCD of all differences between consecutive regular values of both sides
|
| (1 if none). The model works on `X = (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.
|
|
|
| 1. **Time delta** (`dt`, magnitude group 0):
|
| bit length `k` of `dt` as a 4-bit value `min(k, 15)`; 15 escapes to a
|
| second nibble `min(k - 15, 15)`; 15 again escapes to the raw 64-bit value
|
| as 16 nibbles (negative values always take this path). Then the `k - 1`
|
| bits below the leading one, MSB first, in nibbles; the first three nibbles
|
| use the full prefix as context, later ones its low 12 bits.
|
| 2. **Tick type**: tree of `bid changed`, `ask changed` and, if both changed,
|
| `both moved by the same amount`.
|
| 3. **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).
|
| 4. **Bid move** (if changed): with M > 1 the new residue (4 bits); then the
|
| coarse move `dq`: when the residue changed, `dq == 0` is possible and a
|
| `nonzero` decision precedes the sign; the magnitude `|dq| - 1` is coded like
|
| the time delta (group 1).
|
| 5. **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.
|
| 6. **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),
|
| update `p += (target - p) * 2 / (2n + 3)`, `n` saturates at 255.
|
| * **Hash table**: `2^table_bits` counters 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 1 `x2 = xmid`,
|
| else `x1 = xmid + 1`; leading equal bytes are shifted out. The encoder
|
| flushes the 4 bytes of `x1`; 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.
|