Files
siloqy/prod/docs/VIOLET_PART_SPEC_OA_TODO_PASS3.md
Codex 9c068619e1 VIOLET: CRITICAL parity-disappointment TODO + OA partial spec PASS 3
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>
2026-06-17 09:01:49 +02:00

12 KiB
Raw Blame History

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.

  1. 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)".
  2. 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.
  3. ExitDecisionaction: 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.


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_thresholdExitDecision(EXIT, FIXED_TP, priority=1); if it breaches sl_thresholdExitDecision(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.

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.