Operator directive: VIOLET must do IDENTICALLY what BLUE does for the three parity flags — approximation cannot guarantee bit-for-bit functioning. Reworked live_blue_source.py to call BLUE's OWN code paths, not reconstruct/substitute them. boost/beta — was reading the published DOLPHIN_FEATURES.acb_boost scalar (acb_processor_service's daily value). BLUE's trader does NOT use that for sizing; it recomputes live via acb.get_dynamic_boost_from_hz(exf_latest, w750_velocity, direction) with a bare AdaptiveCircuitBreaker() and NO ob_engine (nautilus_event_trader.py on_exf_update:4769 / rollover prewarm:2710). New _source_boost_beta replicates that call exactly (reads exf_latest + latest_eigen_scan.w750_velocity; 0.0→None like BLUE; on stale exf ValueError → neutral, mirroring BLUE's "ACB Stale Data Fallback"). The published acb_boost is never read. Test pins bit-identity against the real ACB. signal-gen (dc_status) — was AlphaSignalGenerator() bare defaults; coincidentally equal to BLUE today, but BLUE builds it from ENGINE_KWARGS (trader:128-133, threaded at esf_alpha_orchestrator.py:180-191), so a champion retune would silently diverge. Now constructed with BLUE_SIGNAL_GEN_KWARGS (vel_div_* imported from the kernel constants). Test parses ENGINE_KWARGS from the trader source and asserts each param matches — drift becomes a red test, not a silent miss. OB — was a reinvented HazelcastOBProvider reading asset_*_ob with custom parsing. Now uses BLUE's OWN HZOBProvider + OBFeatureEngine, wired exactly as _wire_obf (nautilus_event_trader.py:4967-4980): step_live(assets, bar_idx) then get_market. Engine is injectable + persistent so OB accumulation matches BLUE across scans (caller owns it). Deleted: HazelcastOBProvider, _extract_acb, the status-label mc path. mc_scale fix (begin_day cat/env thresholds) retained. Module docstring + structural-divergence doc updated: all three flags FIXED; only _derive_mc_scale remains hand-replicated (pinned by formula test). OPEN follow-up: launcher shadow_decision_step should pass a persistent ob_engine + bar_idx for cross-scan OB history. 34 tests (33 + live-HZ smoke deselected): boost/beta-vs-ACB bit-identity (incl. w750=0→None, no-exf, stale ValueError, ignores acb_boost), signal-gen ENGINE_KWARGS pin (parametrized), OB wiring (step_live call, HZ coords, neutral paths), mc_scale formula, sequence dc/selector parity, anomaly handling. violet-only; no shared-file edits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.5 KiB
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
-
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 bysizing.py/live_factor_source.py.OBFeatureEngine.get_market(ob_features.py) — wrapped bylive_blue_source.py.AlphaSignalGenerator.generate(alpha_signal_generator.py) — wrapped bylive_blue_source.py.AlphaAssetSelector/AlphaBetSizer— wrapped byalpha_wrappers.py.map_internal_conviction_to_exchange_leverage(bingx/leverage.py) — wrapped byexchange_leverage.py.
-
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)
- 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 puremc_scale_from(cat, env)helper on the BLUE side (BLUE-domain change, operator-gated) that BOTH engines call. - Single ambiguity owner. Surfaces like MC (
statuslabel vsbegin_daythresholds) and ACB (publishedacb_boostvs trader recompute) have two disagreeing sources; pick ONE canonical per factor and document it (see_derive_mc_scaledocstring). - 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.