Skip to content

Orion Delivery — feat/anchor-saturation-guard

Vesper — branch complete and all green per your Q1–Q4 rulings. Ready for review + merge.

Branch

feat/anchor-saturation-guard (off main, 5 commits, +692 / −1)

Commits

# Hash Subject
C1 fe80c08 feat(config): AnchorSaturationGuardConfig dataclass + YAML defaults (Phase 7.3)
C2 b6d9459 feat(main_loop,db): anchor saturation rolling window + circuit_breaker session_id column
C3 4d457fd feat(main_loop): anchor saturation guard evaluation + DEGRADED trigger (Step 8.5)
C4 dc704e2 feat(db,main_loop): record_circuit_breaker_event writer + guard persistence
C5 a5334d6 test(guard): anchor saturation guard — 16 tests (Phase 7.3)

Tests

  • New: 16 tests in tests/test_anchor_saturation_guard.py, all green.
  • Part A (4): config validation — enabled toggle, lookback bound, bias bound, prevalence bounds.
  • Part B (10): evaluation via MagicMock(spec=NEOEngine) — window-not-full no-op, below-bias no-op, below-prevalence no-op, sparse-outlier prevalence gate, positive-bias trigger, negative-bias trigger (symmetry), context payload, one-shot dedup (no second persist on tick 2), [ANCHOR_SAT] WARNING emission, persist-failure swallowed (DEGRADED still entered).
  • Part C (2): integration against a real StateManager — round-trip verifies session_id is populated, breaker label is "anchor_saturation_guard", and manual_reset_required=1.
  • Regression: 87 tests green in 1.62s across guard + reconciler + config + anchor-error telemetry suites.

Run command:

python -m pytest tests/test_anchor_saturation_guard.py tests/test_ledger_reconciler.py \
  tests/test_flag_036_wallet_truth_reconciliation.py tests/test_reconciler_anomaly_log.py \
  tests/test_config.py tests/test_config_invariants.py tests/test_anchor_error_stat.py -q

Spec compliance

Trigger logic (Step 8.5, after momentum counter write, before the "no intents" log):

if not enabled:                                                   -> no-op
if window.maxlen is None or len(window) < window.maxlen:          -> no-op  (fill-only)
mean_err    = mean(window)
prev_pct    = 100 * count(|x| > prevalence_threshold_bps) / n
fire if abs(mean_err) >= bias_threshold_bps AND prev_pct >= prevalence_pct

On the first firing tick in a session: 1. _anchor_guard_triggered_this_session = True is set before side effects (re-entry safe). 2. WARNING log [ANCHOR_SAT] DEGRADED triggered — mean=…bps | prev=…% | lookback=… | bias_thr=… | prev_thr=…bps | prev_pct_thr=…%. 3. One circuit_breaker_events row via record_circuit_breaker_event(breaker="anchor_saturation_guard", manual_reset_required=True, context={…}). Writer failure is swallowed (logged at ERROR) and does not block DEGRADED. 4. _enter_degraded_mode("anchor_saturation_guard_exceeded") — idempotent, cancels live orders on first entry (existing D2.2 infrastructure). 5. intents cleared (returned []) so Step 9 submit is a no-op this tick.

On subsequent ticks in the same session the guard stays silent (one-shot flag), DEGRADED stays on, and the pre-trade gate at execution_engine.py:1030 blocks any new submits until restart.

Atlas-locked defaults wired identically across config.yaml, config_live_stage1.yaml, and config.example.yaml: - enabled: true - lookback_ticks: 25 - bias_threshold_bps: 7.0 - prevalence_threshold_bps: 5.0 - prevalence_pct: 40.0

Symmetry: abs(mean(window)) handles both the S40 regime (−10 bps cap, 100% prevalence) and the S41 regime (+3.93 bps mean) with no code path asymmetry.

Deviations from spec

One — already pre-approved in your Q4 ruling. Added session_id column to circuit_breaker_events via additive _ensure_column(conn, "circuit_breaker_events", "session_id", "INTEGER") migration in state_manager.py. Existing rows are NULL, new rows auto-populate from self._current_session_id. Pattern matches reconciler_anomaly_log and inventory_truth_snapshots. No other deviations.

Files touched

config/config.example.yaml             |  15 ++
config/config.yaml                     |  17 ++
config/config_live_stage1.yaml         |  10 ++
neo_engine/config.py                   |  91 ++   (AnchorSaturationGuardConfig + validator + loader)
neo_engine/main_loop.py                | 121 ++   (window init, append, evaluator, Step 8.5 wire-in)
neo_engine/state_manager.py            |  76 ++   (column migration, writer, count helper)
tests/test_anchor_saturation_guard.py  | 363 ++   (new)

Operator impact

  • Healthy sessions: zero observable change. Window populates passively; evaluator returns early until the first fully-populated tick and continues returning early while the statistical condition is below threshold.
  • First trigger (per session): one WARNING log line, one DB row, DEGRADED transition, live orders cancelled. No further triggers this session.
  • Recovery: restart required (matches D2.2 pattern). Fresh session starts with cleared flag and empty window.

Apply instructions (Windows)

Patches live at 02 Projects/NEO Trading Engine/patches/feat-anchor-saturation-guard/ (5 files, 0001 → 0005). From Katja's VS Code terminal:

cd C:\Users\Katja\Documents\NEO GitHub\neo-2026
git checkout -b feat/anchor-saturation-guard main
git am "C:\Users\Katja\Documents\Claude Homebase Neo\02 Projects\NEO Trading Engine\patches\feat-anchor-saturation-guard\*.patch"
python -m pytest tests/test_anchor_saturation_guard.py -q

Expected: 16 passed. Then run the wider regression if you want the same 87-green sweep I ran here.

Status

C1–C5 complete. Branch is clean and ready. Awaiting your review.

— Orion