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)
|
# battuta di cuore del collettore catena (stato runtime, come gli altri monitor)
|
||||||
data/chain_collect/
|
data/chain_collect/
|
||||||
data/vrp_f_watch/
|
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)
|
# log esecuzioni del book live (stato runtime, contiene fill/fee del conto reale)
|
||||||
data/live/
|
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
|
annunciati), che il `:07` beccava — 4 volte in 63 giorni. **GTAA01, XS01, VRP01, XSR01 NON sono
|
||||||
nel book live.**
|
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`:
|
**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
|
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.
|
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 ·
|
monitor_health · collect_chain · paper_* · trades_db ·
|
||||||
journal · analista
|
journal · analista
|
||||||
scripts/cron_{book,daily,chain,opt_snapshot,vol_term,review}.sh
|
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/memory/ → LA MEMORIA (indice in testa a questo file)
|
||||||
docs/research/ → BRIEF-0822 · RESULTS-0822 (§1-77) · SPEC-scale-key
|
docs/research/ → BRIEF-0822 · RESULTS-0822 (§1-77) · SPEC-scale-key
|
||||||
docs/diary/YYYY-MM-DD.md → una voce per esperimento (146)
|
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).
|
che resta comunque vincolato a ~$20k+ di capitale e venue con funding eseguibile).
|
||||||
|
|
||||||
Uso: `uv run python scripts/live/cc01_regime_watch.py`
|
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
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -28,6 +35,7 @@ import requests
|
|||||||
ROOT = Path(__file__).resolve().parents[2]
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
sys.path.insert(0, str(ROOT))
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
|
RUNS = ROOT / "data" / "cc01_regime" / "runs.jsonl"
|
||||||
HL_INFO = "https://api.hyperliquid.xyz/info"
|
HL_INFO = "https://api.hyperliquid.xyz/info"
|
||||||
HOURS_PER_YEAR = 24 * 365
|
HOURS_PER_YEAR = 24 * 365
|
||||||
WARN_ANN = 0.10 # >=10% ann. su media 30g -> WARN
|
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(" CC01 REGIME WATCH — funding medio 1-mese (Hyperliquid, orario, tokenless)")
|
||||||
print("=" * 78)
|
print("=" * 78)
|
||||||
worst = "QUIET"
|
worst = "QUIET"
|
||||||
|
misure = {}
|
||||||
for coin in ("BTC", "ETH"):
|
for coin in ("BTC", "ETH"):
|
||||||
f = fetch_recent_funding(coin)
|
f = fetch_recent_funding(coin)
|
||||||
if len(f) < 24 * 7:
|
if len(f) < 24 * 7:
|
||||||
@@ -86,8 +95,16 @@ def main() -> None:
|
|||||||
"WARN" if ann30 >= WARN_ANN else "QUIET")
|
"WARN" if ann30 >= WARN_ANN else "QUIET")
|
||||||
if state == "ALERT" or (state == "WARN" and worst == "QUIET"):
|
if state == "ALERT" or (state == "WARN" and worst == "QUIET"):
|
||||||
worst = state
|
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(f" {coin}: media 30g {ann30:+7.1%} ann. media 7g {ann7:+7.1%} ann. -> {state}")
|
||||||
print("-" * 78)
|
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":
|
if worst == "ALERT":
|
||||||
print(" >>> ALERT CC01: funding 30g >= 15% ann. — regime ricco SOSTENUTO.")
|
print(" >>> ALERT CC01: funding 30g >= 15% ann. — regime ricco SOSTENUTO.")
|
||||||
print(" Riaprire il dossier cash-and-carry (vincoli invariati: ~$20k+,")
|
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,
|
MonitorSpec("cuscino_watch", "live", "cuscino_watch.jsonl", 1.0, max_age_h=3.0,
|
||||||
gate="cuscino USDC di regolamento: allerta + riconversione automatica",
|
gate="cuscino USDC di regolamento: allerta + riconversione automatica",
|
||||||
open_labeled=False),
|
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