PINK: TUI Hz fix + DC gate + ACB boost + 10 new tests (104/104 green)
TUI Hz fix: - hazelcast_projection.py: write_engine_snapshot now writes all NAUTILUS-era field aliases (trades_executed, current_leverage, open_positions as list, last_scan_number, last_vel_div, vol_ok, open_notional) so gear_rows/capital panel work with no TUI changes. - dolphin_status_pink.py: _normalize_eng_for_tui() safety-net translation added; render() uses it on every Hz read. DC gate (SYSTEM BIBLE §4.2, champion config): - pink_direct.py: _dc_contradicts() — 7-tick lookback, 0.75 bps threshold. Rising price (chg > 0.75 bps) blocks ENTER via dataclasses.replace(HOLD, DC_CONTRADICT). Price history deque initialized in connect(); dc_skip_contradicts=True enforced. ACB boost (SYSTEM BIBLE §10): - hazelcast_feed.py: fix wrong key "latest_acb" → "acb_boost" (DOLPHIN_FEATURES key written by acb_processor_service.py). - pink_direct.py: _last_acb_boost read from scan_payload["acb_boost"] first (scan bridge may embed it), then Hz direct fallback. Applied to intent.leverage via dataclasses.replace() after IntentEngine.plan(), capped at 3x. - _last_scan_number, _last_vel_div, _last_vol_ok tracked from scan_payload. OBF gate: NOT implemented. OBF shards (DOLPHIN_FEATURES_SHARD_*) require new Hz map connections + symbol routing. Gap documented; requires separate decision. Tests: TestDCGate (5) + TestNormalizeEngForTui (5) — 10 new, 104 total, all green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -274,9 +274,19 @@ class PinkDirectRuntime:
|
||||
_enter_frozen: bool = field(default=False, init=False, repr=False, compare=False)
|
||||
# Last known posture — carried into Hz writes for TUI/algo monitoring
|
||||
_last_posture: str = field(default="APEX", init=False, repr=False, compare=False)
|
||||
# Scan-derived fields for Hz writes and DC gate
|
||||
_last_scan_number: int = field(default=0, init=False, repr=False, compare=False)
|
||||
_last_vel_div: float = field(default=0.0, init=False, repr=False, compare=False)
|
||||
_last_vol_ok: bool = field(default=True, init=False, repr=False, compare=False)
|
||||
# Price history for Direction Confirmation (DC) gate — last 10 prices (5 needed for 7-bar)
|
||||
_price_history: Any = field(default=None, init=False, repr=False, compare=False)
|
||||
# ACB boost — multiplied into intent leverage (SYSTEM BIBLE §10); default=1.0 (no-op)
|
||||
_last_acb_boost: float = field(default=1.0, init=False, repr=False, compare=False)
|
||||
|
||||
async def connect(self, initial_capital: float = 25000.0) -> None:
|
||||
"""Connect data feed, venue, seed capital from exchange, start WS stream."""
|
||||
from collections import deque
|
||||
self._price_history = deque(maxlen=10)
|
||||
await self.data_feed.connect()
|
||||
venue = self.kernel.venue
|
||||
if hasattr(venue, "connect"):
|
||||
@@ -593,21 +603,57 @@ class PinkDirectRuntime:
|
||||
if isinstance(scan_payload.get("esof_payload"), dict)
|
||||
else None,
|
||||
)
|
||||
# Track posture for Hz writes
|
||||
# Track scan-derived fields for Hz writes and DC gate
|
||||
self._last_posture = str(scan_payload.get("posture") or "APEX")
|
||||
self._last_vel_div = float(scan_payload.get("vel_div") or scan_payload.get("velocity_divergence") or 0.0)
|
||||
self._last_vol_ok = bool(scan_payload.get("vol_ok", True))
|
||||
self._last_scan_number = int(scan_payload.get("scan_number") or snapshot.scan_number or 0)
|
||||
# ACB boost — read from scan_payload (scan bridge may embed it) or Hz direct
|
||||
acb_data = scan_payload.get("acb_boost") or {}
|
||||
if isinstance(acb_data, dict) and "boost" in acb_data:
|
||||
self._last_acb_boost = max(0.1, float(acb_data.get("boost", 1.0)))
|
||||
else:
|
||||
# Fall back to Hz direct read (non-blocking — features_map is blocking proxy)
|
||||
try:
|
||||
feed = getattr(self.data_feed, "features_map", None)
|
||||
if feed is not None:
|
||||
raw = feed.get("acb_boost")
|
||||
if raw:
|
||||
import json as _json
|
||||
d = _json.loads(raw)
|
||||
self._last_acb_boost = max(0.1, float(d.get("boost", 1.0)))
|
||||
except Exception:
|
||||
pass # Hz read failure must never affect trading
|
||||
return dict(
|
||||
getattr(runtime, "latest_bundle_dict", {}) or bundle.as_dict()
|
||||
)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def _dc_contradicts(self, lookback: int = 7, min_bps: float = 0.75) -> bool:
|
||||
"""Direction Confirmation gate (SYSTEM BIBLE §4.2, champion config).
|
||||
|
||||
Returns True if the price over the last `lookback` ticks ROSE by ≥ min_bps bps
|
||||
(0.75 bps). A rising price contradicts a SHORT signal → block ENTER.
|
||||
|
||||
Champion: dc_skip_contradicts=True, dc_leverage_boost=1.0 (no boost).
|
||||
"""
|
||||
hist = self._price_history
|
||||
if hist is None or len(hist) < lookback + 1:
|
||||
return False # not enough history → NEUTRAL, allow entry
|
||||
p0 = hist[-lookback - 1]
|
||||
p1 = hist[-1]
|
||||
if p0 <= 0:
|
||||
return False
|
||||
chg_bps = (p1 - p0) / p0 * 10_000.0
|
||||
return chg_bps > min_bps # rising price → CONTRADICT → skip
|
||||
|
||||
def _hz_publish(self, slot_dict: dict, acc: dict) -> None:
|
||||
"""Fire-and-forget Hz write after any kernel state change.
|
||||
|
||||
Computes system leverage (our_leverage = notional/capital) for the Hz
|
||||
snapshot — this is the PINK/BLUE dual-leverage invariant: system leverage
|
||||
reflects real margin utilisation; exchange leverage (1-3x cap) is set at
|
||||
the BingX API level and never touches this path.
|
||||
snapshot — PINK/BLUE dual-leverage invariant: system leverage reflects real
|
||||
margin utilisation; exchange leverage (1-3x cap) is set at BingX API level.
|
||||
"""
|
||||
if self.hz_state_writer is None:
|
||||
return
|
||||
@@ -617,7 +663,12 @@ class PinkDirectRuntime:
|
||||
capital = float(acc.get("capital") or 0.0)
|
||||
our_leverage = (size * ep / capital) if capital > 1e-10 else 0.0
|
||||
self.hz_state_writer.write_engine_snapshot(
|
||||
slot_dict, acc, posture=self._last_posture, our_leverage=our_leverage
|
||||
slot_dict, acc,
|
||||
posture=self._last_posture,
|
||||
our_leverage=our_leverage,
|
||||
scan_number=self._last_scan_number,
|
||||
vel_div=self._last_vel_div,
|
||||
vol_ok=self._last_vol_ok,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -780,13 +831,25 @@ class PinkDirectRuntime:
|
||||
closed=False,
|
||||
)
|
||||
|
||||
# Price history for DC gate — update before decide() so current tick is included
|
||||
if self._price_history is not None and snapshot.price and snapshot.price > 0:
|
||||
self._price_history.append(float(snapshot.price))
|
||||
|
||||
context = DecisionContext(
|
||||
# E-provided available_capital when present (E rules); K-fallback otherwise.
|
||||
capital=float(acc.get("available_capital") or acc.get("capital", 0.0)),
|
||||
open_positions=int(acc.get("open_positions", 0)),
|
||||
trade_seq=int(acc.get("trade_seq", 0)),
|
||||
)
|
||||
# DC gate (Direction Confirmation, SYSTEM BIBLE §4.2):
|
||||
# Check BEFORE DecisionEngine so a CONTRADICT voids the ENTER without
|
||||
# touching the kernel. Champion params: 7-tick lookback, 0.75 bps threshold.
|
||||
# dc_skip_contradicts = True → rising price during short window = HOLD.
|
||||
dc_blocked = self._dc_contradicts()
|
||||
decision = self.decision_engine.decide(snapshot, context, legacy_position)
|
||||
if dc_blocked and decision.action == DecisionAction.ENTER:
|
||||
import dataclasses
|
||||
decision = dataclasses.replace(decision, action=DecisionAction.HOLD, reason="DC_CONTRADICT")
|
||||
self._emit("decision", decision=decision)
|
||||
|
||||
intent_context = IntentContext(
|
||||
@@ -797,6 +860,13 @@ class PinkDirectRuntime:
|
||||
plan = self.intent_engine.plan(decision, intent_context, legacy_position)
|
||||
intent = plan.intent
|
||||
|
||||
# ACB boost (SYSTEM BIBLE §10): multiply intent leverage by the current boost
|
||||
# factor from acb_processor_service. Capped at exchange_leverage_cap (3x).
|
||||
if self._last_acb_boost != 1.0 and intent is not None:
|
||||
import dataclasses as _dc
|
||||
boosted_lev = min(3.0, max(1.0, float(intent.leverage or 1.0) * self._last_acb_boost))
|
||||
intent = _dc.replace(intent, leverage=boosted_lev)
|
||||
|
||||
if decision.action in {DecisionAction.ENTER, DecisionAction.EXIT}:
|
||||
kernel_intent = _decision_to_kernel_intent(decision, intent, slot_id=0)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user