Files
Cerbero-Bite/docs/02-architecture.md
T
Adriano 881bc8a1bf Phase 0: project skeleton
- pyproject.toml with uv, deps for runtime + gui + backtest + dev
- ruff/mypy strict config, pre-commit hooks for ruff/mypy/pytest
- src/cerbero_bite/ layout with empty modules ready for Phase 1+
- structlog JSONL logger with daily rotation
- click CLI with placeholder subcommands (status, start, kill-switch,
  gui, replay, config hash, audit verify)
- 6 smoke tests passing, mypy --strict clean, ruff clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 23:10:30 +02:00

265 lines
13 KiB
Markdown

# 02 — Architettura
## Vista a blocchi
```
┌─────────────────────────────────┐
│ ADRIANO (utente) │
└──────┬──────────────────────────┘
│ Telegram (report + conferma)
┌─────────────────────────────────────────────────────────────────┐
│ CERBERO BITE (rule engine) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ scheduler │ │ state store │ │ audit logger │ │
│ │ (APScheduler)│ │ (SQLite) │ │ (append-only) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ decision orchestrator (core/) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ entry │ │ sizing │ │ exit │ │ greeks │ │ │
│ │ │validator │ │ engine │ │ decision │ │ aggregator│ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │liquidity │ │ combo │ │ kelly │ │ │
│ │ │ gate │ │ builder │ │ recalib │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ MCP client wrappers (clients/) │ │
│ │ Deribit │ Hyperliquid │ Macro │ Sentiment │ Portfolio │ │
│ │ Memory │ Telegram │ Brain-Bridge │ │
│ └─────────────────────────────────────────────────────────┘ │
└────────────────────────┬────────────────────────────────────────┘
│ MCP JSON-RPC
┌───────────────────────────────┐ ┌───────────────────┐
│ MCP servers │ │ Cerbero core │
│ (esistenti in CerberoSuite) │───▶│ (esecuzione) │
└───────────────────────────────┘ └───────────────────┘
┌────────────────┐
│ Deribit │
│ API │
└────────────────┘
```
## Stack tecnologico
| Strato | Scelta | Motivazione |
|---|---|---|
| Linguaggio | Python 3.12+ | Coerente con `mcp_cerbero_brain` e l'ecosistema Cerbero |
| Async runtime | `asyncio` | Necessario per i client MCP |
| Scheduler | `APScheduler` | Cron-like + interval-based, in-process |
| Persistenza stato | SQLite (file singolo) | Zero deploy overhead, transazionale, sufficiente per ≤ 4 posizioni |
| Persistenza log | File `.jsonl` rotativi (gzip > 30g) | Audit-friendly, append-only, parseable |
| Validazione config | `pydantic` v2 | Schema a runtime su `strategy.yaml` |
| Test | `pytest` + `pytest-asyncio` + `hypothesis` (property-based) | TDD imposto |
| Type checking | `mypy --strict` | Disciplina, niente sorprese a runtime |
| Format/lint | `ruff` | Standard del progetto |
| Dependency manager | `uv` | Coerente con `mcp_cerbero_brain` |
| MCP client | SDK ufficiale `mcp` | Connessione ai server già configurati in `.mcp.json` |
| Notifiche | MCP `cerbero-telegram` | Riusa canale esistente |
| GUI | `streamlit` ≥ 1.40 + `plotly` | Dashboard locale, processo separato (vedi `11-gui-streamlit.md`) |
## Layout cartelle
```
Cerbero_Bite/
├── README.md
├── pyproject.toml
├── uv.lock
├── strategy.yaml # config golden
├── strategy.local.yaml.example # override locale (gitignored)
├── docs/ # questa documentazione
├── src/cerbero_bite/
│ ├── __init__.py
│ ├── __main__.py # entry point CLI
│ ├── core/ # algoritmi puri (no I/O)
│ │ ├── entry_validator.py
│ │ ├── sizing_engine.py
│ │ ├── exit_decision.py
│ │ ├── greeks_aggregator.py
│ │ ├── liquidity_gate.py
│ │ ├── combo_builder.py
│ │ └── kelly_recalibration.py
│ ├── clients/ # wrapper MCP
│ │ ├── deribit.py
│ │ ├── hyperliquid.py
│ │ ├── macro.py
│ │ ├── sentiment.py
│ │ ├── portfolio.py
│ │ ├── memory.py
│ │ ├── telegram.py
│ │ └── brain_bridge.py
│ ├── runtime/ # I/O, scheduling, orchestrazione
│ │ ├── scheduler.py
│ │ ├── orchestrator.py
│ │ ├── monitoring.py
│ │ └── alert_manager.py
│ ├── state/ # persistenza
│ │ ├── repository.py
│ │ ├── models.py # SQLAlchemy o dataclasses
│ │ └── migrations/
│ ├── config/ # caricamento e validazione yaml
│ │ ├── schema.py
│ │ └── loader.py
│ ├── reporting/ # report umani
│ │ ├── pre_trade.py
│ │ ├── post_trade.py
│ │ └── daily_digest.py
│ ├── gui/ # Streamlit dashboard (vedi 11-gui-streamlit.md)
│ │ ├── main.py
│ │ ├── pages/
│ │ ├── components/
│ │ └── data_layer.py
│ └── safety/ # kill switch, dead man, audit
│ ├── kill_switch.py
│ ├── dead_man.py
│ └── audit_log.py
├── tests/
│ ├── unit/ # test puri sui moduli core/
│ ├── integration/ # test con MCP fake
│ ├── golden/ # scenari deterministici di riferimento
│ └── fixtures/
├── scripts/
│ ├── reset_state.py
│ ├── replay_day.py # replay forensico di una giornata
│ └── recalibrate_kelly.py
└── data/
├── state.sqlite # gitignored
└── log/ # gitignored
```
## Principio di separazione
- **`core/`** contiene **funzioni pure**: input → output, niente I/O,
niente MCP, niente time. Testabili con dati statici. Sono il cuore
della strategia e devono restare riproducibili al byte.
- **`clients/`** contiene wrapper sui server MCP. Ogni client espone
una API tipizzata che usa internamente JSON-RPC. Mai logica di
business qui.
- **`runtime/`** orchestrazione: composizione di `clients/` + `core/` +
`state/`. È l'unico strato che può fare I/O e ha effetti collaterali.
- **`state/`** persistenza. Mai logica di business. Solo CRUD.
- **`config/`** caricamento di `strategy.yaml`, validazione, esposizione
immutabile dei parametri.
- **`reporting/`** generazione di stringhe per Telegram. Niente logica
di trading, solo formatting.
- **`safety/`** controlli trasversali (vedere `07-risk-controls.md`).
## Decision orchestrator — sequenza tipo per "Lunedì 14:00 UTC"
```python
async def evaluate_entry():
# 1. Stato sistema
if not safety.system_healthy(): return
if state.has_open_position(): return
# 2. Dati di mercato
spot = await deribit.get_index_price("ETH")
dvol = await deribit.get_dvol()
funding = await sentiment.get_funding_cross_exchange("ETH")
macro = await macro.get_calendar(days=18)
holdings = await portfolio.get_holdings()
# 3. Algoritmi puri
bias = entry_validator.compute_bias(spot, trend_30d, funding)
if bias == "no_entry": return log_and_skip()
capital = await portfolio.get_capital()
chain = await deribit.get_options_chain("ETH", dte_target=18)
short_strike, long_strike = combo_builder.select_strikes(
chain, bias, spot, config
)
if short_strike is None: return log_and_skip("no_strike")
if not liquidity_gate.passes(short_strike, long_strike, deribit_book):
return log_and_skip("illiquid")
n = sizing_engine.compute_contracts(
capital, max_loss_per_contract, dvol, config
)
if n < 1: return log_and_skip("undersize")
proposal = combo_builder.build(short_strike, long_strike, n, mid_price)
# 4. Conferma
await telegram.send(reporting.pre_trade(proposal))
confirmation = await wait_user_confirmation(timeout="60min")
if not confirmation.accepted: return log_and_skip("rejected")
# 5. Esecuzione via Cerbero core
instruction = combo_builder.to_cerbero_instruction(proposal)
await memory.push_user_instruction(instruction, source="cerbero-bite")
# 6. Stato
state.create_position(proposal, dvol_at_entry=dvol)
audit_logger.log("ENTRY_PROPOSED", proposal)
```
## Sequenza tipo per "monitoring 12h"
```python
async def evaluate_open_positions():
if not safety.system_healthy(): return
for position in state.list_open_positions():
spot = await deribit.get_index_price("ETH")
dvol = await deribit.get_dvol()
mark = await deribit.get_combo_mark(position.legs)
delta_short = await deribit.get_instrument(position.short_leg).delta
decision = exit_decision.evaluate(
position=position,
spot=spot,
dvol_now=dvol,
mark_now=mark,
delta_short_now=delta_short,
now=datetime.now(timezone.utc),
config=config,
)
if decision.action == "HOLD":
audit_logger.log("HOLD", position.id, decision.reason)
continue
await telegram.send(reporting.exit_proposal(position, decision))
confirmation = await wait_user_confirmation(timeout="30min")
if not confirmation.accepted:
audit_logger.log("EXIT_DEFERRED", position.id)
continue
await memory.push_user_instruction(
combo_builder.close_instruction(position),
source="cerbero-bite"
)
state.mark_closing(position.id)
```
## Failure modes e retry
| Modalità | Risposta |
|---|---|
| MCP non risponde (timeout) | Retry esponenziale 3 tentativi (1s, 5s, 30s); poi alert + skip ciclo |
| MCP risponde con dato palesemente rotto (es. orderbook tutti a 0) | Skip ciclo, alert |
| Confidence Adriano scaduta | Skip apertura; le chiusure restano in coda con alta priorità |
| Stato SQLite corrotto | Kill switch attivato, alert manuale richiesto |
| Cerbero core non riceve l'istruzione | Engine si aspetta ack via `cerbero-memory.get_pending`; se entro 5 min ack assente, alert e blocco apertura |
Vedi `07-risk-controls.md` per il dettaglio completo dei kill switch.
## Concorrenza e idempotenza
- Una sola istanza dell'engine alla volta (file lock su `data/.lockfile`).
- Tutti i ticker di scheduler sono **idempotenti**: se il sistema crasha
durante un'apertura, al riavvio il monitoring ricostruisce lo stato
da SQLite + Cerbero core (`cerbero-memory.get_state`) e prosegue.
- Ogni proposta ha un `proposal_id` UUID. Cerbero core ignora
duplicati con lo stesso id.