diff --git a/prod/docs/VIOLET_BUILD_SPEC__SIZING_PARITY.md b/prod/docs/VIOLET_BUILD_SPEC__SIZING_PARITY.md new file mode 100644 index 0000000..ac829ec --- /dev/null +++ b/prod/docs/VIOLET_BUILD_SPEC__SIZING_PARITY.md @@ -0,0 +1,111 @@ +# 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.