Files
Cerbero-Bite/docs/06-operational-flow.md
T
Adriano Dal Pastro 8a1fb823dc docs: profilo B, collettore research, colonna source, backup giornaliero
Allinea la documentazione alle modifiche di v1.7.0.

- 01-strategy-rules: §3.2 delta short 0.18 (banda 0.12-0.22, OTM>=10%)
  con la motivazione dell'analisi (credit/width ~6% irraggiungibile a
  delta basso); delta_by_dvol aggressiva ritarata; §3.3 width fino a 6%
  (griglia strike 100pt); §3.4 credit/width min 30% -> 8%.
- 05-data-model: option_chain_snapshots alimentata da due collettori
  (live/research) via colonna source; CREATE TABLE + indice aggiornati;
  book_depth_top3 popolato sulle righe research; migrazioni 6 e 7 in
  tabella; backup orario -> giornaliero con nota storica.
- 10-config-spec: valori structure profilo B + nuovo blocco
  research_collector.
- 06-operational-flow: cron summary con i due collettori opzioni.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 08:31:59 +00:00

296 lines
12 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 — Daily (entry)
Trigger: cron `0 14 * * *` (ogni giorno 14:00 UTC). Crypto è 24/7:
la cadenza di candidatura non è gateata sulla settimana — sono i gate
quantitativi a decidere se entrare o saltare il giorno.
```
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: skip (componente in-process, copertura indiretta dai probe deribit/hyperliquid/macro)
- 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.
> **Nota — la tolleranza "3 fallimenti" vale solo per i probe del
> health check.** Tutti i tick schedulati (entry, monitor, snapshot,
> …) girano dentro `orchestrator._safe`, che escala **qualsiasi**
> eccezione non gestita a `alert_manager.critical` → kill switch
> armato al **primo** errore, anche transitorio. È il percorso che ha
> fermato il bot il 31/05/2026 (singolo `McpTimeoutError` nel tick
> entry). Vedi la tabella trigger in `07-risk-controls.md`.
## Flusso 5b — Manual actions consumer
Trigger: cron `*/1 * * * *` (job APScheduler `manual_actions`).
```
1. Mentre la coda ha righe non consumate:
- leggi `next_unconsumed_action` (oldest-first)
- dispatch per kind:
arm_kill → KillSwitch.arm(reason, source="manual_gui")
disarm_kill → KillSwitch.disarm(reason, source="manual_gui")
force_close / approve_proposal / reject_proposal → result="not_supported"
- mark_action_consumed con consumed_by="engine" e result
2. Latenza tipica end-to-end (enqueue da GUI → effetto): ≤ 60 sec.
```
Il consumer è il **canale unico** di scrittura dalla GUI verso il
runtime: ogni transizione del kill switch passa dalla classe
`KillSwitch` per mantenere SQLite e audit chain in lock-step. Vedi
`runtime/manual_actions_consumer.py` e `docs/11-gui-streamlit.md`.
## 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 * * *` | Entry evaluation | Giornaliera |
| `0 2,14 * * *` | Position monitoring | 2× giorno |
| `0 12 1 * *` | Kelly recalibration | Mensile |
| `*/5 * * * *` | Health check | 5 min |
| `*/15 * * * *` | Market snapshot (calibrazione soglie) | 15 min |
| `*/15 * * * *` | Option chain snapshot (live, finestra DTE strategia) | 15 min |
| `0 * * * *` | Option chain research (full-chain + book_depth, opt-in) | Orario |
| `0 0 * * *` | Backup SQLite + rotation log | Giornaliero |
| `0 8 * * *` | Daily digest Telegram | Giornaliero |
Tutti gli orari in UTC.
## Modalità operativa (interruttori `RuntimeFlags`)
Il bot riconosce due interruttori indipendenti, letti da
`.env` al boot tramite `cerbero_bite.config.runtime_flags.load_runtime_flags()`:
| Variabile d'ambiente | Default | Cosa abilita |
|---|---|---|
| `CERBERO_BITE_ENABLE_DATA_ANALYSIS` | `true` | Job `market_snapshot` ogni 15 min: raccolta dati MCP, scrittura tabella `market_snapshots`, calibrazione soglie. |
| `CERBERO_BITE_ENABLE_STRATEGY` | `false` | Job `entry` (daily 14:00 UTC) e `monitor` (2× giorno): valutazione regole §2-§9 di `01-strategy-rules.md` e proposta/esecuzione ordini. |
I job di infrastruttura (`health`, `backup`, `manual_actions`) sono
**sempre attivi**, indipendentemente dai flag, perché tengono in vita il
kill switch e la persistenza.
### Profilo "solo analisi dati" (default)
Configurazione standard del periodo di soak post-deploy:
```env
CERBERO_BITE_ENABLE_DATA_ANALYSIS=true
CERBERO_BITE_ENABLE_STRATEGY=false
```
Effetto: il bot raccoglie snapshot di mercato, alimenta `market_snapshots`,
ma **non** invia entry né chiude posizioni autonomamente. I metodi
`run_entry`/`run_monitor` restano richiamabili manualmente da CLI
(`cerbero-bite dry-run --cycle entry|monitor`) e tramite `manual_actions`
per testing e validazione.
### Profilo "trading attivo"
```env
CERBERO_BITE_ENABLE_DATA_ANALYSIS=true
CERBERO_BITE_ENABLE_STRATEGY=true
```
Effetto: tutti i job canonici vengono installati nello scheduler. Lo
switch va fatto solo dopo che la qualità dei dati raccolti è stata
validata e Adriano dà esplicito consenso al passaggio.
### Disattivazione completa dell'analisi dati
Caso eccezionale (manutenzione, problema MCP):
```env
CERBERO_BITE_ENABLE_DATA_ANALYSIS=false
CERBERO_BITE_ENABLE_STRATEGY=false
```
Il bot resta vivo per health check e ricezione di manual actions, ma
non interroga MCP per dati di mercato e non opera. Il kill switch resta
operativo.