Files
Cerbero-Bite/docs/06-operational-flow.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

217 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 06 — Flussi operativi
Tre flussi principali, tutti orchestrati dallo scheduler interno
APScheduler.
## Flusso 1 — Avvio engine
```
1. Carica strategy.yaml + strategy.local.yaml (override)
2. Validazione schema (pydantic) — fail veloce
3. Acquisisci lock file (data/.lockfile)
4. Apri SQLite, esegui migrations
5. Health check MCP server (versioni + ping)
6. Riconciliatore stato:
a. legge positions con status in {awaiting_fill, open, closing}
b. per ognuna chiama cerbero-memory.is_acknowledged + Deribit get_positions
c. allinea SQLite alla realtà del broker (sempre la realtà vince)
d. log delle discrepanze come WARNING
7. Health check sistema OK? → arma scheduler
KO? → kill switch + alert + idle
8. Log ENGINE_START
```
L'avvio è progettato per essere **safe**: se qualcosa non torna, il
sistema si rifiuta di operare. Mai partire con uno stato dubbio.
## Flusso 2 — Settimanale (entry)
Trigger: cron `0 14 * * MON` (lunedì 14:00 UTC).
```
START
├── safety.system_healthy()? → no → log + skip
├── state.has_open_position()? → yes → log + skip
├── snapshot dati di mercato (parallel):
│ spot, dvol, funding_perp, funding_cross, macro_calendar,
│ holdings_pct, capital
├── entry_validator.validate_entry → fail → log ENTRY_REJECTED + reasons
├── entry_validator.compute_bias → None → log ENTRY_REJECTED ("no_bias")
├── deribit.get_options_chain (DTE 14-21)
├── combo_builder.select_strikes → None → log ENTRY_REJECTED ("no_strike")
├── deribit.get_orderbook per le 2 gambe
├── liquidity_gate.check → fail → log ENTRY_REJECTED ("illiquid")
├── sizing_engine.compute_contracts → 0 → log ENTRY_REJECTED ("undersize")
├── combo_builder.build → ComboProposal
├── greeks_aggregator.aggregate
├── reporting.pre_trade(proposal) → testo Telegram
├── telegram.request_confirmation(timeout=60min)
│ ├── confermato:
│ │ ├── state.create_position (status="proposed")
│ │ ├── memory.push_user_instruction
│ │ ├── state.update(status="awaiting_fill")
│ │ └── log ENTRY_CONFIRMED
│ ├── rifiutato:
│ │ └── log ENTRY_REJECTED_BY_USER
│ └── timeout:
│ └── log ENTRY_TIMEOUT
└── END
```
### Tempo previsto end-to-end
- Snapshot dati: 3-5 secondi
- Algoritmi puri: < 0.5 secondi
- Conferma utente: variabile (max 60 min)
Il critical path automatico è **sotto 10 secondi**. La latenza umana
non rallenta i prezzi: se Adriano risponde dopo 30 minuti, l'engine
prima di inviare l'istruzione **rivaluta** spot, mid e dvol e abortisce
l'apertura se le condizioni sono cambiate (slippage > 8% o dvol fuori
banda o macro nuova).
## Flusso 3 — Monitoring (12h)
Trigger: cron `0 2,14 * * *` (02:00 e 14:00 UTC, ogni giorno).
```
START
├── safety.system_healthy()? → no → kill switch (no auto-close ciechi)
├── per ogni position con status="open":
│ ├── snapshot:
│ │ spot, dvol, mark_combo, delta_short, return_4h
│ ├── exit_decision.evaluate
│ ├── action == HOLD:
│ │ └── log EXIT_EVALUATED("hold")
│ ├── action == CLOSE_*:
│ │ ├── reporting.exit_proposal(position, decision)
│ │ ├── telegram.request_confirmation(timeout=30min)
│ │ │ ├── confermato:
│ │ │ │ ├── memory.push_user_instruction (close)
│ │ │ │ ├── state.update(status="closing")
│ │ │ │ └── log EXIT_CONFIRMED
│ │ │ ├── rifiutato:
│ │ │ │ └── log EXIT_DEFERRED (resterà in HOLD fino a prossimo ciclo)
│ │ │ └── timeout:
│ │ │ └── escalation: per CLOSE_STOP/CLOSE_VOL/CLOSE_DELTA
│ │ │ → invia comunque l'istruzione (urgenza prevale)
│ │ │ per altri motivi → log EXIT_TIMEOUT, attende prossimo ciclo
│ └── continue
└── END
```
### Politica di escalation
I trigger di uscita non sono uguali. Alcuni hanno **urgenza alta** che
giustifica l'esecuzione anche senza conferma utente entro la finestra:
| Trigger | Urgenza | Comportamento su timeout |
|---|---|---|
| `CLOSE_PROFIT` | Bassa | Attendi prossimo ciclo (può migliorare) |
| `CLOSE_STOP` | **Alta** | Esegui chiusura senza conferma esplicita |
| `CLOSE_VOL` | **Alta** | Esegui chiusura senza conferma |
| `CLOSE_DELTA` | **Alta** | Esegui chiusura senza conferma |
| `CLOSE_TIME` | Media | Attendi 1 ciclo extra; al secondo timeout esegui |
| `CLOSE_AVERSE` | Media | Attendi prossimo ciclo |
Su escalation hard, Telegram riceve un messaggio **post-fact**: "ho
chiuso la posizione X per stop loss perché non ho ricevuto risposta in
30 min e l'urgenza non consentiva attesa".
## Flusso 4 — Mensile (Kelly recalibration)
Trigger: cron `0 12 1 * *` (primo di ogni mese alle 12:00 UTC).
```
1. Carica trades chiusi negli ultimi 365 giorni
2. kelly_recalibration.recalibrate
3. Genera report mensile:
- performance vs simulazione MC
- win rate, avg win/loss, edge
- kelly suggerito vs corrente
- violazioni regole (deve essere 0)
4. telegram.send (informativo, no conferma)
5. brain_bridge.write_note(
path="strategies/cerberus-bite/monthly-report-YYYY-MM.md",
content=report
)
6. log KELLY_RECALIBRATED
```
L'aggiornamento di `strategy.yaml` resta **manuale**: Adriano legge il
report, discute con Milito, e committa il nuovo file di config (con
giustificazione nel commit message). Nessun auto-update.
## Flusso 5 — Health check periodico
Trigger: ogni 5 minuti.
```
1. ping ogni MCP usato → ms latency
2. SQLite read-write probe (transazione fittizia)
3. lock file ancora valido
4. ultima fill di Cerbero core ricevuta entro tempo atteso
5. state.system_state.kill_switch == 0
Se qualsiasi passo fallisce:
- log HEALTH_DEGRADED
- se 3 fallimenti consecutivi → kill_switch + alert
- se 5 fallimenti consecutivi → restart automatico (1 sola volta al giorno)
```
## Flusso 6 — Recovery dopo crash
Quando l'engine riparte:
1. Riapre SQLite + log della giornata.
2. Per ogni posizione `awaiting_fill`:
- Chiede a `cerbero-memory.get_status(instruction_id)`
- Se filled → aggiorna a `open`
- Se cancelled → aggiorna a `cancelled`
- Se ancora pending → mantiene stato e riprende il monitoraggio
3. Per ogni posizione `closing`:
- Stessa logica
4. Per ogni posizione `open`:
- Verifica che esista realmente sul broker (Deribit `get_positions`)
- Se non esiste → flag `state_inconsistent` + alert + kill switch
5. Riconciliazione completata → ENGINE_START.
Il sistema **mai** prende decisioni di trading durante recovery: solo
allinea lo stato. Il primo decision loop avviene al prossimo trigger
naturale.
## Diagramma di stato di una posizione
```
┌─ rejected_by_user ──→ cancelled
(entry) ├─ timeout ──────────→ cancelled
proposed ─────────────► │
├─ confirmed
awaiting_fill
├─ no_fill ──────────→ cancelled
├─ filled ───────────► open
│ │
│ (exit_decision != HOLD)
│ │
│ ▼
│ closing
│ │
│ ├─ no_fill (escalated) → manual_intervention
│ └─ filled ──────────→ closed
```
## Cron summary
| Cron | Trigger | Frequenza |
|---|---|---|
| `0 14 * * MON` | Entry evaluation | Settimanale |
| `0 2,14 * * *` | Position monitoring | 2× giorno |
| `0 12 1 * *` | Kelly recalibration | Mensile |
| `*/5 * * * *` | Health check | 5 min |
| `0 0 * * *` | Backup SQLite + rotation log | Giornaliero |
| `0 8 * * *` | Daily digest Telegram | Giornaliero |
Tutti gli orari in UTC.