152 lines
7.5 KiB
Markdown
# 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
| |||
| |||
* `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.
|