Files
Cerbero-Bite/docs/06-operational-flow.md
T
Adriano da88e7f746 docs: align 05/06/09/11 with implemented GUI Phases A–D
* docs/11-gui-streamlit.md — replaces the original spec with what was
  actually built: implementation status table, real page filenames
  (1_Status, 2_Audit, 3_Equity, 4_History, 5_Position), per-page
  inventory of implemented vs deferred sections, GUI ↔ engine table
  showing arm_kill/disarm_kill via manual_actions and the
  not_supported markers for force_close + approve/reject_proposal,
  consumer signature with cron */1, lock model clarified (no GUI
  lockfile), DoD updated with current state.
* docs/05-data-model.md — manual_actions is no longer "pianificata":
  populated by gui/data_layer.py, drained by the manual_actions job;
  per-kind status table (arm/disarm OK, others not_supported).
* docs/09-development-roadmap.md — Phase 4.5 marked implemented with
  per-task / markers for the deferred items (auto-refresh,
  AppTest, force-close hook).
* docs/06-operational-flow.md — adds Flusso 5b describing the
  manual_actions consumer pattern (enqueue → KillSwitch transition →
  audit log linkage).

360/360 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 13:31:25 +02:00

230 lines
9.4 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: 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.
## 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 * * 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.