881bc8a1bf
- 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>
217 lines
8.5 KiB
Markdown
217 lines
8.5 KiB
Markdown
# 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.
|