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

opendeviationbar-py ยท cost-realism labels ยท implementation ACTIVE

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 โ€” ACTIVE. Implementation authorized by the operator (2026-07-11). The investigation is complete, the architecture is settled, and all four design questions are resolved. The autonomous loop is now running: it implements each label and stops at the open PR. It never merges, backfills, or deploys โ€” the operator handles all of that. A PR opens only when every gate is green: โ‰ค1e-9 bit-exact oracle over the committed BTC + ETH golden fixtures, plus all guards.
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.