500 lines
20 KiB
Python
500 lines
20 KiB
Python
|
|
"""
|
||
|
|
Unit tests for PINK namespace routing and isolation.
|
||
|
|
|
||
|
|
Validates:
|
||
|
|
- ch_writer ch_put_pink targets dolphin_pink
|
||
|
|
- journal _db_for_strategy routes pink -> dolphin_pink
|
||
|
|
- journal write_snapshot selects pink sink
|
||
|
|
- dolphin_actor ch_put mapping for pink
|
||
|
|
- No cross-contamination between BLUE/PRODGREEN/PINK
|
||
|
|
"""
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
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.parent / "nautilus_dolphin"))
|
||
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
|
|
|
||
|
|
|
||
|
|
class TestChWriterPink(unittest.TestCase):
|
||
|
|
"""Test ch_writer ch_put_pink targets dolphin_pink database."""
|
||
|
|
|
||
|
|
@patch("prod.ch_writer._CHWriter")
|
||
|
|
def test_ch_put_pink_targets_dolphin_pink(self, MockWriter):
|
||
|
|
mock_instance = MagicMock()
|
||
|
|
MockWriter.return_value = mock_instance
|
||
|
|
MockWriter.reset_mock()
|
||
|
|
|
||
|
|
# Re-import to pick up the mock
|
||
|
|
import importlib
|
||
|
|
import prod.ch_writer as ch_mod
|
||
|
|
importlib.reload(ch_mod)
|
||
|
|
|
||
|
|
# After reload, the module-level singletons are recreated
|
||
|
|
# We need to verify ch_put_pink calls the right writer
|
||
|
|
# The simplest approach: verify the _writer_pink singleton has db="dolphin_pink"
|
||
|
|
|
||
|
|
def test_writer_pink_db_attribute(self):
|
||
|
|
"""Verify _writer_pink targets dolphin_pink database."""
|
||
|
|
from prod.ch_writer import _writer_pink
|
||
|
|
self.assertEqual(_writer_pink._db, "dolphin_pink")
|
||
|
|
|
||
|
|
def test_writer_prodgreen_unchanged(self):
|
||
|
|
"""Verify PRODGREEN writer is unchanged."""
|
||
|
|
from prod.ch_writer import _writer_prodgreen
|
||
|
|
self.assertEqual(_writer_prodgreen._db, "dolphin_prodgreen")
|
||
|
|
|
||
|
|
def test_writer_blue_unchanged(self):
|
||
|
|
"""Verify BLUE writer is unchanged."""
|
||
|
|
from prod.ch_writer import _writer
|
||
|
|
self.assertEqual(_writer._db, "dolphin")
|
||
|
|
|
||
|
|
def test_writer_green_unchanged(self):
|
||
|
|
"""Verify GREEN writer is unchanged."""
|
||
|
|
from prod.ch_writer import _writer_green
|
||
|
|
self.assertEqual(_writer_green._db, "dolphin_green")
|
||
|
|
|
||
|
|
def test_ch_put_pink_exists(self):
|
||
|
|
"""Verify ch_put_pink function exists and is callable."""
|
||
|
|
from prod.ch_writer import ch_put_pink
|
||
|
|
self.assertTrue(callable(ch_put_pink))
|
||
|
|
|
||
|
|
def test_ch_put_pink_calls_put(self):
|
||
|
|
"""Verify ch_put_pink delegates to _writer_pink.put."""
|
||
|
|
from prod.ch_writer import _writer_pink
|
||
|
|
with patch.object(_writer_pink, 'put') as mock_put:
|
||
|
|
from prod.ch_writer import ch_put_pink
|
||
|
|
ch_put_pink("test_table", {"key": "value"})
|
||
|
|
mock_put.assert_called_once_with("test_table", {"key": "value"})
|
||
|
|
|
||
|
|
|
||
|
|
class TestJournalRouting(unittest.TestCase):
|
||
|
|
"""Test bingx/journal.py strategy->DB routing."""
|
||
|
|
|
||
|
|
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_pink_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_prodgreen_unchanged(self):
|
||
|
|
from prod.bingx.journal import _db_for_strategy
|
||
|
|
self.assertEqual(_db_for_strategy("prodgreen"), "dolphin_prodgreen")
|
||
|
|
|
||
|
|
def test_db_for_strategy_green_unchanged(self):
|
||
|
|
from prod.bingx.journal import _db_for_strategy
|
||
|
|
self.assertEqual(_db_for_strategy("green"), "dolphin_green")
|
||
|
|
|
||
|
|
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_prodprefix_unchanged(self):
|
||
|
|
"""Existing prod* prefix fallback must still work for unknown prod names."""
|
||
|
|
from prod.bingx.journal import _db_for_strategy
|
||
|
|
self.assertEqual(_db_for_strategy("prodfoo"), "dolphin_prodgreen")
|
||
|
|
|
||
|
|
def test_db_for_strategy_unknown_default(self):
|
||
|
|
from prod.bingx.journal import _db_for_strategy
|
||
|
|
self.assertEqual(_db_for_strategy("unknown"), "dolphin")
|
||
|
|
|
||
|
|
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
|
||
|
|
sink = _STRATEGY_SINK_MAP["pink"]
|
||
|
|
self.assertTrue(callable(sink))
|
||
|
|
self.assertEqual(getattr(sink, "__name__", ""), "ch_put_pink")
|
||
|
|
|
||
|
|
|
||
|
|
class TestJournalSinkSelection(unittest.TestCase):
|
||
|
|
"""Test that write_snapshot selects the correct sink for pink strategy."""
|
||
|
|
|
||
|
|
@patch("prod.bingx.journal._STRATEGY_SINK_MAP")
|
||
|
|
def test_write_snapshot_uses_pink_sink(self, mock_map):
|
||
|
|
from prod.bingx.journal import write_snapshot, BingxJournalSnapshot
|
||
|
|
|
||
|
|
mock_sink = MagicMock()
|
||
|
|
mock_map.get.return_value = mock_sink
|
||
|
|
|
||
|
|
snapshot = BingxJournalSnapshot(
|
||
|
|
ts=1000000,
|
||
|
|
strategy="pink",
|
||
|
|
account_id="BINGX-vst",
|
||
|
|
ledger_authority="exchange",
|
||
|
|
payload={
|
||
|
|
"account": {"balances": [{"asset": "USDT", "total": 25000.0, "free": 25000.0}]},
|
||
|
|
"positions": {},
|
||
|
|
},
|
||
|
|
fingerprint="abc123",
|
||
|
|
)
|
||
|
|
write_snapshot(snapshot)
|
||
|
|
|
||
|
|
# Verify the sink map was consulted for "pink"
|
||
|
|
mock_map.get.assert_called_with("pink")
|
||
|
|
# Verify the pink sink was called (not prodgreen or green)
|
||
|
|
mock_sink.assert_called_once()
|
||
|
|
|
||
|
|
|
||
|
|
class TestExecutionConfigFields(unittest.TestCase):
|
||
|
|
"""Test that execution.py reads config-driven journal_strategy/journal_db."""
|
||
|
|
|
||
|
|
def test_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_config_defaults_none(self):
|
||
|
|
from prod.bingx.config import BingxExecClientConfig
|
||
|
|
config = BingxExecClientConfig()
|
||
|
|
self.assertIsNone(config.journal_strategy)
|
||
|
|
self.assertIsNone(config.journal_db)
|
||
|
|
|
||
|
|
|
||
|
|
class TestBuildActorConfigOverrides(unittest.TestCase):
|
||
|
|
"""Test launch_dolphin_live actor DB override behavior."""
|
||
|
|
|
||
|
|
def test_v7_journal_db_does_not_overwrite_adaptive_exit_shadow_db(self):
|
||
|
|
from prod.launch_dolphin_live import build_actor_config
|
||
|
|
|
||
|
|
with patch.dict(
|
||
|
|
os.environ,
|
||
|
|
{
|
||
|
|
"DOLPHIN_ADAPTIVE_EXIT_DB": "dolphin_pink",
|
||
|
|
"DOLPHIN_V7_JOURNAL_DB": "dolphin_pink_v7",
|
||
|
|
"DOLPHIN_FIXED_TP_PCT": "0.0020",
|
||
|
|
},
|
||
|
|
clear=False,
|
||
|
|
):
|
||
|
|
cfg = build_actor_config()
|
||
|
|
self.assertEqual(cfg["adaptive_exit"]["shadow_db"], "dolphin_pink")
|
||
|
|
self.assertEqual(cfg["v7_journal_db"], "dolphin_pink_v7")
|
||
|
|
self.assertEqual(cfg["engine"]["fixed_tp_pct"], 0.0020)
|
||
|
|
|
||
|
|
|
||
|
|
class TestPinkLauncherPhases(unittest.TestCase):
|
||
|
|
"""Test the standalone PINK phase gate helpers."""
|
||
|
|
|
||
|
|
def test_single_leg_is_default_phase(self):
|
||
|
|
from prod.launch_dolphin_pink import PinkPhase, _resolve_pink_phase, _resolve_pink_exit_leg_ratios
|
||
|
|
|
||
|
|
with patch.dict(os.environ, {}, clear=False):
|
||
|
|
self.assertEqual(_resolve_pink_phase(), PinkPhase.SINGLE_LEG)
|
||
|
|
self.assertEqual(_resolve_pink_exit_leg_ratios(PinkPhase.SINGLE_LEG), (1.0,))
|
||
|
|
|
||
|
|
def test_multi_exit_uses_configured_leg_ratios(self):
|
||
|
|
from prod.launch_dolphin_pink import PinkPhase, _resolve_pink_exit_leg_ratios, _resolve_pink_phase
|
||
|
|
|
||
|
|
with patch.dict(
|
||
|
|
os.environ,
|
||
|
|
{
|
||
|
|
"DOLPHIN_PINK_PHASE": "multi_exit",
|
||
|
|
"DOLPHIN_PINK_EXIT_LEG_RATIOS": "0.25,0.75,1.0",
|
||
|
|
},
|
||
|
|
clear=False,
|
||
|
|
):
|
||
|
|
self.assertEqual(_resolve_pink_phase(), PinkPhase.MULTI_EXIT)
|
||
|
|
self.assertEqual(_resolve_pink_exit_leg_ratios(PinkPhase.MULTI_EXIT), (0.25, 0.75, 1.0))
|
||
|
|
|
||
|
|
|
||
|
|
class TestCapitalSourcePriority(unittest.TestCase):
|
||
|
|
"""BingX/PINK must prefer the BingX journal over portfolio fallbacks."""
|
||
|
|
|
||
|
|
def test_bingx_journal_wins_over_portfolio_and_engine(self):
|
||
|
|
from nautilus_dolphin.nautilus.dolphin_actor import DolphinActor
|
||
|
|
|
||
|
|
class Dummy:
|
||
|
|
def __init__(self):
|
||
|
|
self.live_mode = True
|
||
|
|
self.dolphin_config = {"native_mode": False}
|
||
|
|
self._last_portfolio_capital = 777.0
|
||
|
|
self.engine = type("E", (), {"capital": 555.0})()
|
||
|
|
|
||
|
|
def _exec_venue_name(self):
|
||
|
|
return "BINGX"
|
||
|
|
|
||
|
|
def _get_bingx_ledger_capital(self):
|
||
|
|
return 1234.5
|
||
|
|
|
||
|
|
def _get_portfolio_capital(self):
|
||
|
|
return 888.0
|
||
|
|
|
||
|
|
dummy = Dummy()
|
||
|
|
capital = DolphinActor._authoritative_capital(dummy)
|
||
|
|
self.assertEqual(capital, 1234.5)
|
||
|
|
|
||
|
|
|
||
|
|
class TestDolphinActorPinkMapping(unittest.TestCase):
|
||
|
|
"""Test DolphinActor correctly maps pink strategy to pink sink."""
|
||
|
|
|
||
|
|
def test_actor_pink_strategy_uses_pink_sink(self):
|
||
|
|
"""Verify pink strategy in actor config selects ch_put_pink."""
|
||
|
|
# We can't fully instantiate DolphinActor (needs nautilus),
|
||
|
|
# but we can test the mapping logic directly.
|
||
|
|
from ch_writer import ch_put_pink, ch_put_prodgreen, ch_put_green
|
||
|
|
|
||
|
|
_STRATEGY_CH_SINK = {
|
||
|
|
'blue': None,
|
||
|
|
'green': ch_put_green,
|
||
|
|
'prodgreen': ch_put_prodgreen,
|
||
|
|
'pink': ch_put_pink,
|
||
|
|
}
|
||
|
|
|
||
|
|
self.assertIs(_STRATEGY_CH_SINK['pink'], ch_put_pink)
|
||
|
|
self.assertIs(_STRATEGY_CH_SINK['prodgreen'], ch_put_prodgreen)
|
||
|
|
self.assertIs(_STRATEGY_CH_SINK['green'], ch_put_green)
|
||
|
|
|
||
|
|
|
||
|
|
class TestPinkConfigFile(unittest.TestCase):
|
||
|
|
"""Test that pink.yml has correct namespace settings."""
|
||
|
|
|
||
|
|
def test_pink_config_exists(self):
|
||
|
|
config_path = Path("/mnt/dolphinng5_predict/prod/configs/pink.yml")
|
||
|
|
self.assertTrue(config_path.exists(), "pink.yml must exist")
|
||
|
|
|
||
|
|
def test_pink_config_has_correct_strategy(self):
|
||
|
|
import yaml
|
||
|
|
config_path = Path("/mnt/dolphinng5_predict/prod/configs/pink.yml")
|
||
|
|
with open(config_path) as f:
|
||
|
|
cfg = yaml.safe_load(f)
|
||
|
|
self.assertEqual(cfg["strategy_name"], "pink")
|
||
|
|
self.assertEqual(cfg["hazelcast"]["state_map"], "DOLPHIN_STATE_PINK")
|
||
|
|
self.assertEqual(cfg["hazelcast"]["imap_pnl"], "DOLPHIN_PNL_PINK")
|
||
|
|
self.assertEqual(cfg["adaptive_exit"]["shadow_db"], "dolphin_pink")
|
||
|
|
self.assertEqual(cfg["engine"]["fixed_tp_pct"], 0.0020)
|
||
|
|
|
||
|
|
|
||
|
|
class TestPinkLauncher(unittest.TestCase):
|
||
|
|
"""Test PINK launcher defaults."""
|
||
|
|
|
||
|
|
def test_pink_defaults(self):
|
||
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
|
|
from launch_dolphin_pink import PINK_DEFAULTS
|
||
|
|
self.assertEqual(PINK_DEFAULTS["strategy_name"], "pink")
|
||
|
|
self.assertEqual(PINK_DEFAULTS["state_map"], "DOLPHIN_STATE_PINK")
|
||
|
|
self.assertEqual(PINK_DEFAULTS["pnl_map"], "DOLPHIN_PNL_PINK")
|
||
|
|
self.assertEqual(PINK_DEFAULTS["trader_id"], "DOLPHIN-PINK-001")
|
||
|
|
self.assertEqual(PINK_DEFAULTS["journal_strategy"], "pink")
|
||
|
|
self.assertEqual(PINK_DEFAULTS["journal_db"], "dolphin_pink")
|
||
|
|
self.assertEqual(PINK_DEFAULTS["fixed_tp_pct"], 0.0020)
|
||
|
|
self.assertEqual(PINK_DEFAULTS["vol_p60_threshold"], -1000000000.0)
|
||
|
|
|
||
|
|
def test_apply_pink_namespace_env_forces_testnet_namespace(self):
|
||
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
|
|
from launch_dolphin_pink import _apply_pink_namespace_env
|
||
|
|
|
||
|
|
with patch.dict(
|
||
|
|
os.environ,
|
||
|
|
{
|
||
|
|
"DOLPHIN_BINGX_ENV": "LIVE",
|
||
|
|
"DOLPHIN_BINGX_ALLOW_MAINNET": "1",
|
||
|
|
"DOLPHIN_STATE_MAP": "DOLPHIN_STATE_PRODGREEN",
|
||
|
|
"DOLPHIN_PNL_MAP": "DOLPHIN_PNL_PRODGREEN",
|
||
|
|
"DOLPHIN_STRATEGY_NAME": "prodgreen",
|
||
|
|
},
|
||
|
|
clear=False,
|
||
|
|
):
|
||
|
|
_apply_pink_namespace_env()
|
||
|
|
self.assertEqual(os.environ["DOLPHIN_BINGX_ENV"], "VST")
|
||
|
|
self.assertEqual(os.environ["DOLPHIN_BINGX_ALLOW_MAINNET"], "0")
|
||
|
|
self.assertEqual(os.environ["DOLPHIN_STRATEGY_NAME"], "pink")
|
||
|
|
self.assertEqual(os.environ["DOLPHIN_STATE_MAP"], "DOLPHIN_STATE_PINK")
|
||
|
|
self.assertEqual(os.environ["DOLPHIN_PNL_MAP"], "DOLPHIN_PNL_PINK")
|
||
|
|
self.assertEqual(os.environ["DOLPHIN_FIXED_TP_PCT"], "0.0020")
|
||
|
|
|
||
|
|
def test_apply_pink_actor_overrides_forces_alias_and_blue_sync_isolation(self):
|
||
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
|
|
from launch_dolphin_pink import _apply_pink_actor_overrides
|
||
|
|
|
||
|
|
actor_cfg = {
|
||
|
|
"strategy_name": "prodgreen",
|
||
|
|
"hazelcast": {
|
||
|
|
"state_map": "DOLPHIN_STATE_PRODGREEN",
|
||
|
|
"imap_pnl": "DOLPHIN_PNL_PRODGREEN",
|
||
|
|
"state_map_aliases": ["DOLPHIN_STATE_GREEN"],
|
||
|
|
"imap_pnl_aliases": ["DOLPHIN_PNL_GREEN"],
|
||
|
|
},
|
||
|
|
"adaptive_exit": {"shadow_db": "dolphin_prodgreen"},
|
||
|
|
"v7_journal_db": "dolphin_prodgreen",
|
||
|
|
"sync_bar_idx_from_blue": True,
|
||
|
|
}
|
||
|
|
updated = _apply_pink_actor_overrides(actor_cfg)
|
||
|
|
self.assertEqual(updated["strategy_name"], "pink")
|
||
|
|
self.assertEqual(updated["hazelcast"]["state_map"], "DOLPHIN_STATE_PINK")
|
||
|
|
self.assertEqual(updated["hazelcast"]["imap_pnl"], "DOLPHIN_PNL_PINK")
|
||
|
|
self.assertEqual(updated["hazelcast"]["state_map_aliases"], [])
|
||
|
|
self.assertEqual(updated["hazelcast"]["imap_pnl_aliases"], [])
|
||
|
|
self.assertEqual(updated["adaptive_exit"]["shadow_db"], "dolphin_pink")
|
||
|
|
self.assertEqual(updated["v7_journal_db"], "dolphin_pink")
|
||
|
|
self.assertEqual(updated["vol_p60_threshold"], -1000000000.0)
|
||
|
|
self.assertEqual(updated["paper_trade"]["vol_p60"], -1000000000.0)
|
||
|
|
self.assertFalse(updated["sync_bar_idx_from_blue"])
|
||
|
|
|
||
|
|
def test_apply_pink_actor_overrides_respects_env_vol_threshold(self):
|
||
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
|
|
from launch_dolphin_pink import _apply_pink_actor_overrides
|
||
|
|
|
||
|
|
with patch.dict(os.environ, {"DOLPHIN_PINK_VOL_P60_THRESHOLD": "0.00007000"}, clear=False):
|
||
|
|
updated = _apply_pink_actor_overrides({"hazelcast": {}, "adaptive_exit": {}})
|
||
|
|
self.assertEqual(updated["vol_p60_threshold"], 0.00007000)
|
||
|
|
|
||
|
|
|
||
|
|
class TestIsolationGuards(unittest.TestCase):
|
||
|
|
"""Verify PINK never aliases to BLUE namespaces."""
|
||
|
|
|
||
|
|
def test_pink_config_no_blue_maps(self):
|
||
|
|
import yaml
|
||
|
|
config_path = Path("/mnt/dolphinng5_predict/prod/configs/pink.yml")
|
||
|
|
with open(config_path) as f:
|
||
|
|
cfg = yaml.safe_load(f)
|
||
|
|
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)
|
||
|
|
|
||
|
|
def test_pink_aliases_empty(self):
|
||
|
|
import yaml
|
||
|
|
config_path = Path("/mnt/dolphinng5_predict/prod/configs/pink.yml")
|
||
|
|
with open(config_path) as f:
|
||
|
|
cfg = yaml.safe_load(f)
|
||
|
|
aliases = cfg["hazelcast"].get("state_map_aliases", [])
|
||
|
|
pnl_aliases = cfg["hazelcast"].get("imap_pnl_aliases", [])
|
||
|
|
self.assertEqual(aliases, [])
|
||
|
|
self.assertEqual(pnl_aliases, [])
|
||
|
|
|
||
|
|
|
||
|
|
class TestPinkClickHouseSchema(unittest.TestCase):
|
||
|
|
"""Test that PINK CH schema files exist."""
|
||
|
|
|
||
|
|
def test_schema_dir_exists(self):
|
||
|
|
schema_dir = Path("/mnt/dolphinng5_predict/prod/clickhouse/pink")
|
||
|
|
self.assertTrue(schema_dir.is_dir())
|
||
|
|
|
||
|
|
def test_pink_schema_files_include_namespace_tags(self):
|
||
|
|
schema_dir = Path("/mnt/dolphinng5_predict/prod/clickhouse/pink")
|
||
|
|
for file_name in [
|
||
|
|
"account_events.sql",
|
||
|
|
"trade_events.sql",
|
||
|
|
"v7_decision_events.sql",
|
||
|
|
"adaptive_exit_shadow.sql",
|
||
|
|
]:
|
||
|
|
text = (schema_dir / file_name).read_text()
|
||
|
|
self.assertIn("runtime_namespace", text)
|
||
|
|
self.assertIn("strategy_namespace", text)
|
||
|
|
self.assertIn("event_namespace", text)
|
||
|
|
self.assertIn("actor_name", text)
|
||
|
|
self.assertIn("exec_venue", text)
|
||
|
|
self.assertIn("data_venue", text)
|
||
|
|
|
||
|
|
|
||
|
|
class TestPinkRowTagging(unittest.TestCase):
|
||
|
|
"""Test PINK writes carry standalone namespace tags."""
|
||
|
|
|
||
|
|
def test_dolphin_actor_tagged_ch_put_injects_pink_namespace(self):
|
||
|
|
from nautilus_dolphin.nautilus.dolphin_actor import DolphinActor
|
||
|
|
|
||
|
|
actor = DolphinActor.__new__(DolphinActor)
|
||
|
|
actor._strategy_name = "pink"
|
||
|
|
actor._pink_row_tags = {
|
||
|
|
"runtime_namespace": "pink",
|
||
|
|
"strategy_namespace": "pink",
|
||
|
|
"event_namespace": "pink",
|
||
|
|
"actor_name": "DolphinActor",
|
||
|
|
"exec_venue": "BINGX",
|
||
|
|
"data_venue": "BINANCE",
|
||
|
|
}
|
||
|
|
actor._ch_put_base = MagicMock()
|
||
|
|
|
||
|
|
DolphinActor._pink_tagged_ch_put(actor, "trade_events", {"ts": 1, "strategy": "pink"})
|
||
|
|
|
||
|
|
actor._ch_put_base.assert_called_once()
|
||
|
|
table, row = actor._ch_put_base.call_args.args
|
||
|
|
self.assertEqual(table, "trade_events")
|
||
|
|
self.assertEqual(row["strategy"], "pink")
|
||
|
|
self.assertEqual(row["runtime_namespace"], "pink")
|
||
|
|
self.assertEqual(row["strategy_namespace"], "pink")
|
||
|
|
self.assertEqual(row["event_namespace"], "pink")
|
||
|
|
self.assertEqual(row["actor_name"], "DolphinActor")
|
||
|
|
self.assertEqual(row["exec_venue"], "BINGX")
|
||
|
|
self.assertEqual(row["data_venue"], "BINANCE")
|
||
|
|
|
||
|
|
def test_bingx_execution_client_tag_helper_returns_pink_tags(self):
|
||
|
|
from prod.bingx.execution import BingxExecutionClient
|
||
|
|
|
||
|
|
client = BingxExecutionClient.__new__(BingxExecutionClient)
|
||
|
|
client._journal_strategy = "pink"
|
||
|
|
tags = BingxExecutionClient._pink_observability_tags(client)
|
||
|
|
self.assertEqual(tags["runtime_namespace"], "pink")
|
||
|
|
self.assertEqual(tags["strategy_namespace"], "pink")
|
||
|
|
self.assertEqual(tags["event_namespace"], "pink")
|
||
|
|
self.assertEqual(tags["actor_name"], "BingxExecutionClient")
|
||
|
|
self.assertEqual(tags["exec_venue"], "BINGX")
|
||
|
|
self.assertEqual(tags["data_venue"], "BINGX")
|
||
|
|
|
||
|
|
def test_adaptive_exit_engine_tag_helper_returns_pink_tags(self):
|
||
|
|
from adaptive_exit.adaptive_exit_engine import AdaptiveExitEngine
|
||
|
|
|
||
|
|
engine = object.__new__(AdaptiveExitEngine)
|
||
|
|
engine._strategy_name = "pink"
|
||
|
|
tags = AdaptiveExitEngine._row_tags(engine)
|
||
|
|
self.assertEqual(tags["runtime_namespace"], "pink")
|
||
|
|
self.assertEqual(tags["strategy_namespace"], "pink")
|
||
|
|
self.assertEqual(tags["event_namespace"], "pink")
|
||
|
|
self.assertEqual(tags["actor_name"], "AdaptiveExitEngine")
|
||
|
|
self.assertEqual(tags["exec_venue"], "BINGX")
|
||
|
|
self.assertEqual(tags["data_venue"], "BINGX")
|
||
|
|
|
||
|
|
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 PINK schema file: {filename}",
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_schema_targets_dolphin_pink(self):
|
||
|
|
schema_dir = Path("/mnt/dolphinng5_predict/prod/clickhouse/pink")
|
||
|
|
for sql_file in schema_dir.glob("*.sql"):
|
||
|
|
content = sql_file.read_text()
|
||
|
|
self.assertIn(
|
||
|
|
"dolphin_pink", content,
|
||
|
|
f"{sql_file.name} must reference dolphin_pink database",
|
||
|
|
)
|
||
|
|
self.assertNotIn(
|
||
|
|
"dolphin_prodgreen", content,
|
||
|
|
f"{sql_file.name} must not reference dolphin_prodgreen",
|
||
|
|
)
|
||
|
|
self.assertNotIn(
|
||
|
|
"dolphin_green", content,
|
||
|
|
f"{sql_file.name} must not reference dolphin_green",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|