refactor: telegram + portfolio in-process (drop shared MCP)
Each bot now manages its own notification + portfolio aggregation: * TelegramClient calls the public Bot API directly via httpx, reading CERBERO_BITE_TELEGRAM_BOT_TOKEN / CERBERO_BITE_TELEGRAM_CHAT_ID from env. No credentials → silent disabled mode. * PortfolioClient composes DeribitClient + HyperliquidClient + the new MacroClient.get_asset_price/eur_usd_rate to expose equity (EUR) and per-asset exposure as the bot's own slice (no cross-bot view). * mcp-telegram and mcp-portfolio removed from MCP_SERVICES / McpEndpoints and the cerbero-bite ping CLI; health_check no longer probes portfolio. Docs (02/04/06/07) and docker-compose updated to reflect the new architecture. 353/353 tests pass; ruff clean; mypy src clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
"""Wrapper around ``mcp-hyperliquid``.
|
||||
|
||||
Cerbero Bite consumes a single tool: ``get_funding_rate`` for ETH-PERP,
|
||||
used by entry filter §2.6 of ``docs/01-strategy-rules.md`` (cap on the
|
||||
absolute annualised funding rate).
|
||||
Cerbero Bite consumes:
|
||||
|
||||
* ``get_funding_rate`` — entry filter §2.6 cap on absolute annualised
|
||||
funding rate (``docs/01-strategy-rules.md``).
|
||||
* ``get_account_summary`` and ``get_positions`` — feed the in-process
|
||||
portfolio aggregator (equity + ETH/BTC exposure on the perp side).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from cerbero_bite.clients._base import HttpToolClient
|
||||
from cerbero_bite.clients._exceptions import McpDataAnomalyError
|
||||
@@ -47,3 +51,19 @@ class HyperliquidClient:
|
||||
tool="get_funding_rate",
|
||||
)
|
||||
return Decimal(str(rate)) * Decimal(HOURLY_FUNDING_PERIODS_PER_YEAR)
|
||||
|
||||
async def get_account_summary(self) -> dict[str, Any]:
|
||||
"""Account equity and balances (USD)."""
|
||||
raw: Any = await self._http.call("get_account_summary", {})
|
||||
return raw if isinstance(raw, dict) else {}
|
||||
|
||||
async def get_positions(self) -> list[dict[str, Any]]:
|
||||
"""Open perp positions (list of dicts)."""
|
||||
raw: Any = await self._http.call("get_positions", {})
|
||||
if isinstance(raw, list):
|
||||
return raw
|
||||
if isinstance(raw, dict):
|
||||
inner = raw.get("positions")
|
||||
if isinstance(inner, list):
|
||||
return inner
|
||||
return []
|
||||
|
||||
Reference in New Issue
Block a user