205 lines
12 KiB
Markdown
205 lines
12 KiB
Markdown
|
|
# 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:
|
||
|
|
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.
|