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
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os as _os
|
import os as _os
|
||||||
import sys
|
import sys
|
||||||
from collections import deque
|
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_factor_source import esof_score_from_features, posture_from_engine_snapshot
|
||||||
from .live_factors import extract_live_sizing_factors
|
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
|
# 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.
|
# cluster). HZOBProvider opens its own connection to these, exactly as BLUE's _wire_obf.
|
||||||
HZ_CLUSTER = _os.environ.get("HZ_CLUSTER", "dolphin")
|
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
|
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(
|
def _source_boost_beta(
|
||||||
client: hazelcast.HazelcastClient,
|
client: hazelcast.HazelcastClient,
|
||||||
*,
|
*,
|
||||||
date_str: str,
|
date_str: str,
|
||||||
trade_direction: int,
|
trade_direction: int,
|
||||||
acb: Optional[AdaptiveCircuitBreaker] = None,
|
acb: Optional[AdaptiveCircuitBreaker] = None,
|
||||||
|
prior: Optional[tuple[float, float]] = None,
|
||||||
) -> tuple[float, float]:
|
) -> tuple[float, float]:
|
||||||
"""Recompute (boost, beta) EXACTLY as BLUE's live trader does — NOT from acb_boost.
|
"""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):
|
WHAT THE ACB IS DOING, IN EFFECT (AdaptiveCircuitBreaker v6, "inverse" mode):
|
||||||
acb = AdaptiveCircuitBreaker() # bare, no args (trader 578/585)
|
- ``boost`` = a DAILY position-size governor >= 1.0 driven by EXTERNAL FACTORS (ExF:
|
||||||
info = acb.get_dynamic_boost_from_hz(
|
funding_btc/dvol_btc/fng/taker from DOLPHIN_FEATURES.exf_latest). When stress
|
||||||
date_str=today,
|
signals fire, ``boost = 1 + 0.5*ln(1+signals)``; else 1.0. INVERSE = it leans size
|
||||||
exf_snapshot=DOLPHIN_FEATURES['exf_latest'],
|
UP under stress, it does not cut (adaptive_circuit_breaker.py:591-593).
|
||||||
w750_velocity=latest_eigen_scan['w750_velocity'] or None,
|
- ``beta`` = regime sensitivity in {BETA_HIGH=0.8, BETA_LOW=0.2}, keyed on the w750
|
||||||
direction=trade_direction, # NO ob_engine in the live path
|
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.
|
||||||
boost, beta = info['boost'], info['beta']
|
- 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
|
BLUE's LIVE path passes NO ob_engine (on_exf_update:4769 / rollover prewarm:2710), so the
|
||||||
(acb_processor_service) and is NOT what drives BLUE's sizing, so we never read it.
|
ACB's OB Sub-4 macro-regime beta modulation (x1.25 stress / x0.85 calm,
|
||||||
On stale exf (>12h) get_dynamic_boost_from_hz raises ValueError; BLUE logs
|
adaptive_circuit_breaker.py:613-628) is DORMANT in live — only the NPZ backtest path
|
||||||
'ACB Stale Data Fallback' and keeps the prior boost/beta — VIOLET has no prior, so it
|
get_dynamic_boost_for_date applies it. Per operator recollection (2026-06-16), OB Sub-4
|
||||||
returns the neutral identity (1.0, 0.0)."""
|
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"))
|
exf = _jsonish(_read_hz_map(client, "DOLPHIN_FEATURES", "exf_latest"))
|
||||||
if not isinstance(exf, Mapping):
|
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"))
|
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
|
w750 = _coerce_float(eigen.get("w750_velocity"), None) if isinstance(eigen, Mapping) else None
|
||||||
acb = acb or AdaptiveCircuitBreaker()
|
acb = acb or AdaptiveCircuitBreaker()
|
||||||
@@ -190,13 +219,17 @@ def _source_boost_beta(
|
|||||||
date_str=date_str,
|
date_str=date_str,
|
||||||
exf_snapshot=dict(exf),
|
exf_snapshot=dict(exf),
|
||||||
w750_velocity=float(w750) if w750 else None, # 0.0 → None, matches BLUE
|
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:
|
except ValueError as exc:
|
||||||
return 1.0, 0.0 # ACB Stale Data Fallback (BLUE keeps prior; VIOLET neutral)
|
# BLUE logs "ACB Stale Data Fallback" and keeps the prior boost/beta.
|
||||||
boost = _coerce_float(info.get("boost"), 1.0) or 1.0
|
LOGGER.info("ACB Stale Data Fallback (%s) — keeping prior boost/beta=%s", exc, fallback)
|
||||||
beta = _coerce_float(info.get("beta"), 0.0) or 0.0
|
return fallback
|
||||||
return max(0.0, boost), max(0.0, beta)
|
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]:
|
def _scan_view(payload: Any) -> Mapping[str, Any]:
|
||||||
@@ -349,6 +382,7 @@ def source_live_blue_sizing_factors(
|
|||||||
ob_engine: Optional[OBFeatureEngine] = None,
|
ob_engine: Optional[OBFeatureEngine] = None,
|
||||||
bar_idx: int = 0,
|
bar_idx: int = 0,
|
||||||
date_str: Optional[str] = None,
|
date_str: Optional[str] = None,
|
||||||
|
prior_boost_beta: Optional[tuple[float, float]] = None,
|
||||||
) -> LiveBlueSourceResult:
|
) -> LiveBlueSourceResult:
|
||||||
"""Read BLUE's live surfaces and RECONSTRUCT the factor plane the way BLUE computes it.
|
"""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). Needs trade_direction, so computed after it.
|
||||||
acb_boost, acb_beta = _source_boost_beta(
|
acb_boost, acb_beta = _source_boost_beta(
|
||||||
client, date_str=today, trade_direction=trade_direction, acb=acb,
|
client, date_str=today, trade_direction=trade_direction, acb=acb,
|
||||||
|
prior=prior_boost_beta,
|
||||||
)
|
)
|
||||||
|
|
||||||
candidate_market = scan_history.market_data(selector.lookback)
|
candidate_market = scan_history.market_data(selector.lookback)
|
||||||
@@ -425,6 +460,12 @@ def source_live_blue_sizing_factors(
|
|||||||
"posture": posture,
|
"posture": posture,
|
||||||
}
|
}
|
||||||
factors = extract_live_sizing_factors(hz_snapshot=hz_snapshot)
|
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(
|
return LiveBlueSourceResult(
|
||||||
factors=factors,
|
factors=factors,
|
||||||
acb_boost=acb_boost,
|
acb_boost=acb_boost,
|
||||||
|
|||||||
@@ -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)
|
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():
|
def test_boost_beta_does_not_read_published_acb_boost():
|
||||||
# An acb_boost scalar is present but MUST be ignored (BLUE recomputes from exf).
|
# An acb_boost scalar is present but MUST be ignored (BLUE recomputes from exf).
|
||||||
eigen = {"w750_velocity": 0.0012}
|
eigen = {"w750_velocity": 0.0012}
|
||||||
|
|||||||
Reference in New Issue
Block a user