ombra di TradingBook documentata al §1; cc01_regime_watch registra i giri e sta in monitor_health (Fixes #11, Fixes #12)
- CLAUDE.md §1 OMBRA: cron :44 sulla stessa crontab, legge il conto con la STESSA chiave live di cerbero-mcp (verificato per hash), scrive solo nel suo albero (3 giri misurati, nessun file di data/ o logs/ di qui cambiato), non invia per assenza di codice e non per permesso. §12 rimanda. - cc01: ogni giro con entrambi gli asset misurati scrive data/cc01_regime/runs.jsonl (ts ms, stato, funding 30g/7g); se non vede, non scrive e lo dice. monitor_health: MonitorSpec giornaliera, max_age 30h, percorso incrociato con cc01.RUNS da un test (P1). - tests/test_cc01_regime_watch.py (4, rossi prima). Suite completa: 1074 passed. Autorizzato da Adriano il 16/09 dopo la revisione settimanale. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,8 @@ data/fee_watch/
|
||||
# battuta di cuore del collettore catena (stato runtime, come gli altri monitor)
|
||||
data/chain_collect/
|
||||
data/vrp_f_watch/
|
||||
# giri misurati del trigger di regime CC01 (letti da monitor_health, #12)
|
||||
data/cc01_regime/
|
||||
|
||||
# log esecuzioni del book live (stato runtime, contiene fill/fee del conto reale)
|
||||
data/live/
|
||||
|
||||
@@ -59,6 +59,28 @@ dal 2026-08-25; era `:07`). Due vincoli, entrambi misurati: fuori dai ~26s del m
|
||||
annunciati), che il `:07` beccava — 4 volte in 63 giorni. **GTAA01, XS01, VRP01, XSR01 NON sono
|
||||
nel book live.**
|
||||
|
||||
**OMBRA (non e' questo repo, ma gira sullo stesso conto).** Dal 2026-09-13 la crontab ha anche
|
||||
`44 * * * * /opt/AI-OS/automations/TradingBook/scripts/cron_ombra.sh`: il **book traslocato**
|
||||
(`Adriano/TradingBook`, stesso codice di questo repo al 13/09) calcola ogni ora il target e lo
|
||||
scrive in un registro, **senza inviare**. Finestra dichiarata fino al **30/09/2026**; confronto col
|
||||
`:47` di qui con `TradingBook/scripts/confronta_giornata.py`. Verificato il 16/09 (issue #11):
|
||||
- **cosa legge**: il conto Deribit tramite il gateway interno di TradingBook, con
|
||||
`TradingBook/secrets/deribit.json`, il cui `client_id` **coincide** (confronto per hash) con
|
||||
`DERIBIT_CLIENT_ID_LIVE` di cerbero-mcp, cioe' **la chiave con cui questo libro manda gli ordini**;
|
||||
il feed tramite `TradingBook/data/raw`, un **collegamento** a `data/raw` di qui (sola lettura).
|
||||
- **dove scrive**: solo nel suo albero, `logs/cron_ombra.log`, `data/live/ombra.jsonl` e
|
||||
`data/live/equity_seen.json`. Sui giri delle 18:17, 19:17 e 20:44 del 16/09 **nessun file** di
|
||||
`data/` o `logs/` di questo progetto e' cambiato: il watermark di qui non lo tocca (P16).
|
||||
- **perche' non invia**: `book_ombra.py` non contiene il percorso d'invio, e
|
||||
`TradingBook/tests/test_ombra_non_invia.py` lo verifica sul sorgente. 🚨 **La garanzia e' il CODICE,
|
||||
non il permesso**: la chiave puo' fare trading. Una chiave Deribit di **sola lettura** per l'ombra
|
||||
la crea l'operatore; finche' non c'e', qualunque script di TradingBook con un percorso d'invio
|
||||
opererebbe con soldi veri.
|
||||
- **orario**: `:44` con `timeout -k 10 150`, quindi finisce entro le `:46:40`, prima del giro di qui.
|
||||
Era `:17` fino al 16/09 e cadeva nello slot di release del martedi' (15/09 09:17: manutenzione
|
||||
11051, due BUY calcolati sul paper capital nel registro d'ombra); da TradingBook#16 un conto o una
|
||||
posizione illeggibile danno un giro **saltato**, come qui danno lo stop.
|
||||
|
||||
**RICERCA (paper).** Portafoglio a 5 sleeve `src/portfolio/sleeves.active_sleeves`:
|
||||
TP01 33 / XS01 15 / VRP01 12 / SKH01 20 / GTAA01 20. **Non e' il book live** — quasi tutti i numeri
|
||||
di portafoglio del progetto sono su questa serie, non su quella che gira.
|
||||
@@ -675,6 +697,7 @@ scripts/live/ → book_execute · venue_watch · fee_watch · edg
|
||||
monitor_health · collect_chain · paper_* · trades_db ·
|
||||
journal · analista
|
||||
scripts/cron_{book,daily,chain,opt_snapshot,vol_term,review}.sh
|
||||
(la crontab ha anche cron_ombra.sh di TradingBook: vedi §1, OMBRA)
|
||||
docs/memory/ → LA MEMORIA (indice in testa a questo file)
|
||||
docs/research/ → BRIEF-0822 · RESULTS-0822 (§1-77) · SPEC-scale-key
|
||||
docs/diary/YYYY-MM-DD.md → una voce per esperimento (146)
|
||||
|
||||
@@ -13,11 +13,18 @@ Hyperliquid (tokenless, stessa fonte certificata di fetch_hl_funding.py) e stamp
|
||||
che resta comunque vincolato a ~$20k+ di capitale e venue con funding eseguibile).
|
||||
|
||||
Uso: `uv run python scripts/live/cc01_regime_watch.py`
|
||||
(candidato a una riga in cron_daily.sh: e' read-only, nessun ordine, nessuno stato.)
|
||||
(in cron_daily.sh: read-only verso il mercato, nessun ordine.)
|
||||
|
||||
REGISTRA OGNI GIRO MISURATO (dal 2026-09-16, #12) in `data/cc01_regime/runs.jsonl`: `ts` in ms,
|
||||
stato, funding 30g/7g per asset. Serve a `monitor_health`, che ne legge il percorso da `RUNS`
|
||||
(P1). Dal 03/09 al 16/09 lo script e' morto 14 notti di fila (#10) e nessuno l'ha visto, perche'
|
||||
non lasciava niente da sorvegliare. Un giro che non misura entrambi gli asset NON scrive: non e'
|
||||
una barra, e se lo fosse un cc01 cieco resterebbe «OK».
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
@@ -28,6 +35,7 @@ import requests
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
RUNS = ROOT / "data" / "cc01_regime" / "runs.jsonl"
|
||||
HL_INFO = "https://api.hyperliquid.xyz/info"
|
||||
HOURS_PER_YEAR = 24 * 365
|
||||
WARN_ANN = 0.10 # >=10% ann. su media 30g -> WARN
|
||||
@@ -75,6 +83,7 @@ def main() -> None:
|
||||
print(" CC01 REGIME WATCH — funding medio 1-mese (Hyperliquid, orario, tokenless)")
|
||||
print("=" * 78)
|
||||
worst = "QUIET"
|
||||
misure = {}
|
||||
for coin in ("BTC", "ETH"):
|
||||
f = fetch_recent_funding(coin)
|
||||
if len(f) < 24 * 7:
|
||||
@@ -86,8 +95,16 @@ def main() -> None:
|
||||
"WARN" if ann30 >= WARN_ANN else "QUIET")
|
||||
if state == "ALERT" or (state == "WARN" and worst == "QUIET"):
|
||||
worst = state
|
||||
misure[coin] = {"ann30": ann30, "ann7": ann7, "stato": state}
|
||||
print(f" {coin}: media 30g {ann30:+7.1%} ann. media 7g {ann7:+7.1%} ann. -> {state}")
|
||||
print("-" * 78)
|
||||
if len(misure) < 2:
|
||||
# P5: «non vedo» non e' «QUIET». Nessuna riga: monitor_health lo vedra' FERMO.
|
||||
print(f" NON VEDO: misurati {len(misure)} asset su 2 — nessuna riga registrata in {RUNS.name}.")
|
||||
return
|
||||
RUNS.parent.mkdir(parents=True, exist_ok=True)
|
||||
with RUNS.open("a", encoding="utf-8") as fh:
|
||||
fh.write(json.dumps({"ts": int(time.time() * 1000), "stato": worst, **misure}) + "\n")
|
||||
if worst == "ALERT":
|
||||
print(" >>> ALERT CC01: funding 30g >= 15% ann. — regime ricco SOSTENUTO.")
|
||||
print(" Riaprire il dossier cash-and-carry (vincoli invariati: ~$20k+,")
|
||||
|
||||
@@ -98,6 +98,13 @@ MONITORS: tuple[MonitorSpec, ...] = (
|
||||
MonitorSpec("cuscino_watch", "live", "cuscino_watch.jsonl", 1.0, max_age_h=3.0,
|
||||
gate="cuscino USDC di regolamento: allerta + riconversione automatica",
|
||||
open_labeled=False),
|
||||
# Trigger di regime CC01 (cron_daily 00:30, GIORNALIERO, #12): dal 03/09 al 16/09 e' morto 14
|
||||
# notti di fila senza che nessuno lo vedesse (#10), perche' non scriveva niente da sorvegliare.
|
||||
# Il percorso e' quello di `cc01_regime_watch.RUNS` (un test li incrocia, P1). max_age 30h =
|
||||
# un giro mancato + margine; timbri di esecuzione -> open_labeled=False.
|
||||
MonitorSpec("cc01_regime_watch", "cc01_regime", "runs.jsonl", 24.0, max_age_h=30.0,
|
||||
gate="trigger di regime CC01: WARN>=10% / ALERT>=15% funding 30g (riapre il dossier)",
|
||||
open_labeled=False),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
"""cc01_regime_watch registra ogni giro misurato, e monitor_health lo sorveglia (#12).
|
||||
|
||||
PERCHE' ESISTE. Dal 03/09 al 16/09/2026 lo script e' morto 14 notti di fila con
|
||||
ModuleNotFoundError (#10) e nessuno se n'e' accorto: `cron_daily.sh` va avanti dopo un errore,
|
||||
e `monitor_health` guarda solo le serie che i monitor scrivono — cc01 non ne scriveva. Ora ogni
|
||||
giro misurato lascia una riga, e un cc01 fermo diventa FERMO come ogni altro sorvegliante.
|
||||
|
||||
Niente rete: il funding di Hyperliquid e' finto.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from src.live.monitor_health import MONITORS, check_all # noqa: E402
|
||||
|
||||
|
||||
def _carica():
|
||||
sp = importlib.util.spec_from_file_location("cc01_regime_watch",
|
||||
ROOT / "scripts" / "live" / "cc01_regime_watch.py")
|
||||
m = importlib.util.module_from_spec(sp)
|
||||
sp.loader.exec_module(m)
|
||||
return m
|
||||
|
||||
|
||||
W = _carica()
|
||||
SPEC = next((m for m in MONITORS if m.name == "cc01_regime_watch"), None)
|
||||
|
||||
|
||||
def _funding_annuo(ann: float, ore: int = 24 * 35):
|
||||
"""Una serie oraria costante che annualizza a `ann`."""
|
||||
return lambda coin, days=35: np.full(ore, ann / W.HOURS_PER_YEAR)
|
||||
|
||||
|
||||
def test_e_registrato_in_monitor_health_sul_file_che_scrive():
|
||||
# P1: il bersaglio della sorveglianza si deriva dal codice sorvegliato, non si ridichiara
|
||||
assert SPEC is not None, "cc01_regime_watch non e' in monitor_health.MONITORS"
|
||||
assert ROOT / "data" / SPEC.directory / SPEC.series == W.RUNS
|
||||
assert SPEC.cadence_h == 24.0 and SPEC.open_labeled is False and SPEC.max_age_h <= 30.0
|
||||
|
||||
|
||||
def test_un_giro_misurato_scrive_una_riga(tmp_path, monkeypatch):
|
||||
runs = tmp_path / "runs.jsonl"
|
||||
monkeypatch.setattr(W, "RUNS", runs)
|
||||
monkeypatch.setattr(W, "fetch_recent_funding", _funding_annuo(0.12))
|
||||
W.main()
|
||||
righe = [json.loads(r) for r in runs.read_text().splitlines()]
|
||||
assert len(righe) == 1
|
||||
r = righe[0]
|
||||
ora_ms = datetime.now(timezone.utc).timestamp() * 1000
|
||||
assert abs(r["ts"] - ora_ms) < 60_000, "ts in millisecondi epoch, come legge monitor_health"
|
||||
assert r["stato"] == "WARN"
|
||||
assert abs(r["BTC"]["ann30"] - 0.12) < 1e-9 and abs(r["ETH"]["ann7"] - 0.12) < 1e-9
|
||||
|
||||
|
||||
def test_un_giro_senza_dati_non_scrive_e_non_dice_quiet(tmp_path, monkeypatch, capsys):
|
||||
# un giro che non ha misurato non e' una barra: se scrivesse, un cc01 cieco resterebbe «OK»
|
||||
runs = tmp_path / "runs.jsonl"
|
||||
monkeypatch.setattr(W, "RUNS", runs)
|
||||
monkeypatch.setattr(W, "fetch_recent_funding", _funding_annuo(0.12, ore=10))
|
||||
W.main()
|
||||
assert not runs.exists()
|
||||
out = capsys.readouterr().out
|
||||
assert "regime QUIET" not in out and "nessuna riga registrata" in out
|
||||
|
||||
|
||||
def test_monitor_health_legge_le_righe_di_cc01(tmp_path, monkeypatch):
|
||||
# integrazione: le righe scritte da cc01 sono barre per monitor_health, e un cc01 fermo e' FERMO
|
||||
runs = tmp_path / "data" / SPEC.directory / SPEC.series
|
||||
runs.parent.mkdir(parents=True)
|
||||
monkeypatch.setattr(W, "RUNS", runs)
|
||||
monkeypatch.setattr(W, "fetch_recent_funding", _funding_annuo(0.05))
|
||||
W.main()
|
||||
ieri = json.loads(runs.read_text())
|
||||
ieri["ts"] -= 24 * 3600 * 1000
|
||||
runs.write_text(json.dumps(ieri) + "\n" + runs.read_text())
|
||||
|
||||
adesso = datetime.now(timezone.utc)
|
||||
stato = {r["name"]: r for r in check_all(tmp_path, now=adesso + timedelta(minutes=1), monitors=(SPEC,))}
|
||||
assert stato["cc01_regime_watch"]["status"] == "OK"
|
||||
fermo = check_all(tmp_path, now=adesso + timedelta(hours=31), monitors=(SPEC,))[0]
|
||||
assert fermo["status"] == "FERMO"
|
||||
Reference in New Issue
Block a user