# VIOLET ↔ BLUE parity: structural divergence & re-derivation debt **Date:** 2026-06-16 **Status:** ACKNOWLEDGED TRADEOFF / open architectural debt **Raised by:** operator, during the V3.4c review. ## The problem, stated plainly VIOLET reproduces BLUE's sizing behaviour **bit-for-bit by intent**, but it does so in a **different module / file / scope structure** than BLUE. BLUE's logic lives in one place — the monolithic `NDAlphaEngine` (`nautilus_dolphin/nautilus_dolphin/nautilus/esf_alpha_orchestrator.py`), which holds day-state (`_day_base_boost`, `_day_beta`, `_day_mc_scale`, `_day_posture`), constructs its own `signal_gen`/`bet_sizer`, and runs `begin_day` / `_try_entry` inline. VIOLET re-expresses that same logic spread across: `sizing.py`, `live_blue_source.py`, `live_factors.py`, `live_factor_source.py`, `decision_engine.py`, `alpha_wrappers.py`. **Consequence (the operator's concern, verbatim intent):** because VIOLET imitates the computations *while* ending up with a different structure, any *orderly, systemic, verifiable* BLUE↔VIOLET algo parity comparison — and any future refactor of either side — is **much harder**. The surfaces do not line up 1:1, so a diff between the two engines is not mechanical; it requires a human to know which VIOLET fragment mirrors which BLUE line. ## Two kinds of reuse — and only one is safe 1. **WRAPPED kernels (safe — single source of truth).** VIOLET imports and calls BLUE's actual kernel objects. A BLUE change propagates automatically. - `esof_size_mult_from_score`, `parse_esof_payload`, `esof_score_from_payload` (`esof_size_gate.py`) — wrapped by `sizing.py` / `live_factor_source.py`. - `OBFeatureEngine.get_market` (`ob_features.py`) — wrapped by `live_blue_source.py`. - `AlphaSignalGenerator.generate` (`alpha_signal_generator.py`) — wrapped by `live_blue_source.py`. - `AlphaAssetSelector` / `AlphaBetSizer` — wrapped by `alpha_wrappers.py`. - `map_internal_conviction_to_exchange_leverage` (`bingx/leverage.py`) — wrapped by `exchange_leverage.py`. 2. **HAND-REPLICATED arithmetic (the debt — duplicated formulas, drift-prone).** VIOLET transcribes BLUE's pure float arithmetic into its own functions. A BLUE change here is SILENT in VIOLET until someone notices. ## Re-derivation inventory (the drift liabilities) | Computation | BLUE authority (file:line) | VIOLET replica | Parity safety-net today | Drift risk | |---|---|---|---|---| | 5-factor compose + caps | `esf_alpha_orchestrator.py:600-619` | `sizing.VioletSizer.compose` | `@gate` Monte-Carlo vs REAL orchestrator (bit-identity) | LOW (gated) | | `regime_size_mult` = boost·(1+β·s³)·mc | `esf_alpha_orchestrator.py:898-909` | `sizing.VioletSizer.regime_size_mult` | same gate | LOW-MED | | `strength_cubic` | `esf_alpha_orchestrator.py:872-885` | `sizing.VioletSizer.strength_cubic` | same gate | LOW-MED | | `market_ob_mult` consensus | `esf_alpha_orchestrator.py:587-595` | `sizing.VioletSizer.market_ob_mult` | same gate | MED | | `dc_lev_mult` | `esf_alpha_orchestrator.py:575-577` | `sizing.VioletSizer.dc_lev_mult` | unit only | MED (but ≡1.0 while dc_leverage_boost=1.0) | | **`mc_scale`** | `esf_alpha_orchestrator.py:956-962` (`begin_day`) | `live_blue_source._derive_mc_scale` | parametrized formula test (8 cases inc. divergences) | LOW-MED (only remaining hand-replica) | | `boost`/`beta` source | trader recompute `acb.get_dynamic_boost_from_hz(exf_latest)` | **FIXED 2026-06-16**: `_source_boost_beta` calls the SAME `get_dynamic_boost_from_hz` over exf_latest+w750 (bare `AdaptiveCircuitBreaker()`, no ob_engine) | bit-identity test vs the real ACB | LOW | | `dc_status` config | `signal_gen` built with ENGINE_KWARGS `:180-191` | **FIXED**: `AlphaSignalGenerator(**BLUE_SIGNAL_GEN_KWARGS)` | param test parses ENGINE_KWARGS from trader source | LOW | | OB feed | live OB accumulation via `HZOBProvider` | **FIXED**: BLUE's own `HZOBProvider` + `OBFeatureEngine` + `step_live`/`get_market` (persistent engine + bar_idx) | wiring test (HZ coords, step_live call) | LOW (caller must pass persistent engine) | **Update 2026-06-16:** the three MED-risk flags above were brought to bit-identity per operator directive ("VIOLET should do *identically* what BLUE does"). boost/beta now call the SAME `get_dynamic_boost_from_hz` BLUE's trader calls (the published `acb_boost` is NOT used); dc_status uses `AlphaSignalGenerator` pinned to BLUE's ENGINE_KWARGS; OB uses BLUE's `HZOBProvider`. The reinvented `HazelcastOBProvider` and the `_extract_acb`/`status`-label paths were deleted. The ONLY remaining hand-replicated arithmetic is `_derive_mc_scale` (begin_day computes it inline inside an un-callable method), pinned by a formula test. OPEN follow-up: the launcher (`shadow_decision_step`) must pass a PERSISTENT `ob_engine` + per-scan-incrementing `bar_idx` into `source_live_blue_sizing_factors` so OB accumulation matches BLUE across scans; today it single-shots, which is wired-correctly but historyless. ## Why we accept it (for now) - The kernels that carry the heavy alpha are WRAPPED, not copied. - The composition arithmetic IS gated bit-for-bit against the real orchestrator. - VIOLET must stay a *read-only, DARK* mirror of a *running* BLUE; it cannot import BLUE's live in-process day-state, so some reconstruction from published HZ surfaces is unavoidable. ## Mitigations (recommended, not yet done) 1. **Parity-pin every hand-replicated formula.** For each row above, add a test that imports BLUE's authoritative function/constant and asserts VIOLET's replica equals it over a sampled grid — converting silent drift into a red test. Where BLUE's logic is trapped inside `begin_day` (mc_scale), refactor a *pure* `mc_scale_from(cat, env)` helper **on the BLUE side** (BLUE-domain change, operator-gated) that BOTH engines call. 2. **Single ambiguity owner.** Surfaces like MC (`status` label vs `begin_day` thresholds) and ACB (published `acb_boost` vs trader recompute) have two disagreeing sources; pick ONE canonical per factor and document it (see `_derive_mc_scale` docstring). 3. **Backplane convergence (the real fix).** When the DITAv2 Rust middleware becomes the shared backplane, BOTH BLUE and VIOLET should consume factors from it rather than each computing/replicating — collapsing this divergence at the source. Until then, every new hand-replication MUST be logged in this table. ## Maintenance rule Any change to a BLUE formula in the left column REQUIRES a matching change + test update in the VIOLET replica in the same PR. Any NEW hand-replication MUST add a row here.