# VIOLET — partial spec for another agent, PASS 7 (OA TODO): V5 SELECTION + MULTI-ASSET SLOTS + CAPITAL ALLOCATION Date: 2026-06-17. Continues PASS 1–6 (same V0→V6 plan). PASS 7 is the **V5** layer of the ladder: faithful IRP/ARS asset RANKING, MULTI-ASSET SLOT management (concurrent positions), and CAPITAL ALLOCATION across slots. Everything DARK and as independent, separately-testable units sharing the `contracts_v3` vocabulary. **⚠️ Relevance to CRITICAL #1 (read `VIOLET_TODO_CRITICAL.md`).** The disappointing live parity (pick-match 1.5%, no-pick 86%, **but sizing near-identical when assets align**) points squarely at SELECTION/TIMING, i.e. THIS layer. PASS 7 BUILDS the faithful selection machinery and pins the ranking bit-for-bit to BLUE's `AlphaAssetSelector` (Task 36). That is COMPLEMENTARY to — not a substitute for — the live-aggregate parity root-cause, which remains **Claude's** job (the live join/alignment in `parity_report.py` is the other suspect). Do NOT attempt the live root-cause here; build the units + the unit-level bit-identity pin. **Read first (authoritative references — READ ONLY):** - `nautilus_dolphin/nautilus_dolphin/nautilus/alpha_asset_selector.py` — `AlphaAssetSelector.rank_assets` (IRP selection, ARS scoring, BIBLE §5) — the ranking authority. - `prod/docs/SYSTEM_BIBLE.md` §5 (selection), §490 "OB Sub-1: ARS adjusted ±5%/10% by per-asset OB depth quality before sorting", §735 OB sub-systems. (BIBLE is directionally right but can be outdated — verify against the kernel code.) - `prod/clean_arch/violet/alpha_wrappers.py` (`VioletAssetSelector`, the single-pick wrapper) + `decision_engine.py` (how a pick + `ars_score` flow today) + `sizing.py` (notional model) — READ, do NOT edit (in-flight). - ENGINE_KWARGS selection knobs (`nautilus_event_trader.py:127+`): `use_asset_selection=True`, `min_irp_alignment=0.0` ("gold spec: no IRP filter"), `max_slots` (launcher venue config). - Stablecoin exclusion: `decision_engine.STABLECOIN_SYMBOLS` (must match BLUE's set). --- ## 0. HARD RULES (identical to PASS 1–6 — 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** — selection/slots/allocation are pure logic over scan + factor inputs; no orders, no venue, no service/HZ control. - **V-TYPES on all new code**; faithful poison-guards only; NO arbitrary caps. - **NEW-FILE-ONLY** under `prod/clean_arch/violet/selection/` (+ extend `contracts_v3.py`). Do NOT modify in-flight files (`alpha_wrappers.py`, `decision_engine.py`, `sizing.py`, `live_blue_source.py`, the PASS-5/6 exec files, `cadence.py`, `clock.py`). READ + IMPORT them. ## 0a. COMMIT / BRANCH POLICY (3 shared-index collisions on 2026-06-16 — non-negotiable) Own `git worktree` (`git worktree add ../vp-oa7 -b agent/oa-violet7`) 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`. `git grep` only. --- ## I. SHARED INTERFACE EXTENSIONS (add to `contracts_v3.py`; never fork a parallel type) Reuse PASS-3..6 types. ADD the selection/slot vocabulary (all `StrictModel` / `Annotated`): 1. **`AssetRank`** — `asset: Symbol`, `ars_score: float (finite)`, `ob_adjusted_score: float (finite)`, `irp_alignment: float`, `irp_passed: bool`, `rank: int (ge=0)`, `excluded_reason: str = ""` (e.g. "STABLECOIN"/"IRP_FILTER"/"" ). 2. **`SlotState`** — `slot_id: int (ge=0)`, `asset: Optional[Symbol]`, `status: str` ("FREE"/"HELD"/"PENDING"), `held_since_ns: Optional[MonoNs]`. 3. **`CapitalAllocation`** — `slot_id: int`, `asset: Symbol`, `allocated_capital: float (ge=0)`, `notional_fraction: float (ge=0)`, `conviction_leverage: float (ge=0)`. 4. **`SlotPolicy`** — `max_slots: Annotated[int, Field(ge=1)]`, `hysteresis_bars: Annotated[int, Field(ge=0)]` (anti-churn), `allow_reentry: bool`. If a task needs another field, ADD it here and note it. --- ## TASK 31 — Selection contracts + SlotPolicy **Why.** The shared selection/slot vocabulary everything else imports. **Affected files (NEW):** extend `contracts_v3.py` (the §I types); `prod/clean_arch/violet/selection/__init__.py`; `prod/clean_arch/violet/selection/test_violet_selection_contracts.py`. **Pass criteria.** All types construct + poison-reject (non-finite scores, negative capital, max_slots ≥ 1); `excluded_reason` enumerated values documented. No edits outside the new/extended files. ## TASK 32 — ARS ranking pipeline (faithful to AlphaAssetSelector + OB Sub-1 + IRP filter) **Why.** The heart of selection: reproduce BLUE's full ranking — ARS score → OB Sub-1 adjustment → IRP alignment filter → stablecoin exclusion → sort. The single-pick `VioletAssetSelector` covers part of this; PASS 7 builds the FULL multi-asset ranking on top WITHOUT editing it. **Affected files (NEW):** `prod/clean_arch/violet/selection/rank_pipeline.py`, `prod/clean_arch/violet/selection/test_violet_rank_pipeline.py`. **Interface/approach.** `rank_assets(market_data: dict[Symbol, list[float]], *, regime_direction: int, ob_market=None, min_irp_alignment: float, stablecoins: frozenset[str]) -> list[AssetRank]`: - Compute the base ARS exactly as `AlphaAssetSelector` (WRAP/transcribe — cite file:line). - **OB Sub-1** (BIBLE §490): adjust ARS ±5%/10% by per-asset OB depth quality BEFORE sorting (read the kernel for the exact factors/percentages; if `ob_market` is None, skip the adjustment, as the no-OB path does). - **IRP alignment filter:** drop assets with alignment < `min_irp_alignment` (default 0.0 = no filter, per gold spec) → mark `irp_passed`/`excluded_reason`. - **Stablecoin exclusion:** any asset in `stablecoins` → excluded (must equal `decision_engine.STABLECOIN_SYMBOLS`; assert in test). - Sort descending by `ob_adjusted_score`; assign `rank`. Pure function, deterministic, V-TYPES out. **Pass criteria.** On crafted market data the ranked order + ARS/ob_adjusted scores match a direct `AlphaAssetSelector` computation; stablecoins always excluded; IRP filter respected; OB-off path equals base ARS. No edits outside the 2 files. ## TASK 33 — Multi-asset slot manager (max_slots, assignment, hysteresis) **Why.** BLUE/VIOLET hold up to `max_slots` concurrent positions; the slot manager decides which ranked assets occupy slots, with no double-occupancy and anti-churn hysteresis. **Affected files (NEW):** `prod/clean_arch/violet/selection/slot_manager.py`, `prod/clean_arch/violet/selection/test_violet_slot_manager.py`. **Interface/approach.** `SlotManager(policy: SlotPolicy)` holding `list[SlotState]`. `assign(ranked: list[AssetRank], held: dict[Symbol, OpenPositionView], now_ns) -> list[SlotState]`: keep currently HELD assets in their slots; fill FREE slots from the top of `ranked` (excluding already-held and excluded assets); never assign the same asset to two slots; respect `hysteresis_bars` (do not evict a freshly-taken slot to chase a higher rank within the hysteresis window). READ how BLUE/the trader manages `max_slots` (launcher venue config + engine) and transcribe the rule; if BLUE is single-slot today (`max_slots=1`), the manager must still be correct + generalize to N. **Pass criteria.** Held assets retained; free slots filled by rank; no double-occupancy; hysteresis prevents churn; max_slots respected. Hypothesis: |HELD slots| ≤ max_slots always; no asset in two slots. No edits outside the 2 files. ## TASK 34 — Capital allocation across slots **Why.** Turn slot occupancy + conviction into per-slot capital + notional, honoring BLUE's sizing convention (notional = base_fraction × conviction × capital) and the margin-study findings. **Affected files (NEW):** `prod/clean_arch/violet/selection/capital_allocator.py`, `prod/clean_arch/violet/selection/test_violet_capital_allocator.py`. **Interface/approach.** First READ how BLUE allocates capital across concurrent slots (shared pool vs per-slot; the margin study `blue_margin_envelope_study` + `sizing.py` are the references — notional = 0.20 × conviction × capital; capital under-utilized at 1 slot was DELIBERATE). Define `allocate(slots: list[SlotState], convictions: dict[Symbol, float], *, capital: float, base_fraction: float) -> list[CapitalAllocation]` reproducing that convention EXACTLY (cite the source). Do NOT invent a normalization BLUE doesn't do. **Pass criteria.** Per-slot notional == base_fraction × conviction × capital (the documented model); sum-of-notionals behavior matches BLUE's (shared-capital, not artificially normalized, unless BLUE normalizes — verify); poison guards reject non-finite/negative. Cite the allocation source. No edits outside the 2 files. ## TASK 35 — Selection→slot→intent multi-asset flow harness (DARK) **Why.** Compose the V5 path end-to-end: `rank_pipeline` → `slot_manager` → `capital_allocator` → (existing sizing) → `ExecIntent` per occupied slot (PASS-4 Task 17), DARK. **Affected files (NEW):** `prod/clean_arch/violet/selection/multi_asset_flow.py`, `prod/clean_arch/violet/selection/test_violet_multi_asset_flow.py`. **Interface/approach.** `run_selection_cycle(market_data, held, *, factors, capital, policy, ...) -> list[ExecIntent]` wiring the four units. Reuse `sizing.VioletSizer` for conviction (import, don't fork). DARK: emits intents, never orders. Deterministic. **Pass criteria.** A multi-asset scenario yields one intent per occupied slot with correct asset/qty/notional; held assets not re-entered (unless `allow_reentry`); max_slots respected end-to-end; deterministic. No edits outside the 2 files. ## TASK 36 — Ranking bit-identity gate vs the real AlphaAssetSelector **Why.** Pin the ranking to BLUE's actual `AlphaAssetSelector` over a sampled grid — the unit-level parity that, once green, removes selection-math as a suspect for CRITICAL #1 (leaving timing/join as the remaining live-aggregate suspect for Claude). **Affected files (NEW):** `prod/clean_arch/violet/selection/test_violet_rank_parity_gate.py`; gate report → `prod/VIOLET_dev/reports/violet_rank_parity_.json`. **Interface/approach.** Over many synthetic universes (varied asset counts, price histories, directions, with/without OB), assert `rank_pipeline.rank_assets` produces the SAME ordering + the SAME ARS/ob_adjusted scores (`==`) as a direct `AlphaAssetSelector` run on the same inputs. Record mismatch count (must be 0). **Pass criteria (`@pytest.mark.gate`).** ≥ 200 universes, zero ranking/score mismatches; report archived. If mismatches appear, that IS a finding — report them, do not loosen. No edits outside the new file. --- ## Composition map ``` contracts_v3 (+AssetRank/SlotState/CapitalAllocation/SlotPolicy) 32 rank_pipeline(market, ob, irp, stables) → [AssetRank] (faithful to AlphaAssetSelector + OB Sub-1) 33 slot_manager(ranked, held, policy) → [SlotState] (max_slots, hysteresis, no double-occ) 34 capital_allocator(slots, convictions, cap)→ [CapitalAllocation] (BLUE notional convention) 35 multi_asset_flow: 32→33→34→ sizing → [ExecIntent] (DARK V5 cycle) 36 rank_parity_gate: rank_pipeline == AlphaAssetSelector (bit-identity) ← narrows CRITICAL #1 ``` Integration (running the V5 cycle on the live reactor, wiring intents to the PASS-5/6 exec stack) is the OWNER's job later. ## Recommended order **31 (contracts) → 32 (rank pipeline) → 36 (rank parity gate) → 33 (slots) → 34 (allocation) → 35 (flow harness)**. 36 right after 32 so the ranking is pinned before building on it. ## Still NOT in scope (operator/owner only, or Claude) - **The live BLUE↔VIOLET aggregate parity root-cause (CRITICAL #1)** — Claude's job; this pass only pins ranking-math + builds V5 units. - **Alpha re-timing / sub-second actuation of entries** — VBT re-certification (research), not a unit. - **DARK soak start; V4 live execution; HZ-bridge; VIBRISS.** - Any edit to in-flight / shared files in §0.