From 262cada66413f30f890db5ba93e2949322aa26f0 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 16 Jun 2026 18:22:29 +0200 Subject: [PATCH] =?UTF-8?q?VIOLET=20V3.4c:=20ACB=20boost/beta=20=E2=80=94?= =?UTF-8?q?=20keep-prior=20on=20stale=20exf,=20decision=20logging,=20seman?= =?UTF-8?q?tics=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per operator: VIOLET must keep a "prior" on stale ExF rather than collapse to neutral, and the ACB's role must be commented-in + logged. - KEEP-PRIOR on stale/missing exf: BLUE keeps _day_base_boost/_day_beta on the stale branch (update_acb_boost isn't called). _source_boost_beta now takes `prior` (boost,beta) and returns it on ValueError / no-exf, seeded by the first successful compute. Threaded as `prior_boost_beta` through source_live_blue_sizing_factors (caller persists last good). - LOGGING: violet.live_blue_source logger emits live boost/beta + the full sourced plane. - SEMANTICS: documents the ACB in effect (inverse v6) and CONFIRMS ACB IS in the sizing layer via _day_base_boost/_day_beta → _update_regime_size_mult (esf_alpha_orchestrator.py:771-772, 898-909). OB Sub-4 beta modulation dormant in live — per operator found NON-PERFORMANT and deliberately bypassed; TODO_SOMEDAY in-code to find documentary confirmation. 37 tests. violet-only; no shared-file edits. Co-Authored-By: Claude Opus 4.8 --- prod/clean_arch/violet/live_blue_source.py | 83 ++++++++++++++----- .../violet/test_violet_live_blue_source.py | 37 +++++++++ 2 files changed, 99 insertions(+), 21 deletions(-) diff --git a/prod/clean_arch/violet/live_blue_source.py b/prod/clean_arch/violet/live_blue_source.py index ec642fb7..261fcc1b 100644 --- a/prod/clean_arch/violet/live_blue_source.py +++ b/prod/clean_arch/violet/live_blue_source.py @@ -33,6 +33,7 @@ test here (see test_signal_gen_params_match_blue_engine_kwargs / the mc_scale fo from __future__ import annotations import json +import logging import os as _os import sys from collections import deque @@ -65,6 +66,8 @@ from .decision_engine import SizingFactors from .live_factor_source import esof_score_from_features, posture_from_engine_snapshot from .live_factors import extract_live_sizing_factors +LOGGER = logging.getLogger("violet.live_blue_source") + # Hazelcast coordinates — MUST equal nautilus_event_trader.py:107-108 (BLUE's live # cluster). HZOBProvider opens its own connection to these, exactly as BLUE's _wire_obf. HZ_CLUSTER = _os.environ.get("HZ_CLUSTER", "dolphin") @@ -155,33 +158,59 @@ def _derive_mc_scale(payload: Any) -> float: return 0.5 if mc_orange else 1.0 +# Inverse-ACB neutral identity — used ONLY at cold start (no prior yet AND no fresh exf). +# In continuous operation exf is warmed, so the first call seeds the prior and this is +# never the steady-state value. +_BOOST_BETA_NEUTRAL = (1.0, 0.0) + + def _source_boost_beta( client: hazelcast.HazelcastClient, *, date_str: str, trade_direction: int, acb: Optional[AdaptiveCircuitBreaker] = None, + prior: Optional[tuple[float, float]] = None, ) -> tuple[float, float]: """Recompute (boost, beta) EXACTLY as BLUE's live trader does — NOT from acb_boost. - BLUE (nautilus_event_trader.py on_exf_update:4769 / rollover prewarm:2710): - acb = AdaptiveCircuitBreaker() # bare, no args (trader 578/585) - info = acb.get_dynamic_boost_from_hz( - date_str=today, - exf_snapshot=DOLPHIN_FEATURES['exf_latest'], - w750_velocity=latest_eigen_scan['w750_velocity'] or None, - direction=trade_direction, # NO ob_engine in the live path - ) - boost, beta = info['boost'], info['beta'] + WHAT THE ACB IS DOING, IN EFFECT (AdaptiveCircuitBreaker v6, "inverse" mode): + - ``boost`` = a DAILY position-size governor >= 1.0 driven by EXTERNAL FACTORS (ExF: + funding_btc/dvol_btc/fng/taker from DOLPHIN_FEATURES.exf_latest). When stress + signals fire, ``boost = 1 + 0.5*ln(1+signals)``; else 1.0. INVERSE = it leans size + UP under stress, it does not cut (adaptive_circuit_breaker.py:591-593). + - ``beta`` = regime sensitivity in {BETA_HIGH=0.8, BETA_LOW=0.2}, keyed on the w750 + eigenvalue-velocity (>= threshold → HIGH). It sets how strongly per-bar signal + strength amplifies size: regime_size_mult = base_boost*(1 + beta*strength^3)*mc_scale. + - Both become the engine's _day_base_boost / _day_beta via update_acb_boost + (esf_alpha_orchestrator.py:771-772), which then feed BLUE's SIZING directly: + _update_regime_size_mult (:898-909) = _day_base_boost * (1 + _day_beta*strength^3) + * _day_mc_scale. So the ACB IS in the sizing layer — confirmed, not incidental. - The published ``DOLPHIN_FEATURES['acb_boost']`` is a SEPARATE daily publish - (acb_processor_service) and is NOT what drives BLUE's sizing, so we never read it. - On stale exf (>12h) get_dynamic_boost_from_hz raises ValueError; BLUE logs - 'ACB Stale Data Fallback' and keeps the prior boost/beta — VIOLET has no prior, so it - returns the neutral identity (1.0, 0.0).""" + BLUE's LIVE path passes NO ob_engine (on_exf_update:4769 / rollover prewarm:2710), so the + ACB's OB Sub-4 macro-regime beta modulation (x1.25 stress / x0.85 calm, + adaptive_circuit_breaker.py:613-628) is DORMANT in live — only the NPZ backtest path + get_dynamic_boost_for_date applies it. Per operator recollection (2026-06-16), OB Sub-4 + beta modulation was found NON-PERFORMANT and deliberately removed/bypassed — so passing + no ob_engine is INTENTIONAL design, not an oversight. We pass no ob_engine to match BLUE + bit-for-bit either way. + # TODO_SOMEDAY: find the documentary confirmation of the OB Sub-4 non-performant bypass + # (operator: likely SYSTEM_BIBLE v7 lineage or a deep code comment / the in-progress + # INDEX_DOLPHINNG5_PREDICT doc). Behaviour is already bit-identical to live BLUE. + + The published DOLPHIN_FEATURES.acb_boost is acb_processor_service's SEPARATE daily value + and is never read here. + + STALE / MISSING exf: BLUE keeps the prior _day_base_boost/_day_beta (update_acb_boost + simply isn't called on the stale branch), so VIOLET KEEPS ``prior`` too. ``prior`` is + seeded by the first successful compute — exf is warmed in continuous BLUE operation, so + the steady state always has a prior; only a cold start with no prior AND no fresh exf + falls back to the neutral identity.""" + fallback = prior if prior is not None else _BOOST_BETA_NEUTRAL exf = _jsonish(_read_hz_map(client, "DOLPHIN_FEATURES", "exf_latest")) if not isinstance(exf, Mapping): - return 1.0, 0.0 + LOGGER.debug("ACB: no exf_latest — keeping prior boost/beta=%s", fallback) + return fallback eigen = _jsonish(_read_hz_map(client, "DOLPHIN_FEATURES", "latest_eigen_scan")) w750 = _coerce_float(eigen.get("w750_velocity"), None) if isinstance(eigen, Mapping) else None acb = acb or AdaptiveCircuitBreaker() @@ -190,13 +219,17 @@ def _source_boost_beta( date_str=date_str, exf_snapshot=dict(exf), w750_velocity=float(w750) if w750 else None, # 0.0 → None, matches BLUE - direction=trade_direction, + direction=trade_direction, # NO ob_engine — matches BLUE live ) - except ValueError: - return 1.0, 0.0 # ACB Stale Data Fallback (BLUE keeps prior; VIOLET neutral) - boost = _coerce_float(info.get("boost"), 1.0) or 1.0 - beta = _coerce_float(info.get("beta"), 0.0) or 0.0 - return max(0.0, boost), max(0.0, beta) + except ValueError as exc: + # BLUE logs "ACB Stale Data Fallback" and keeps the prior boost/beta. + LOGGER.info("ACB Stale Data Fallback (%s) — keeping prior boost/beta=%s", exc, fallback) + return fallback + boost = max(0.0, _coerce_float(info.get("boost"), 1.0) or 1.0) + beta = max(0.0, _coerce_float(info.get("beta"), 0.0) or 0.0) + LOGGER.debug("ACB live boost=%.6f beta=%.6f (signals=%s w750=%s dir=%s)", + boost, beta, info.get("signals"), w750, trade_direction) + return boost, beta def _scan_view(payload: Any) -> Mapping[str, Any]: @@ -349,6 +382,7 @@ def source_live_blue_sizing_factors( ob_engine: Optional[OBFeatureEngine] = None, bar_idx: int = 0, date_str: Optional[str] = None, + prior_boost_beta: Optional[tuple[float, float]] = None, ) -> LiveBlueSourceResult: """Read BLUE's live surfaces and RECONSTRUCT the factor plane the way BLUE computes it. @@ -401,6 +435,7 @@ def source_live_blue_sizing_factors( # acb_boost). Needs trade_direction, so computed after it. acb_boost, acb_beta = _source_boost_beta( client, date_str=today, trade_direction=trade_direction, acb=acb, + prior=prior_boost_beta, ) candidate_market = scan_history.market_data(selector.lookback) @@ -425,6 +460,12 @@ def source_live_blue_sizing_factors( "posture": posture, } factors = extract_live_sizing_factors(hz_snapshot=hz_snapshot) + LOGGER.debug( + "live BLUE plane: asset=%s posture=%s boost=%.4f beta=%.4f mc_scale=%.2f " + "esof=%s ob=(%s,%s) dc=%s dir=%s", + selected_asset, posture, acb_boost, acb_beta, mc_scale, esof_score, + ob_median_imbalance, ob_agreement_pct, dc_status, trade_direction, + ) return LiveBlueSourceResult( factors=factors, acb_boost=acb_boost, diff --git a/prod/clean_arch/violet/test_violet_live_blue_source.py b/prod/clean_arch/violet/test_violet_live_blue_source.py index 289fd911..120b4109 100644 --- a/prod/clean_arch/violet/test_violet_live_blue_source.py +++ b/prod/clean_arch/violet/test_violet_live_blue_source.py @@ -130,6 +130,43 @@ def test_boost_beta_neutral_on_stale_exf_valueerror(): assert _source_boost_beta(client, date_str="2026-06-16", trade_direction=-1) == (1.0, 0.0) +def test_boost_beta_keeps_prior_on_stale_exf(): + # BLUE keeps _day_base_boost/_day_beta on stale exf; VIOLET keeps the seeded prior. + stale = dict(_EXF, _staleness_s={"funding_btc": 999999.0}) + client = _FakeClient({"DOLPHIN_FEATURES": {"exf_latest": json.dumps(stale), + "latest_eigen_scan": json.dumps({"w750_velocity": 0.001})}}) + assert _source_boost_beta(client, date_str="2026-06-16", trade_direction=-1, + prior=(1.37, 0.8)) == (1.37, 0.8) + + +def test_boost_beta_keeps_prior_when_no_exf(): + client = _FakeClient({"DOLPHIN_FEATURES": {}}) + assert _source_boost_beta(client, date_str="2026-06-16", trade_direction=-1, + prior=(1.5, 0.2)) == (1.5, 0.2) + + +def test_boost_beta_cold_start_no_prior_is_neutral(): + # No prior AND no fresh exf (cold start) → neutral identity, never silently wrong. + client = _FakeClient({"DOLPHIN_FEATURES": {}}) + assert _source_boost_beta(client, date_str="2026-06-16", trade_direction=-1, prior=None) == (1.0, 0.0) + + +def test_source_live_blue_threads_prior_boost_beta_on_stale(): + stale = dict(_EXF, _staleness_s={"funding_btc": 999999.0}) + client = _FakeClient({ + "DOLPHIN_FEATURES": { + "exf_latest": json.dumps(stale), + "mc_forewarner_latest": json.dumps({"catastrophic_prob": 0.02, "envelope_score": 0.9}), + "latest_eigen_scan": json.dumps({"assets": ["BTCUSDT"], "asset_prices": [100.0], "w750_velocity": 0.001}), + }, + "DOLPHIN_STATE_BLUE": {"latest_nautilus": json.dumps({"posture": "APEX"})}, + }) + res = source_live_blue_sizing_factors( + client, assets=["BTCUSDT"], ob_engine=_FakeOBEngine(), prior_boost_beta=(1.42, 0.8), + ) + assert res.factors.boost == 1.42 and res.factors.beta == 0.8 + + def test_boost_beta_does_not_read_published_acb_boost(): # An acb_boost scalar is present but MUST be ignored (BLUE recomputes from exf). eigen = {"w750_velocity": 0.0012}