โ€บNavigation

โ† Audits

Crypto cost-realism labels investigation and ADR pre-lock

HALTED2026-07-08

Eleven read-only investigation passes worked out how to record, for every crypto bar, the worst price a real order would actually have filled at in the seconds after the bar closed โ€” landing on a separate database table filled in afterwards from the stored tick history โ€” and left four design questions for the supervisor before any code could be locked in.

Why it carries this status

Lifecycle, not result. This says where the audit sits in its process โ€” never whether what it found was good.

**Status**: SCAFFOLDED ยท INVESTIGATION COMPLETE, ADR DRAFTED, AWAITING TERRY SIGN-OFF
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/CLAUDE.md

2026-08-17. Adjudicated 2026-08-17: the challenge pass imported the sibling 07-15 folder's 'COMPLETE' header as evidence for this one. This folder's own header still reads AWAITING TERRY SIGN-OFF and OPEN-QUESTIONS-FOR-TERRY.md Q1-Q4 are unanswered. Challenge pass said: The quote exists verbatim at CLAUDE.md:3, but it is a stale header, not a live blocker, and the proposal's blocked_on is factually wrong. (1) Its central claim โ€” 'the folder was never updated after Terry's 2026-07-14 directive' โ€” is false: PILOT-BTCUSDT.md, which landed IN this f

Blocked on Terry Li โ€” the folder's declared blocker is his sign-off on the four open questions before the ADR locks (OPEN-QUESTIONS-FOR-TERRY.md Q1โ€“Q4), and PILOT-BTCUSDT.md Step 4 adds a second gate: no extension beyond BTCUSDT without his explicit go. Note the folder was never updated after Terry's 2026-07-14 directive; the dashboard twin records the block as subsequently cleared, but no file inside this folder says so.

The verdict

The audit's own conclusion, reproduced in full from the source below. Not a summary โ€” this is the document, rendered. Links inside it that point at unpublished files are shown as plain text rather than as links that would 404 here.

VERDICT โ€” Crypto Cost-Realism Labels

Plain English: A backtest that assumes you buy at the bar's close price and sell later at close price is lying twice โ€” you can't trade instantly (the price moves against you in the seconds before your order fills = slippage), and Binance takes a fee on every trade. The forex pipeline already records, for every bar, the worst price a real market order would have filled at within 3 seconds of bar close (plus the exit leg 3s later, plus benchmark prices). Crypto records none of it. This work closes that gap for the crypto table, adapted for the fact that Binance spot trades have no bid/ask โ€” so we assume the worst trade print in the forward window was our fill.

These are labels (they see 3โ€“6 seconds into the future), never features. A feature that could see the future would look spectacular and poison every model trained on it โ€” the exact leakage class this team was burned by before.


The 5 locked decisions (evidence-backed, not Terry's to relitigate)

L1 โ€” Separate label table, not inline columns

opendeviationbar_cache.open_deviation_bar_labels, mirroring the parent: ENGINE = ReplacingMergeTree(computed_at), PARTITION BY (symbol, threshold_decimal_bps). Why forced: the parent table is ReplacingMergeTree(computed_at); any inline label written post-hoc gets wiped the next time the sidecar/kintsugi re-inserts a fresher row version with NULL labels. Inline is only safe if every writer computes labels at insert time โ€” which the streaming engine cannot do (it lacks the forward window at bar close). A separate table = zero new writers on open_deviation_bars โ†’ satisfies writer-ownership #280 by construction. (Round-1 guards + Round-2 bar-identity.)

L2 โ€” Post-hoc backfill over the Parquet tick cache, sidecar path intentionally not involved

The Tier-1 Parquet tick cache is a sufficient forward-window substrate: zero interior missing days for all 16 symbols, spanning first-barโ†’last-bar. This directly answers HANDOFF hard-req 3's "document why the sidecar path is intentionally not involved": labels need the forward window the streaming engine doesn't have at bar close. Backfill runner = a stripped fork of repair_direct_parquet.py's scan skeleton; its --no-service-stop refusal does NOT transfer (that gate exists for bar-table partition races; a separate-table writer has none). (Round-2 tick-cache + oracle-and-backfill.)

L3 โ€” Key on first_agg_trade_id; labels survive repairs as dangling/missing, never silently wrong

Bar construction is a pure deterministic function of (anchor boundary, immutable aggTrade stream, threshold). Under both sidecar clean-slate DELETE+refill and every kintsugi repair that commits (they abort unless the recomputed tail rejoins at the exact TID), bars re-appear at bit-identical keys. The only staleness mode is a dangling label (key deleted by a repair that rewrote a corrupted range) or a missing one (new key) โ€” never a silently-wrong label. Mitigation: the primary read joins from bars (bars LEFT JOIN labels) so unlabeled bars read as NULL and dangling labels are simply not selected; the JOIN also matches the stored last_agg_trade_id/close_time_us consistency columns, so a same-key-but-reshaped bar reads NULL rather than a stale label; a periodic anti-join/consistency sweep re-labels missing keys (the pattern kintsugi's residual sweeper already uses). first_agg_trade_id is the structural choice: it's the ORDER BY / dedup key, strictly monotone under Stathera, and the key every production DELETE already scopes on. (Round-2 bar-identity: uniqueness verified collision-free on 4 partitions / ~25.4M bars.)

L4 โ€” Window membership is TID-anchored, not pure-timestamp

tick.agg_trade_id > bar.last_agg_trade_id AND normalize_us(tick.timestamp) < bar.close_time_us + Hยท1e6. Why forced: 100% of pre-2025 bars have ms-derived close times (ยตs value โ‰ก Binance ms ร—1000), so many distinct aggTrades share one timestamp and a pure-timestamp boundary is ill-defined. TID-anchoring excludes the breach trade deterministically (by TID, not time) and is immune to the ms/ยตs mix. Empty window โ†’ NaN โ†’ NULL, never 0, never a copied close (HANDOFF ยง3.3) โ€” and this is not an edge case: at H=1s, 18โ€“24% of illiquid-symbol bars have no in-window trade (8.9โ€“13.3% at 3s). (Round-2 tick-cache ยง3โ€“4.)

L5 โ€” Fee rides a ClickHouse DEFAULT-expression column with the constant pinned in the COMMENT

Forex precedent: derived cost columns are Nullable(Float64) DEFAULT <pure expr over raw label columns> with the formula + provenance in the COMMENT ("replay-safe"). Crypto: label_fwd_{H}s_roundtrip_cost_bps as a DEFAULT expression embedding the pinned VIP5 + 25%-BNB taker constant, with value + retrieval date + Binance schedule URL in the COMMENT โ€” swappable via ALTER MODIFY COLUMN DEFAULT without touching Rust, and it keeps the countIf(isNotNull()) coverage-audit surface a COMMENT-only constant would lose. (Round-1 ch-schema + writer; Round-2 oracle.)


The 4 decisions โ€” resolved (Terry may override any; see OPEN-QUESTIONS-FOR-TERRY.md)

  1. Horizon set โ†’ 3s. Corrected after reading the consumer directly: gate05/cost_from_ch.py is parameterized over {1,3,5,10}s and defaults to 3s (it reads the forex table; crypto has no label consumer yet). The handoff also says 3s โ€” no conflict. 1s lives only in a secondary benchmark run (BENCHMARK_2026-04-29.md), not the gate. Ship 3s; schema stays horizon-parameterized so 1s/5s/10s is a cheap add-later backfill. (An earlier draft framed this as a blocking 3s-vs-1s conflict โ€” retracted.)
  2. Steady-state labeling โ†’ backfill-only now + a follow-up issue for the recurring top-up job.
  3. mql5 probe twin โ†’ deferred to a later, separate mql5 PR (mql5/findings/forex_shared_data/orthogonality_probe.py targets the crypto table).
  4. Acceptance โ†’ "100% minus per-symbol-quantified empty-window NULLs" (forex's ยฑ5%-vs-reference precedent), not literal 100%.

Sequencing: ADR does not lock until Terry answers. Per the handoff, ping after reading (done, msg 7458) and again before the ADR locks.

source: findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/verdict.md

Still owed 11

What it claims, and what backs each claim 22

Every row pairs a claim with the file it came from and the verbatim text in that file. The sources sit above the deploy root, so the quote is embedded and the path is printed as text rather than linked โ€” a link would resolve on a laptop and 404 here.

ClaimEvidence
Crypto bars carry no execution-cost labels at all today, while the forex table already has thirty-two of them.
MEASURED
crypto: 132 columns, 0 label columns, ~139.0M rows, 62 partitions (16 symbols ร— 4 thresholds except ETHUSDT at 2); forex: 117.9M rows, 32 [LABEL] columns
- **Crypto table** `opendeviationbar_cache.open_deviation_bars`: 132 cols, **0** `label_%`, **~139.0M rows** (live snapshot: 138,999,887 at first read, 138,999,909 a few queries later โ€” the table grows continuously; re-verify at implementation)
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/INVESTIGATION-DIGEST.md
The handoff's claim that the forex labels are '100% populated' is wrong โ€” measured coverage varies by family from 99.98% down to 48.25%.
REFUTED
entry family 99.98%, exit family 91.5โ€“93.5%, abdi-ranaldo 48.25% (fxview_cache.forex_bars, 117.9M rows)
| 1 | HANDOFF:8 says forex is "100% populated"; measured entry 99.98%, exit 91.5โ€“93.5%, abdi-ranaldo 48.25% | **Not 100%.** ADR cites the measured coverage + the ยฑ5%-vs-reference acceptance model, not "100%". (Feeds Q4.) |
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/CONTRADICTIONS-RESOLVED.md
Locked decision L1: the labels must live in their own table, because the parent table's deduplication engine would silently wipe any label written into it after the fact.
CONFIRMED
0 new writers on open_deviation_bars; satisfies writer-ownership #280 by construction
**Why forced:** the parent table is `ReplacingMergeTree(computed_at)`; any inline label written post-hoc gets **wiped** the next time the sidecar/kintsugi re-inserts a fresher row version with NULL labels.
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/VERDICT.md
Locked decision L2: the stored Parquet tick cache is a sufficient substrate for computing the forward window, with no interior missing days across all sixteen symbols.
MEASURED
16 symbols, zero interior missing days; two tail carve-outs (14 symbols co-frozen at 2026-04-12; BTC/ETH lag bars 1โ€“2 days on Vision publication)
The Tier-1 Parquet tick cache is a **sufficient** forward-window substrate: zero interior missing days for all 16 symbols, spanning first-barโ†’last-bar.
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/VERDICT.md
Locked decision L3: keying the labels on the bar's first trade ID means a repair can leave a label dangling or missing, but never silently wrong โ€” uniqueness was verified collision-free.
MEASURED
4 partitions checked, ~25.4M bars, 0 key collisions
*(Round-2 bar-identity: uniqueness verified collision-free on 4 partitions / ~25.4M bars.)*
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/VERDICT.md
Locked decision L4: window membership must be anchored on trade ID rather than timestamp, because every pre-2025 bar has a millisecond-derived close time that makes a timestamp boundary ambiguous.
CONFIRMED
100% of pre-2025 bars ms-derived (close_time_us % 1000 = 0)
**Why forced:** 100% of pre-2025 bars have ms-derived close times (ยตs value โ‰ก Binance ms ร—1000), so many distinct aggTrades share one timestamp and a pure-timestamp boundary is ill-defined.
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/VERDICT.md
Empty forward windows are not an edge case on thin symbols โ€” up to a quarter of bars have no trade at all in a one-second window โ€” so the null policy is load-bearing.
MEASURED
BCH 8.9%@3s / 18.4%@1s; FIL 13.3%@3s / 23.9%@1s; UNI 11.3%@3s / 21.6%@1s; BTC 0.33%@3s
Empty window โ†’ **NaN โ†’ NULL**, never 0, never a copied close (HANDOFF ยง3.3) โ€” and this is *not* an edge case: at H=1s, 18โ€“24% of illiquid-symbol bars have no in-window trade (8.9โ€“13.3% at 3s).
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/VERDICT.md
Locked decision L5: the Binance fee constant rides a database DEFAULT expression with the pinned value, retrieval date and schedule URL in the column comment, so it can be swapped without touching Rust.
CONFIRMED
1 derived column; VIP5 + 25% BNB taker fee constant
`label_fwd_{H}s_roundtrip_cost_bps` as a DEFAULT expression embedding the pinned VIP5 + 25%-BNB taker constant, with **value + retrieval date + Binance schedule URL** in the COMMENT โ€” swappable via `ALTER MODIFY COLUMN DEFAULT` without touching Rust
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/VERDICT.md
The horizon question resolved to three seconds with no conflict: the live consumer is parameterised over four horizons and defaults to 3s, matching the handoff.
CONFIRMED
gate05/cost_from_ch.py parameterized over {1,3,5,10}s, defaults to 3s
> **Decision:** ship **3s**, schema + kernel horizon-parameterized. Adding 1s/5s/10s later is a > marginal backfill (one scan bounded by max H). *(An earlier draft overstated this as a > blocking 3s-vs-1s conflict โ€” corrected after reading the consumer code directly.)*
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/OPEN-QUESTIONS-FOR-TERRY.md
A nine-assertion leakage guard suite was specified so labels can never enter a feature surface; only one assertion had shipped at the time of writing.
CONFIRMED
9 assertions Aโ€“I; 1 merged (D, PR #590), 8 pending
Forex has no such test to port; this suite is new IP. Assertion **D** is already merged to main (**PR #590**); the rest ship with the implementation PRs.
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/GUARD-SUITE.md
A completeness critic found ten contradictions between the eleven investigation reports; each was resolved against the code rather than left for the ADR to inherit.
CONFIRMED
10 contradictions resolved; 4 further unevidenced claims down-weighted
The completeness critic flagged 10 inter-report contradictions. Each is resolved here with the code-backed reading, so the ADR cites the right one.
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/CONTRADICTIONS-RESOLVED.md
The separate-table architecture is justified by a prior failure: an earlier batch passed every compute-layer gate yet shipped completely unpopulated because five write surfaces each hand-listed columns and none were wired.
CONFIRMED
~5 write surfaces; 0% populated on ship; 1 writer / 1 column list in the new design
- **#544โ†’#556 lesson**: batch-5 passed every compute-layer gate but shipped **0%-populated** because ~5 write surfaces each hand-listed columns and none were wired. Caught only by a post-deploy live coverage audit. The separate-table architecture **eliminates this class** (one writer, one column list โ€” nothing to forget to wire).
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/INVESTIGATION-DIGEST.md
There is no forex precedent for computing labels after the fact in SQL โ€” the crypto design is a new shape justified on its own merits, not by parity.
REFUTED
**No forex post-hoc-SQL precedent.** Crypto's post-hoc-over-Parquet is a *new* shape justified on its own merits (forward window unavailable at bar close), not "forex does it".
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/CONTRADICTIONS-RESOLVED.md
The crypto oracle tolerance is stricter than forex's, not merely equivalent: three columns can be bit-exact and the rest within 1e-9.
ASSERTED
crypto โ‰ค1e-9 (3 columns bit-exact); forex's own forward-window reproducibility 1e-8
Crypto **can** hit โ‰ค1e-9 post-hoc (deterministic scan, no summation-order race). Don't phrase it as "same discipline as forex" โ€” it's *stricter*. worst_buy/sell/arrival bit-exact; TWAP/VWAP โ‰ค1e-9 with a documented summation bound.
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/CONTRADICTIONS-RESOLVED.md
An operational blocker was found: the tick files are readable only by the odb user, so the backfill worker must run as that user.
CONFIRMED
file mode 0600, owner odb:odb
- **Access blocker**: tick files are `0600 odb:odb`; nasimubd can't read them. The backfill worker MUST run as user `odb` (systemd oneshot, like the seeder).
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/INVESTIGATION-DIGEST.md
The acceptance criterion is deliberately not literal full coverage โ€” it is full coverage minus per-symbol quantified empty-window nulls, mirroring the forex ยฑ5% precedent.
CONFIRMED
5-point batch-6 gate; forex precedent ยฑ5% vs reference forward-column null rate
Acceptance (ADR ยง9 Q4): **100% minus per-symbol-quantified empty-window NULLs**, never literal 100%. The NULL taxonomy below makes "minus what, exactly" a number, not a hand-wave.
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/COVERAGE-AUDIT-SPEC.md
The coverage audit and both pilot plans are written but were never executed by the automated loop โ€” every red step is operator-owned.
CONFIRMED
3 authored-not-executed operator procedures (COVERAGE-AUDIT-SPEC, PILOT-SUIUSDT, PILOT-BTCUSDT)
**Status**: AUTHORED, NEVER EXECUTED BY THE LOOP โ€” the audit runs operator-side after each backfill (pilot and full). All queries are read-only SELECTs (R2).
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/COVERAGE-AUDIT-SPEC.md
The pilot target was changed from an illiquid symbol to Bitcoin on Terry's instruction, which flips the acceptance test from expecting quantified nulls to expecting essentially none.
CONFIRMED
tracking issue #614; preconditions expect 8 label columns; BTC 3s windows effectively never empty
**Supersedes `PILOT-SUIUSDT.md` as the first backfill target** per Terry's 2026-07-14 directive in the General topic โ€” *"BTC first and only for now if faster"* (msg 8112) plus the non-null acceptance check *"Check to make sure the columns are non-null"* (msg 8130).
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/PILOT-BTCUSDT.md
The investigation's own live-query numbers are flagged as provenance-weak: they lived only in ephemeral tool-result files and must be re-run and persisted before the ADR is cited as authority.
OPEN
7 re-runnable queries listed in the Evidence section
**Provenance caveat** (critic): these numbers lived only in ephemeral tool-result files during the investigation. Before the ADR is cited as authority, re-run the coverage + uniqueness queries at implementation time and persist the output into this folder.
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/INVESTIGATION-DIGEST.md
No GitHub tracking issue existed when the investigation closed, although the handoff requires one before any column comment is written.
CONFIRMED
0 tracking issues at 2026-07-08; #614 appears later in the pilot documents
- **No tracking issue exists** (checked 2026-07-08) โ€” one must be CREATED before any COMMENT is written (HANDOFF hard-req 2). The "#544โ†’#556" are merged PRs, not issues.
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/INVESTIGATION-DIGEST.md
The load-bearing asymmetry with forex: forex tolerates two concurrent writers only because they share one bit-identical kernel, which crypto cannot cheaply reproduce across 132 columns.
CONFIRMED
132 columns would need bit-exact reproduction under the inline design; 0 coordination needed under the separate-table design
**The separate table dissolves the asymmetry**: the label writer owns its own table outright, coexisting with sidecar + kintsugi with zero coordination โ€” exactly the clean boundary the writer-ownership model wants.
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/FOREX-REFERENCE-MAP.md
The whole design rests on the fact that the streaming engine cannot compute these values at bar close, because the forward window has not happened yet.
CONFIRMED
This directly answers HANDOFF hard-req 3's "document why the sidecar path is intentionally not involved": labels need the *forward* window the streaming engine doesn't have at bar close.
findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/VERDICT.md

The audit folder 23 markdown files

Source of record: findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/ โ€” not published, so these are listed rather than linked.

FileRole
CLAUDE.mdHub: status header, what the investigation was, converged architecture, document index, related PRs, provenance discipline.
CONTRADICTIONS-RESOLVED.mdThe critic's 10 inter-report contradictions with the code-backed resolution for each, plus down-weighted unevidenced claims.
COVERAGE-AUDIT-SPEC.mdThe operator-run 5-point coverage audit: baseline capture, coverage, row parity, value sanity, consistency queries. Authored, never executed by the loop.
FOREX-REFERENCE-MAP.mdWhat the forex side actually does, column by column, and the keep / adapt / new delta for crypto.
GUARD-SUITE.mdThe Aโ€“I label-leakage guard assertions, their surfaces and ship status, plus the defence-in-depth layering.
INVESTIGATION-DIGEST.mdThe condensed 11-sweep findings with file:line and query evidence โ€” the ADR's citation base, plus the re-runnable query list.
OPEN-QUESTIONS-FOR-TERRY.mdThe four decisions escalated to the supervisor, each resolved into a proposal ready to approve or override.
PILOT-BTCUSDT.mdStep-by-step operator-run BTC pilot plan that supersedes the SUI pilot per Terry's 2026-07-14 directive, with gates, rollback and trap table.
PILOT-SUIUSDT.mdThe original small-symbol pilot plan; authored, never executed, superseded as first target.
VERDICT.mdRoot verdict document (uppercase filename, not the conventional lowercase verdict.md): plain-English conclusion, the 5 locked decisions, the 4 questions resolved-pending-Terry.
evidence/README.mdIndex of the frozen raw subagent reports, round 1 and round 2, with the surface each covered.
evidence/round1-01-ch-schema-ground-truth.mdFrozen sweep: live ClickHouse schema for crypto and forex, label columns, comment taxonomy, coverage.
evidence/round1-02-pending-queue-mechanics.mdFrozen sweep: the forex in-stream pending queue โ€” windows, finalization, restart behaviour.
evidence/round1-03-writer-and-schema-authoring.mdFrozen sweep: write path, nullability, where column comments are authored, writer ownership.
evidence/round1-04-provenance-bbc-and-ingest-spec.mdFrozen sweep: the broker-behaviour provenance template (BBC-006/009/010) and the 'why 3 seconds' question.
evidence/round1-05-campaign-template.mdFrozen sweep: the forex exit-leg backfill campaign โ€” batching, resume, audit, anti-patterns.
evidence/round1-06-guards-and-manifest.mdFrozen sweep: label/feature separation on both sides and the source list for the guard assertions.
evidence/round1-07-completeness-critique.mdFrozen sweep: the completeness critic โ€” missing facts, contradictions, unevidenced claims.
evidence/round2-01-oracle-and-backfill-template.mdFrozen sweep: the #544โ†’#556 story, the bartels oracle recipe, and reuse of the direct-Parquet repair runner.
evidence/round2-02-tick-cache-substrate.mdFrozen sweep: Parquet tick-cache coverage, millisecond/microsecond semantics, per-symbol empty-window rates.
evidence/round2-03-bar-identity-and-label-staleness.mdFrozen sweep: bar-identity determinism under repairs, the key choice, and the join precedent.
evidence/round2-04-consumers-and-dashboard.mdFrozen sweep: read-side consumers, dashboard conventions, and the absence of a tracking issue.
evidence/round2-05-horizon-ssot-reconciliation.mdFrozen sweep: the 1s-vs-3s horizon single-source-of-truth reconciliation, grounded in files.

Dashboard twin

findings/dashboard/campaigns/2026-07-08-crypto-cost-realism-labels/

Generated by findings/dashboard/build_audits.py from findings/evolution/audits/2026-07-08-crypto-cost-realism-labels/AUDIT_LEDGER.json โ€” never hand-edited. Each quote was verified to occur in the file named beside it when the ledger was written.