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.
| Quantity | Value | Meaning |
|---|---|---|
| Crypto cost-realism columns | 0 -> 8 new columns (7 measured + 1 derived) at a 3s horizon | the gap this closed |
| Crypto bars in scope | ~139M | full backfill target |
| Oracle parity | 1,310 bars (BTC 451 + ETH 859), residual ~1e-13, bit-exact on 5 selection labels | Rust matches the answer key |
| Adversarial findings | 28 on PR-A (21 refuted, 7 amended) + 18 on PR-B | both PRs survived attack |
| BTC backfill coverage audit 2026-07-15 | 5.96M labels, ~100% non-null | the merge landed and was verified |
| Fee pin / observed round-trip cost | 2.325 bps per side; realised 4.58-15.73 bps | fee constant and observed range |
| Firing | What it did |
|---|---|
| 01 | Tracking issue #614 created, ledger activated, PR-A worktree stood up |
| 02 | Clean-room numpy oracle written; BTC/ETH golden answer-key CSVs committed |
| 03 | Production Rust kernel written; three independent verification legs green |
| 04 | labels:* command namespace and leakage guards A/E/G |
| 05 | Adversarial pass: 28 findings adjudicated; PR #618 opened (the contracted hard stop) |
| 07 | PR-B: 18 findings adjudicated, PR #619 opened, campaign saturated |
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.
'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.
opendeviationbar-py ยท cost-realism labels ยท loop DONE โ fleet rollout gated
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.
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.
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.
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).
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.
A separate tableopen_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:
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.
| # | Decision | Status |
|---|---|---|
| D1 | Separate label table, mirroring the parent engine โ not inline columns | LOCKED |
| D2 | Post-hoc backfill over the Parquet tick cache; the live sidecar path is intentionally not involved | LOCKED |
| D3 | Key on first_agg_trade_id; LEFT JOIN from bars at read (unlabeled bars โ NULL labels, never silently wrong) | LOCKED |
| D4 | Trade-ID-anchored window, half-open ยตs; empty window โ NULL, never 0 | LOCKED |
| D5 | Fee rides a ClickHouse DEFAULT-expression column, constant (VIP5 + 25% BNB taker) pinned in the COMMENT | LOCKED |
| Q1 | Horizon set โ 3s (matches the handoff and the live gate's default); schema horizon-parameterized so 1s/5s/10s are a cheap add-later backfill | DECIDED ยท 3s |
| Q2 | Steady-state labeling of new bars โ backfill-only now + a follow-up issue for the recurring top-up job | CHOSEN |
| Q3 | mql5 probe twin โ the same leak fix in the mql5 copy is deferred to a later, separate effort | DEFERRED |
| Q4 | Acceptance = 100% minus per-symbol-quantified empty-window NULLs (not literal 100%) | CHOSEN |
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.
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.
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.
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.
| Dimension | Forex | Crypto | |
|---|---|---|---|
| Fill-price source | bid/ask quotes | no bid/ask โ worst trade print in window | ADAPT |
| Where labels are computed | live, in the streaming writer | offline, post-hoc over the tick cache | NEW |
| Storage | inline columns | separate table, joined at read | NEW |
| Window boundary | timestamp ยตs | trade-ID-anchored (ms/ยตs mix pre-2025) | ADAPT |
| Empty window | never (dense FX) | load-bearing NULL policy (thin symbols) | NEW |
| Fee | spread-embedded | explicit VIP5 + 25% BNB, pinned | NEW |
| VWAP | mean-of-mids (no volume) | true ฮฃ(pยทq)/ฮฃq (aggTrades carry qty) | ADAPT โ |
| Backfill template + oracle | per-symbol sequential ยท โค1e-9 vs numpy | same discipline | KEEP |
| Label/feature guard test | comment tags only (no test) | real guard suite AโI | NEW |
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:
3s throughout.gate05/cost_from_ch.py โ is parameterized over {1,3,5,10}s and defaults to 3s (its own docstring calls 3s "the gate-blocking primitive"). It reads the forex table today; crypto has no label consumer yet.1s shows up only as a secondary horizon in one adversarial benchmark run (BENCHMARK_2026-04-29.md) โ not in the gate itself.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.
Deferred. The same leak fix in the mql5 copy is a later, separate effort โ out of scope for this one.
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.
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 page | Stage | Date |
|---|---|---|---|
| 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.
The loop only writes code and opens PRs; it touches no production system. Merge, backfill, and deploy remain operator-owned.