Documentation
Record format, version 1
Summary. Record format version 1 is the contract of every sealed hour. It states where the files of a sealed hour sit in storage you own, and how time, JSON and hashes are encoded. It defines the raw file, the normalized tables, and the hourly and daily manifests that form the hash chain. It lists the ten checks a verifier makes. Within version 1, only optional manifest fields and new columns may be added.
What follows is the format document itself, unedited apart from its title.
Status: draft. This document states what a segment contains and what a verifier checks. It is the public contract of format major 1.
Layout
<prefix>/recorder=<recorder>/venue=<venue>/channel=<channel>/date=YYYY-MM-DD/
manifest.json daily manifest
manifest.json.sig detached signature over the daily manifest
hour=HH/
raw.jsonl.zst every frame exactly as received
<table>.parquet normalized tables of the channel
manifest.json hourly manifest
manifest.json.sig detached signature over the hourly manifest
Channels: trades, book, funding, liquidations. A frame belongs to the UTC hour of its frame time ("Raw file", below).
Encodings
- Time. int64 nanoseconds since the Unix epoch. In JSON, an RFC 3339 UTC string with the
Zdesignator and exactly nine fractional digits (2026-10-03T14:00:00.000000000Z); one instant has one encoding. - JSON. Manifests and raw-file lines are RFC 8785 canonical JSON. Numbers are integers within ±(2^53 − 1); every other value is a string.
- Hashes. BLAKE3, 32 bytes, lowercase hexadecimal.
Raw file
A frame's frame time is the earliest receive_time among its receipts that carry clock_error_bound, or among all its receipts when none does. Frames are ordered and assigned to hours by frame time, and normalized rows carry it in receive_time.
raw.jsonl.zst is zstd-compressed; each line is one envelope, in order of frame time:
| Field | Type | Meaning |
|---|---|---|
payload | string | The frame: verbatim text, or base64 of a binary frame |
payload_encoding | text or base64 | How payload encodes the frame |
exchange_time | time, optional | Time the venue stated for the frame; the earliest when it states several |
receipts | array | One per replica that received the frame |
Each receipt: replica (a or b), connection (opaque), receive_time, clock_offset (the receiving host clock's estimated offset from UTC, in signed integer nanoseconds, positive when the host clock is fast), and clock_error_bound (a non-negative integer of nanoseconds bounding the absolute error of receive_time against UTC, below). Both are absent when no estimate existed or the replica withheld it (time daemon unsynchronized, sample older than 16 s, clock stepped, or source not NTS-authenticated), which the manifest states.
Clock calibration
- Point of timestamping.
receive_timeis read when the replica holds the complete frame from its connection (after transport decryption and frame reassembly), for every venue. - Bound.
clock_error_bound= abs(clock_offset) + root dispersion + root delay / 2 + 15e-6 × the sample's age + 1.25 ns, rounded up to the next nanosecond, from the time daemon's latest sample: the absolute offset, plus RFC 5905's root distance (root dispersion plus half the root delay), plus dispersion growth at RFC 5905's frequency tolerance (PHI, 15 ppm), plus 1.25 ns for the nanosecond resolution at which the sample states its offset, root delay and root dispersion (0.5 ns each, halved for the root delay). The age is measured on a monotonic clock from the moment the replica starts reading the sample. - Conditions. The bound holds when the upstream reference is correct, the host clock's frequency error stays within 15 ppm, the clock is not stepped between the sample and the receipt, and the time daemon reports itself synchronized. A replica withholds
clock_offsetandclock_error_boundwhile the time daemon reports itself unsynchronized, when the latest sample is older than 16 s, for a receipt whose sample did not come from a source authenticated with Network Time Security (NTS, RFC 8915), and after a step until the time daemon has updated the clock from a measurement taken after the step. - Kind of bound. The bound is a maximum-error bound under these conditions, not a probabilistic interval, and no coverage factor is claimed.
- **
receive_timeandexchange_time.**exchange_timeis the venue's stated time for the frame; the venue's clock carries no bound in this record, so the difference between the two times is not a one-way network delay and can be negative.
Normalized tables
Parquet, zstd-compressed, one per table even when it has no rows. Every row references its frame by raw_index, the zero-based line index in the hour's raw.jsonl.zst, and carries that frame's frame time in receive_time. Values are exactly as the venue sent them: no rounding, no unit or currency conversion, no inference. Venue fields the tables do not define stay in the raw file.
| Column | Parquet type | Tables |
|---|---|---|
raw_index | int64, required | all |
instrument | string, required | all |
trade_id | string, optional | trades |
side | string; optional in trades (the venue's word), required in the book tables (bid or ask) | all |
price, size | DECIMAL(38, 18) on FIXED_LEN_BYTE_ARRAY(16), required | all |
exchange_time | TIMESTAMP(NANOS, UTC) on int64, optional | all |
receive_time | TIMESTAMP(NANOS, UTC) on int64, required | all |
Tables: trades.parquet (channel trades); book_snapshots.parquet (the snapshots the venue sent, one row per level) and book_deltas.parquet (one row per changed level; a size of zero removes the level) (channel book). A frame with a value that DECIMAL(38, 18) cannot hold exactly, or a frame of a row-carrying type that does not parse as the venue's documented structure, yields no rows; the hourly manifest lists its line index in unrepresentable_frames, and the hour is partial with reason unrepresentable_value.
Hourly manifest
| Field | Meaning |
|---|---|
format_version | 1 |
recorder, venue, channel | Identity of the chain |
interval | The hour: start_time, end_time (exclusive) |
files | Each file of the hour directory: name, size, blake3, rows |
frame_time_bounds | Earliest and latest frame time, absent when there are no frames |
sequence_gaps | instrument, after_id, before_id for each break in a venue sequence |
checksum_failures | instrument, receive_time (the receive_time of the receipt whose replica found the mismatch), expected, computed for each venue checksum mismatch |
unrepresentable_frames | Raw-file line indices kept in the raw file only; absent when empty |
replicas | Per replica: replica, frames, covered_ns, reconnect_windows, gap_free_runs, and clock (all below) |
replica_agreement | Frames seen by both replicas and by one_only, and trade IDs in conflict |
conflicts | Each trade_id of an instrument that raw-file lines carry with different side, price, size or exchange_time (channel trades): instrument, trade_id, and lines (each raw_index and the blake3 of the line's bytes without its terminating newline, U+000A); absent when there are none |
completeness | status (complete, partial, missing) and reasons |
previous_manifest_blake3 | Hash of the previous hour's manifest bytes; null at the chain's start |
Each entry of reconnect_windows carries start_time, end_time (exclusive) and, when the venue closed the connection that the window follows with a close frame carrying a status code, close_code: that frame's status code (RFC 6455, section 7.4). A window that continues from the previous hour keeps its close_code. The verifier does not check close_code.
Each entry of gap_free_runs (channel trades) states an interval during which one connection of the replica was subscribed and the replica detected no gap in the venue's trade sequence for one instrument: instrument, connection, start_time, and end_time (exclusive). A run's end_time is 1 ns after the connection's last frame before the first event that breaks the run (a disconnection, a gap detected in the venue's trade sequence for that instrument, a checksum failure, or a resubscription), or the end of the hour; the next run starts where the break was detected, so each break lies between two stated times. A run states that the replica detected no gap, not that none occurred. gap_free_runs is absent when the replica states no run. The verifier checks their references (check 9), not the venue sequences they rest on, and checks conflicts by reference (check 10).
Each replica with receipts in the raw file carries clock: source (nts; present exactly when error_bound_max is), error_bound_max (the largest clock_error_bound of the replica's receipts), and unbounded (the count of its receipts without one); source and error_bound_max are absent when every receipt of the replica is unbounded. A replica with no receipts in the hour has no clock. The reason clock_offset_unknown means that at least one frame has no receipt with clock_offset; a replica's unbounded can be nonzero without it.
Every hour has a manifest, including an hour with no data, so the chain never skips an hour. Completeness reasons: reconnect, sequence_gap, checksum_failure, clock_offset_unknown, journal_torn, journal_corrupt, recorder_down, unrepresentable_value, destination_unavailable.
Daily manifest
format_version, recorder, venue, channel, date, hours (each hour, blake3, status, in order) and previous_manifest_blake3 (the previous day's daily manifest, null at the start).
Signatures
manifest.json.sig is a detached JWS (RFC 7515) with an unencoded payload (RFC 7797) over the exact bytes of manifest.json: protected header alg Ed25519 (RFC 9864), kid, b64 false, crit ["b64"]. Keys are published as a JWK Set (RFC 7517) of OKP keys (RFC 8037) with RFC 7638 thumbprints as kid.
What a verifier checks
- Every manifest is canonical JSON with
format_version1; an unknown major is rejected. - Every listed file exists with the listed size and BLAKE3 hash.
- The raw file decompresses; every line is a canonical envelope; frame times lie in the hour and never decrease; the line count equals the listed rows.
- Every table of the channel is listed, has the v1 schema and the listed row count, and each row's
raw_indexexists and never decreases, and itsreceive_timeequals the frame time of its frame. - Hours are consecutive and each manifest carries the previous manifest's hash; the same holds for days, and each daily manifest lists exactly its day's hourly manifests. Copies of one channel in several directories are one chain; a manifest held twice with different bytes is a fork and fails.
- Every signature verifies against a key valid at signing time.
- Each receipt carries
clock_error_boundexactly when it carriesclock_offset, and the bound is at least the absolute value of the offset. - Each replica with receipts carries
clock, and no other replica does; its values equal those recomputed from the raw file; the reasons includeclock_offset_unknownexactly when some raw-file line has no receipt withclock_error_bound. - Each entry of
gap_free_runslies within the hour and ends after it starts, and itsconnectionappears in a receipt of its replica. - Each
conflictsline'sraw_indexexists, itsblake3is the hash of that raw-file line's bytes without its terminating newline, U+000A, and every line of one conflict carries itstrade_id.
Within major 1, only optional manifest fields and new columns may be added; readers ignore fields they do not know.
Nox works in writing. Email contact@noxfze.com with your venue and the instruments to record.