VIOLET_PART_SPEC_OA_TODO_PASS4.md — six plan-derived independent units continuing from PASS 3, extending the shared contracts_v3 vocabulary (adds ExecIntent / CapitalState / VolGateResult / AlphaBar): 14. Volatility regime gate (faithful vol_ok — the entry precondition). 15. AlphaExitEngineV7 wrapper (V3a deferred — wrap BLUE's exit engine as-is, discretionary pri 2). 16. Capital/economics provenance ledger (capital = anchor + Σ deltas, event_seq, exactly-one-row). 17. Execution-intent emitter, DARK (V4 boundary — ShadowDecision → ExecIntent, log-only sink). 18. Alpha-side reactor data feed (V2 NT Binance DATA spike → AlphaDataFeedPort, data-only). 19. MAX_HOLD + MEAN_REVERSION scan-driven exit timers. Each ships + tests standalone; composition map + recommended order (14→16→15→19→17→18) + the same HARD RULES + commit/branch policy. VIBRISS/adaptive-TP explicitly excluded. Also: added PASS 3 + PASS 4 to the review queue in VIOLET_TODO_CRITICAL.md §2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
12 KiB
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 -- <files> 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:
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.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).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 AlphaBars; 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.