PINK Phase 0 and 1: VST WS confirmed plus AccountSnapshotV2 account core
This commit is contained in:
@@ -1,350 +0,0 @@
|
||||
"""Operator-facing bootstrap helpers for DITAv2.
|
||||
|
||||
This module keeps the wiring explicit:
|
||||
- control plane selection
|
||||
- Zinc plane selection
|
||||
- projection sink selection
|
||||
- venue adapter selection
|
||||
|
||||
The defaults stay safe and testable. Real shared-memory or live BingX wiring
|
||||
is only enabled when the caller opts in via arguments or environment.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
import asyncio
|
||||
import inspect
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from prod.bingx.config import BingxExecClientConfig
|
||||
from prod.bingx.config import BingxInstrumentProviderConfig
|
||||
from prod.bingx.enums import BingxEnvironment
|
||||
|
||||
from .bingx_venue import BingxVenueAdapter
|
||||
from .control import BackendMode
|
||||
from .control import ControlPlane
|
||||
from .control import ControlUpdate
|
||||
from .control import KernelControlSnapshot
|
||||
from .control import KernelMode
|
||||
from .control import KernelVerbosity
|
||||
from .control import build_control_plane
|
||||
from .mock_venue import MockVenueAdapter
|
||||
from .mock_venue import MockVenueScenario
|
||||
from .projection import HazelcastProjection
|
||||
from .projection import build_projection
|
||||
from .real_control_plane import RealZincControlPlane
|
||||
from .real_control_plane import RealZincUnavailable
|
||||
from .real_zinc_plane import RealZincPlane
|
||||
from .real_zinc_plane import RealZincUnavailable as RealZincPlaneUnavailable
|
||||
from .rust_backend import ExecutionKernel
|
||||
from .venue import VenueAdapter
|
||||
from .zinc_plane import InMemoryZincPlane
|
||||
from .zinc_plane import ZincPlane
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[3]
|
||||
load_dotenv(PROJECT_ROOT / ".env")
|
||||
|
||||
|
||||
class LauncherVenueMode(str, Enum):
|
||||
MOCK = "MOCK"
|
||||
BINGX = "BINGX"
|
||||
|
||||
|
||||
class LauncherZincMode(str, Enum):
|
||||
IN_MEMORY = "IN_MEMORY"
|
||||
REAL = "REAL"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DITAv2LauncherBundle:
|
||||
"""Concrete runtime components assembled by the launcher."""
|
||||
|
||||
kernel: ExecutionKernel
|
||||
control_plane: ControlPlane
|
||||
projection: HazelcastProjection
|
||||
zinc_plane: ZincPlane
|
||||
venue: VenueAdapter
|
||||
|
||||
def close(self) -> None:
|
||||
_maybe_close(self.venue)
|
||||
_maybe_close(self.zinc_plane)
|
||||
_maybe_close(self.control_plane)
|
||||
|
||||
|
||||
def _env_upper(name: str, default: str = "") -> str:
|
||||
return str(os.environ.get(name, default)).strip().upper()
|
||||
|
||||
|
||||
def _env_bool(name: str, default: bool = False) -> bool:
|
||||
raw = os.environ.get(name)
|
||||
if raw is None:
|
||||
return default
|
||||
return str(raw).strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def _resolve_control_mode() -> KernelMode | None:
|
||||
raw = _env_upper("DITA_V2_MODE", "")
|
||||
if raw == KernelMode.DEBUG.value:
|
||||
return KernelMode.DEBUG
|
||||
if raw == KernelMode.NORMAL.value:
|
||||
return KernelMode.NORMAL
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_control_verbosity() -> KernelVerbosity | None:
|
||||
raw = _env_upper("DITA_V2_VERBOSITY", "")
|
||||
if raw == KernelVerbosity.TRACE.value:
|
||||
return KernelVerbosity.TRACE
|
||||
if raw == KernelVerbosity.VERBOSE.value:
|
||||
return KernelVerbosity.VERBOSE
|
||||
if raw == KernelVerbosity.QUIET.value:
|
||||
return KernelVerbosity.QUIET
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_backend_mode() -> BackendMode | None:
|
||||
raw = _env_upper("DITA_V2_BACKEND_MODE", "")
|
||||
if raw == BackendMode.BINGX.value:
|
||||
return BackendMode.BINGX
|
||||
if raw == BackendMode.MOCK.value:
|
||||
return BackendMode.MOCK
|
||||
return None
|
||||
|
||||
|
||||
def _control_update_from_env() -> ControlUpdate | None:
|
||||
fields: dict[str, Any] = {}
|
||||
mode = _resolve_control_mode()
|
||||
if mode is not None:
|
||||
fields["mode"] = mode
|
||||
verbosity = _resolve_control_verbosity()
|
||||
if verbosity is not None:
|
||||
fields["verbosity"] = verbosity
|
||||
backend_mode = _resolve_backend_mode()
|
||||
if backend_mode is not None:
|
||||
fields["backend_mode"] = backend_mode
|
||||
raw = os.environ.get("DITA_V2_DEBUG_CLICKHOUSE")
|
||||
if raw is not None:
|
||||
fields["debug_clickhouse_enabled"] = _env_bool("DITA_V2_DEBUG_CLICKHOUSE", True)
|
||||
raw = os.environ.get("DITA_V2_TRACE_TRANSITIONS")
|
||||
if raw is not None:
|
||||
fields["trace_transitions"] = _env_bool("DITA_V2_TRACE_TRANSITIONS", False)
|
||||
raw = os.environ.get("DITA_V2_MIRROR_TO_HAZELCAST")
|
||||
if raw is not None:
|
||||
fields["mirror_to_hazelcast"] = _env_bool("DITA_V2_MIRROR_TO_HAZELCAST", True)
|
||||
raw = os.environ.get("DITA_V2_ACTIVE_SLOT_LIMIT")
|
||||
if raw is not None:
|
||||
try:
|
||||
fields["active_slot_limit"] = max(1, int(str(raw).strip()))
|
||||
except Exception:
|
||||
pass
|
||||
raw = os.environ.get("DITA_V2_RECONCILE_ON_RESTART")
|
||||
if raw is not None:
|
||||
fields["reconcile_on_restart"] = _env_bool("DITA_V2_RECONCILE_ON_RESTART", True)
|
||||
return ControlUpdate(**fields) if fields else None
|
||||
|
||||
|
||||
def _resolve_venue_mode(venue_mode: Optional[str] = None) -> LauncherVenueMode:
|
||||
raw = _env_upper("DITA_V2_VENUE", venue_mode or LauncherVenueMode.MOCK.value)
|
||||
if raw == LauncherVenueMode.BINGX.value:
|
||||
return LauncherVenueMode.BINGX
|
||||
return LauncherVenueMode.MOCK
|
||||
|
||||
|
||||
def _resolve_zinc_mode(zinc_mode: Optional[str] = None) -> LauncherZincMode:
|
||||
raw = _env_upper("DITA_V2_ZINC", zinc_mode or LauncherZincMode.IN_MEMORY.value)
|
||||
if raw == LauncherZincMode.REAL.value:
|
||||
return LauncherZincMode.REAL
|
||||
return LauncherZincMode.IN_MEMORY
|
||||
|
||||
|
||||
def _resolve_hazelcast_real(prefer_real_hazelcast: Optional[bool] = None) -> bool:
|
||||
if prefer_real_hazelcast is not None:
|
||||
return bool(prefer_real_hazelcast)
|
||||
raw = _env_upper("DITA_V2_HAZELCAST", "")
|
||||
return raw in {"REAL", "REAL_HZ", "HAZELCAST"}
|
||||
|
||||
|
||||
def build_bingx_exec_client_config(
|
||||
*,
|
||||
environment: Optional[BingxEnvironment] = None,
|
||||
allow_mainnet: Optional[bool] = None,
|
||||
recv_window_ms: Optional[int] = None,
|
||||
default_leverage: Optional[int] = None,
|
||||
exchange_leverage_cap: Optional[int] = None,
|
||||
prefer_websocket: Optional[bool] = None,
|
||||
sizing_mode: Optional[str] = None,
|
||||
) -> BingxExecClientConfig:
|
||||
"""Build the direct BingX config used by the DITAv2 launcher."""
|
||||
|
||||
resolved_environment = environment or (
|
||||
BingxEnvironment.LIVE if _env_upper("DOLPHIN_BINGX_ENV", "VST") == "LIVE" else BingxEnvironment.VST
|
||||
)
|
||||
resolved_allow_mainnet = _env_bool("DOLPHIN_BINGX_ALLOW_MAINNET", False) if allow_mainnet is None else bool(allow_mainnet)
|
||||
resolved_recv_window = int(os.environ.get("DOLPHIN_BINGX_RECV_WINDOW_MS", "5000")) if recv_window_ms is None else int(recv_window_ms)
|
||||
resolved_default_leverage = int(os.environ.get("DOLPHIN_BINGX_DEFAULT_LEVERAGE", "1")) if default_leverage is None else int(default_leverage)
|
||||
resolved_exchange_cap = int(os.environ.get("DOLPHIN_BINGX_EXCHANGE_LEVERAGE_CAP", "3")) if exchange_leverage_cap is None else int(exchange_leverage_cap)
|
||||
resolved_prefer_ws = _env_bool("DOLPHIN_BINGX_PREFER_WEBSOCKET", False) if prefer_websocket is None else bool(prefer_websocket)
|
||||
resolved_sizing_mode = sizing_mode or os.environ.get("DOLPHIN_BINGX_SIZING_MODE", "testnet")
|
||||
return BingxExecClientConfig(
|
||||
api_key=os.environ.get("BINGX_API_KEY"),
|
||||
secret_key=os.environ.get("BINGX_SECRET_KEY"),
|
||||
environment=resolved_environment,
|
||||
allow_mainnet=resolved_allow_mainnet,
|
||||
recv_window_ms=max(1, resolved_recv_window),
|
||||
default_leverage=max(1, resolved_default_leverage),
|
||||
exchange_leverage_cap=max(1, resolved_exchange_cap),
|
||||
prefer_websocket=resolved_prefer_ws,
|
||||
sizing_mode=resolved_sizing_mode,
|
||||
journal_strategy=os.environ.get("DOLPHIN_BINGX_JOURNAL_STRATEGY", "dita_v2"),
|
||||
journal_db=os.environ.get("DOLPHIN_BINGX_JOURNAL_DB", "dolphin_pink"),
|
||||
instrument_provider=BingxInstrumentProviderConfig(load_all=True),
|
||||
)
|
||||
|
||||
|
||||
def _build_control_plane(
|
||||
*,
|
||||
prefix: str,
|
||||
control_plane: Optional[ControlPlane] = None,
|
||||
) -> ControlPlane:
|
||||
plane = control_plane or build_control_plane(prefix=prefix)
|
||||
update = _control_update_from_env()
|
||||
if update is not None:
|
||||
plane.update(update)
|
||||
return plane
|
||||
|
||||
|
||||
def _build_zinc_plane(
|
||||
*,
|
||||
prefix: str,
|
||||
slot_count: int,
|
||||
zinc_mode: Optional[LauncherZincMode] = None,
|
||||
zinc_plane: Optional[ZincPlane] = None,
|
||||
) -> ZincPlane:
|
||||
if zinc_plane is not None:
|
||||
return zinc_plane
|
||||
resolved_mode = zinc_mode or _resolve_zinc_mode()
|
||||
if resolved_mode is LauncherZincMode.REAL:
|
||||
try:
|
||||
return RealZincPlane(prefix=prefix, slot_count=slot_count, create=True)
|
||||
except (RealZincPlaneUnavailable, RealZincUnavailable, Exception):
|
||||
pass
|
||||
return InMemoryZincPlane()
|
||||
|
||||
|
||||
def _build_venue(
|
||||
*,
|
||||
venue_mode: Optional[LauncherVenueMode] = None,
|
||||
mock_scenario: Optional[MockVenueScenario] = None,
|
||||
bingx_config: Optional[BingxExecClientConfig] = None,
|
||||
bingx_backend: Optional[Any] = None,
|
||||
venue: Optional[VenueAdapter] = None,
|
||||
) -> VenueAdapter:
|
||||
if venue is not None:
|
||||
return venue
|
||||
resolved_mode = venue_mode or _resolve_venue_mode()
|
||||
if resolved_mode is LauncherVenueMode.BINGX:
|
||||
backend = bingx_backend
|
||||
if backend is None:
|
||||
from prod.clean_arch.adapters.bingx_direct import BingxDirectExecutionAdapter
|
||||
|
||||
backend = BingxDirectExecutionAdapter(bingx_config or build_bingx_exec_client_config())
|
||||
return BingxVenueAdapter(backend=backend)
|
||||
return MockVenueAdapter(mock_scenario)
|
||||
|
||||
|
||||
def _maybe_close(obj: Any) -> None:
|
||||
for method_name in ("close", "disconnect"):
|
||||
method = getattr(obj, method_name, None)
|
||||
if method is None:
|
||||
continue
|
||||
try:
|
||||
result = method()
|
||||
except TypeError:
|
||||
continue
|
||||
if inspect.isawaitable(result):
|
||||
try:
|
||||
asyncio.run(result)
|
||||
except RuntimeError:
|
||||
pass
|
||||
break
|
||||
|
||||
|
||||
def build_launcher_bundle(
|
||||
*,
|
||||
max_slots: int = 10,
|
||||
prefix: Optional[str] = None,
|
||||
control_plane: Optional[ControlPlane] = None,
|
||||
projection: Optional[HazelcastProjection] = None,
|
||||
projection_client: Optional[Any] = None,
|
||||
zinc_plane: Optional[ZincPlane] = None,
|
||||
venue: Optional[VenueAdapter] = None,
|
||||
venue_mode: Optional[LauncherVenueMode | str] = None,
|
||||
zinc_mode: Optional[LauncherZincMode | str] = None,
|
||||
bingx_config: Optional[BingxExecClientConfig] = None,
|
||||
bingx_backend: Optional[Any] = None,
|
||||
mock_scenario: Optional[MockVenueScenario] = None,
|
||||
) -> DITAv2LauncherBundle:
|
||||
"""Build a fully wired DITAv2 runtime bundle.
|
||||
|
||||
Defaults stay non-destructive:
|
||||
- in-memory Zinc plane
|
||||
- in-process control plane
|
||||
- mock venue
|
||||
- callback projection unless a Hazelcast client is supplied
|
||||
"""
|
||||
|
||||
resolved_prefix = (prefix or os.environ.get("DITA_V2_PREFIX", "dita_v2")).strip() or "dita_v2"
|
||||
if isinstance(venue_mode, LauncherVenueMode):
|
||||
resolved_venue_mode = venue_mode
|
||||
elif isinstance(venue_mode, str):
|
||||
resolved_venue_mode = LauncherVenueMode(venue_mode.strip().upper())
|
||||
else:
|
||||
resolved_venue_mode = None
|
||||
if isinstance(zinc_mode, LauncherZincMode):
|
||||
resolved_zinc_mode = zinc_mode
|
||||
elif isinstance(zinc_mode, str):
|
||||
resolved_zinc_mode = LauncherZincMode(zinc_mode.strip().upper())
|
||||
else:
|
||||
resolved_zinc_mode = None
|
||||
|
||||
active_control_plane = _build_control_plane(prefix=resolved_prefix, control_plane=control_plane)
|
||||
control_snapshot = active_control_plane.read()
|
||||
active_projection = projection or build_projection(
|
||||
client=projection_client,
|
||||
prefer_real_hazelcast=_resolve_hazelcast_real(),
|
||||
control_snapshot=control_snapshot,
|
||||
)
|
||||
active_zinc_plane = _build_zinc_plane(
|
||||
prefix=resolved_prefix,
|
||||
slot_count=int(max_slots),
|
||||
zinc_mode=resolved_zinc_mode,
|
||||
zinc_plane=zinc_plane,
|
||||
)
|
||||
active_venue = _build_venue(
|
||||
venue_mode=resolved_venue_mode,
|
||||
mock_scenario=mock_scenario,
|
||||
bingx_config=bingx_config,
|
||||
bingx_backend=bingx_backend,
|
||||
venue=venue,
|
||||
)
|
||||
kernel = ExecutionKernel(
|
||||
max_slots=int(max_slots),
|
||||
control_plane=active_control_plane,
|
||||
venue=active_venue,
|
||||
projection=active_projection,
|
||||
projection_client=projection_client,
|
||||
zinc_plane=active_zinc_plane,
|
||||
)
|
||||
return DITAv2LauncherBundle(
|
||||
kernel=kernel,
|
||||
control_plane=active_control_plane,
|
||||
projection=active_projection,
|
||||
zinc_plane=active_zinc_plane,
|
||||
venue=active_venue,
|
||||
)
|
||||
Reference in New Issue
Block a user