# VIOLET — partial spec for another agent, PASS 4 (OA TODO) Date: 2026-06-17. Continues PASS 3 (same V0→V6 plan, same independent-unit discipline). PASS 3 delivered the venue-feed / mechanical-exit / restore / slippage / cadence layer; PASS 4 adds the ENTRY GATE, the EXIT KERNELS (V7 + time-based), the ACCOUNTING ledger, the EXECUTION-INTENT boundary, and the ALPHA-SIDE data feed. Drawn strictly from `VIOLET_DEV_SPEC_AND_PLAN.md` — nothing invented. Each task is an independent, separately-testable unit; the ONLY coupling is the shared `contracts_v3.py` vocabulary (extended in §I). --- ## 0. HARD RULES (identical to PASS 1/2/3 — 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 DARK** — no orders, no execution, no keys, no service/HZ control, no PROGREEN. - **V-TYPES on all new code**; 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`, `alpha_wrappers.py`, `exchange_leverage.py`, `22_violet_decisions.sql`). READ + IMPORT them. - **VIBRISS stays DARK** — do NOT build adaptive-TP / VIBRISS-governable mechanisms here. ## 0a. COMMIT / BRANCH POLICY (3 shared-index collisions on 2026-06-16 — non-negotiable) Own `git worktree` (`git worktree add ../vp-oa4 -b agent/oa-violet4`) strongly preferred. Else never `git add -A`; `git commit -F msg -- ` with explicit pathspec; verify `git show --stat --format="" HEAD` lists ONLY your files. One commit/task, prefix `VIOLET OA:`, Co-Authored-By trailer. Tests on `/home/dolphin/siloqy_env/bin/python3 -m pytest -q`. `git grep` only (recursive grep/find time out on CIFS). ## 0b. ClickHouse (read-only): `http://localhost:8123`, user `dolphin`/key `dolphin_ch_2026`; VIOLET db `dolphin_violet`, BLUE db `dolphin`. NEVER write/alter production tables. --- ## I. SHARED INTERFACE EXTENSIONS (add to `contracts_v3.py` from PASS 3; never fork a parallel type) Reuse PASS-3 `VenueTick`, `OpenPositionView`, `ExitDecision`. ADD: 1. **`ExecIntent`** (StrictModel) — a DARK would-be order: `asset: Symbol`, `side: str`, `qty: Qty`, `exchange_leverage: Annotated[int, Field(ge=1)]`, `maker_policy: str`, `target_notional: float`, `ts_ns: MonoNs`, `reason: str` ("ENTRY"/"EXIT"). NEVER sent. 2. **`CapitalState`** (StrictModel) — `capital: float (ge=0, finite)`, `anchor: float`, `delta_sum: float`, `event_seq: Seq`, `pnl_source: str`, `capital_source: str`. Capital is ALWAYS anchor + Σ deltas (never a last-value snapshot — the zombie-trade lesson). 3. **`VolGateResult`** (StrictModel) — `vol_ok: bool`, `metric: float`, `threshold: float`. If a task needs a field not listed, ADD it to `contracts_v3.py` and note it. --- ## TASK 14 — Volatility regime gate [plan: entry precondition `vol_ok` / vol_p60_threshold] **Why.** BLUE gates entries on a volatility regime check (`vol_ok`, `vol_p60_threshold`); `VioletDecisionEngine.decide(... vol_ok=...)` already takes it as a param but nothing computes it faithfully. This unit computes `vol_ok` the way BLUE does so the entry gate is real, not assumed. **Affected files (NEW only):** `prod/clean_arch/violet/vol_gate.py`, `prod/clean_arch/violet/test_violet_vol_gate.py`. (imports `contracts_v3`.) **Interface/approach.** First READ how BLUE computes the vol gate (search the orchestrator / the engine for `vol_regime_ok` / `vol_p60` / the percentile threshold; transcribe the EXACT formula). Define `compute_vol_gate(price_histories: dict[str, list[float]], *, threshold: float) -> VolGateResult` (`@typed`) reproducing BLUE's metric + comparison. Pure function. **Tests / pass criteria.** Parity: hand-built histories where BLUE's formula gives a known vol_ok; assert match. Boundary: at/just-below/just-above threshold. Hypothesis: result finite, vol_ok bool. DONE when the formula is transcribed from live BLUE (cite file:line in a docstring), tests pass, no edits outside the 2 files. --- ## TASK 15 — AlphaExitEngineV7 wrapper [plan: V3a deferred — "run exit-v7 as-is first, refactor later"] **Why.** The plan wraps BLUE's live multi-leg exit engine `AlphaExitEngineV7` behind a V-TYPES boundary and runs it AS-IS. Its outputs are DISCRETIONARY exits (priority 2) that, per the exit doctrine, can NEVER override the mechanical TP/SL (PASS-3 Task 9). **Affected files (NEW only):** `prod/clean_arch/violet/exit_v7_wrapper.py`, `prod/clean_arch/violet/test_violet_exit_v7_wrapper.py`. (imports `contracts_v3`.) **Interface/approach.** WRAP, don't reimplement: import `nautilus_dolphin.nautilus.alpha_exit_v7_engine.AlphaExitEngineV7`; build its context (`make_context`) and call `evaluate` over an `OpenPositionView` + market state; map its result to an `ExitDecision(reason=…, priority=2)`. Refined domain types in/out. The wrapper must NOT change V7's logic. Document any state V7 needs. **Tests / pass criteria.** For crafted inputs, the wrapper's `ExitDecision` matches what `AlphaExitEngineV7.evaluate` returns (same exit/hold + reason), priority always 2. DONE when wrap parity tests pass, no edits outside the 2 files. --- ## TASK 16 — Capital / economics provenance ledger [plan: V1 inheritance — capital = anchor + Σ deltas] **Why.** Binding VIOLET doctrine (from the zombie-trade incident + malformed-open audit): capital is ALWAYS anchor + Σ signed per-trade deltas, NEVER a last-value snapshot; every row carries `pnl_source`/`capital_source` provenance + `event_seq`; exactly-one-row-per-event. Per-trade sizer feedback uses trade-realized PnL, never capital deltas (shared-account foreign-fill immunity). **Affected files (NEW only):** `prod/clean_arch/violet/economics_ledger.py`, `prod/clean_arch/violet/test_violet_economics_ledger.py`. (emits `CapitalState`.) **Interface/approach.** `EconomicsLedger(anchor: float)` with `apply(delta: float, *, event_seq: int, pnl_source: str) -> CapitalState` (monotonic event_seq; rejects out-of-order / duplicate seq; capital = anchor + Σ deltas). `capital()` returns the derived value, never a stored snapshot. Reject NaN/inf deltas. No CH writes (pure in-memory ledger + a `to_row()` for a future sink). **Tests / pass criteria.** Σ-delta correctness over a sequence; duplicate/out-of-order event_seq rejected; capital never goes negative below a floor guard; Hypothesis: capital == anchor + sum of accepted deltas, always. DONE when tests pass, no edits outside the 2 files. --- ## TASK 17 — Execution-intent emitter (DARK) [plan: V4 prep — feeds the ExecDeadlineDriver] **Why.** V2 built the `ExecDeadlineDriver` + synthetic intents; V4 will turn decisions into real orders. This unit is the DARK boundary that converts a decision into an `ExecIntent` (a would-be order) — shadow-logged, NEVER sent — so V4 only has to flip the sink from log to venue. **Affected files (NEW only):** `prod/clean_arch/violet/exec_intent.py`, `prod/clean_arch/violet/test_violet_exec_intent.py`. (imports `contracts_v3`; composes PASS-2 `tradeability.py` if present, else recompute via `exchange_leverage.py`.) **Interface/approach.** `to_exec_intent(decision: ShadowDecision, *, capital: float, maker_policy: str = "maker_both") -> ExecIntent` — derive qty from notional/price, exchange_leverage from the L3 mapping, attach maker policy. Pure projection; an `IntentSink` protocol with a default `LoggingIntentSink` (shadow-logs, never sends). Hard guard: this module imports NOTHING that can place an order; assert no venue/order symbol is importable here (a test enforces it). **Tests / pass criteria.** Intent fields correct vs the decision + L3 mapping; `LoggingIntentSink` records, never sends; a test asserts the module has no order-placing dependency. DONE when tests pass, no edits outside the 2 files. --- ## TASK 18 — Alpha-side reactor data-feed adapter [plan: V2 NT Binance DATA client spike → feed] **Why.** The V2 spike GO-qualified the Nautilus Binance DATA client as VIOLET's alpha-side feed (separate feed process recommended; public-data only; dummy keys for the factory). This unit delivers that feed behind a clean port so the reactor consumes normalized bars without coupling to NT internals. **Affected files (NEW only):** `prod/clean_arch/violet/alpha_data_feed.py`, `prod/clean_arch/violet/test_violet_alpha_data_feed.py`. **Interface/approach.** Define `AlphaDataFeedPort` (ABC): `start()`, `latest_bar(asset)`, `stop()`, yielding a normalized bar type (`AlphaBar` — add to `contracts_v3`: asset, open/high/low/ close > 0, volume ≥ 0, ts_ns, source). Implement `NautilusBinanceDataFeed` (READ the V2 spike notes / `dolphin_actor*.py` / `live_price_feed.py` for the NT data-client wiring; public-data, dummy keys, NO execution). Implement `MockDataFeed` (deterministic). NO orders, data-only. **Tests / pass criteria.** Mock feed yields valid `AlphaBar`s; poison bars rejected; the NT adapter parses a recorded bar message into a correct `AlphaBar` (captured-frame fixture, no live connection in the unit test). DONE when contract finalized + parse-tested, no live feed in unit tests, no edits outside the 2 files. --- ## TASK 19 — MAX_HOLD + MEAN_REVERSION scan-driven exit timers [plan: scan-driven exits, V6 bible] **Why.** The plan keeps MAX_HOLD and MEAN_REVERSION as SCAN-driven exits (champion params are 5s-bar-denominated; re-timing requires VBT re-cert). They are mechanical/time-based exits that compose with the exit framework (PASS-3 Task 9) at the right priority. **Affected files (NEW only):** `prod/clean_arch/violet/time_exits.py`, `prod/clean_arch/violet/test_violet_time_exits.py`. (imports `contracts_v3`.) **Interface/approach.** READ BLUE's exit manager for the EXACT MAX_HOLD (`max_hold_bars`) and mean-reversion rules (`AlphaExitManager` / the orchestrator). Define `evaluate_time_exits(pos: OpenPositionView, *, bars_held: int, vel_div: float) -> ExitDecision` — `bars_held >= max_hold_bars` → `ExitDecision(EXIT, "MAX_HOLD")`; mean-reversion condition → `ExitDecision(EXIT, "MEAN_REVERSION")`; else HOLD. Scan-cadence (no sub-second). Priority below mechanical SL/TP, above pure discretionary (document the chosen priority vs Task 9/15). **Tests / pass criteria.** MAX_HOLD fires exactly at the bar threshold; mean-reversion fires on the BLUE condition (transcribed, cited); neither fires early. DONE when tests pass with the rule cited from live BLUE, no edits outside the 2 files. --- ## Composition map ``` contracts_v3 (VenueTick, OpenPositionView, ExitDecision, ExecIntent, CapitalState, VolGateResult, AlphaBar) ├── 14 vol_gate(histories) → VolGateResult (entry gate; feeds decide vol_ok) ├── 18 alpha_data_feed → AlphaBar stream (alpha-side reactor feed) ├── 15 exit_v7_wrapper(pos, state) → ExitDecision(pri 2) ┐ ├── 19 time_exits(pos, bars, veldiv) → ExitDecision ├─ exit stack, ranked by priority, │ (PASS-3 9 mechanical = pri 0/1; 10 SL floor = pri 0) ┘ mechanical always wins ├── 16 economics_ledger(anchor,Δ…) → CapitalState (accounting; never last-value) └── 17 exec_intent(decision,capital) → ExecIntent (DARK) (V4 boundary; log-only sink) ``` Integration (ranking the exit stack, wiring the feed into the reactor, the ledger into the journal, the intent sink into the driver) is the OWNER's job later — NOT part of these units. ## Recommended order **14 (vol gate) → 16 (ledger) → 15 (V7 wrap) → 19 (time exits) → 17 (exec intent) → 18 (data feed)**. 14/16 are small + foundational; 18 is the heaviest (NT adapter). ## Pass/sprint nomenclature (re-asked) — see `VIOLET_TODO_CRITICAL.md` §3 "pass" = a sub-sprint work-package (a batch of self-contained tasks for one agent); a V-stage (V0…V6) = the project's "Sprint N" / epic. Renaming passes to "sprints" over-claims scope. The later REVIEW + INTEGRATE + E2E of all passes is Claude's queued work. ## Still NOT in scope (operator/owner only) DARK soak start; V4 live order placement / BingX ExecutionClient; HZ-bridge refactor; VIBRISS / adaptive-TP; the V3.4c parity root-cause (CRITICAL #1); any edit to the in-flight files in §0.