โ€บNavigation
Closing reconciliation

Gave crypto bars the same execution-cost safety net forex already had โ€” what a real order would actually have filled at, plus the fee โ€” so a strategy cannot look profitable on paper by ignoring slippage. Both pull requests were built, merged, and Bitcoin was backfilled and audited.

What it measured
QuantityValueMeaning
Crypto cost-realism columns0 -> 8 new columns (7 measured + 1 derived) at a 3s horizonthe gap this closed
Crypto bars in scope~139Mfull backfill target
Oracle parity1,310 bars (BTC 451 + ETH 859), residual ~1e-13, bit-exact on 5 selection labelsRust matches the answer key
Adversarial findings28 on PR-A (21 refuted, 7 amended) + 18 on PR-Bboth PRs survived attack
BTC backfill coverage audit 2026-07-155.96M labels, ~100% non-nullthe merge landed and was verified
Fee pin / observed round-trip cost2.325 bps per side; realised 4.58-15.73 bpsfee constant and observed range
How it ran
FiringWhat it did
01Tracking issue #614 created, ledger activated, PR-A worktree stood up
02Clean-room numpy oracle written; BTC/ETH golden answer-key CSVs committed
03Production Rust kernel written; three independent verification legs green
04labels:* command namespace and leakage guards A/E/G
05Adversarial pass: 28 findings adjudicated; PR #618 opened (the contracted hard stop)
07PR-B: 18 findings adjudicated, PR #619 opened, campaign saturated
What it produced
Carries forward

Fleet rollout (ETH, SUI + 14 co-frozen symbols) awaits Terry's explicit go โ€” BTC first and only for now. Plus R1/R5/R6/R7 backfiller-robustness follow-ups and a recurring top-up job. Data-hygiene note: the in-folder manifest.json still reads 'ACTIVE โ€” implementation authorized 2026-07-11', stale and superseded by the ledger.

Why it is closed

'LOOP DONE (saturated 2026-07-14). The operator merged PRs #618 + #619 on 2026-07-14, and the BTC backfill ran and passed its coverage audit on 2026-07-15.' (index.html status line)

Lifecycle status is process state, not a judgement of the findings. Results are stated as numbers with their uncertainty. Generated between CLOSING-RECONCILIATION markers โ€” regenerate rather than edit.

Dashboard โ€บ Crypto candidates โ€บ Crypto Cost-Realism Labels

opendeviationbar-py ยท cost-realism labels ยท loop DONE โ€” fleet rollout gated

Crypto Cost-Realism Labels

Give crypto bars the same execution-cost safety net the forex side already has โ€” so a strategy can never look profitable on paper by ignoring slippage and fees. This page explains the plan in plain English and draws the full pipeline, end to end.

Status โ€” LOOP DONE (saturated 2026-07-14). Implementation was authorized by the operator (2026-07-11). The loop shipped every label column and stopped at the red step as contracted; the operator merged PRs #618 + #619 on 2026-07-14, and the BTC backfill ran and passed its coverage audit on 2026-07-15 (5.96M labels, ~100% non-null). What remains is operator/Terry-owned: the fleet rollout (ETH, SUI + 14 co-frozen symbols) awaits Terry's explicit go โ€” "BTC first and only for now" โ€” plus the R1/R5/R6/R7 backfiller robustness follow-ups. Tracking issue: #614.
0
crypto cost-realism columns today
8
new crypto label columns (3s)
~139M
crypto bars to label
3s
label horizon โ€” decided

Crypto figures above (0 today ยท ~139M bars) are the investigation's read-only ClickHouse snapshot, re-verified at implementation time before any number is cited as authority.

The problem, in plain English

A backtest that assumes you buy at a bar's closing price and later sell at a closing price is lying to you twice:

The forex side of this project already records, for every bar, the worst price a real market order would actually have filled at within 3 seconds of the bar closing (plus the exit leg 3 seconds later, plus fair benchmark prices). The crypto side records none of it โ€” verified live: the crypto table has zero such columns. This work closes that gap.

๐Ÿท๏ธ These are labels, not features

They peek 3โ€“6 seconds into the future to answer "what would this have cost?". That's fine for scoring a strategy after the fact โ€” but catastrophic if a model ever trained on one: it would "see the future," look brilliant, and be useless in production. Every column is stamped NOT A FEATURE and fenced off in code (guard suite; assertion D already merged in PR #590).

๐Ÿช™ The one crypto twist: no bid/ask

Forex knows the exact ask price you'd pay. Binance spot trades don't carry a bid/ask โ€” only completed trade prints. So our fill assumption is the worst trade that actually printed in the window: the highest price if you were buying, the lowest if you were selling.

What we're proposing โ€” one architecture

A separate table open_deviation_bar_labels, keyed on (symbol, threshold, first_agg_trade_id), filled in afterwards (offline, over the Parquet tick cache), and joined to the bars at read time.

Why not just add columns to the existing bar table? Three hard reasons from the investigation:

The decisions on the table

Five are locked by evidence (not up for debate). Four more are resolved into this proposal โ€” Terry can override any, but nothing is left blocking.

#DecisionStatus
D1Separate label table, mirroring the parent engine โ€” not inline columnsLOCKED
D2Post-hoc backfill over the Parquet tick cache; the live sidecar path is intentionally not involvedLOCKED
D3Key on first_agg_trade_id; LEFT JOIN from bars at read (unlabeled bars โ†’ NULL labels, never silently wrong)LOCKED
D4Trade-ID-anchored window, half-open ยตs; empty window โ†’ NULL, never 0LOCKED
D5Fee rides a ClickHouse DEFAULT-expression column, constant (VIP5 + 25% BNB taker) pinned in the COMMENTLOCKED
Q1Horizon set โ€” 3s (matches the handoff and the live gate's default); schema horizon-parameterized so 1s/5s/10s are a cheap add-later backfillDECIDED ยท 3s
Q2Steady-state labeling of new bars โ€” backfill-only now + a follow-up issue for the recurring top-up jobCHOSEN
Q3mql5 probe twin โ€” the same leak fix in the mql5 copy is deferred to a later, separate effortDEFERRED
Q4Acceptance = 100% minus per-symbol-quantified empty-window NULLs (not literal 100%)CHOSEN

End-to-end flowchart โ€” how we'll do it

The project pipeline from Terry's handoff to a queryable label table. The violet star is the gate we're at right now: nothing below it starts until Terry gives the green light.

1 ยท Handoff โ€” Terry, 2026-07-07 "give crypto the forex-side cost-realism labels" 2 ยท Read-only investigation 11 subagent sweeps + completeness critic ยท zero production mutations 3 ยท ADR draft + this page separate table ยท post-hoc backfill ยท worst-print fill โ€” shipped (#592/#606) โœ“ Operator-authorized โ€” implementation started Loop runs from here; stops at each open PR. Operator merges + runs production steps. 4 ยท Oracle PR clean-room kernel + sha-pinned tick fixtures + /labels:* mise namespace 5 ยท Schema + backfill-runner PR CREATE open_deviation_bar_labels ยท fork of repair_direct_parquet.py 6 ยท Backfill run on bigblack runs as user odb ยท sidecar + kintsugi stay LIVE ยท operator-gated, per step 7 ยท Coverage audit 5-point gate (coverage ยท sanity ยท row-parity ยท baseline) โ†’ audit folder 8 ยท Dashboard + read contract VIEW open_deviation_bars_with_labels (bars โจ labels) โ€” the primary surface
done gate โ€” you are here next PR operator-gated (bigblack) planned
The stop that remains is the PR. The loop implements and opens PRs, then halts โ€” it never merges or runs any bigblack step. Review, merge, backfill, and deploy are operator-owned, each authorized by you one at a time.

The implementation loop โ€” and where it stops

The build runs as an autonomous loop: each label goes oracle โ†’ kernel โ†’ wiring โ†’ guards โ†’ PR, and every iteration appends one HTML page to the journal below. The loop owns everything up to the open PR (green) and then stops dead โ€” the red step is a hard halt. Merge, table creation, backfill, and deploy are operator-owned (amber) and never happen on the loop's own initiative.

P0 ยท Bootstrap the loop read ADR + guard suite ยท create /labels:* mise namespace ยท worktree off main S1 ยท Oracle โ€” freeze the answer key clean-room numpy reference + sha-pinned tick fixtures (BTC + ETH) S2โ€“S3 ยท Rust kernel, proven โ‰ค 1e-9 worst_buy / worst_sell / arrival bit-exact ยท TWAP / VWAP โ‰ค 1e-9 S4 ยท Wire the single writer + guards one column list (no #556) ยท guard suite Aโ€“I ยท coverage gate โ€” all hermetic S5 ยท Open the PR (adversarially challenged) attacker โ†’ defender ยท check-full green ยท Challenged & Held โ–  LOOP STOPS HERE โ€” hand off to operator no merge ยท no deploy ยท no backfill inside loop scope Operator-owned โ€” after the loop review โ†’ merge โ†’ CREATE table + backfill on bigblack (per-step go) โ†’ coverage audit โ†’ deploy each iteration โ†’ a new index_iter_NN page + ledger row
loop-owned (automated) loop STOPS here operator-owned (after the loop) repeat per iteration โ†’ appends to the ledger
The red step is the hard stop. The loop's entire job is to implement, prove, and open the PR โ€” then it halts and hands off. It never merges, creates the ClickHouse table, backfills, or deploys; those are operator steps, each separately authorized on bigblack.

How one label actually gets computed

Zoom in on a single bar. This is the compute kernel the Oracle PR pins bit-for-bit, then the backfill runs it over every bar in the tick cache.

One closed bar symbol ยท threshold ยท first/last_agg_trade_id ยท close_time_us Open the forward tick window (from Parquet) agg_trade_id > last_agg_trade_id AND ts < close + Hยท1e6 โ†’ [close, close+H) empty window โ†’ NULL never 0 ยท ~9% @3s ยท ~24% @1s (thin) Compute the entry-leg values worst_buy=max(price) ยท worst_sell=min(price) ยท arrival ยท TWAP ยท VWAP=ฮฃpq/ฮฃq Compute the exit leg window [close+H, close+H+3s) โ†’ worst-case exit fill (cover / liquidate) Round-trip cost (derived, DEFAULT expression) slippage + 2ร—taker ยท VIP5 + 25% BNB constant pinned in COMMENT + date + URL Write ONE row โ†’ open_deviation_bar_labels ReplacingMergeTree ยท key = first_agg_trade_id ยท idempotent re-run Read-time JOIN open_deviation_bars LEFT JOIN labels โ†’ VIEW โ€ฆ_with_labels Consumer patterns gate05 reads label_fwd_3s_* (parameterized) โ€” the honest cost verdict

Forex โ†’ crypto: keep / adapt / new

Terry named the forex side (mql5 repo, fxview-core) as the reference to mirror. The forex table carries 32 label columns across the full {1,3,5,10}s ladder (29 label_fwd_* + 3 derived); we port its 3s slice โ€” 7 measured columns + 1 derived round-trip cost = 8 new columns on crypto. Here's the full delta.

DimensionForexCrypto
Fill-price sourcebid/ask quotesno bid/ask โ€” worst trade print in windowADAPT
Where labels are computedlive, in the streaming writeroffline, post-hoc over the tick cacheNEW
Storageinline columnsseparate table, joined at readNEW
Window boundarytimestamp ยตstrade-ID-anchored (ms/ยตs mix pre-2025)ADAPT
Empty windownever (dense FX)load-bearing NULL policy (thin symbols)NEW
Feespread-embeddedexplicit VIP5 + 25% BNB, pinnedNEW
VWAPmean-of-mids (no volume)true ฮฃ(pยทq)/ฮฃq (aggTrades carry qty)ADAPT โ†‘
Backfill template + oracleper-symbol sequential ยท โ‰ค1e-9 vs numpysame disciplineKEEP
Label/feature guard testcomment tags only (no test)real guard suite Aโ€“INEW

The horizon question โ€” resolved to 3s (Q1)

Decision: ship 3s, schema horizon-parameterized. The horizon H becomes part of the column name (label_fwd_{H}s_*), so it matters โ€” but there is no conflict to arbitrate: Handoff and gate agree on 3s, so that's what we ship. The schema and kernel stay horizon-parameterized, so adding 1s / 5s / 10s later is a marginal backfill (one scan bounded by the largest H), not a redesign. Verified by reading the consumer code directly โ€” gate05 defaults to 3s.

Q2โ€“Q4 โ€” resolved (Terry can override)

Q2 ยท Steady-state labeling

Backfill-only now + a follow-up issue for the recurring top-up job. Keeps the first PR focused; avoids new standing surface before the core lands.

Q3 ยท mql5 probe twin

Deferred. The same leak fix in the mql5 copy is a later, separate effort โ€” out of scope for this one.

Q4 ยท Acceptance criterion

100% minus per-symbol-quantified empty-window NULLs โ€” matches forex's real precedent. Literal 100% is impossible (thin symbols have genuinely empty windows) and would be a false gate.

Implementation journal โ€” append-only

Each iteration of the loop above appends one HTML page to this folder (index_iter_NN_<verbose_slug>.html) plus one row to the machine-readable ledger LOOP_LEDGER.json. Pages are never edited or overwritten โ€” a new iteration is always a new sibling, so the full build history stays intact and auditable (same convention as the batch-5 / batch-6 implementation journals).

#Iteration pageStageDate
No iterations yet โ€” the loop is armed and populates this on its first firing. The first row will be S1 ยท Oracle.

Machine-readable ledger: LOOP_LEDGER.json โ€” append-only; iterations[] grows by one per page.

What happens next

  1. The loop implements, label by label โ€” S1 oracle โ†’ S2โ€“S3 kernel (โ‰ค1e-9 bit-exact) โ†’ S4 wiring + guards โ†’ S5 opens a PR, then stops. One bounded stage per firing; each appends a journal page above.
  2. The operator reviews + merges each PR โ€” the loop never merges.
  3. Then the operator-gated production steps โ€” CREATE table โ†’ 139M-row backfill on bigblack โ†’ coverage audit โ†’ deploy โ€” each authorized one at a time. The loop never touches these.

The loop only writes code and opens PRs; it touches no production system. Merge, backfill, and deploy remain operator-owned.