# VIOLET Build Spec — Full Sizing Parity (orchestrator wrap-all → bit-identity) **Status:** READY TO BUILD. Self-contained brief for an agent with repo access (no prior session context assumed). Branch `exp/pink-ditav2-sprint0-20260530`. Background/derivation: `VIOLET_V3_FINDINGS.md` §8b/§8c. Doctrine: memory `violet_v3_alpha_doctrine` (if loaded) — key rules restated below. ## 1. Objective Make VIOLET's sizing reproduce live BLUE's conviction-leverage **bit-for-bit**. VIOLET already reproduces the base cubic curve (V3a) and the EsoF haircut (V3.2). What's missing is the rest of BLUE's full sizing composition (3 more multipliers + cap logic), which lives in `esf_alpha_orchestrator`, not in the base bet-sizer. Wrap those, compose exactly, and prove identity with a Monte-Carlo gate. ## 2. Non-negotiable constraints - **WRAP, DON'T REIMPLEMENT.** Call BLUE's actual kernels; do not re-derive their math. Bit-identity is only achievable by running the real code. (Reimplementation will fail the gate on float ordering.) - **ZERO edits to shared files:** `prod/nautilus_event_trader.py`, `prod/clean_arch/dita_v2/*`, `prod/clean_arch/dita/decision.py`, `nautilus_dolphin/**`, `blue_parity.py`. Mechanical check per commit: `git diff --name-only` must not contain them. - **VIOLET stays DARK** — no execution, no orders. This is a sizing-math layer only. - **V-TYPES** (`prod/clean_arch/violet/domain.py`): refined types at boundaries, `@typed` (beartype) on public methods, `StrictModel` for value objects, reject-at-source. - **Follow BLUE in all regards** — no filters/hygiene BLUE lacks. ## 3. The exact target composition (authoritative) Source: `nautilus_dolphin/nautilus_dolphin/nautilus/esf_alpha_orchestrator.py` ~lines 597-619. Reproduce in EXACT operation order (float order matters for bit-identity): ``` raw_leverage = size_result["leverage"] # base cubic (AlphaBetSizer) * dc_lev_mult # signal_gen.dc_leverage_boost if signal.dc_status=="CONFIRM" else 1.0 * regime_size_mult # ACB: _day_base_boost * (1 + _day_beta * strength^3) * _day_mc_scale * market_ob_mult # OB cross-asset consensus (1.0 default; 0.85..1.20) * _esof_size_mult # EsoF haircut [0,1] clamped_max = min(base_max_leverage * regime_size_mult * market_ob_mult * _esof_size_mult, abs_max_leverage) if _day_posture == 'STALKER': clamped_max = min(clamped_max, 2.0) leverage = min(raw_leverage, clamped_max) leverage = max(bet_sizer.min_leverage, leverage) notional = capital * size_result["fraction"] * leverage ``` Gold-spec caps (`prod/docs/FROZEN_ALGO_SPEC_GOLD_REFERENCE.md`): `base_max_leverage=8.0` (soft), `abs_max_leverage=9.0` (hard). NOTE V3a currently constructs the base sizer with `max_leverage=9.0` — **change to 8.0** (the boost lifts toward 9). ## 4. Wrap surfaces (what to wrap, where) | Multiplier | Wrap target | API | |---|---|---| | base `size_result` | `nautilus_dolphin/.../alpha_bet_sizer.py` `AlphaBetSizer.calculate_size` | already wrapped: `prod/clean_arch/violet/alpha_wrappers.py` `VioletBetSizer` (fix `max_leverage=8.0`) | | `_esof_size_mult` | `nautilus_dolphin/.../esof_size_gate.py` `esof_size_mult_from_score` | already wrapped: `prod/clean_arch/violet/modulation.py` `VioletSizeModulation` | | `regime_size_mult` | `nautilus_dolphin/.../adaptive_circuit_breaker.py` `AdaptiveCircuitBreaker` | `preload_w750([dates])`, `get_dynamic_boost_for_date(date)`/`get_dynamic_boost_from_hz(...)` → `{boost, beta}`; per-bar `regime_size_mult = base_boost*(1+beta*strength^3)*mc_scale` (orchestrator :901-909). Needs eigenvalues data (auto-resolves to `/mnt/dolphin_training/data/eigenvalues` etc.) | | `dc_lev_mult` | `esf_alpha_orchestrator` signal_gen (`signal.dc_status`, `signal_gen.dc_leverage_boost`) | wrap the signal generator; `dc_lev_mult = dc_leverage_boost if dc_status=="CONFIRM" else 1.0` | | `market_ob_mult` | `nautilus_dolphin/.../ob_features.py` `OBFeatureEngine` | `get_market(bar_idx, symbols)` → imbalance/agreement; formula at orchestrator :587-595 | | `_day_posture` (STALKER) | orchestrator posture state | 2.0 cap when STALKER | **Preferred approach (most faithful):** instantiate and drive the REAL `esf_alpha_orchestrator` sizing path so the composition runs BLUE's own code. If full orchestrator instantiation proves too heavy, the fallback is to wrap each component above and replicate ONLY the ~8-line composition block verbatim (it is trivial deterministic arithmetic — bit-identical if op-order is preserved). Decide after a spike on orchestrator instantiation cost. ## 5. Validation gate (BINDING — operator-specified) 1. **Monte-Carlo the ENTIRE JOINT input universe** of both surfaces together: `vel_div × ACB signals(funding/dvol/fng/taker) × w750_vel/β × esof_score × mc_scale × ob imbalance/agreement × posture × capital`. Hammer interactions (cap@9, EsoF-on-boosted, STALKER). N ≥ 1e6 samples. 2. **Match to BIT IDENTITY** vs BLUE's actual-code output (float-for-float, `==`, not approx). A statistical match HIDES composition bugs; bit-identity won't. Any mismatch = wrapper bug (op order / rounding / cap) → fix → re-run. 3. **THEN upstream** — replay recorded `dolphin.trade_events` (and/or live scans) through the wrapped chain; compare to recorded `leverage`. (Caveat: recorded `boost_at_entry`/ `beta_at_entry` are mostly placeholder `1.0` — do NOT validate against those fields; validate against `leverage` itself, and use the live ACB to produce boosts.) ## 6. Reusable existing pieces - `prod/clean_arch/violet/alpha_wrappers.py` — `VioletBetSizer`, `SizeDecision` (V-TYPES). - `prod/clean_arch/violet/modulation.py` — `VioletSizeModulation` (EsoF fold, the wrap pattern). - `prod/clean_arch/violet/test_violet_modulation.py` / `test_violet_alpha_wrappers.py` — test patterns (hypothesis + drift-guards) to mirror. - Import-root pattern for `nautilus_dolphin.nautilus.*`: see `_import_esof_gate()` in `modulation.py` / `_import_blue_alpha()` in `alpha_wrappers.py`. ## 7. Deliverables & acceptance - New `prod/clean_arch/violet/sizing.py` (or extend `modulation.py`): a `VioletSizer` that composes the 5 multipliers + caps, returning a V-TYPES `SizeDecision` with the full conviction leverage. - `test_violet_sizing.py`: unit + hypothesis + the **MC bit-identity gate** (`@pytest.mark.gate`) + the upstream replay check. Gate report → `prod/VIOLET_dev/reports/`. - ACCEPT when: bit-identity gate passes at N≥1e6; upstream replay matches recorded `leverage` within tolerance attributable only to live-ACB vs recorded; full violet suite green; shared-files-clean; VIOLET still DARK. ## 8. Watch-outs (learned) - `boost_at_entry`/`beta_at_entry` in trade_events = placeholder `1.0` (don't trust them). - `beta` recorded as {0,1} in some places vs config {0.2,0.8} — get beta from the live ACB, not recorded fields. - ACB needs eigenvalues data on disk; verify the path resolves on the prod host before the upstream step. - `min_leverage` floor and the STALKER 2.0 cap are easy to forget — both are in the gate.