Skip to content

Orion Delivery — Branch #6 feat/distance-to-touch-diagnostic

To: Katja CC: Vesper, Atlas From: Orion Date: 2026-04-19


Status

Branch #6 complete. Three commits on feat/distance-to-touch-diagnostic, cut off 059f528 (Branch #5 tip). Full Phase 7.3 primary-metric pipeline now wired end-to-end: schema → per-tick write → session aggregate. Zero regressions — +25 passing / 0 new failures vs Branch #5 baseline (466 / 373 → 491 / 371; the −2 on failed is two pre-existing TestPaperTickTelemetryPersistence tests that fell out green as a side-effect of Commit 2's fixture fix).

Commit SHA Subject
1 a288d8e feat(state): add distance_to_touch columns to system_metrics (Branch #6 Phase 7.3)
2 50e40e5 feat(main-loop): compute, log, and persist distance-to-touch per tick
3 99483c6 feat(summary): per-side distance-to-touch aggregate block (Branch #6 Phase 7.3)

Patches at 02 Projects/NEO Trading Engine/patches/branch-6-distance-to-touch-diagnostic/ — three numbered .patch files.

Built against Vesper's Q1/Q2/Q3 rulings (columns on system_metrics, signed values, live-order source, NULL when no live order). All three rulings are pinned by tests.

What landed, per commit

Commit 1 — schema migration

Two new REAL columns on system_metrics: distance_to_touch_bid_bps, distance_to_touch_ask_bps. Both NULLable. Dual-path initialization:

  • CREATE TABLE IF NOT EXISTS system_metrics extended for fresh DBs.
  • _ensure_column(...) migration entries added for existing DBs (the live neo_live_stage1.db takes this path).

record_system_metric() gained two optional kwargs defaulting to None — pre-Branch-6 callers leave the columns NULL (confirmed by unchanged test_record_system_metric_persisted after an assertion was added to check the new columns default to NULL).

Tests (in tests/test_state_manager.py):

  • test_record_system_metric_persisted — extended: new columns default to NULL when caller omits them.
  • test_record_system_metric_distance_to_touch_persisted (new) — explicit negative BUY (-1.25) and positive ASK (+11.75) round-trip intact. Pins the signed contract at the persistence layer.
  • test_system_metrics_schema_has_distance_to_touch_columns (new) — PRAGMA table_info confirms both columns exist after initialize_database().

Commit 2 — per-tick compute + persist + log

New helper NEOEngine._compute_distance_to_touch(snapshot) returning tuple[Optional[float], Optional[float]]. Called from _persist_tick_telemetry. The bid side measures (best_bid − our_live_buy_price) / best_bid × 10 000; the ask side mirrors on the sell side. Source of the "our" price is self._state.get_live_order_by_side(...) — actual resting order, not intent (Vesper Q3).

Null semantics, per Vesper Q3:

  • Missing live order on a side → NULL for that side.
  • Missing or zero best_bid/best_ask → NULL for that side.
  • Any exception computing a side → NULL for that side (telemetry-swallows-exceptions contract honored per side, tick never affected).

Signed, per Vesper Q2. No abs() anywhere on the write path. A negative value means our quote is at or through the contra touch — the Phase 7.3 alarm signal.

Structured log emitted when at least one side is non-None:

distance_to_touch bid_bps=<value> ask_bps=<value>

Values passed straight through to record_system_metric(distance_to_touch_bid_bps=..., distance_to_touch_ask_bps=...).

Tests (in tests/test_main_loop.py):

  • test_paper_tick_persists_system_metrics — extended: both new kwargs NULL when no live order.
  • test_paper_tick_distance_to_touch_computed_from_live_orders (new) — BUY 0.999 at best_bid 1.0 → +10.0 bps; SELL 1.1011 at best_ask 1.1 → +10.0 bps.
  • test_paper_tick_distance_to_touch_preserves_negative_sign (new) — BUY 1.0001 at best_bid 1.0 → −1.0 bps. Sign round-trips through the full tick.
  • test_paper_tick_distance_to_touch_per_side_null_when_touch_missing (new) — best_bid=None → BUY NULL, SELL still computed independently.

Fixture note (incidental): the _make_engine fixture uses NEOEngine.__new__(...) to bypass __init__, so it was missing _last_valuation_snapshot_ts and valuation_snapshot_interval_seconds. Two pre-existing TestPaperTickTelemetryPersistence tests were failing for this reason (verified pre-existing by stashing my changes). Added two fixture lines to initialize both; these two tests now pass green as a side-effect. Documented in the commit body.

Commit 3 — session aggregates in summarize_paper_run.py

New public functions:

  • load_distance_to_touch_summary(db_path) — reads system_metrics for the latest session, drops NULL samples per side, returns:
    {
        "session_id": <int>,
        "bid": {"n": ..., "min": ..., "p50": ..., "p95": ..., "max": ..., "crossings": ...},
        "ask": {"n": ..., "min": ..., "p50": ..., "p95": ..., "max": ..., "crossings": ...},
    }
    
    crossings = count of ticks where the signed distance is < 0. Returns None when no session, no rows, or all-NULL distances.
  • render_distance_to_touch_summary(dt) — matches house style ("Title:" header + "\n".join(lines)).

Wired into main() after render_execution_quality_summary, same print-with-blank-line pattern as quote-quality.

Parallel to load_quote_quality_summary, not an extension of it — load_quote_quality_summary early-returns when mid is None, which would silence the alarm block on any run where the live engine_state mid is unset. Keeping them independent means the distance-to-touch block fires off system_metrics alone.

Also new: private helper _percentile(values, p) doing linear interpolation (numpy method='linear' semantics). stdlib statistics only covers quartiles/deciles + median; we need exact p50/p95 at small sample counts.

Legacy dist_to_bid / dist_to_ask / near_5bps retained unchanged per Vesper's ruling — remove in a follow-up branch after S40 confirms coherent numbers.

Tests (new file, tests/test_distance_to_touch_summary.py, 18 tests):

  • _percentile: empty, single-value, even/odd count medians, p95 linear interp on 1..20, unsorted input.
  • load_distance_to_touch_summary:
  • None paths: no session / no rows / all-NULL.
  • Per-side population: bid-only, ask-only, both populated with interleaved NULLs.
  • Crossings count strictly < 0 (zero does not count — Vesper's contract).
  • NULL rows not counted as crossings.
  • Latest-session scoping (100 crossings in an older session do not leak).
  • p50/p95 math pinned against numpy linear (1..20 → p50=10.5, p95=19.05).
  • render_distance_to_touch_summary:
  • Shape: header + BUY + SELL; crossings phrase on every side.
  • Signed formatting: negatives print with -, positives with +.
  • No-samples side still shows crossings(<0): 0 for operator clarity.

What the new block looks like

A healthy calibration session:

Distance-to-Touch Summary:
BUY: n=412 | min=-0.87 | p50=+5.24 | p95=+11.93 | max=+14.10 bps | crossings(<0): 3
SELL: n=408 | min=+0.12 | p50=+6.08 | p95=+12.45 | max=+15.02 bps | crossings(<0): 0

An early session with only ask-side live order history:

Distance-to-Touch Summary:
BUY: no samples | crossings(<0): 0
SELL: n=18 | min=+10.20 | p50=+11.40 | p95=+12.10 | max=+12.30 bps | crossings(<0): 0

crossings(<0) is the Phase 7.3 alarm line. Non-zero on a passive-mode session means we're at or through the contra touch — pennying or a pricing bug.

Test posture

  • Branch #6-specific tests: 18 new (test_distance_to_touch_summary) + 3 new (test_state_manager) + 3 new (test_main_loop) = 24 new passing, plus 2 pre-existing TestPaperTickTelemetryPersistence now green from the Commit 2 fixture fix.
  • Full suite vs Branch #5 baseline: 491 passed / 371 failed (was 466 / 373). Net +25 passed, −2 failed. No new regressions.
  • The 371 remaining failures are the same pre-existing FLAG-034 fixture-orphan cluster and other baseline issues carried from Branches #1–#5. Unchanged by Branch #6.

Copy-paste for Windows VS Code terminal

# Mirror branch from commits (cut off Branch #5 tip)
git fetch origin
git checkout -b feat/distance-to-touch-diagnostic 059f528

# Apply the three patches
git am "02 Projects\NEO Trading Engine\patches\branch-6-distance-to-touch-diagnostic\0001-feat-state-add-distance_to_touch-columns-to-system_m.patch"
git am "02 Projects\NEO Trading Engine\patches\branch-6-distance-to-touch-diagnostic\0002-feat-main-loop-compute-log-and-persist-distance-to-t.patch"
git am "02 Projects\NEO Trading Engine\patches\branch-6-distance-to-touch-diagnostic\0003-feat-summary-per-side-distance-to-touch-aggregate-bl.patch"

# Verify
git log --oneline -4
python -m pytest tests/test_state_manager.py tests/test_main_loop.py tests/test_distance_to_touch_summary.py -q

# After Vesper sign-off:
git push -u origin feat/distance-to-touch-diagnostic

What this unlocks

  • Phase 7.3 calibration has a signed, per-tick, persisted primary metric now. Every offset sweep session's summary block will surface p50/p95/max passive distance and the crossings count.
  • Operational alarm. crossings(<0) on a paper session should be exactly 0 under normal passive market-making. Non-zero = we're at or through the contra touch, which means either (a) a pricing bug shipped or (b) offset calibration drifted too aggressive. Visible in the terminal at session end.
  • Phase 7.3 sweep readability. Comparing p50 and p95 across offset configurations will tell us the distribution cost of each offset choice, not just a mid-based average.

Branch queue after this

  • Branch #7 fix/wal-checkpoint-hardening — FLAG-035, the paper shakedown before live.
  • S40 after 6+7 merge, then Phase 7.3 offset calibration sweeps.

Standing by for Vesper review.

— Orion