VIOLET_TODO_CRITICAL.md (prominent review queue): - 🔴 CRITICAL #1: VIOLET↔BLUE parity report (PASS-2 Task 4, violet_parity_20260616_220412.md) is VERY DISAPPOINTING — pick-match 1.5%, no-pick 86%, same-asset 13.6%. KEY NUANCE: on the 43 aligned rows sizing is near-identical (lev_err median 0.0) → the divergence is SELECTION / TIMING / comparison-ALIGNMENT, NOT the sizing math. Root-cause before any soak/V4. (Claude.) - #2: OA PASS-1 + PASS-2 reported done, NOT yet reviewed — review queue listed. - #3: future integration/E2E + the pass-vs-sprint nomenclature note (pass = sub-sprint work-package; V-stage = sprint/epic, matching existing usage). VIOLET_PART_SPEC_OA_TODO_PASS3.md (lengthy, plan-derived, independent units with shared interfaces — nothing invented): contracts_v3 (VenueTick/OpenPositionView/ExitDecision) + VenuePriceFeedPort+BingX adapter, MechanicalExitGuard (armed TP/SL), sub-second SL/ADVSL floor guard, event-sourced restore from trade_reconstruction, venue lead/lag slippage metric, cadence per-action Q schedule + telemetry. Each ships + tests standalone; composition map + order given. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
12 KiB
VIOLET — partial spec for another agent, PASS 3 (OA TODO)
Date: 2026-06-17. Follows PASS 1 + PASS 2 (both reported done — review pending, see
VIOLET_TODO_CRITICAL.md). PASS 3 is drawn STRICTLY from the existing VIOLET dev plan
(VIOLET_DEV_SPEC_AND_PLAN.md, the V0→V6 ladder + its named deferred items) — nothing invented.
Every task is an INDEPENDENT UNIT: it can be built, tested and function on its own, and it
composes with the others ONLY through the explicit shared interfaces in §I.
0. HARD RULES (identical to PASS 1/2 — re-read those; summarized)
- Never edit shared files (
prod/nautilus_event_trader.py,clean_arch/dita_v2/**,dita/decision.py,nautilus_dolphin/**,blue_parity.py,prod/bingx/leverage.py). READ only. - VIOLET stays DARK — no orders, no execution, no VST keys, no service start/stop, no HZ restart, no PROGREEN.
- V-TYPES on all new code (
StrictModel/Annotated[…Field]/@typed); faithful poison-guards only. - NEW-FILE-ONLY; do NOT modify in-flight files:
live_blue_source.py,shadow_live_factors.py,live_factor_source.py,live_factors.py,decision_engine.py,sizing.py,shadow_journal.py,cadence.py,clock.py,divergence.py,22_violet_decisions.sql. READ + IMPORT them.
0a. COMMIT / BRANCH POLICY (3 shared-index collisions on 2026-06-16 — take this seriously)
Own git worktree (git worktree add ../vp-oa3 -b agent/oa-violet3) STRONGLY preferred. Else
never git add -A; commit with explicit pathspec git commit -F msg -- <files> and verify
git show --stat --format="" HEAD lists ONLY your files. One commit per task, prefix
VIOLET OA:, Co-Authored-By trailer. Tests on /home/dolphin/siloqy_env/bin/python3. Use
git grep (recursive grep/find time out on CIFS).
I. SHARED INTERFACES (agreed contracts — define these EXACTLY; the tasks depend on them)
These are the only coupling points between PASS-3 units. Put the type definitions in ONE new
module prod/clean_arch/violet/contracts_v3.py (Task 8 creates it; later tasks import it).
All are StrictModel / Annotated V-TYPES.
VenueTick— one normalized venue quote (exchange-agnostic):asset: Symbol,bid: Px,ask: Px,mark: Px,last: Px,mono_ns: MonoNs,venue: str(e.g. "BINGX","BINANCE"). All prices > 0 finite. This is the SAME normalized tick named in the plan's "Venue price feed port (CRITICAL gap)".OpenPositionView— the minimal open-position state an exit/guard needs:asset: Symbol,side: str("SHORT"/"LONG"),entry_price: Px,qty: Qty,entry_ts_ns: MonoNs,bars_held: BarsHeld,tp_threshold: float(fixed TP pct),sl_threshold: float(stop pct),leverage: float.ExitDecision—action: str("HOLD"/"EXIT"),reason: str("FIXED_TP"/"CATASTROPHIC_SL"/"ADVSL"/"NONE"/…),price: Px,priority: int(CATASTROPHIC/ADVSL=0 > FIXED_TP=1 > DISCRETIONARY=2 — the plan's mandated order).
If a later task needs a field not here, ADD it to contracts_v3.py (and note it), never fork a
parallel type.
TASK 8 — VenuePriceFeedPort + BingX adapter (DARK, data-only) [plan: "Venue price feed port (CRITICAL gap)"]
Why. The plan flags this as CRITICAL: OBF is Binance-wired (alpha side), but exits fill on the EXECUTION venue, and FET showed scan-vs-BingX divergence (0.2176 vs 0.1878 = 15%). VIOLET needs an exchange-agnostic price port. This unit delivers the port interface + a BingX adapter, data-only (no orders).
Affected files (NEW only): prod/clean_arch/violet/contracts_v3.py (the §I types),
prod/clean_arch/violet/venue_price_feed.py, prod/clean_arch/violet/test_violet_venue_price_feed.py.
Interface/approach. Define VenuePriceFeedPort (ABC): latest(asset) -> Optional[VenueTick],
subscribe(assets), close(). Implement BingxSwapPriceFeed(VenuePriceFeedPort) consuming the
BingX swap WS bookTicker/markPrice (READ the existing BingX WS conventions in the repo; data-only,
no auth needed for public streams — confirm). Implement MockPriceFeed (deterministic, for
tests + for the other tasks). All ticks validated into VenueTick at ingress (poison-reject
non-finite/≤0).
Tests / pass criteria. Mock feed round-trips ticks as VenueTick; poison ticks rejected;
the BingX adapter parses a recorded/sample bookTicker frame into a correct VenueTick (use a
captured frame fixture, NOT a live connection in the unit test). DONE when: VenueTick contract
finalized, mock + adapter parse-tested, no live WS in unit tests, no edits outside the 3 files.
TASK 9 — MechanicalExitGuard (armed TP/SL) [plan: LINK TP-miss structural fix]
Why. The plan's structural fix for the LINKUSDT −$1,248 TP-miss: TP/SL are MECHANICAL exits owned by an exit guard with armed price thresholds set at entry; policy layers (V7/MARAS) can only contribute DISCRETIONARY exits and can NEVER mask mechanical ones. Total priority: CATASTROPHIC/ADVSL > fixed TP > discretionary.
Affected files (NEW only): prod/clean_arch/violet/mechanical_exit_guard.py,
prod/clean_arch/violet/test_violet_mechanical_exit_guard.py. (imports contracts_v3.)
Interface/approach. MechanicalExitGuard.evaluate(pos: OpenPositionView, tick: VenueTick) -> ExitDecision. Pure function of position + current venue price: compute pnl_pct from
entry/side/price; if it reaches the armed tp_threshold → ExitDecision(EXIT, FIXED_TP, priority=1); if it breaches sl_threshold → ExitDecision(EXIT, CATASTROPHIC_SL, priority=0);
else HOLD. Prices off the VENUE tick (not scan). No discretionary logic here — this layer is the
mechanical floor only.
Tests / pass criteria. SHORT + LONG: TP fires exactly at threshold, SL at stop, neither fires inside the band; priority ordering correct (SL outranks TP if both somehow true). Hypothesis property: output always finite, priority ∈ {0,1,2}, EXIT only when threshold crossed. DONE when: deterministic threshold tests + property test pass, no edits outside the 2 files.
TASK 10 — Sub-second catastrophic-SL / ADVSL floor guard [plan: versioned SAFETY DEVIATION]
Why. The plan's one sanctioned sub-second behaviour: a catastrophic-SL/ADVSL floor evaluated at the fastest cadence against a fast price source, to fix the scan-dark unmanaged-position hazard (XLM/FET class). "Evaluate at fastest cadence (shadow-log would-be actions), actuate at Q."
Affected files (NEW only): prod/clean_arch/violet/sl_floor_guard.py,
prod/clean_arch/violet/test_violet_sl_floor_guard.py. (imports contracts_v3, composes Task 9.)
Interface/approach. SLFloorGuard(deadline_ns) with on_tick(pos, tick) -> Optional[ExitDecision]
that EVALUATES every tick (sub-second) and SHADOW-LOGS would-be SL exits, but only RETURNS an
actuation when the configured fast-SL condition holds (catastrophic threshold worse than the
mechanical stop, OR ADVSL trailing breach). Track evaluate-count vs actuate-count (shadow delta).
DARK: returns the decision; never sends orders.
Tests / pass criteria. A tick stream that dips intra-scan triggers the fast SL evaluation and records the would-be exit; the actuate gate fires only on the catastrophic/ADVSL condition; evaluate-count > actuate-count proven. DONE when tests pass, no edits outside the 2 files.
TASK 11 — Event-sourced restore from trade_reconstruction [plan: VIOLET restore commitment, Option C]
Why. The plan commits VIOLET to NEVER restore from position_state snapshots; instead replay
the chain-tokened trade_reconstruction OPEN/PARTIAL_EXIT/CLOSE journal. Open = ROOT OPEN with no
terminal CLOSE; size = entry − Σ legs; holding from ABSOLUTE entry_ts (kills the bars_held≈0 /
MAX_HOLD-reset class by construction); chain token verified.
Affected files (NEW only): prod/clean_arch/violet/event_restore.py,
prod/clean_arch/violet/test_violet_event_restore.py. (imports contracts_v3 → emits
OpenPositionViews.)
Interface/approach. restore_open_positions(journal_rows: list[dict]) -> list[OpenPositionView]
— pure function over journal rows (asset, chain_root_trade_id, chain_token, leg type, qty,
realized legs, entry_ts, entry_price). Reconstruct each chain; an open position is a ROOT OPEN
with no matching terminal CLOSE; qty = entry − Σ partial-exit legs; bars_held derived from absolute
entry_ts vs now (NEVER from a stored counter). Reject chain-token mismatches to a quarantine list
(returned alongside), never crash.
Tests / pass criteria. Fixtures: clean open, fully-closed (→ not restored), partial-exit chain (qty correct), chain-token mismatch (→ quarantined), dead-session entry_ts (bars_held computed from ts, never negative). Hypothesis: qty ≥ 0, bars_held ≥ 0 always. DONE when tests pass, no edits outside the 2 files.
TASK 12 — Venue lead/lag slippage metric [plan: V1 metrics — venue lead/lag]
Why. The plan's continuous signed SHORT-entry slippage metric: venue fill vs Binance mid at decision time — answers "is BingX discounting our signal". Rerunnable analysis.
Affected files (NEW only): prod/clean_arch/violet/slippage_metric.py,
prod/clean_arch/violet/test_violet_slippage_metric.py, report →
prod/VIOLET_dev/reports/violet_slippage_<UTC>.json.
Interface/approach. signed_entry_slippage(decision_mid: float, venue_fill: float, side: str) -> float (signed bps; positive = adverse) + an aggregator over recorded rows producing a
distribution (mean/median/p95). Read-only over recorded data (CH dolphin/dolphin_violet, no
writes). Pure functions; V-TYPES the rows.
Tests / pass criteria. Sign convention exact for SHORT and LONG on hand-built cases; aggregator
stats correct on a synthetic fixture (--self-test). DONE when self-test passes + a report can be
produced on the prod host, no production writes, no edits outside the files.
TASK 13 — Cadence per-action Q schedule + evaluate/actuate telemetry [plan: cadence quantizer]
Why. The plan's cadence quantizer: each action (SL/TP/ENTRY/OBF/ExoF) gets its own
quantization Q; evaluate at fastest cadence (shadow evidence), actuate at Q; step Q down later.
cadence.py (the CadenceControlPlane) exists; this unit adds a per-action Q-SCHEDULE LOADER +
a shadow-delta TELEMETRY recorder WITHOUT modifying cadence.py.
Affected files (NEW only): prod/clean_arch/violet/cadence_schedule.py,
prod/clean_arch/violet/test_violet_cadence_schedule.py.
Interface/approach. Read cadence.py for Action + CadenceControlPlane API. Provide
load_q_schedule(mapping) -> dict[Action, int_ns] (validated; the plan's initial table: SL tight,
TP=scan, ENTRY=scan, OBF~1s) and CadenceTelemetry that records per-action evaluate-count vs
actuate-count and emits a shadow-delta summary. Compose with CadenceControlPlane by wrapping its
due() calls (do not edit it).
Tests / pass criteria. Schedule loader validates/rejects bad Q values; telemetry counts evaluate > actuate under a synthetic action stream; per-action Q honored. DONE when tests pass, no edits outside the 2 files.
Composition map (how the units fit — the "well-known interfaces")
Task 8 contracts_v3 (VenueTick, OpenPositionView, ExitDecision) ← the shared vocabulary
│
├── Task 8 venue_price_feed → VenueTick stream
├── Task 9 mechanical_exit_guard(pos, tick) → ExitDecision (consumes VenueTick)
├── Task 10 sl_floor_guard(pos, tick) → ExitDecision (composes Task 9)
├── Task 11 event_restore(journal) → [OpenPositionView] (feeds 9/10)
├── Task 12 slippage_metric(mid, fill, side)→ bps (uses VenueTick mids)
└── Task 13 cadence_schedule/telemetry → per-action Q + deltas
Integration (wiring these into the live reactor + decision/exit path) is the OWNER's job later, NOT part of these units. Each ships standalone + tested.
Recommended order
8 (contracts + feed) → 9 (mechanical exit) → 11 (restore) → 10 (SL floor) → 13 (cadence) → 12
(slippage). Task 8 first because contracts_v3.py is the shared vocabulary everything imports.
Still NOT in scope (operator/owner only)
DARK soak start; V4 live execution / BingX ExecutionClient; HZ-bridge refactor; the V3.4c parity root-cause (CRITICAL #1 — Claude's review job); any edit to the in-flight files in §0.