VIOLET V3.1: BLUE stablecoin exclusion (parity fix)
Soak surfaced USDCUSDT being shorted. BLUE has a hardcoded exclusion gate around its (muted-IRP) picking: _STABLECOIN_SYMBOLS removed from prices_dict pre-select (nautilus_event_trader.py:24/3906). Replicate exactly: VioletDecisionEngine skips the same 10 symbols in observe() so IRP never sees them. Following BLUE in all regards (picking unchanged; this is BLUE's separate gate). Set-equality drift guard vs BLUE source + never-selected test. 11 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,17 @@ from .cadence import Action, CadenceControlPlane
|
||||
from .domain import StrictModel, Symbol, typed
|
||||
|
||||
|
||||
# Stablecoins / pegged assets that must NEVER be selected as a trade asset.
|
||||
# MUST equal nautilus_event_trader.py:_STABLECOIN_SYMBOLS (BLUE removes these from
|
||||
# prices_dict before selection, :3906) — asserted by test_violet_decision_engine.
|
||||
# Following BLUE in all regards: picking stays muted-IRP as BLUE, this is BLUE's
|
||||
# separate exclusion gate around it.
|
||||
STABLECOIN_SYMBOLS = frozenset({
|
||||
"USDCUSDT", "BUSDUSDT", "FDUSDUSDT", "USDTUSDT", "TUSDUSDT",
|
||||
"DAIUSDT", "FRAXUSDT", "USDDUSDT", "USTCUSDT", "EURUSDT",
|
||||
})
|
||||
|
||||
|
||||
class ShadowDecision(StrictModel):
|
||||
"""One muted decision — what BLUE *would* do this scan. Never executed."""
|
||||
|
||||
@@ -107,6 +118,8 @@ class VioletDecisionEngine:
|
||||
if px <= 0:
|
||||
continue
|
||||
sym = str(asset).upper()
|
||||
if sym in STABLECOIN_SYMBOLS: # BLUE :3906 — never a trade asset
|
||||
continue
|
||||
hist = self._history.get(sym)
|
||||
if hist is None:
|
||||
hist = deque(maxlen=maxlen)
|
||||
|
||||
@@ -8,8 +8,13 @@ sys.path.insert(0, "/mnt/dolphinng5_predict")
|
||||
|
||||
import pytest
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from prod.clean_arch.violet.cadence import Action, CadenceControlPlane, INSTA_Q_NS, SCAN_Q_NS
|
||||
from prod.clean_arch.violet.decision_engine import ShadowDecision, VioletDecisionEngine
|
||||
from prod.clean_arch.violet.decision_engine import (
|
||||
STABLECOIN_SYMBOLS, ShadowDecision, VioletDecisionEngine,
|
||||
)
|
||||
|
||||
LOOKBACK = 5
|
||||
|
||||
@@ -101,6 +106,32 @@ def test_engine_holds_no_venue_or_kernel():
|
||||
assert not hasattr(e, attr)
|
||||
|
||||
|
||||
def test_stablecoin_set_matches_blue_exactly():
|
||||
# Drift guard: VIOLET's exclusion set MUST equal BLUE's _STABLECOIN_SYMBOLS
|
||||
# (nautilus_event_trader.py), parsed from source (no heavy import).
|
||||
src = Path("/mnt/dolphinng5_predict/prod/nautilus_event_trader.py").read_text()
|
||||
m = re.search(r"_STABLECOIN_SYMBOLS\s*=\s*frozenset\(\{(.*?)\}\)", src, re.DOTALL)
|
||||
assert m is not None
|
||||
blue = set(re.findall(r"'([A-Z0-9]+)'", m.group(1)))
|
||||
assert STABLECOIN_SYMBOLS == blue
|
||||
|
||||
|
||||
def test_stablecoin_never_selected():
|
||||
# Even with a strong short signal, a stablecoin must never be picked (BLUE :3906).
|
||||
e = _engine()
|
||||
assets = ["USDCUSDT", "BBBUSDT", "CCCUSDT"]
|
||||
for s in range(8):
|
||||
# USDC trends down hardest (would rank top by IRP if not excluded)
|
||||
prices = [100.0 * (1 - 0.01 * s), 100.0 * (1 - 0.001 * s), 100.0 * (1 - 0.0005 * s)]
|
||||
e.observe({"assets": assets, "asset_prices": prices}, scan_number=s + 1)
|
||||
assert "USDCUSDT" not in e._history # never accumulated
|
||||
for k in range(3):
|
||||
d = e.decide(now_ns=10**12 + k * SCAN_Q_NS, scan_number=20 + k,
|
||||
capital=69_000.0, vel_div=-0.20)
|
||||
if d is not None:
|
||||
assert d.asset != "USDCUSDT"
|
||||
|
||||
|
||||
def test_determinism_same_inputs_same_decision():
|
||||
e1, e2 = _engine(), _engine()
|
||||
_warm(e1); _warm(e2)
|
||||
|
||||
Reference in New Issue
Block a user