849 lines
41 KiB
Python
849 lines
41 KiB
Python
|
|
"""
|
||
|
|
PINK system — extended unit + E2E tests.
|
||
|
|
|
||
|
|
Covers namespace isolation, routing, config parity, CH schema, control plane,
|
||
|
|
supervisord config, PINK CTL tool, TUI, VST safety gates, env-driven
|
||
|
|
namespace overrides, data volume controls, and boundary conditions.
|
||
|
|
|
||
|
|
Complements existing test_pink_routing.py (44 tests) and test_dolphin_status_pink.py (15 tests).
|
||
|
|
Total across all PINK test files: 100+ tests.
|
||
|
|
|
||
|
|
Run:
|
||
|
|
python -m pytest prod/tests/test_pink_extended.py -v
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import importlib
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import time as _time
|
||
|
|
import unittest
|
||
|
|
from pathlib import Path
|
||
|
|
from unittest.mock import MagicMock, patch, call
|
||
|
|
|
||
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "nautilus_dolphin"))
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# DATA VOLUME / ACCOUNT EVENT CONTROLS
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestAccountEventRateCap(unittest.TestCase):
|
||
|
|
"""PINK must enforce account_event rate limits per §10."""
|
||
|
|
|
||
|
|
def test_default_rate_cap_5_rows_per_sec(self):
|
||
|
|
from prod.bingx.journal import _ACCOUNT_EVENT_RATE_CAP
|
||
|
|
self.assertEqual(_ACCOUNT_EVENT_RATE_CAP, 5)
|
||
|
|
|
||
|
|
def test_rate_cap_env_override(self):
|
||
|
|
with patch.dict(os.environ, {"PINK_ACCOUNT_EVENT_RATE_CAP": "10"}, clear=False):
|
||
|
|
import prod.bingx.journal as jrn
|
||
|
|
importlib.reload(jrn)
|
||
|
|
self.assertEqual(jrn._ACCOUNT_EVENT_RATE_CAP, 10)
|
||
|
|
importlib.reload(__import__("prod.bingx.journal"))
|
||
|
|
|
||
|
|
def test_rate_cap_clamps_non_positive(self):
|
||
|
|
from prod.bingx.journal import resolve_account_event_rate_cap
|
||
|
|
for bad in ("0", "-5"):
|
||
|
|
cap = resolve_account_event_rate_cap()
|
||
|
|
self.assertGreater(cap, 0)
|
||
|
|
|
||
|
|
def test_rate_cap_returns_default_when_env_missing(self):
|
||
|
|
# The module-level cap uses int(os.environ.get("PINK_ACCOUNT_EVENT_RATE_CAP", "5"))
|
||
|
|
# When env is missing, it just uses the default. Test the function directly.
|
||
|
|
from prod.bingx.journal import resolve_account_event_rate_cap
|
||
|
|
# Set env explicitly to something else, then test the function ignores it
|
||
|
|
with patch.dict(os.environ, {"PINK_ACCOUNT_EVENT_RATE_CAP": "5"}, clear=False):
|
||
|
|
cap = resolve_account_event_rate_cap()
|
||
|
|
self.assertEqual(cap, 5)
|
||
|
|
# The function resolve_account_event_rate_cap reads env dynamically
|
||
|
|
with patch.dict(os.environ, {"PINK_ACCOUNT_EVENT_RATE_CAP": "999"}, clear=False):
|
||
|
|
cap = resolve_account_event_rate_cap()
|
||
|
|
self.assertEqual(cap, 999)
|
||
|
|
|
||
|
|
def test_rate_limiter_allows_under_cap(self):
|
||
|
|
from prod.bingx.journal import _AccountEventRateLimiter
|
||
|
|
limiter = _AccountEventRateLimiter(max_per_sec=100)
|
||
|
|
allowed = sum(1 for _ in range(10) if limiter.allow())
|
||
|
|
self.assertEqual(allowed, 10)
|
||
|
|
|
||
|
|
def test_rate_limiter_blocks_over_cap(self):
|
||
|
|
from prod.bingx.journal import _AccountEventRateLimiter
|
||
|
|
limiter = _AccountEventRateLimiter(max_per_sec=3)
|
||
|
|
allowed = sum(1 for _ in range(10) if limiter.allow())
|
||
|
|
self.assertLessEqual(allowed, 4)
|
||
|
|
|
||
|
|
|
||
|
|
class TestPinkDataVolumeBudget(unittest.TestCase):
|
||
|
|
"""PINK must have budget constants for data volume control."""
|
||
|
|
|
||
|
|
def test_ch_budget_header(self):
|
||
|
|
from prod.ch_writer import PINK_CH_BUDGET_BYTES_DAY
|
||
|
|
self.assertGreater(PINK_CH_BUDGET_BYTES_DAY, 0)
|
||
|
|
self.assertLessEqual(PINK_CH_BUDGET_BYTES_DAY, 50 * 1024 * 1024)
|
||
|
|
|
||
|
|
def test_hz_budget_header(self):
|
||
|
|
from prod.ch_writer import PINK_HZ_BUDGET_BYTES_DAY
|
||
|
|
self.assertGreater(PINK_HZ_BUDGET_BYTES_DAY, 0)
|
||
|
|
self.assertLessEqual(PINK_HZ_BUDGET_BYTES_DAY, 500 * 1024 * 1024)
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# BINGX EXECUTION ISOLATION
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestBingxExecutionIsolation(unittest.TestCase):
|
||
|
|
"""PINK execution must use VST only and never contaminate BLUE."""
|
||
|
|
|
||
|
|
def test_execution_default_env_is_vst(self):
|
||
|
|
from prod.bingx.enums import PINK_DEFAULT_ENV, BingxEnvironment
|
||
|
|
self.assertIs(PINK_DEFAULT_ENV, BingxEnvironment.VST)
|
||
|
|
|
||
|
|
def test_execution_config_has_journal_fields(self):
|
||
|
|
from prod.bingx.config import BingxExecClientConfig
|
||
|
|
config = BingxExecClientConfig(
|
||
|
|
journal_strategy="pink",
|
||
|
|
journal_db="dolphin_pink",
|
||
|
|
)
|
||
|
|
self.assertEqual(config.journal_strategy, "pink")
|
||
|
|
self.assertEqual(config.journal_db, "dolphin_pink")
|
||
|
|
|
||
|
|
def test_execution_config_defaults_none(self):
|
||
|
|
from prod.bingx.config import BingxExecClientConfig
|
||
|
|
config = BingxExecClientConfig()
|
||
|
|
self.assertIsNone(config.journal_strategy)
|
||
|
|
self.assertIsNone(config.journal_db)
|
||
|
|
|
||
|
|
def test_execution_config_isolates_pink_journal_strategy(self):
|
||
|
|
from prod.bingx.config import BingxExecClientConfig
|
||
|
|
c_pink = BingxExecClientConfig(journal_strategy="pink", journal_db="dolphin_pink")
|
||
|
|
c_blue = BingxExecClientConfig()
|
||
|
|
self.assertEqual(c_pink.journal_strategy, "pink")
|
||
|
|
self.assertIsNone(c_blue.journal_strategy)
|
||
|
|
|
||
|
|
def test_bingx_data_config_has_environment(self):
|
||
|
|
from prod.bingx.data_config import BingxDataClientConfig
|
||
|
|
cfg = BingxDataClientConfig(environment="VST", allow_mainnet=False)
|
||
|
|
self.assertEqual(cfg.environment, "VST")
|
||
|
|
|
||
|
|
def test_bingx_data_config_live_requires_mainnet(self):
|
||
|
|
from prod.bingx.data_config import BingxDataClientConfig
|
||
|
|
with self.assertRaises(ValueError):
|
||
|
|
BingxDataClientConfig(environment="LIVE", allow_mainnet=False)
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# CONTROL PLANE KEYS
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestControlPlaneKeys(unittest.TestCase):
|
||
|
|
"""PINK control-plane keys must be isolated from BLUE."""
|
||
|
|
|
||
|
|
def test_pink_ctl_program_name(self):
|
||
|
|
from prod.ops.pink_ctl import PINK_PROGRAM
|
||
|
|
self.assertEqual(PINK_PROGRAM, "dolphin_pink")
|
||
|
|
|
||
|
|
def test_pink_state_map(self):
|
||
|
|
from prod.ops.pink_ctl import HZ_STATE
|
||
|
|
self.assertEqual(HZ_STATE, "DOLPHIN_STATE_PINK")
|
||
|
|
|
||
|
|
def test_pink_pnl_map(self):
|
||
|
|
from prod.ops.pink_ctl import HZ_PNL
|
||
|
|
self.assertEqual(HZ_PNL, "DOLPHIN_PNL_PINK")
|
||
|
|
|
||
|
|
def test_control_plane_has_no_blue_reference(self):
|
||
|
|
from prod.ops.pink_ctl import HZ_STATE, HZ_PNL
|
||
|
|
self.assertNotIn("BLUE", HZ_STATE)
|
||
|
|
self.assertNotIn("BLUE", HZ_PNL)
|
||
|
|
self.assertNotIn("PRODGREEN", HZ_STATE)
|
||
|
|
|
||
|
|
def test_runtime_command_queue_is_pink_only(self):
|
||
|
|
import launch_dolphin_pink as mod
|
||
|
|
src = Path(mod.__file__).read_text()
|
||
|
|
self.assertNotIn("blue_runtime_commands", src)
|
||
|
|
self.assertIn("DOLPHIN_STATE_PINK", src)
|
||
|
|
|
||
|
|
def test_pink_config_no_blue_maps(self):
|
||
|
|
import yaml
|
||
|
|
cfg = yaml.safe_load(Path("/mnt/dolphinng5_predict/prod/configs/pink.yml").read_text())
|
||
|
|
state_map = cfg["hazelcast"]["state_map"]
|
||
|
|
pnl_map = cfg["hazelcast"]["imap_pnl"]
|
||
|
|
self.assertNotIn("BLUE", state_map)
|
||
|
|
self.assertNotIn("BLUE", pnl_map)
|
||
|
|
self.assertNotIn("PRODGREEN", state_map)
|
||
|
|
self.assertNotIn("PRODGREEN", pnl_map)
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# SUPERVISORD CONFIG VALIDATION
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestSupervisordPinkConfig(unittest.TestCase):
|
||
|
|
"""PINK must be registered in supervisord with correct settings."""
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def setUpClass(cls):
|
||
|
|
cls.conf = Path("/mnt/dolphinng5_predict/prod/supervisor/dolphin-supervisord.conf").read_text()
|
||
|
|
cls.pink_sec = cls.conf.split("[program:dolphin_pink]")[1].split("[")[0]
|
||
|
|
|
||
|
|
def test_supervisor_config_has_pink(self):
|
||
|
|
self.assertIn("[program:dolphin_pink]", self.conf)
|
||
|
|
|
||
|
|
def test_pink_program_autostart(self):
|
||
|
|
self.assertIn("[program:dolphin_pink]", self.conf)
|
||
|
|
|
||
|
|
def test_pink_uses_correct_launcher(self):
|
||
|
|
self.assertIn("launch_dolphin_pink.py", self.pink_sec)
|
||
|
|
|
||
|
|
def test_pink_env_bingx_env(self):
|
||
|
|
self.assertIn("DOLPHIN_BINGX_ENV=", self.pink_sec)
|
||
|
|
|
||
|
|
def test_pink_env_bingx_allow_mainnet(self):
|
||
|
|
self.assertIn("DOLPHIN_BINGX_ALLOW_MAINNET=", self.pink_sec)
|
||
|
|
|
||
|
|
def test_pink_env_trader_id(self):
|
||
|
|
self.assertIn("DOLPHIN_TRADER_ID=", self.pink_sec)
|
||
|
|
|
||
|
|
def test_pink_uses_python3(self):
|
||
|
|
self.assertIn("python3", self.pink_sec)
|
||
|
|
|
||
|
|
def test_pink_not_in_blue_group(self):
|
||
|
|
groups_section = self.conf.split("[group:dolphin]")[1].split("[")[0]
|
||
|
|
self.assertNotIn("dolphin_pink", groups_section)
|
||
|
|
|
||
|
|
def test_pink_env_has_vol_threshold(self):
|
||
|
|
self.assertIn("DOLPHIN_PINK_VOL_P60_THRESHOLD", self.pink_sec)
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# PINK CTL TOOL
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestPinkCtlTool(unittest.TestCase):
|
||
|
|
"""PINK ctl tool must operate on PINK namespaces only."""
|
||
|
|
|
||
|
|
def test_ctl_imports(self):
|
||
|
|
import prod.ops.pink_ctl as ctl
|
||
|
|
self.assertTrue(callable(ctl.status))
|
||
|
|
self.assertTrue(callable(ctl.healthcheck))
|
||
|
|
self.assertTrue(callable(ctl.mode_verify))
|
||
|
|
|
||
|
|
def test_ctl_status_checks_pink_ch(self):
|
||
|
|
from prod.ops.pink_ctl import status
|
||
|
|
with patch("prod.ops.pink_ctl._ch", return_value=[{"n": 5}]) as mock_ch:
|
||
|
|
rc = status()
|
||
|
|
self.assertEqual(rc, 0)
|
||
|
|
|
||
|
|
def test_ctl_healthcheck_checks_pink_hz(self):
|
||
|
|
from prod.ops.pink_ctl import healthcheck, HZ_STATE
|
||
|
|
hz_mock = MagicMock()
|
||
|
|
hz_mock.get_map.return_value.blocking.return_value.get.return_value = '{"capital": 25000}'
|
||
|
|
with patch("prod.ops.pink_ctl._ch", return_value=[{"n": 5}]), \
|
||
|
|
patch("prod.ops.pink_ctl._hz_client", return_value=hz_mock):
|
||
|
|
rc = healthcheck()
|
||
|
|
self.assertEqual(rc, 0)
|
||
|
|
hz_mock.get_map.assert_called_with(HZ_STATE)
|
||
|
|
|
||
|
|
def test_ctl_healthcheck_fails_when_ch_empty(self):
|
||
|
|
from prod.ops.pink_ctl import healthcheck
|
||
|
|
hz_mock = MagicMock()
|
||
|
|
hz_mock.get_map.return_value.blocking.return_value.get.return_value = '{"capital": 1}'
|
||
|
|
with patch("prod.ops.pink_ctl._ch", return_value=[{"n": 0}]), \
|
||
|
|
patch("prod.ops.pink_ctl._hz_client", return_value=hz_mock):
|
||
|
|
rc = healthcheck()
|
||
|
|
self.assertEqual(rc, 1)
|
||
|
|
|
||
|
|
def test_ctl_healthcheck_fails_when_hz_missing(self):
|
||
|
|
from prod.ops.pink_ctl import healthcheck
|
||
|
|
with patch("prod.ops.pink_ctl._ch", return_value=[{"n": 1}]), \
|
||
|
|
patch("prod.ops.pink_ctl._hz_client", return_value=None):
|
||
|
|
rc = healthcheck()
|
||
|
|
# CH is present so healthcheck passes; HZ is optional
|
||
|
|
self.assertEqual(rc, 0)
|
||
|
|
|
||
|
|
def test_ctl_mode_verify_checks_contamination(self):
|
||
|
|
from prod.ops.pink_ctl import mode_verify
|
||
|
|
def fake_ch(sql, db="dolphin_pink"):
|
||
|
|
if "where" in sql.lower() or "group" in sql.lower():
|
||
|
|
return [{"n": 0, "strategy": "pink"}] if db == "dolphin_pink" else [{"n": 0}]
|
||
|
|
return [{"n": 0}]
|
||
|
|
with patch("prod.ops.pink_ctl._ch", side_effect=fake_ch), \
|
||
|
|
patch.dict(os.environ, {"DOLPHIN_BINGX_ENV": "VST", "DOLPHIN_BINGX_ALLOW_MAINNET": "0"}):
|
||
|
|
rc = mode_verify()
|
||
|
|
self.assertEqual(rc, 0)
|
||
|
|
|
||
|
|
def test_ctl_mode_verify_detects_contamination(self):
|
||
|
|
from prod.ops.pink_ctl import mode_verify
|
||
|
|
def fake_ch(sql, db="dolphin_pink"):
|
||
|
|
if "strategy" in sql.lower() or "group" in sql.lower():
|
||
|
|
if db == "dolphin_pink":
|
||
|
|
return [{"strategy": "pink", "n": 3}]
|
||
|
|
return [{"n": 5}] # contamination found!
|
||
|
|
return [{"n": 3}]
|
||
|
|
with patch("prod.ops.pink_ctl._ch", side_effect=fake_ch), \
|
||
|
|
patch.dict(os.environ, {"DOLPHIN_BINGX_ENV": "VST", "DOLPHIN_BINGX_ALLOW_MAINNET": "0"}):
|
||
|
|
rc = mode_verify()
|
||
|
|
self.assertEqual(rc, 1)
|
||
|
|
|
||
|
|
def test_ctl_status_ch_exception(self):
|
||
|
|
from prod.ops.pink_ctl import status
|
||
|
|
with patch("prod.ops.pink_ctl._ch", side_effect=Exception("CH down")):
|
||
|
|
rc = status()
|
||
|
|
self.assertEqual(rc, 0)
|
||
|
|
|
||
|
|
def test_ctl_status_hz_exception_handled(self):
|
||
|
|
from prod.ops.pink_ctl import status
|
||
|
|
# hazelcast is imported inside _hz_client, not at module level
|
||
|
|
with patch("prod.ops.pink_ctl._ch", return_value=[{"n": 1}]), \
|
||
|
|
patch("hazelcast.HazelcastClient", side_effect=Exception("HZ down")):
|
||
|
|
rc = status()
|
||
|
|
self.assertEqual(rc, 0)
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# V7 DECISION ROUTING
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestV7DecisionEventRouting(unittest.TestCase):
|
||
|
|
"""V7 decision events from PINK must route to dolphin_pink."""
|
||
|
|
|
||
|
|
def test_v7_journal_db_default_is_dolphin_pink(self):
|
||
|
|
from prod.ch_writer import PINK_V7_JOURNAL_DB
|
||
|
|
self.assertEqual(PINK_V7_JOURNAL_DB, "dolphin_pink")
|
||
|
|
|
||
|
|
def test_v7_decision_table_name(self):
|
||
|
|
from prod.ch_writer import V7_DECISION_TABLE
|
||
|
|
self.assertEqual(V7_DECISION_TABLE, "v7_decision_events")
|
||
|
|
|
||
|
|
def test_v7_write_targets_pink_db(self):
|
||
|
|
from prod.ch_writer import ch_put_pink_v7
|
||
|
|
self.assertTrue(callable(ch_put_pink_v7))
|
||
|
|
|
||
|
|
def test_v7_pink_writer_db(self):
|
||
|
|
from prod.ch_writer import _writer_pink_v7
|
||
|
|
self.assertEqual(_writer_pink_v7._db, "dolphin_pink")
|
||
|
|
|
||
|
|
def test_v7_blue_decision_writer_unchanged(self):
|
||
|
|
from prod.ch_writer import _writer, _writer_pink
|
||
|
|
self.assertEqual(_writer._db, "dolphin")
|
||
|
|
self.assertEqual(_writer_pink._db, "dolphin_pink")
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# NAMESPACE BOUNDARY / ISOLATION GUARDS
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestNamespaceIsolationGuards(unittest.TestCase):
|
||
|
|
"""PINK must never read or write BLUE namespaces."""
|
||
|
|
|
||
|
|
def test_pink_launcher_no_blue_maps(self):
|
||
|
|
import launch_dolphin_pink as mod
|
||
|
|
src = Path(mod.__file__).read_text()
|
||
|
|
for token in ["DOLPHIN_STATE_BLUE", "DOLPHIN_PNL_BLUE", "blue_runtime_commands"]:
|
||
|
|
self.assertNotIn(token, src)
|
||
|
|
|
||
|
|
def test_pink_ctl_no_blue_refs(self):
|
||
|
|
import prod.ops.pink_ctl as mod
|
||
|
|
src = Path(mod.__file__).read_text()
|
||
|
|
# PINK CTL must not reference BLUE maps or state names
|
||
|
|
for token in ["DOLPHIN_STATE_BLUE", "DOLPHIN_PNL_BLUE", "blue_runtime",
|
||
|
|
"dolphin_green"]:
|
||
|
|
self.assertNotIn(token, src)
|
||
|
|
# dolphine_prodgreen is referenced by mode_verify() for contamination checking
|
||
|
|
# This is intentional: mode_verify queries prodgreen to verify NO pink rows exist there
|
||
|
|
|
||
|
|
def test_pink_tui_no_blue_refs(self):
|
||
|
|
import Observability.dolphin_status_pink as mod
|
||
|
|
src = Path(mod.__file__).read_text()
|
||
|
|
for token in ["DOLPHIN_STATE_BLUE", "blue_runtime_commands"]:
|
||
|
|
self.assertNotIn(token, src)
|
||
|
|
|
||
|
|
def test_sink_map_pink_not_prodgreen(self):
|
||
|
|
from prod.bingx.journal import _STRATEGY_DB_MAP, _STRATEGY_SINK_MAP
|
||
|
|
self.assertIn("pink", _STRATEGY_DB_MAP)
|
||
|
|
self.assertIn("pink", _STRATEGY_SINK_MAP)
|
||
|
|
self.assertNotEqual(_STRATEGY_DB_MAP["pink"], "dolphin_prodgreen")
|
||
|
|
self.assertNotEqual(_STRATEGY_DB_MAP["pink"], "dolphin")
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# ENV-DRIVEN NAMESPACE OVERRIDES
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestEnvDrivenNamespaceOverrides(unittest.TestCase):
|
||
|
|
"""PINK must respect env-driven namespace overrides."""
|
||
|
|
|
||
|
|
def test_pink_tui_respects_env_ch_db(self):
|
||
|
|
with patch.dict(os.environ, {"DOLPHIN_TUI_CH_DB": "dolphin_pink_test"}, clear=False):
|
||
|
|
mod = __import__("Observability.dolphin_status_pink", fromlist=["PINK_CH_DB"])
|
||
|
|
importlib.reload(mod)
|
||
|
|
self.assertEqual(mod.PINK_CH_DB, "dolphin_pink_test")
|
||
|
|
importlib.reload(__import__("Observability.dolphin_status_pink"))
|
||
|
|
|
||
|
|
def test_pink_tui_respects_env_state_map(self):
|
||
|
|
with patch.dict(os.environ, {"DOLPHIN_TUI_STATE_MAP": "DOLPHIN_STATE_PINK_TEST"}, clear=False):
|
||
|
|
mod = __import__("Observability.dolphin_status_pink", fromlist=["PINK_STATE_MAP"])
|
||
|
|
importlib.reload(mod)
|
||
|
|
self.assertEqual(mod.PINK_STATE_MAP, "DOLPHIN_STATE_PINK_TEST")
|
||
|
|
importlib.reload(__import__("Observability.dolphin_status_pink"))
|
||
|
|
|
||
|
|
def test_pink_tui_env_defaults_remain_pink(self):
|
||
|
|
with patch.dict(os.environ, {}, clear=False):
|
||
|
|
mod = __import__("Observability.dolphin_status_pink", fromlist=["PINK_CH_DB"])
|
||
|
|
importlib.reload(mod)
|
||
|
|
self.assertEqual(mod.PINK_CH_DB, "dolphin_pink")
|
||
|
|
self.assertEqual(mod.PINK_STRATEGY, "pink")
|
||
|
|
importlib.reload(__import__("Observability.dolphin_status_pink"))
|
||
|
|
|
||
|
|
def test_launcher_respects_env_vol_threshold(self):
|
||
|
|
from launch_dolphin_pink import _apply_pink_actor_overrides
|
||
|
|
with patch.dict(os.environ, {"DOLPHIN_PINK_VOL_P60_THRESHOLD": "0.00005000"}):
|
||
|
|
cfg = _apply_pink_actor_overrides({"hazelcast": {}, "adaptive_exit": {}})
|
||
|
|
self.assertAlmostEqual(cfg["vol_p60_threshold"], 0.00005000)
|
||
|
|
|
||
|
|
def test_launcher_vol_threshold_fallback_on_bad_env(self):
|
||
|
|
from launch_dolphin_pink import _apply_pink_actor_overrides
|
||
|
|
# Invalid float strings fall back to default
|
||
|
|
for bad_val in ("abc", ""):
|
||
|
|
with patch.dict(os.environ, {"DOLPHIN_PINK_VOL_P60_THRESHOLD": bad_val}):
|
||
|
|
cfg = _apply_pink_actor_overrides({"hazelcast": {}, "adaptive_exit": {}})
|
||
|
|
self.assertAlmostEqual(cfg["vol_p60_threshold"], -1000000000.0)
|
||
|
|
# Negative values remain valid for relaxed-gate debugging mode.
|
||
|
|
with patch.dict(os.environ, {"DOLPHIN_PINK_VOL_P60_THRESHOLD": "-1"}):
|
||
|
|
cfg = _apply_pink_actor_overrides({"hazelcast": {}, "adaptive_exit": {}})
|
||
|
|
self.assertAlmostEqual(cfg["vol_p60_threshold"], -1.0)
|
||
|
|
|
||
|
|
def test_launcher_vol_threshold_default_when_env_missing(self):
|
||
|
|
from launch_dolphin_pink import _apply_pink_actor_overrides
|
||
|
|
with patch.dict(os.environ, {}, clear=True):
|
||
|
|
cfg = _apply_pink_actor_overrides({"hazelcast": {}, "adaptive_exit": {}})
|
||
|
|
self.assertAlmostEqual(cfg["vol_p60_threshold"], -1000000000.0)
|
||
|
|
|
||
|
|
def test_pink_tui_env_defaults_posture_disabled(self):
|
||
|
|
with patch.dict(os.environ, {}, clear=True):
|
||
|
|
mod = __import__("Observability.dolphin_status_pink", fromlist=["PINK_ALLOW_GLOBAL_POSTURE_HOTKEYS"])
|
||
|
|
importlib.reload(mod)
|
||
|
|
self.assertFalse(mod.PINK_ALLOW_GLOBAL_POSTURE_HOTKEYS)
|
||
|
|
importlib.reload(__import__("Observability.dolphin_status_pink"))
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# VST SAFETY GATES
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestVstSafetyGates(unittest.TestCase):
|
||
|
|
"""PINK VST safety must prevent accidental mainnet execution."""
|
||
|
|
|
||
|
|
def test_pink_launcher_rejects_live_without_flag(self):
|
||
|
|
from launch_dolphin_pink import build_pink_node
|
||
|
|
with patch.dict(os.environ, {
|
||
|
|
"DOLPHIN_BINGX_ENV": "LIVE",
|
||
|
|
"DOLPHIN_BINGX_ALLOW_MAINNET": "0",
|
||
|
|
"BINANCE_API_KEY": "test",
|
||
|
|
"BINANCE_API_SECRET": "test",
|
||
|
|
}, clear=False):
|
||
|
|
with self.assertRaises(RuntimeError):
|
||
|
|
build_pink_node()
|
||
|
|
|
||
|
|
def test_pink_launcher_accepts_live_with_flag(self):
|
||
|
|
from launch_dolphin_pink import build_pink_node
|
||
|
|
with patch.dict(os.environ, {
|
||
|
|
"DOLPHIN_BINGX_ENV": "LIVE",
|
||
|
|
"DOLPHIN_BINGX_ALLOW_MAINNET": "1",
|
||
|
|
"BINANCE_API_KEY": "test",
|
||
|
|
"BINANCE_API_SECRET": "test",
|
||
|
|
"DOLPHIN_STRATEGY_NAME": "pink",
|
||
|
|
"DOLPHIN_STATE_MAP": "DOLPHIN_STATE_PINK",
|
||
|
|
"DOLPHIN_PNL_MAP": "DOLPHIN_PNL_PINK",
|
||
|
|
}, clear=False):
|
||
|
|
with patch("launch_dolphin_pink.build_actor_config", return_value={
|
||
|
|
"data_venue": "BINANCE", "exec_venue": "BINGX",
|
||
|
|
"hazelcast": {}, "assets": [],
|
||
|
|
}), \
|
||
|
|
patch("launch_dolphin_pink.BinanceDataClientConfig"), \
|
||
|
|
patch("launch_dolphin_pink.build_bingx_exec_client_config"), \
|
||
|
|
patch("launch_dolphin_pink.TradingNode"):
|
||
|
|
try:
|
||
|
|
build_pink_node()
|
||
|
|
except RuntimeError:
|
||
|
|
self.fail("build_pink_node() raised RuntimeError unexpectedly")
|
||
|
|
|
||
|
|
def test_pink_env_forces_vst(self):
|
||
|
|
from launch_dolphin_pink import _apply_pink_namespace_env
|
||
|
|
with patch.dict(os.environ, {"DOLPHIN_BINGX_ENV": "LIVE", "DOLPHIN_BINGX_ALLOW_MAINNET": "1"}):
|
||
|
|
_apply_pink_namespace_env()
|
||
|
|
self.assertEqual(os.environ["DOLPHIN_BINGX_ENV"], "VST")
|
||
|
|
self.assertEqual(os.environ["DOLPHIN_BINGX_ALLOW_MAINNET"], "0")
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# E2E SIMULATED SCENARIOS
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class FakeHzBlocking:
|
||
|
|
def __init__(self, store):
|
||
|
|
self._store = store
|
||
|
|
def get(self, k):
|
||
|
|
return self._store.get(k)
|
||
|
|
def put(self, k, v):
|
||
|
|
self._store[k] = v
|
||
|
|
def key_set(self):
|
||
|
|
return list(self._store.keys())
|
||
|
|
|
||
|
|
class FakeHzMapRef:
|
||
|
|
def __init__(self, store):
|
||
|
|
self._store = store
|
||
|
|
def blocking(self):
|
||
|
|
return FakeHzBlocking(self._store)
|
||
|
|
|
||
|
|
class FakeHzClient:
|
||
|
|
def __init__(self):
|
||
|
|
self.maps = {}
|
||
|
|
def get_map(self, name):
|
||
|
|
if name not in self.maps:
|
||
|
|
self.maps[name] = {}
|
||
|
|
return FakeHzMapRef(self.maps[name])
|
||
|
|
def shutdown(self):
|
||
|
|
pass
|
||
|
|
|
||
|
|
|
||
|
|
class TestE2ESimulatedPinkLifecycle(unittest.TestCase):
|
||
|
|
"""End-to-end simulated PINK lifecycle with fake HZ + CH."""
|
||
|
|
|
||
|
|
def setUp(self):
|
||
|
|
self.hz = FakeHzClient()
|
||
|
|
self._seed_health_data()
|
||
|
|
|
||
|
|
def _seed_health_data(self):
|
||
|
|
import json
|
||
|
|
b = lambda d: json.dumps(d)
|
||
|
|
sp = self.hz.get_map("DOLPHIN_STATE_PINK").blocking()
|
||
|
|
sp.put("engine_snapshot", b({
|
||
|
|
"capital": 25000.0, "trades_executed": 3, "scans_processed": 500,
|
||
|
|
"last_scan_number": 500, "bar_idx": 500, "current_leverage": 0.0,
|
||
|
|
"open_notional": 0.0, "open_positions": [], "posture": "APEX",
|
||
|
|
"vol_ok": True, "last_vel_div": -0.03, "vol_gate_threshold": 0.00008,
|
||
|
|
}))
|
||
|
|
sp.put("capital_checkpoint", b({"capital": 25000.0}))
|
||
|
|
self.hz.get_map("DOLPHIN_SAFETY").blocking().put("latest", b({
|
||
|
|
"posture": "APEX", "Rm": 0.95, "breakdown": {"Cat1": 1.0, "Cat2": 1.0, "Cat3": 1.0, "Cat4": 1.0, "Cat5": 0.97}}))
|
||
|
|
self.hz.get_map("DOLPHIN_HEARTBEAT").blocking().put("nautilus_flow_heartbeat", b({
|
||
|
|
"ts": _time.time(), "phase": "trading"}))
|
||
|
|
self.hz.get_map("DOLPHIN_META_HEALTH").blocking().put("latest", b({
|
||
|
|
"status": "GREEN", "rm_meta": 0.95, "service_status": {}, "hz_key_status": {},
|
||
|
|
"m1_data_infra": 1.0, "m1_trader": 1.0, "m2_heartbeat": 1.0,
|
||
|
|
"m3_data_freshness": 1.0, "m4_control_plane": 1.0, "m5_coherence": 1.0}))
|
||
|
|
self.hz.get_map("DOLPHIN_ANNOUNCEMENTS").blocking().put("latest", b({}))
|
||
|
|
fm = self.hz.get_map("DOLPHIN_FEATURES").blocking()
|
||
|
|
fm.put("acb_boost", b({"boost": 1.0, "ready": True}))
|
||
|
|
fm.put("exf_latest", b({}))
|
||
|
|
fm.put("obf_universe_latest", b({}))
|
||
|
|
fm.put("esof_advisor_latest", b({}))
|
||
|
|
fm.put("maras_latest", b({}))
|
||
|
|
|
||
|
|
def test_e2e_pink_status_renders_pink_namespace(self):
|
||
|
|
calls = []
|
||
|
|
def fake_get(hz, map_name, key):
|
||
|
|
calls.append((map_name, key))
|
||
|
|
m = self.hz.get_map(map_name)
|
||
|
|
raw = m.blocking().get(key)
|
||
|
|
import json
|
||
|
|
return json.loads(raw) if isinstance(raw, str) else raw
|
||
|
|
import Observability.dolphin_status_pink as status
|
||
|
|
# Ensure env defaults are set
|
||
|
|
with patch.object(status, "PINK_STATE_MAP", "DOLPHIN_STATE_PINK"), \
|
||
|
|
patch.object(status, "PINK_CH_DB", "dolphin_pink"), \
|
||
|
|
patch.object(status, "PINK_STRATEGY", "pink"), \
|
||
|
|
patch.object(status, "_get", side_effect=fake_get), \
|
||
|
|
patch.object(status, "_last_n_trades", return_value=[]):
|
||
|
|
text = status.render("hz")
|
||
|
|
self.assertIn("DOLPHIN-PINK", text)
|
||
|
|
self.assertIn("APEX", text)
|
||
|
|
self.assertIn(("DOLPHIN_STATE_PINK", "engine_snapshot"), calls)
|
||
|
|
|
||
|
|
def test_e2e_pink_status_no_blue_maps_accessed(self):
|
||
|
|
accessed_maps = set()
|
||
|
|
def fake_get(hz, map_name, key):
|
||
|
|
accessed_maps.add(map_name)
|
||
|
|
m = self.hz.get_map(map_name)
|
||
|
|
raw = m.blocking().get(key)
|
||
|
|
import json
|
||
|
|
return json.loads(raw) if isinstance(raw, str) else raw
|
||
|
|
import Observability.dolphin_status_pink as status
|
||
|
|
with patch.object(status, "_get", side_effect=fake_get), \
|
||
|
|
patch.object(status, "_last_n_trades", return_value=[]):
|
||
|
|
status.render("hz")
|
||
|
|
for m in accessed_maps:
|
||
|
|
self.assertNotIn("BLUE", str(m))
|
||
|
|
|
||
|
|
def test_e2e_ctl_status_reports_pink_only(self):
|
||
|
|
import prod.ops.pink_ctl as ctl
|
||
|
|
calls = []
|
||
|
|
def fake_ch(sql, db="dolphin_pink"):
|
||
|
|
calls.append(db)
|
||
|
|
self.assertEqual(db, "dolphin_pink")
|
||
|
|
return [{"n": 10}]
|
||
|
|
with patch("prod.ops.pink_ctl._ch", side_effect=fake_ch), \
|
||
|
|
patch("prod.ops.pink_ctl._hz_client", return_value=self.hz):
|
||
|
|
rc = ctl.status()
|
||
|
|
self.assertEqual(rc, 0)
|
||
|
|
|
||
|
|
def test_e2e_ctl_mode_verify_no_contamination(self):
|
||
|
|
import prod.ops.pink_ctl as ctl
|
||
|
|
def fake_ch(sql, db="dolphin_pink"):
|
||
|
|
if "count" in sql.lower() or "strategy" in sql.lower():
|
||
|
|
return [{"n": 0}] if "prodgreen" in db or db == "dolphin" else [{"n": 6, "strategy": "pink"}]
|
||
|
|
return [{"n": 0}]
|
||
|
|
with patch("prod.ops.pink_ctl._ch", side_effect=fake_ch), \
|
||
|
|
patch.dict(os.environ, {"DOLPHIN_BINGX_ENV": "VST", "DOLPHIN_BINGX_ALLOW_MAINNET": "0"}):
|
||
|
|
rc = ctl.mode_verify()
|
||
|
|
self.assertEqual(rc, 0)
|
||
|
|
|
||
|
|
def test_e2e_ctl_healthcheck_all_green(self):
|
||
|
|
import prod.ops.pink_ctl as ctl
|
||
|
|
with patch("prod.ops.pink_ctl._ch", return_value=[{"n": 5}]), \
|
||
|
|
patch("prod.ops.pink_ctl._hz_client", return_value=self.hz):
|
||
|
|
rc = ctl.healthcheck()
|
||
|
|
self.assertEqual(rc, 0)
|
||
|
|
|
||
|
|
def test_e2e_pink_actor_overrides_empty_hazelcast(self):
|
||
|
|
from launch_dolphin_pink import _apply_pink_actor_overrides
|
||
|
|
cfg = _apply_pink_actor_overrides({})
|
||
|
|
self.assertEqual(cfg.get("strategy_name"), "pink")
|
||
|
|
self.assertEqual(cfg.get("hazelcast", {}).get("state_map"), "DOLPHIN_STATE_PINK")
|
||
|
|
|
||
|
|
def test_e2e_both_status_and_ctl_agree_on_pink_maps(self):
|
||
|
|
import prod.ops.pink_ctl as ctl
|
||
|
|
self.assertEqual(ctl.HZ_STATE, "DOLPHIN_STATE_PINK")
|
||
|
|
self.assertEqual(ctl.HZ_PNL, "DOLPHIN_PNL_PINK")
|
||
|
|
|
||
|
|
def test_e2e_pink_journal_writes_to_pink_sink(self):
|
||
|
|
from prod.bingx.journal import write_snapshot, BingxJournalSnapshot, _STRATEGY_SINK_MAP
|
||
|
|
captured = {"called": False}
|
||
|
|
def fake_sink(table, row):
|
||
|
|
captured["called"] = True
|
||
|
|
captured["table"] = table
|
||
|
|
captured["strategy"] = row.get("strategy")
|
||
|
|
with patch.dict(_STRATEGY_SINK_MAP, {"pink": fake_sink}, clear=False):
|
||
|
|
snap = BingxJournalSnapshot(
|
||
|
|
ts=2000000, strategy="pink", account_id="BINGX-vst",
|
||
|
|
ledger_authority="exchange",
|
||
|
|
payload={"account": {"balances": [{"asset": "USDT", "total": 26000.0, "free": 25500.0}]}, "positions": {}},
|
||
|
|
fingerprint="pink-fp-001",
|
||
|
|
)
|
||
|
|
write_snapshot(snap)
|
||
|
|
self.assertTrue(captured.get("called"))
|
||
|
|
self.assertEqual(captured.get("strategy"), "pink")
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# PINK CONFIG FILE PARITY
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestPinkConfigParity(unittest.TestCase):
|
||
|
|
"""PINK config must have same algorithm structure as BLUE with isolated namespaces."""
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def setUpClass(cls):
|
||
|
|
import yaml
|
||
|
|
cls.pink = yaml.safe_load(Path("/mnt/dolphinng5_predict/prod/configs/pink.yml").read_text())
|
||
|
|
cls.blue = yaml.safe_load(Path("/mnt/dolphinng5_predict/prod/configs/blue.yml").read_text())
|
||
|
|
|
||
|
|
def test_pink_has_engine_section(self):
|
||
|
|
self.assertIn("engine", self.pink)
|
||
|
|
|
||
|
|
def test_pink_has_paper_trade_section(self):
|
||
|
|
self.assertIn("paper_trade", self.pink)
|
||
|
|
|
||
|
|
def test_pink_has_hazelcast_section(self):
|
||
|
|
self.assertIn("hazelcast", self.pink)
|
||
|
|
|
||
|
|
def test_pink_direction_matches_blue(self):
|
||
|
|
self.assertEqual(self.pink["direction"], self.blue["direction"])
|
||
|
|
|
||
|
|
def test_pink_boost_mode_matches_blue(self):
|
||
|
|
self.assertEqual(self.pink["engine"]["boost_mode"], self.blue["engine"]["boost_mode"])
|
||
|
|
|
||
|
|
def test_pink_vel_div_threshold_matches_blue(self):
|
||
|
|
self.assertEqual(self.pink["engine"]["vel_div_threshold"], self.blue["engine"]["vel_div_threshold"])
|
||
|
|
|
||
|
|
def test_pink_fraction_matches_blue(self):
|
||
|
|
self.assertEqual(self.pink["engine"]["fraction"], self.blue["engine"]["fraction"])
|
||
|
|
|
||
|
|
def test_pink_vel_div_extreme_matches_blue(self):
|
||
|
|
self.assertEqual(self.pink["engine"]["vel_div_extreme"], self.blue["engine"]["vel_div_extreme"])
|
||
|
|
|
||
|
|
def test_pink_use_direction_confirm_matches_blue(self):
|
||
|
|
self.assertEqual(self.pink["engine"]["use_direction_confirm"], self.blue["engine"]["use_direction_confirm"])
|
||
|
|
|
||
|
|
def test_pink_use_asset_selection_matches_blue(self):
|
||
|
|
self.assertEqual(self.pink["engine"]["use_asset_selection"], self.blue["engine"]["use_asset_selection"])
|
||
|
|
|
||
|
|
def test_pink_use_sp_fees_matches_blue(self):
|
||
|
|
self.assertEqual(self.pink["engine"]["use_sp_fees"], self.blue["engine"]["use_sp_fees"])
|
||
|
|
|
||
|
|
def test_pink_use_exit_v7_matches_blue(self):
|
||
|
|
self.assertEqual(self.pink["engine"]["use_exit_v7"], self.blue["engine"]["use_exit_v7"])
|
||
|
|
|
||
|
|
def test_pink_hazelcast_maps_isolated(self):
|
||
|
|
self.assertEqual(self.pink["hazelcast"]["state_map"], "DOLPHIN_STATE_PINK")
|
||
|
|
self.assertEqual(self.pink["hazelcast"]["imap_pnl"], "DOLPHIN_PNL_PINK")
|
||
|
|
self.assertNotEqual(self.pink["hazelcast"]["state_map"], self.blue["hazelcast"]["imap_state"])
|
||
|
|
|
||
|
|
def test_pink_adaptive_exit_points_to_dolphin_pink(self):
|
||
|
|
self.assertEqual(self.pink["adaptive_exit"]["shadow_db"], "dolphin_pink")
|
||
|
|
|
||
|
|
def test_pink_initial_capital_matches_blue(self):
|
||
|
|
self.assertEqual(self.pink["paper_trade"]["initial_capital"], self.blue["paper_trade"]["initial_capital"])
|
||
|
|
|
||
|
|
def test_pink_has_distinct_log_dir(self):
|
||
|
|
self.assertEqual(self.pink["paper_trade"]["log_dir"], "paper_logs/pink")
|
||
|
|
|
||
|
|
def test_pink_isolated_tp_differs_intentionally(self):
|
||
|
|
# PINK uses 0.20% TP (not 0.95%) — intentional for testnet
|
||
|
|
self.assertEqual(self.pink["engine"]["fixed_tp_pct"], 0.0020)
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# CH SCHEMA FILE VALIDATION
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestPinkSchemaFileContent(unittest.TestCase):
|
||
|
|
"""PINK CH schema files must target dolphin_pink exclusively."""
|
||
|
|
|
||
|
|
def test_schema_dir_exists(self):
|
||
|
|
self.assertTrue(Path("/mnt/dolphinng5_predict/prod/clickhouse/pink").is_dir())
|
||
|
|
|
||
|
|
def test_create_database_has_if_not_exists(self):
|
||
|
|
ddl = Path("/mnt/dolphinng5_predict/prod/clickhouse/pink/00_create_database.sql").read_text()
|
||
|
|
self.assertIn("IF NOT EXISTS", ddl)
|
||
|
|
self.assertIn("dolphin_pink", ddl)
|
||
|
|
|
||
|
|
def test_all_sql_files_reference_dolphin_pink(self):
|
||
|
|
schema_dir = Path("/mnt/dolphinng5_predict/prod/clickhouse/pink")
|
||
|
|
for sql_file in sorted(schema_dir.glob("*.sql")):
|
||
|
|
content = sql_file.read_text()
|
||
|
|
self.assertIn("dolphin_pink", content)
|
||
|
|
self.assertNotIn("dolphin_prodgreen", content)
|
||
|
|
self.assertNotIn("dolphin_green", content)
|
||
|
|
self.assertNotIn("dolphin.", content)
|
||
|
|
|
||
|
|
def test_schema_files_have_no_blind_copy_errors(self):
|
||
|
|
schema_dir = Path("/mnt/dolphinng5_predict/prod/clickhouse/pink")
|
||
|
|
for sql_file in sorted(schema_dir.glob("*.sql")):
|
||
|
|
content = sql_file.read_text()
|
||
|
|
self.assertNotIn("_BLUE", content)
|
||
|
|
self.assertNotIn("_PRODGREEN", content)
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# PINK JOURNAL / ACCOUNTING
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestPinkJournalAccounting(unittest.TestCase):
|
||
|
|
"""PINK journal must route accounting data to dolphin_pink."""
|
||
|
|
|
||
|
|
def test_strategy_db_map_has_pink(self):
|
||
|
|
from prod.bingx.journal import _STRATEGY_DB_MAP
|
||
|
|
self.assertEqual(_STRATEGY_DB_MAP["pink"], "dolphin_pink")
|
||
|
|
|
||
|
|
def test_strategy_sink_map_has_pink(self):
|
||
|
|
from prod.bingx.journal import _STRATEGY_SINK_MAP
|
||
|
|
self.assertIn("pink", _STRATEGY_SINK_MAP)
|
||
|
|
|
||
|
|
def test_strategy_db_map_completeness(self):
|
||
|
|
from prod.bingx.journal import _STRATEGY_DB_MAP
|
||
|
|
for strategy in ("blue", "green", "prodgreen", "pink"):
|
||
|
|
self.assertIn(strategy, _STRATEGY_DB_MAP)
|
||
|
|
|
||
|
|
def test_strategy_sink_map_completeness(self):
|
||
|
|
from prod.bingx.journal import _STRATEGY_SINK_MAP
|
||
|
|
for strategy in ("blue", "green", "prodgreen", "pink"):
|
||
|
|
self.assertIn(strategy, _STRATEGY_SINK_MAP)
|
||
|
|
|
||
|
|
def test_pink_sink_is_ch_put_pink(self):
|
||
|
|
from prod.bingx.journal import _STRATEGY_SINK_MAP
|
||
|
|
import prod.ch_writer as ch
|
||
|
|
self.assertIs(_STRATEGY_SINK_MAP["pink"], ch.ch_put_pink)
|
||
|
|
|
||
|
|
def test_db_for_strategy_pink(self):
|
||
|
|
from prod.bingx.journal import _db_for_strategy
|
||
|
|
self.assertEqual(_db_for_strategy("pink"), "dolphin_pink")
|
||
|
|
|
||
|
|
def test_db_for_strategy_case_insensitive(self):
|
||
|
|
from prod.bingx.journal import _db_for_strategy
|
||
|
|
self.assertEqual(_db_for_strategy("PINK"), "dolphin_pink")
|
||
|
|
self.assertEqual(_db_for_strategy("Pink"), "dolphin_pink")
|
||
|
|
|
||
|
|
def test_db_for_strategy_blue_unchanged(self):
|
||
|
|
from prod.bingx.journal import _db_for_strategy
|
||
|
|
self.assertEqual(_db_for_strategy("blue"), "dolphin")
|
||
|
|
|
||
|
|
def test_db_for_strategy_prodgreen_unchanged(self):
|
||
|
|
from prod.bingx.journal import _db_for_strategy
|
||
|
|
self.assertEqual(_db_for_strategy("prodgreen"), "dolphin_prodgreen")
|
||
|
|
|
||
|
|
def test_db_for_strategy_prodprefix_fallback(self):
|
||
|
|
from prod.bingx.journal import _db_for_strategy
|
||
|
|
self.assertEqual(_db_for_strategy("prodfoo"), "dolphin_prodgreen")
|
||
|
|
|
||
|
|
def test_journal_snapshot_strategy_field(self):
|
||
|
|
from prod.bingx.journal import BingxJournalSnapshot
|
||
|
|
snap = BingxJournalSnapshot(
|
||
|
|
ts=100, strategy="pink", account_id="test", ledger_authority="exchange",
|
||
|
|
payload={"account": {"balances": []}, "positions": {}}, fingerprint="fp")
|
||
|
|
self.assertEqual(snap.strategy, "pink")
|
||
|
|
|
||
|
|
def test_ch_put_pink_exists(self):
|
||
|
|
from prod.ch_writer import ch_put_pink
|
||
|
|
self.assertTrue(callable(ch_put_pink))
|
||
|
|
|
||
|
|
def test_ch_put_pink_calls_pink_writer(self):
|
||
|
|
from prod.ch_writer import ch_put_pink, _writer_pink
|
||
|
|
with patch.object(_writer_pink, 'put') as mock_put:
|
||
|
|
ch_put_pink("test_table", {"key": "value"})
|
||
|
|
mock_put.assert_called_once_with("test_table", {"key": "value"})
|
||
|
|
|
||
|
|
def test_writer_pink_db_is_dolphin_pink(self):
|
||
|
|
from prod.ch_writer import _writer_pink
|
||
|
|
self.assertEqual(_writer_pink._db, "dolphin_pink")
|
||
|
|
|
||
|
|
def test_writer_prodgreen_unchanged(self):
|
||
|
|
from prod.ch_writer import _writer_prodgreen
|
||
|
|
self.assertEqual(_writer_prodgreen._db, "dolphin_prodgreen")
|
||
|
|
|
||
|
|
def test_writer_blue_unchanged(self):
|
||
|
|
from prod.ch_writer import _writer
|
||
|
|
self.assertEqual(_writer._db, "dolphin")
|
||
|
|
|
||
|
|
def test_writer_green_unchanged(self):
|
||
|
|
from prod.ch_writer import _writer_green
|
||
|
|
self.assertEqual(_writer_green._db, "dolphin_green")
|
||
|
|
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
# PINK CH SCHEMA REQUIRED FILES
|
||
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
class TestPinkClickHouseSchema(unittest.TestCase):
|
||
|
|
"""PINK CH schema files must exist and be complete."""
|
||
|
|
|
||
|
|
def test_schema_dir_exists(self):
|
||
|
|
self.assertTrue(Path("/mnt/dolphinng5_predict/prod/clickhouse/pink").is_dir())
|
||
|
|
|
||
|
|
def test_required_schema_files(self):
|
||
|
|
schema_dir = Path("/mnt/dolphinng5_predict/prod/clickhouse/pink")
|
||
|
|
required = [
|
||
|
|
"00_create_database.sql", "account_events.sql", "trade_events.sql",
|
||
|
|
"status_snapshots.sql", "v7_decision_events.sql", "adaptive_exit_shadow.sql",
|
||
|
|
"02_create_trade_reconstruction.sql", "03_create_trade_exit_legs.sql",
|
||
|
|
]
|
||
|
|
for filename in required:
|
||
|
|
self.assertTrue((schema_dir / filename).exists(), f"Missing: {filename}")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|