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>
194 lines
6.6 KiB
Markdown
194 lines
6.6 KiB
Markdown
# 07 — Risk Controls
|
||
|
||
Controlli di sicurezza trasversali che non sono parte della strategia
|
||
ma proteggono il sistema da bug, dati corrotti, fallimenti
|
||
infrastrutturali o decisioni umane fuori posto.
|
||
|
||
## Filosofia
|
||
|
||
- **Default deny**: in caso di dubbio, il sistema non fa nulla.
|
||
- **Disarm manuale**: ogni kill switch viene disarmato esplicitamente
|
||
da Adriano via CLI o comando Telegram, mai automaticamente.
|
||
- **Visibilità**: ogni evento di sicurezza viene loggato e notificato
|
||
immediatamente.
|
||
- **No silent close**: una posizione viene chiusa solo a seguito di
|
||
decisione esplicita dei trigger di strategia, mai per "evento
|
||
sospetto".
|
||
|
||
## Kill switch
|
||
|
||
### Stato
|
||
|
||
`system_state.kill_switch ∈ {0, 1}`. Quando = 1, l'engine:
|
||
|
||
- continua i flussi di **sola lettura** (health check, monitoring di
|
||
stato, log)
|
||
- **non** invia istruzioni di apertura
|
||
- **non** invia istruzioni di chiusura **automatiche** (richiede
|
||
intervento manuale via CLI con flag `--force-close-position`)
|
||
- continua a notificare i trigger di uscita ad Adriano via Telegram
|
||
con escalation manuale
|
||
|
||
### Trigger automatici
|
||
|
||
| Causa | Auto-arm | Note |
|
||
|---|---|---|
|
||
| MCP `cerbero-deribit` versione mismatch | Sì | Non procede senza schema atteso |
|
||
| MCP `cerbero-macro` non risponde per >30 min | Sì | No entry possibile |
|
||
| MCP `cerbero-memory` non risponde per >5 min | Sì | No esecuzione possibile |
|
||
| Stato SQLite incoerente con broker | Sì | Diff tra DB e Deribit positions |
|
||
| 3 health check consecutivi falliti | Sì | Engine instabile |
|
||
| Perdita giornaliera > 3% equity | Sì | Hard prohibition Cerbero v4 |
|
||
| Numero posizioni concorrenti > cap | Sì | Bug del sizing engine |
|
||
| Push instruction respinta da Cerbero core | Sì | Dopo 2 retry |
|
||
| Schema config invalido al reload | Sì | Mai partire con config rotta |
|
||
| Comando Adriano `/kill` su Telegram | Sì | Trigger volontario |
|
||
|
||
### Disarm
|
||
|
||
```bash
|
||
cerbero-bite kill-switch --disarm --reason "<motivo>"
|
||
```
|
||
|
||
oppure via Telegram:
|
||
|
||
```
|
||
/disarm <motivo>
|
||
```
|
||
|
||
In entrambi i casi il sistema chiede una conferma esplicita ("yes I am
|
||
sure").
|
||
|
||
## Cap di rischio (oltre alle regole di strategia)
|
||
|
||
Questi cap sono ridondanti rispetto a quelli del §5 della strategia,
|
||
ma sono applicati in modo difensivo come **ultima linea**:
|
||
|
||
| Misura | Limite | Comportamento se superato |
|
||
|---|---|---|
|
||
| Notional combo singolo | 200 EUR | Sizing engine reject |
|
||
| Engagement totale aperto | 1.000 EUR | Sizing engine reject |
|
||
| Posizioni concorrenti CB | 4 (default 1 per strategia) | Reject pre-push |
|
||
| Trade aperti per giorno | 6 (intera Cerbero suite) | Lookup `cerbero-memory.daily_trade_count` |
|
||
| Perdita giornaliera | 3% equity | Kill switch |
|
||
| Distance short strike | < 15% spot OTM | Combo builder reject |
|
||
| Credit / width | < 0.30 | Combo builder reject |
|
||
|
||
Doppia verifica: il sizing_engine applica il cap, **e** il watchdog
|
||
runtime ricontrolla il payload prima di chiamare `push_user_instruction`.
|
||
Se le due misure divergono → kill switch + alert.
|
||
|
||
## Dead-man switch
|
||
|
||
Se l'engine non scrive un evento `HEALTH_OK` per **15 minuti** consecutivi:
|
||
|
||
1. Un processo separato `data/dead-man.sh` (cron in user crontab,
|
||
indipendente dall'engine) rileva il silenzio.
|
||
2. Invia alert su canale Telegram di backup.
|
||
3. Marca SQLite con `system_state.kill_switch=1`.
|
||
4. Adriano interviene manualmente.
|
||
|
||
Il dead-man è scritto in shell minimale (no dipendenze Python) per
|
||
sopravvivere a corruzioni dell'env Python.
|
||
|
||
## Audit log immutabile
|
||
|
||
Oltre al log JSONL standard, ogni decisione di trading produce una
|
||
linea append-only in `data/audit.log` con il **digest SHA-256** della
|
||
linea precedente (chain di hash, stile blockchain semplificato).
|
||
|
||
Esempio:
|
||
|
||
```
|
||
2026-04-27T14:00:01Z|ENTRY_PROPOSED|{...full payload...}|prev_hash=abc123...|hash=def456...
|
||
```
|
||
|
||
Verificabile retroattivamente con `cerbero-bite audit verify`. Una
|
||
discrepanza dell'hash chain è trattata come tampering e arma il kill
|
||
switch.
|
||
|
||
## Dry-run mode
|
||
|
||
Tutti i flussi possono essere eseguiti in modalità `--dry-run`:
|
||
|
||
- Tutti i tool MCP **read-only** vengono chiamati normalmente.
|
||
- Tool MCP **write** (`push_user_instruction`, `kb_write`, `send`) vengono
|
||
loggati ma **non** chiamati.
|
||
- `state.create_position` viene scritto in tabella `dry_positions`
|
||
(schema identico a `positions` ma separata).
|
||
|
||
Usato per:
|
||
- Testing in produzione prima di andare live.
|
||
- Replay di giornate storiche per validazione.
|
||
- Test di nuove versioni di config o algoritmi.
|
||
|
||
## Versionamento config
|
||
|
||
Ogni `strategy.yaml` ha:
|
||
|
||
```yaml
|
||
config_version: "1.0.0"
|
||
config_hash: "<sha256 del file senza questa riga>"
|
||
last_review: "2026-04-26"
|
||
last_reviewer: "Adriano"
|
||
```
|
||
|
||
All'avvio l'engine verifica che `config_hash` corrisponda al contenuto.
|
||
Mismatch → kill switch (qualcuno ha modificato la config senza
|
||
aggiornare l'hash, possibile tampering o errore umano).
|
||
|
||
## Escalation tree
|
||
|
||
```
|
||
Evento anomalo
|
||
│
|
||
├── Severity LOW (es. 1 health check fallito)
|
||
│ └── Log WARNING, continua
|
||
│
|
||
├── Severity MEDIUM (es. MCP timeout occasionale)
|
||
│ ├── Log WARNING + Telegram digest giornaliero
|
||
│ └── Continua, retry next cycle
|
||
│
|
||
├── Severity HIGH (es. 3 health check consecutivi falliti)
|
||
│ ├── Kill switch ARM
|
||
│ ├── Telegram alert immediato
|
||
│ └── Adriano interviene
|
||
│
|
||
└── Severity CRITICAL (es. stato incoerente, hash chain rotto)
|
||
├── Kill switch ARM
|
||
├── Telegram + canale backup BotPapà
|
||
├── Engine si mette in idle (no decisioni, solo monitoring)
|
||
└── Richiede intervento umano per disarmo
|
||
```
|
||
|
||
## Test di resilienza obbligatori
|
||
|
||
Prima del go-live e ad ogni release minor:
|
||
|
||
1. **Chaos test MCP**: simula timeout/errori su ogni MCP, verifica
|
||
che il comportamento documentato in `04-mcp-integration.md` sia
|
||
rispettato.
|
||
2. **State corruption test**: corrompi una riga `positions` e verifica
|
||
che il riconciliatore lo rilevi.
|
||
3. **Hash chain test**: modifica una linea audit e verifica che
|
||
`audit verify` fallisca.
|
||
4. **Replay test**: rigioca una giornata storica in dry-run, confronta
|
||
le decisioni con un set golden.
|
||
5. **Cap saturation test**: simula 4 posizioni concorrenti, verifica
|
||
che il quinto trade venga rifiutato.
|
||
|
||
I risultati sono documentati in `tests/golden/results-YYYY-MM-DD.md`.
|
||
|
||
## Cosa NON è un risk control
|
||
|
||
Per chiarezza, queste cose **non** sono cap né kill switch — sono
|
||
parte della strategia, gestite altrove:
|
||
|
||
- Profit take 50%: regola di strategia.
|
||
- Stop loss 1.5×: regola di strategia.
|
||
- Vol stop +10 DVOL: regola di strategia.
|
||
- Time stop 7 DTE: regola di strategia.
|
||
|
||
I risk control proteggono il **sistema**. La strategia protegge il
|
||
**capitale**. Sono livelli diversi.
|