881bc8a1bf
- 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>
13 KiB
13 KiB
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 diclients/+core/+state/. È l'unico strato che può fare I/O e ha effetti collaterali.state/persistenza. Mai logica di business. Solo CRUD.config/caricamento distrategy.yaml, validazione, esposizione immutabile dei parametri.reporting/generazione di stringhe per Telegram. Niente logica di trading, solo formatting.safety/controlli trasversali (vedere07-risk-controls.md).
Decision orchestrator — sequenza tipo per "Lunedì 14:00 UTC"
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"
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_idUUID. Cerbero core ignora duplicati con lo stesso id.