Sistema dedicato di raccolta dati per scegliere le soglie dei filtri
sui percentili reali invece di valori a istinto.
Nuovi componenti:
* state/migrations/0003_market_snapshots.sql — tabella + index, PK
composta (timestamp, asset). Ogni colonna numerica è NULL-able per
preservare la continuità della serie quando un singolo MCP fallisce.
* state/models.py — MarketSnapshotRecord Pydantic.
* state/repository.py — record_market_snapshot, list_market_snapshots,
_row_to_market_snapshot.
* runtime/market_snapshot_cycle.py — collettore best-effort che chiama
spot/dvol/realized_vol/dealer_gamma/funding_perp/funding_cross/
liquidation_heatmap/macro per ogni asset; raccoglie gli errori in
fetch_errors_json e segna fetch_ok=false ma persiste comunque la
riga.
* clients/deribit.py — generalizzati dealer_gamma_profile(currency),
realized_vol(currency), spot_perp_price(asset). dealer_gamma_profile_eth
resta come alias per la chiamata dell'entry cycle.
* runtime/orchestrator.py — nuovo job APScheduler `market_snapshot`
cron */15 con assets configurabili (default ETH+BTC); il consumer
manual_actions ora dispatcha anche kind=run_cycle cycle=market_snapshot
per la GUI.
* gui/data_layer.py — load_market_snapshots, enqueue_run_cycle accetta
market_snapshot; tipo MarketSnapshotRecord esposto.
* gui/pages/6_📐_Calibrazione.py — selezione asset+finestra, conteggio
fetch_ok, per ogni metrica: istogramma, soglia da strategy.yaml come
vline rossa, percentili P5/P10/P25/P50/P75/P90/P95, % di tick che la
soglia avrebbe filtrato.
* gui/pages/1_📊_Status.py — bottone "📐 Forza snapshot" (4° del pannello
Forza ciclo) per popolare la tabella senza aspettare il cron.
5 nuovi test sul collector (happy, fault tolerance, asset switch,
macro fail, empty assets); test_orchestrator job set aggiornato.
368/368 tests pass; ruff clean; mypy strict src clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the GUI's first write path through the manual_actions queue:
* runtime/manual_actions_consumer.py — drains the queue and
dispatches arm_kill / disarm_kill via KillSwitch (preserving the
audit chain). Unsupported kinds (force_close, approve/reject_proposal)
are marked result="not_supported" so they don't sit forever.
* runtime/orchestrator.py — adds a `manual_actions` job at */1 cron
to the canonical scheduler manifest.
* gui/data_layer.py — write helpers enqueue_arm_kill /
enqueue_disarm_kill (the only write path the GUI uses) plus
load_pending_manual_actions for the pending strip.
* gui/pages/1_📊_Status.py — kill-switch arm/disarm panel with typed
confirmation ("yes I am sure") + reason field; pending-actions table
rendered when the queue is non-empty.
End-to-end smoke against the testnet state.sqlite:
GUI enqueue → consumer dispatch → KillSwitch transition → audit
chain hash linkage holds, "source":"manual_gui" recorded.
7 new unit tests for the consumer (arm, disarm, drain, unsupported,
default-reason, KillSwitchError handling, empty queue); 360/360 pass.
ruff clean; mypy strict src clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each bot now manages its own notification + portfolio aggregation:
* TelegramClient calls the public Bot API directly via httpx, reading
CERBERO_BITE_TELEGRAM_BOT_TOKEN / CERBERO_BITE_TELEGRAM_CHAT_ID from
env. No credentials → silent disabled mode.
* PortfolioClient composes DeribitClient + HyperliquidClient + the new
MacroClient.get_asset_price/eur_usd_rate to expose equity (EUR) and
per-asset exposure as the bot's own slice (no cross-bot view).
* mcp-telegram and mcp-portfolio removed from MCP_SERVICES / McpEndpoints
and the cerbero-bite ping CLI; health_check no longer probes portfolio.
Docs (02/04/06/07) and docker-compose updated to reflect the new
architecture.
353/353 tests pass; ruff clean; mypy src clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sei interventi mirati sui rischi operativi rilevati nell'audit
post-Fase 4. 317 test pass, mypy strict pulito, ruff clean.
1. status CLI: legge SQLite reale e mostra kill_switch, posizioni
aperte, environment, config_version, last_health_check, started_at.
Sostituisce il placeholder "phase 0 skeleton".
2. Lock file single-instance: runtime/lockfile.py acquisisce
data/.lockfile via fcntl.flock al boot di run_forever; un secondo
container fallisce subito con LockError.
3. Backup orario nello scheduler: nuovo job APScheduler 0 * * * *
chiama scripts.backup.backup_database + prune_backups.
4. config_hash enforce su start: il CLI start verifica l'integrità
del file (enforce_hash=True). Mismatch → exit 1 prima di toccare
stato. dry-run resta enforce_hash=False per debug.
5. Connection pooling MCP: RuntimeContext espone un httpx.AsyncClient
long-lived condiviso da tutti i wrapper (limits 20/10
connections/keepalive). aclose() chiamato in run_forever finale.
6. Bias direzionale reale: deribit.historical_close +
deribit.adx_14 popolano TrendContext con spot a 30 giorni e
ADX(14) effettivi. Sblocca bull_put e bear_call. Quando i dati
storici mancano l'engine emette alert MEDIUM e cade su no_entry
in modo deterministico.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>