Files
Cerbero-Bite/docs/06-operational-flow.md
T
Adriano 42b0fbe1ab Phase 4: orchestrator + cycles auto-execute
Componente runtime/ che cabla core+clients+state+safety in un engine
autonomo notify-only: nessuna conferma manuale, ordini combo
piazzati direttamente quando le regole passano. 311 test pass,
copertura totale 94%, runtime/ 90%, mypy strict pulito, ruff clean.

Moduli:
- runtime/alert_manager.py: escalation tree
  LOW/MEDIUM/HIGH/CRITICAL → audit + Telegram + kill switch.
- runtime/dependencies.py: build_runtime() costruisce
  RuntimeContext con tutti i client MCP, repository, audit log,
  kill switch, alert manager.
- runtime/entry_cycle.py: flusso settimanale (snapshot parallelo
  spot/dvol/funding/macro/holdings/equity → validate_entry →
  compute_bias → options_chain → select_strikes →
  liquidity_gate → sizing_engine → combo_builder.build →
  place_combo_order → notify_position_opened).
- runtime/monitor_cycle.py: loop 12h con dvol_history per il
  return_4h, exit_decision.evaluate, close auto-execute.
- runtime/health_check.py: probe parallelo MCP + SQLite +
  environment match; 3 strikes consecutivi → kill switch HIGH.
- runtime/recovery.py: riconciliazione SQLite vs broker
  all'avvio; mismatch → kill switch CRITICAL.
- runtime/scheduler.py: AsyncIOScheduler builder con cron entry
  (lun 14:00), monitor (02/14), health (5min).
- runtime/orchestrator.py: façade boot() + run_entry/monitor/health
  + install_scheduler + run_forever, con env check vs strategy.

CLI:
- start: avvia engine bloccante (asyncio.run + scheduler).
- dry-run --cycle entry|monitor|health: esegue un singolo ciclo
  per debug/test in produzione.
- stop: documenta lo shutdown via SIGTERM al container.

Documentazione:
- docs/06-operational-flow.md riscritto per il modello
  notify-only auto-execute (no conferma manuale, no memory,
  no brain-bridge).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:03:45 +02:00

210 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
I quattro flussi principali del rule engine, tutti orchestrati dallo
scheduler interno APScheduler. Aggiornati al modello di esercizio
**autonomo notify-only** introdotto in Fase 3: Cerbero Bite consulta i
server MCP della suite per leggere il mercato e per inviare l'ordine,
non chiede mai conferma a Adriano e usa Telegram esclusivamente per
notificare quanto fatto.
## Flusso 1 — Avvio engine
```
1. Carica strategy.yaml + strategy.local.yaml (override)
2. Validazione schema + verifica config_hash
3. Acquisisci lock file (data/.lockfile)
4. Apri SQLite, esegui migrations, init_system_state
5. Health check MCP (environment_info, ping read tools)
- cerbero-deribit.environment_info → confronta con
strategy.execution.environment; mismatch → kill switch
6. Riconciliatore stato (vedi flusso 6)
7. Health check sistema OK? → arma scheduler
KO? → kill switch + alert + idle
8. Audit 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 o un
ambiente diverso da quello atteso.
## Flusso 2 — Settimanale (entry)
Trigger: cron `0 14 * * MON` (lunedì 14:00 UTC).
```
START
├── safety.system_healthy()? → no → log + skip
├── repository.has_open_position()? → yes → log + skip
├── snapshot dati di mercato (parallel):
│ spot_eth, dvol, funding_perp,
│ funding_cross, macro_calendar,
│ eth_holdings_pct, capital_eur
├── entry_validator.validate_entry → fail → log ENTRY_REJECTED + reasons
├── entry_validator.compute_bias → None → log ENTRY_REJECTED ("no_bias")
├── deribit.options_chain (DTE 14-21)
├── combo_builder.select_strikes → None → log ENTRY_REJECTED ("no_strike")
├── deribit.orderbook_depth_top3 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
├── repository.create_position(status="proposed")
├── deribit.place_combo_order
│ ├── ordine accettato (state=open|filled):
│ │ ├── repository.update_position_status(awaiting_fill|open)
│ │ ├── repository.create_instruction
│ │ ├── audit ENTRY_PLACED
│ │ └── telegram.notify_position_opened (post-fact)
│ └── ordine rigettato:
│ ├── repository.update_position_status(cancelled)
│ ├── audit ENTRY_REJECTED_BY_BROKER
│ └── telegram.notify_alert (priority=high)
└── END
```
Nessuna conferma manuale: la decisione di apertura è il risultato
deterministico delle regole, non una proposta soggetta ad
approvazione. Il messaggio Telegram è informativo.
### Tempo previsto end-to-end
- Snapshot dati: 3-5 secondi.
- Algoritmi puri: < 0.5 secondi.
- `place_combo_order` su testnet: 1-3 secondi.
Critical path completo sotto 10 secondi nominali.
## 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)
├── salva snapshot in dvol_history (per il calcolo di return_4h)
├── per ogni position con status="open":
│ ├── snapshot:
│ │ spot, dvol, mark_combo (= mid_short - mid_long),
│ │ delta_short, return_4h (vs dvol_history 4h fa)
│ ├── exit_decision.evaluate
│ ├── action == HOLD:
│ │ └── log EXIT_EVALUATED("hold")
│ ├── action == CLOSE_*:
│ │ ├── repository.update_position_status("closing")
│ │ ├── deribit.place_combo_order (direzione inversa)
│ │ │ ├── filled:
│ │ │ │ ├── repository.update_position_status("closed")
│ │ │ │ ├── audit EXIT_FILLED
│ │ │ │ └── telegram.notify_position_closed
│ │ │ └── rejected:
│ │ │ ├── repository.update_position_status("open")
│ │ │ └── telegram.notify_alert (priority=critical,
│ │ │ source="exit_failed")
│ └── continue
└── END
```
Non c'è un ramo "richiesta conferma utente". Ogni `CLOSE_*` viene
eseguito immediatamente; il messaggio Telegram è post-fact e descrive
azione + motivo (formato `notify_position_closed`).
In caso di fallimento del `place_combo_order` di chiusura (broker
respinge, latenza > soglia, ecc.) la posizione viene rimessa in
`open` e generato un alert `critical`: sarà il prossimo ciclo a
ritentare.
## 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 dal repository
2. kelly_recalibration.recalibrate
3. Genera report mensile testuale (markdown)
4. telegram.notify (priority=normal, tag="kelly")
5. salva report come allegato al log JSONL del giorno
6. audit KELLY_RECALIBRATED
```
L'aggiornamento di `strategy.yaml` resta **manuale**: Adriano legge il
report e committa il nuovo file (con giustificazione nel commit
message). Nessun auto-update del kelly_fraction.
## Flusso 5 — Health check periodico
Trigger: ogni 5 minuti.
```
1. Per ogni MCP utilizzato: probe lightweight
- deribit.environment_info
- macro.get_macro_calendar(days=1)
- sentiment.get_cross_exchange_funding (no asset filter)
- hyperliquid.get_funding_rate("ETH")
- portfolio.get_total_portfolio_value
- telegram: skip (notify-only, no probe non invasivo)
2. SQLite read-write probe (transazione fittizia)
3. Lock file ancora valido
4. environment_info.environment == strategy.execution.environment
5. Audit HEALTH_OK / HEALTH_DEGRADED
6. Conta fallimenti consecutivi:
- 3 fallimenti → kill_switch + alert HIGH
- 5 fallimenti → audit + alert CRITICAL (riavvio è demandato a Docker)
```
Il dead-man (`scripts/dead_man.sh`) sorveglia che `HEALTH_OK` venga
scritto: silenzio > 15 min → kill switch via SQLite e alert.
## Flusso 6 — Recovery dopo crash
All'avvio o dopo un riavvio del container:
1. Apre SQLite + log JSONL della giornata.
2. Per ogni `awaiting_fill`/`closing`:
- chiama `deribit.get_positions` per verificare l'esistenza sul broker
- se trovata → aggiorna a `open` (fill confermato)
- se non trovata e l'ordine risulta cancellato → aggiorna a `cancelled`
- se nessuna delle due → flag `state_inconsistent`, kill switch,
alert CRITICAL
3. Per ogni `open`:
- verifica corrispondenza posizione broker vs DB (size, strike,
expiry); discrepanze → kill switch
4. Riconciliazione completata → `audit ENGINE_START`.
Il sistema **mai** prende decisioni di trading durante il recovery:
solo allinea lo stato. Il primo decision loop avviene al prossimo
trigger naturale.
## Diagramma di stato di una posizione
```
proposed
├─ broker_reject ────→ cancelled
├─ submitted ────────► awaiting_fill
├─ no_fill ───────────→ cancelled
├─ filled ────────────► open
│ │
│ (exit_decision != HOLD)
│ │
│ ▼
│ closing
│ │
│ ├─ no_fill → open
│ │ (riprovato al prossimo ciclo)
│ └─ 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.