839285d75b
Cerbero MCP V2 ha spostato get_instruments/get_historical/get_indicators sul router unificato /mcp (rimossi dagli endpoint per-exchange) e ha rinominato get_technical_indicators in get_indicators. Il client chiamava ancora questi tool su /mcp-deribit -> 404 -> kill-switch armato, raccolta option-chain ferma e bias direzionale rotto. - mcp_endpoints: nuovo endpoint `unified` (CERBERO_BITE_MCP_UNIFIED_URL, default http://cerbero-mcp:9000/mcp) - deribit: i 3 tool dati passano dal client unificato con exchange="deribit", interval minuscolo (1D->1d) e parsing del nuovo shape (symbol + native.*); adx_14 usa get_indicators (indicators.adx.adx) - dependencies/gui: iniettano il client unificato in DeribitClient - docker-compose: default in-cluster per l'endpoint /mcp Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
186 lines
5.5 KiB
Python
186 lines
5.5 KiB
Python
"""Runtime dependency container.
|
|
|
|
Builds and wires together every long-lived object the engine needs:
|
|
HTTP clients, repository, audit log, kill switch, alert manager. The
|
|
:func:`build_runtime` factory returns a frozen :class:`RuntimeContext`
|
|
that the orchestrator and the cycle modules pass around — no global
|
|
state, no implicit singletons.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
|
|
import httpx
|
|
|
|
from cerbero_bite.clients._base import DEFAULT_BOT_TAG, HttpToolClient
|
|
from cerbero_bite.clients.deribit import DeribitClient
|
|
from cerbero_bite.clients.hyperliquid import HyperliquidClient
|
|
from cerbero_bite.clients.macro import MacroClient
|
|
from cerbero_bite.clients.portfolio import PortfolioClient
|
|
from cerbero_bite.clients.sentiment import SentimentClient
|
|
from cerbero_bite.clients.telegram import TelegramClient, load_telegram_credentials
|
|
from cerbero_bite.config.mcp_endpoints import McpEndpoints
|
|
from cerbero_bite.config.schema import StrategyConfig
|
|
from cerbero_bite.runtime.alert_manager import AlertManager
|
|
from cerbero_bite.safety.audit_log import AuditLog
|
|
from cerbero_bite.safety.kill_switch import KillSwitch
|
|
from cerbero_bite.state import (
|
|
Repository,
|
|
connect,
|
|
run_migrations,
|
|
transaction,
|
|
)
|
|
|
|
__all__ = ["RuntimeContext", "build_runtime"]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RuntimeContext:
|
|
"""Bag of every wired object used by the runtime."""
|
|
|
|
cfg: StrategyConfig
|
|
db_path: Path
|
|
audit_path: Path
|
|
|
|
repository: Repository
|
|
audit_log: AuditLog
|
|
kill_switch: KillSwitch
|
|
alert_manager: AlertManager
|
|
|
|
deribit: DeribitClient
|
|
macro: MacroClient
|
|
sentiment: SentimentClient
|
|
hyperliquid: HyperliquidClient
|
|
portfolio: PortfolioClient
|
|
telegram: TelegramClient
|
|
|
|
http_client: httpx.AsyncClient
|
|
|
|
clock: Callable[[], datetime]
|
|
|
|
async def aclose(self) -> None:
|
|
"""Close the shared HTTP client. Idempotent."""
|
|
await self.http_client.aclose()
|
|
|
|
|
|
def _utc_now() -> datetime:
|
|
return datetime.now(UTC)
|
|
|
|
|
|
def build_runtime(
|
|
*,
|
|
cfg: StrategyConfig,
|
|
endpoints: McpEndpoints,
|
|
token: str,
|
|
db_path: Path | str,
|
|
audit_path: Path | str,
|
|
bot_tag: str = DEFAULT_BOT_TAG,
|
|
timeout_s: float = 8.0,
|
|
retry_max: int = 3,
|
|
clock: Callable[[], datetime] | None = None,
|
|
) -> RuntimeContext:
|
|
"""Wire every dependency the runtime needs.
|
|
|
|
The SQLite database is migrated and the system_state singleton is
|
|
initialised eagerly so the orchestrator can assume both are
|
|
present.
|
|
"""
|
|
db_path = Path(db_path)
|
|
audit_path = Path(audit_path)
|
|
clk = clock or _utc_now
|
|
|
|
repository = Repository()
|
|
conn = connect(db_path)
|
|
try:
|
|
run_migrations(conn)
|
|
with transaction(conn):
|
|
repository.init_system_state(
|
|
conn, config_version=cfg.config_version, now=clk()
|
|
)
|
|
finally:
|
|
conn.close()
|
|
|
|
def _persist_audit_anchor(line_hash: str) -> None:
|
|
"""Mirror the latest audit chain hash into ``system_state``.
|
|
|
|
Best-effort: if SQLite is locked by another writer the audit
|
|
log itself is still consistent, the anchor will catch up on
|
|
the next append.
|
|
"""
|
|
anchor_conn = connect(db_path)
|
|
try:
|
|
with transaction(anchor_conn):
|
|
repository.set_last_audit_hash(anchor_conn, hex_hash=line_hash)
|
|
except Exception: # pragma: no cover — durability is best-effort
|
|
pass
|
|
finally:
|
|
anchor_conn.close()
|
|
|
|
audit_log = AuditLog(audit_path, on_append=_persist_audit_anchor)
|
|
kill_switch = KillSwitch(
|
|
connection_factory=lambda: connect(db_path),
|
|
repository=repository,
|
|
audit_log=audit_log,
|
|
clock=clk,
|
|
)
|
|
|
|
# Single long-lived AsyncClient shared by every wrapper. httpx pools
|
|
# connections per host so the snapshot stage of the entry cycle
|
|
# avoids paying TLS/TCP handshakes on each call.
|
|
http_client = httpx.AsyncClient(
|
|
timeout=httpx.Timeout(timeout_s),
|
|
limits=httpx.Limits(max_connections=20, max_keepalive_connections=10),
|
|
)
|
|
|
|
def _client(service: str) -> HttpToolClient:
|
|
return HttpToolClient(
|
|
service=service,
|
|
base_url=endpoints.for_service(service),
|
|
token=token,
|
|
bot_tag=bot_tag,
|
|
timeout_s=timeout_s,
|
|
retry_max=retry_max,
|
|
client=http_client,
|
|
)
|
|
|
|
bot_token, chat_id = load_telegram_credentials()
|
|
telegram = TelegramClient(
|
|
bot_token=bot_token,
|
|
chat_id=chat_id,
|
|
http_client=http_client,
|
|
timeout_s=timeout_s,
|
|
)
|
|
alert_manager = AlertManager(
|
|
telegram=telegram, audit_log=audit_log, kill_switch=kill_switch
|
|
)
|
|
|
|
deribit = DeribitClient(_client("deribit"), _client("unified"))
|
|
macro = MacroClient(_client("macro"))
|
|
sentiment = SentimentClient(_client("sentiment"))
|
|
hyperliquid = HyperliquidClient(_client("hyperliquid"))
|
|
portfolio = PortfolioClient(
|
|
deribit=deribit, hyperliquid=hyperliquid, macro=macro
|
|
)
|
|
|
|
return RuntimeContext(
|
|
cfg=cfg,
|
|
db_path=db_path,
|
|
audit_path=audit_path,
|
|
repository=repository,
|
|
audit_log=audit_log,
|
|
kill_switch=kill_switch,
|
|
alert_manager=alert_manager,
|
|
deribit=deribit,
|
|
macro=macro,
|
|
sentiment=sentiment,
|
|
hyperliquid=hyperliquid,
|
|
portfolio=portfolio,
|
|
telegram=telegram,
|
|
http_client=http_client,
|
|
clock=clk,
|
|
)
|