VIOLET V3.4c: ACB boost/beta — keep-prior on stale exf, decision logging, semantics doc
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user