[C] Orion Implementation — Cleanup + anchor error bps
To: Orion CC: Katja (Captain), Atlas From: Vesper Date: 2026-04-18
Context¶
Current main state (as of Apr 17):
- Merged: fix/flag-008 → fix/flag-030 → feat/flag-031 → main
- FLAG-032 verified and closed
- Config pushed: base_size_rlusd: 15.0, max_xrp_exposure: 150.0, max_rlusd_exposure: 150.0, anchor_max_divergence_bps: 10.0
- S36 completed overnight — 19 fills, VW +0.65 bps, engine healthy
Your previous cleanup branch (fix/post-injection-cleanup) no longer exists. It was built in a temporary sandbox (/tmp/neo_git_work) that has been cleared. All design work is intact and described below — rebuild from current main.
Git Rule — MANDATORY¶
All git commands go to Katja's VS Code terminal via copy-paste. Do NOT attempt to run git via the filesystem mount — that path is unreliable for git operations and will show a broken HEAD state. Provide every git command as a copy-paste block. You do not push anything yourself.
Branch¶
Items¶
Item 1 — FLAG-034: Session summary shows fills-only XRP balance, not total¶
File: summarize_paper_run.py
Problem: _get_inventory_balance() reads inventory_ledger.new_balance for XRP and RLUSD. Post-injection, this is fills-only. The capital overlay (35.21 XRP injected Apr 17) is not included. Session summary shows ~36 XRP instead of the actual ~71 XRP (post-session balance).
Fix: After reading fills_only from inventory_ledger, add the net capital delta from capital_events:
def _get_inventory_balance(conn, asset, fallback_key) -> float:
row = conn.execute(
"SELECT new_balance FROM inventory_ledger WHERE asset = ? ORDER BY id DESC LIMIT 1",
(asset,),
).fetchone()
if row:
fills_only = float(row["new_balance"])
else:
fallback = _get_float_state(conn, fallback_key)
fills_only = fallback if fallback is not None else 0.0
# FLAG-034: add capital-events overlay so displayed balance matches
# the live total held by InventoryManager (fills + capital deltas).
overlay = conn.execute(
"SELECT COALESCE(SUM(CASE WHEN event_type='deposit' THEN amount "
"WHEN event_type='withdrawal' THEN -amount ELSE 0 END), 0.0) AS o "
"FROM capital_events WHERE asset = ?",
(asset,),
).fetchone()["o"]
return fills_only + float(overlay)
Note: basis_commit event type is correctly excluded by the CASE WHEN (only deposit/withdrawal affect asset balance).
Required tests (5):
1. No capital events → returns fills_only unchanged
2. XRP deposit → fills_only + deposit amount
3. RLUSD withdrawal → fills_only - withdrawal amount
4. basis_commit event → balance unchanged (excluded from overlay)
5. No ledger rows → uses engine_state fallback + overlay
File to create: tests/test_flag_034_display_overlay.py
Item 2 — max_inventory_usd retirement: remove dead config¶
Context: max_inventory_usd in StrategyConfig has no effect on engine behavior. It lives only in a log.info diagnostic block in main_loop.py:1102 with no branch, no gate, no return. It blocks nothing. The buy_inventory_guard_blocked log label was misleading — it never blocked any buys. Atlas-approved for full removal.
14 files to touch:
| File | Change |
|---|---|
neo_engine/config.py |
Remove max_inventory_usd field from StrategyConfig and its loader kwarg |
neo_engine/strategy_engine.py |
Remove from __init__ log dict (line ~116) |
neo_engine/main_loop.py |
Remove buy_inventory_guard_blocked from _log_no_intents_reason() diagnostic block |
config/config.yaml |
Remove max_inventory_usd line |
config/config_live_stage1.yaml |
Remove max_inventory_usd: 20.0 line (line 78) |
config/config_live_session1.yaml |
Remove if present |
config/config.example.yaml |
Remove if present |
tests/test_task5.py |
Remove from _make_config helper default and all StrategyConfig(...) constructor calls |
tests/test_phase3d.py |
Remove all kwargs and mock assignments |
tests/test_phase4a.py |
Remove all kwargs and mock assignments |
tests/test_main_loop.py |
Remove all kwargs and mock assignments |
neo_simulator/simulation_runner.py |
Remove kwarg |
neo_engine/main_loop_Old.py |
Remove for grep hygiene (archive file) |
neo_engine/strategy_engine_old.py |
Remove for grep hygiene (archive file) |
Smoke test required: All four configs must load cleanly after removal. StrategyConfig must not have the field. Run: python -c "from neo_engine.config import StrategyConfig; print('ok')" and confirm each config file loads without KeyError.
Item 3 — FLAG-033: Startup DB integrity check¶
File: run_paper_session.py
Problem: FLAG-027 protects against kill-during-WAL-checkpoint via pre-run backup. It does NOT protect against OS-level truncation at clean shutdown. Session 32 completed normally but the post-exit checkpoint was interrupted, truncating 311 pages. Current startup sequence does not detect a corrupt DB before running.
Fix: Add _startup_integrity_check(db_path) called BEFORE _create_pre_run_backup():
def _startup_integrity_check(db_path: Path) -> None:
"""FLAG-033: Fail fast on corrupt DB before creating backup or starting engine."""
if not db_path.exists() or db_path.stat().st_size == 0:
return # fresh run or in-memory — no-op
import sqlite3
try:
uri = f"file:{db_path}?mode=ro"
with sqlite3.connect(uri, uri=True) as conn:
result = conn.execute("PRAGMA quick_check").fetchone()
if result is None or result[0] != "ok":
raise RuntimeError(
f"[FLAG-033] DB integrity check FAILED: {db_path}\n"
f"Result: {result}\n"
f"Restore from the most recent backup before running."
)
except sqlite3.DatabaseError as e:
raise RuntimeError(
f"[FLAG-033] DB integrity check raised DatabaseError (file may be truncated): {e}\n"
f"Restore from the most recent backup before running."
) from e
log.info("[FLAG-033] DB integrity check passed (PRAGMA quick_check = ok)")
Ordering: _startup_integrity_check BEFORE _create_pre_run_backup. Corrupt files must not get quietly backed up.
Required tests (5):
1. :memory: path → no-op (skip check)
2. Non-existent path → no-op
3. Empty file → no-op
4. Healthy DB → passes, logs OK
5. Truncated/corrupt DB → raises RuntimeError with clear restore instructions
File to create: tests/test_flag_033_startup_integrity.py
Item 4 — FLAG-028: Add idx_fills_session_id index¶
File: neo_engine/state_manager.py
Problem: fills table has no index on session_id. All session-scoped fill queries (session summary, dashboard session metrics, Segment B analysis) do full table scans.
Fix: Add to the migration block, alongside other post-schema column additions:
Must be IF NOT EXISTS — idempotent across existing DBs.
Required tests (2):
1. Index exists on fills table after migration
2. Index covers the session_id column
File to create: tests/test_flag_028_fills_session_index.py
Item 5 — anchor_error_bps: add |error| > 5 bps reliability stat (NEW — Katja priority)¶
Background: Katja's operating principle as of Apr 18: if |anchor_error_bps| > 5 bps, results from that tick are in unreliable territory. She needs the % of session ticks where this threshold is exceeded to assess session reliability.
What already exists:
- strategy_engine.py:204: self.last_anchor_divergence_bps = (anchor_mid - clob_mid) / clob_mid * 10000 — this IS anchor_error_bps
- main_loop.py: per-tick collection into self._anchor_divergence_obs (list of floats)
- _log_anchor_divergence_summary(): computes mean, median, min, max, cap%, and one-sided buckets (<=0, 0-5, 5-10, 10-12, 12-14, 14-15, >15)
- Session terminal summary already shows: Anchor: mean=X | median=X | range=[X, X] | bias=X
What's missing: The |error| > 5 bps stat (absolute value threshold).
Changes needed:
File: neo_engine/main_loop.py — _log_anchor_divergence_summary()
Add to the per-observation loop:
abs_above_5 = 0
for v in obs:
if abs(v) > 5:
abs_above_5 += 1
# ... existing bucket logic unchanged ...
Compute stat:
Add to log.info extra dict:
Add to engine_state persistence block:
File: summarize_paper_run.py
Read from engine_state in _collect_summary():
Update the anchor display line (~line 421):
_anc_err5 = summary.get("anchor_pct_error_above_5bps")
err5_str = f" | |err|>5bps: {_anc_err5:.1f}%" if _anc_err5 is not None else ""
lines.append(
f"Anchor: mean={_anc_mean:+.2f}bps | median={_anc_median:+.2f}bps"
f" | range=[{_anc_min:+.1f}, {_anc_max:+.1f}] | bias={_bias}{err5_str}"
)
Example output:
Required tests (3):
1. All errors within ±5 bps → pct_error_above_5bps = 0.0
2. Mixed: half outside → pct_error_above_5bps = 50.0
3. All at cap (±10 bps) → pct_error_above_5bps = 100.0
Add to existing anchor test file or create tests/test_anchor_error_stat.py.
Commit Ordering¶
One commit per item — do not bundle across items. Tests travel with the code change they cover.
| # | Commit subject |
|---|---|
| 1 | fix(flag-034): session summary displays total XRP balance (fills + capital overlay) |
| 2 | chore: retire max_inventory_usd dead config (14 files) |
| 3 | fix(flag-033): startup DB integrity check before backup and engine init |
| 4 | fix(flag-028): add idx_fills_session_id index on fills table |
| 5 | feat: anchor_error_bps reliability stat — pct ticks where |error| > 5 bps |
What NOT to Touch¶
anchor_max_divergence_bps: 10.0— hold, no changebase_size_rlusd: 15.0— hold- No other strategy parameters
- No schema changes beyond the index in Item 4
- Do not touch
risk_engine.pyor any fill calculation paths
Git Commands for Katja¶
After each commit passes tests, provide this for Katja's terminal:
Then provide the PR creation command. Katja runs all git commands. You provide the copy-paste blocks.
— Vesper