# 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 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.