diff --git a/config/live.json b/config/live.json
index 7f3841f..1cf4be4 100644
--- a/config/live.json
+++ b/config/live.json
@@ -5,5 +5,7 @@
"max_notional_per_asset_usd": 300,
"max_notional_per_asset_frac": 0.5,
"min_order_usd": 5,
- "disaster_sl_pct": 0.30
+ "disaster_sl_pct": 0.3,
+ "_nota_stale": "Staleness-gate (2026-07-25): se l'ultima barra del feed certificato e' piu' vecchia di max_data_age_days, book_execute NON invia ordini e allerta su Telegram. Il 2026-07-14 il book compro' ETH con il feed fermo da 6 giorni (conto online e posizione leggibile -> gli altri due gate non scattavano). Follow-up raccomandato nel diario 2026-07-15-feed-freeze, ora cablato.",
+ "max_data_age_days": 2
}
diff --git a/scripts/cron_daily.sh b/scripts/cron_daily.sh
index bc90558..8772253 100755
--- a/scripts/cron_daily.sh
+++ b/scripts/cron_daily.sh
@@ -22,5 +22,7 @@ mkdir -p logs
for i in $(seq 1 25); do (echo > /dev/tcp/127.0.0.1/4002) >/dev/null 2>&1 && break; sleep 6; done
uv run --with ib_async python scripts/research/fetch_ib_equities.py --only SPY,QQQ,IWM,TLT,GLD,HYG # ETF GTAA freschi
uv run python scripts/live/paper_combo.py # avanza paper combo (forward-only)
+ # --- REPORT GIORNALIERO Telegram (sola lettura): rompe il silenzio quando il libro e' flat ---
+ uv run python scripts/live/telegram_daily.py # stato conto + perche' non opera + gate
echo "===== done $(date -u '+%H:%M:%SZ') ====="
} >> logs/cron_daily.log 2>&1
diff --git a/scripts/live/book_execute.py b/scripts/live/book_execute.py
index 87ada7b..d099398 100644
--- a/scripts/live/book_execute.py
+++ b/scripts/live/book_execute.py
@@ -43,9 +43,23 @@ def load_config() -> dict:
cfg.setdefault("max_notional_per_asset_usd", 300.0)
cfg.setdefault("min_order_usd", 5.0)
cfg.setdefault("disaster_sl_pct", 0.30)
+ cfg.setdefault("max_data_age_days", 2.0)
return cfg
+def _data_age_days(last_data) -> float | None:
+ """Eta' in giorni dell'ultima barra del feed certificato. None se non interpretabile
+ (trattata come stantia: meglio non operare che operare su una data che non so leggere)."""
+ if last_data in (None, ""):
+ return None
+ try:
+ ts = pd.Timestamp(last_data)
+ ts = ts.tz_localize("UTC") if ts.tz is None else ts.tz_convert("UTC")
+ return float((pd.Timestamp.now(tz="UTC") - ts).total_seconds() / 86400.0)
+ except Exception:
+ return None
+
+
def log_event(rec: dict):
LOG_DIR.mkdir(parents=True, exist_ok=True)
with open(LOG, "a") as f:
@@ -91,6 +105,27 @@ def _run():
"nota": "salto l'esecuzione, non opero a cieco"})
return
+ stale_days = _data_age_days(r.get("last_data"))
+ # .get col default: un chiamante che passa una config senza la chiave deve ricadere sulla
+ # soglia sicura, non sollevare KeyError dentro il percorso d'esecuzione con soldi veri.
+ max_age = float(cfg.get("max_data_age_days", 2.0))
+ if stale_days is None or stale_days > max_age:
+ # FEED STANTIO -> non eseguo. Il 2026-07-14 il book ha comprato ETH con l'ultima barra
+ # ferma al 07-08 (feed congelato 6 giorni, diario 2026-07-15-feed-freeze): il conto era
+ # online e la posizione leggibile, quindi i due gate esistenti NON scattavano. Il segnale
+ # TP01 viene dal feed su disco: se e' vecchio, si opera alla cieca su dati morti.
+ # Il disaster-SL on-book resta la rete di sicurezza su eventuali posizioni aperte.
+ eta = "ignota" if stale_days is None else f"{stale_days:.0f}g"
+ print(f" ๐ FEED STANTIO (ultima barra {r.get('last_data')}, eta' {eta} > {max_age:.0f}g)"
+ " -> NON eseguo su dati morti.")
+ print(" Sbloccare con: uv run python scripts/analysis/rebuild_history.py --asset BTC ETH")
+ if do_execute:
+ notify("๐ BOOK LIVE โ FEED STANTIO, esecuzione saltata",
+ {"ultima_barra": str(r.get("last_data")), "eta": eta,
+ "soglia": f"{max_age:.0f}g",
+ "azione": "rebuild_history.py --asset BTC ETH"})
+ return
+
if r.get("eq_fallback"): # equity reale non leggibile -> sizing su paper_cap
print(f" โ ๏ธ EQUITY FALLBACK (sizing su paper_cap, NON blocco): {r['eq_fallback']}")
if do_execute: # solo diagnostica: l'hard-cap $/asset limita il downside
diff --git a/scripts/live/telegram_daily.py b/scripts/live/telegram_daily.py
new file mode 100644
index 0000000..68bacbd
--- /dev/null
+++ b/scripts/live/telegram_daily.py
@@ -0,0 +1,150 @@
+"""REPORT GIORNALIERO su Telegram โ stato del conto reale, cosa sta facendo il sistema e perche'.
+
+PERCHE' ESISTE (2026-07-25). Gli alert Telegram esistenti (`src/live/notifier`) scattano SOLO su
+ordine eseguito o errore. Con il libro flat โ che e' lo stato normale e corretto quando il trend e'
+giu' โ questo significa **silenzio per settimane**, indistinguibile da un sistema morto. Questo
+report rompe il silenzio ogni giorno e dice tre cose: dove sta il conto, perche' il sistema non
+opera, e quanto manca perche' operi.
+
+NON invia ordini, non tocca posizioni, non legge segreti oltre a quelli gia' usati dall'esecutore.
+E' sola lettura: se fallisce, l'esecuzione oraria del libro non ne risente in alcun modo.
+
+ uv run python scripts/live/telegram_daily.py # calcola e invia
+ uv run python scripts/live/telegram_daily.py --dry-run # stampa e basta, non invia
+"""
+from __future__ import annotations
+
+import json
+import sys
+from datetime import date, datetime, timezone
+from pathlib import Path
+
+import numpy as np
+import pandas as pd
+
+ROOT = Path(__file__).resolve().parents[2]
+sys.path.insert(0, str(ROOT))
+
+from src.live.notifier import send
+
+HORIZONS = (30, 90, 180)
+GATES = [("STATARB-RESID", date(2026, 9, 27), "data/paper_statarb/returns.jsonl", "net_modeled"),
+ ("XSR01", date(2026, 10, 23), "data/paper_xsr/returns.jsonl", "net_modeled")]
+
+
+def _fmt_usd(x: float) -> str:
+ return f"${x:,.2f}"
+
+
+def trend_state() -> list[str]:
+ """Stato TSMOM per asset + quanto manca all'accensione (2 orizzonti su 3 positivi).
+
+ NB convenzione TP01: la direzione e' la media dei SEGNI (-1/+1) sugli orizzonti, poi clippata
+ a >=0 (long-flat). Con 1 orizzonte su 3 positivo la media e' -0.33 -> target 0. Serve quindi
+ che DUE orizzonti siano positivi perche' il libro si accenda: non basta il piu' breve."""
+ from src.data.downloader import load_data
+ out = []
+ for a in ("BTC", "ETH"):
+ try:
+ d = load_data(a, "1h")
+ s = pd.Series(d["close"].astype(float).values,
+ index=pd.to_datetime(d["timestamp"], unit="ms", utc=True))
+ c = s.resample("1D").last().dropna().values
+ if len(c) < max(HORIZONS) + 2:
+ continue
+ px = float(c[-1])
+ refs = {h: float(c[-1 - h]) for h in HORIZONS}
+ sg = {h: (1 if px > refs[h] else -1) for h in HORIZONS}
+ m = float(np.mean(list(sg.values())))
+ tgt = max(0.0, m)
+ line = f" {a} {px:,.0f} ยท segni " + "/".join(f"{sg[h]:+d}" for h in HORIZONS) + \
+ f" โ TP01 {tgt:.2f}"
+ neg = [(h, refs[h]) for h in HORIZONS if sg[h] < 0]
+ if neg and tgt == 0.0:
+ h, r = min(neg, key=lambda t: t[1])
+ line += f"\n accensione a {r:,.0f} ({(r/px-1)*100:+.1f}%)"
+ out.append(line)
+ except Exception as e:
+ out.append(f" {a}: stato trend non calcolabile ({type(e).__name__})")
+ return out
+
+
+def book_state() -> list[str]:
+ """Conto reale, posizioni e target netto correnti. Sola lettura."""
+ try:
+ from src.live.book import book_report
+ r = book_report()
+ eq = r.get("equity")
+ lines = [f" equity {_fmt_usd(eq)}" if eq else " equity non leggibile"]
+ for a in r.get("assets", []):
+ pos, net = a.get("position_usd", 0.0), a.get("net_target", 0.0)
+ stato = "flat" if abs(pos) < 1 and abs(net) < 1 else f"pos {_fmt_usd(pos)} โ target {_fmt_usd(net)}"
+ lines.append(f" {a['asset']}: {stato} (TP {a.get('tp_frac', 0):+.2f} ยท SKH {a.get('skh_sign', 0):+d})")
+ if r.get("skh_error"):
+ lines.append(f" โ ๏ธ SKH feed KO: {r['skh_error']}")
+ return lines
+ except Exception as e:
+ return [f" โ ๏ธ stato libro non leggibile: {type(e).__name__}: {e}"]
+
+
+def monitors() -> list[str]:
+ out = []
+ for name, dec, path, key in GATES:
+ p = ROOT / path
+ giorni = (dec - date.today()).days
+ if not p.exists():
+ out.append(f" {name}: in attesa della prima barra ยท gate {dec} (โ{giorni}g)")
+ continue
+ try:
+ rows = [json.loads(x) for x in p.read_text().splitlines() if x.strip()]
+ if not rows:
+ out.append(f" {name}: 0 barre ยท gate {dec} (โ{giorni}g)")
+ continue
+ r = np.array([x[key] for x in rows], float)
+ sh = float(r.mean() / r.std() * np.sqrt(365)) if r.std() > 0 else 0.0
+ tot = float(np.prod(1 + r) - 1) * 100
+ out.append(f" {name}: {len(rows)}g ยท {tot:+.2f}% ยท Sh {sh:+.2f} ยท gate {dec} (โ{giorni}g)")
+ except Exception:
+ out.append(f" {name}: log illeggibile ยท gate {dec} (โ{giorni}g)")
+ return out
+
+
+def last_trade() -> str:
+ p = ROOT / "data" / "live" / "book_executions.jsonl"
+ if not p.exists():
+ return " nessun ordine registrato"
+ try:
+ rows = [json.loads(x) for x in p.read_text().splitlines() if x.strip()]
+ if not rows:
+ return " nessun ordine registrato"
+ t = rows[-1]
+ d = datetime.fromisoformat(t["ts_utc"]).replace(tzinfo=timezone.utc)
+ giorni = (datetime.now(timezone.utc) - d).days
+ return (f" ultimo: {t['ts_utc'][:10]} ({giorni}g fa) {t['asset']} {t['action']} "
+ f"@ {t.get('price')} ยท totale ordini: {len(rows)}")
+ except Exception:
+ return " ledger ordini illeggibile"
+
+
+def build() -> str:
+ oggi = datetime.now(timezone.utc).strftime("%d/%m %H:%M UTC")
+ L = [f"๐ Book Deribit โ {oggi}", "", "Conto"]
+ L += book_state()
+ L += ["", "Perche' non opera"]
+ L += trend_state()
+ L += ["", "Ordini", last_trade()]
+ L += ["", "Monitor in osservazione"]
+ L += monitors()
+ return "\n".join(L)
+
+
+def main() -> None:
+ txt = build()
+ if "--dry-run" in sys.argv[1:]:
+ print(txt.replace("", "").replace("", ""))
+ return
+ print("inviato" if send(txt) else "NON inviato (config Telegram assente o rete KO)")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/test_book_live.py b/tests/test_book_live.py
index c0aaf4c..9fb5c5c 100644
--- a/tests/test_book_live.py
+++ b/tests/test_book_live.py
@@ -8,6 +8,14 @@ import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[1]
+
+# Data FRESCA per i report finti: dal 2026-07-25 book_execute ha uno staleness-gate bloccante
+# (feed piu' vecchio di 2 giorni -> non esegue). Questi test riguardano skh_error / pos_error /
+# eq_fallback, NON la staleness: con una data fissa marcirebbero appena supera la soglia.
+# Lo staleness-gate ha i suoi test dedicati in tests/test_book_staleness_gate.py.
+def _fresh_bar() -> str:
+ import pandas as _pd
+ return str(_pd.Timestamp.now(tz="UTC").normalize().date())
sys.path.insert(0, str(PROJECT_ROOT))
from src.live.book import W_SKH, W_TP01, book_net_target, build_book_order
@@ -299,7 +307,7 @@ def test_book_execute_surfaces_skh_error(monkeypatch, capsys):
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
canned = dict(
- last_data="2026-07-01", online=True, real_equity=600.0, equity=600.0, eq_basis="test",
+ last_data=_fresh_bar(), online=True, real_equity=600.0, equity=600.0, eq_basis="test",
cap_per_asset=300.0, skh_error="RuntimeError: feed 5m giu",
assets=[dict(asset="BTC", instrument="BTC_USDC-PERPETUAL", tp_frac=0.0, skh_sign=0,
skh_state="flat", net_target=0.0, position_usd=0.0, mark=60000.0, order=None)],
@@ -361,7 +369,7 @@ def test_book_execute_halts_on_unreadable_position(monkeypatch, capsys):
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
canned = dict(
- last_data="2026-07-01", online=True, real_equity=598.0, equity=598.0, eq_basis="mainnet USDC",
+ last_data=_fresh_bar(), online=True, real_equity=598.0, equity=598.0, eq_basis="mainnet USDC",
cap_per_asset=300.0, skh_error=None,
pos_error="posizione non leggibile, assunta FLAT: BTC (RuntimeError: api 500)",
assets=[dict(asset="BTC", instrument="BTC_USDC-PERPETUAL", tp_frac=1.0, skh_sign=1,
@@ -422,7 +430,7 @@ def test_book_execute_eq_fallback_warns_but_proceeds(monkeypatch, capsys):
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
canned = dict(
- last_data="2026-07-01", online=True, real_equity=None, equity=2000.0,
+ last_data=_fresh_bar(), online=True, real_equity=None, equity=2000.0,
eq_basis="paper capital (ipotetico)", cap_per_asset=300.0, skh_error=None, pos_error=None,
eq_fallback="equity reale non leggibile (conto flat) -> sizing su paper_cap $2,000",
assets=[dict(asset="BTC", instrument="BTC_USDC-PERPETUAL", tp_frac=0.0, skh_sign=0,
diff --git a/tests/test_book_staleness_gate.py b/tests/test_book_staleness_gate.py
new file mode 100644
index 0000000..f28baad
--- /dev/null
+++ b/tests/test_book_staleness_gate.py
@@ -0,0 +1,120 @@
+"""Lock dello STALENESS-GATE dell'esecutore live (cablato 2026-07-25).
+
+PERCHE'. Il 2026-07-14 alle 14:00 UTC il book ha comprato ETH ($75) con l'ultima barra del feed
+certificato ferma al 2026-07-08: feed congelato da 6 giorni (diario 2026-07-15-feed-freeze). I due
+gate esistenti non potevano vederlo โ il conto ERA online e la posizione ERA leggibile: proteggono
+dai problemi di CONTO, non da un feed morto. Il diario raccomandava un alert; qui il gate e'
+BLOCCANTE, coerente con gli altri due (`non opero a cieco`).
+
+Questi test bloccano la soglia e il comportamento del calcolo d'eta'. Non toccano la rete.
+"""
+from __future__ import annotations
+
+import json
+import sys
+from pathlib import Path
+
+import pandas as pd
+import pytest
+
+ROOT = Path(__file__).resolve().parents[1]
+sys.path.insert(0, str(ROOT))
+sys.path.insert(0, str(ROOT / "scripts" / "live"))
+
+book_execute = pytest.importorskip("book_execute")
+
+
+def test_soglia_presente_nel_config_live():
+ cfg = json.loads((ROOT / "config" / "live.json").read_text())
+ assert cfg.get("max_data_age_days") == 2, "soglia di staleness assente o cambiata"
+ assert cfg.get("execution_enabled") is True
+ assert cfg.get("disaster_sl_pct") == 0.30
+
+
+def test_default_sicuro_se_il_config_non_la_indica():
+ cfg = book_execute.load_config()
+ assert cfg["max_data_age_days"] > 0
+
+
+def test_eta_barra_fresca_e_sotto_soglia():
+ oggi = pd.Timestamp.now(tz="UTC").normalize()
+ eta = book_execute._data_age_days(oggi)
+ assert eta is not None and eta < 2.0
+
+
+def test_eta_del_caso_reale_del_14_luglio_supera_la_soglia():
+ """Il caso che ha motivato il gate: barra 07-08, esecuzione il 07-14 -> 6 giorni."""
+ eta = (pd.Timestamp("2026-07-14 14:00", tz="UTC") - pd.Timestamp("2026-07-08", tz="UTC"))
+ assert eta.total_seconds() / 86400.0 > 2.0, "il gate non avrebbe fermato l'incidente reale"
+
+
+def test_data_illeggibile_e_trattata_come_stantia():
+ """None = non so leggere la data -> meglio non operare che operare su ignoto."""
+ for cattivo in (None, "", "non-una-data", object()):
+ assert book_execute._data_age_days(cattivo) is None
+
+
+def test_funzionale_feed_stantio_blocca_lesecuzione_e_allerta(monkeypatch, capsys):
+ """Il test che conta: conto ONLINE, posizione LEGGIBILE, ordine PRESENTE โ cioe' la situazione
+ esatta del 2026-07-14 โ ma feed vecchio. Non deve partire nessun ordine e deve arrivare l'alert.
+ """
+ import importlib.util
+ spec = importlib.util.spec_from_file_location(
+ "book_execute_iso", ROOT / "scripts" / "live" / "book_execute.py")
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod)
+
+ canned = dict(
+ last_data="2026-07-08", online=True, real_equity=598.0, equity=598.0,
+ eq_basis="mainnet USDC", cap_per_asset=300.0, skh_error=None, pos_error=None,
+ assets=[dict(asset="ETH", instrument="ETH_USDC-PERPETUAL", tp_frac=0.0, skh_sign=1,
+ skh_state="flat", net_target=75.0, position_usd=0.0, mark=1869.0,
+ order=dict(side="buy"))],
+ orders=[dict(side="buy")],
+ )
+ alerts = []
+ monkeypatch.setattr(mod, "book_report", lambda **k: canned)
+ monkeypatch.setattr(mod, "notify", lambda title, det=None: alerts.append((title, det)))
+ monkeypatch.setattr(mod, "load_config", lambda: dict(
+ execution_enabled=True, min_order_usd=5.0, disaster_sl_pct=0.30, max_data_age_days=2.0))
+ monkeypatch.setattr(sys, "argv", ["book_execute.py", "--execute"]) # ARMATO + execute
+
+ def boom(*a, **k):
+ raise AssertionError("DeribitTrader costruito: il gate NON ha fermato il feed stantio")
+ monkeypatch.setattr(mod, "DeribitTrader", boom)
+
+ mod._run() # non deve sollevare: il gate esce prima
+
+ out = capsys.readouterr().out
+ assert "FEED STANTIO" in out
+ assert "rebuild_history" in out, "il messaggio deve dire come sbloccare"
+ assert any("FEED STANTIO" in t for t, _ in alerts), "alert Telegram non inviato"
+
+
+def test_config_senza_la_chiave_non_esplode(monkeypatch, capsys):
+ """Una config priva di max_data_age_days deve ricadere sul default, non sollevare KeyError
+ dentro il percorso con soldi veri."""
+ import importlib.util
+ spec = importlib.util.spec_from_file_location(
+ "book_execute_iso2", ROOT / "scripts" / "live" / "book_execute.py")
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod)
+ canned = dict(last_data=str(pd.Timestamp.now(tz="UTC").normalize().date()), online=True,
+ real_equity=598.0, equity=598.0, eq_basis="t", cap_per_asset=300.0,
+ skh_error=None, pos_error=None, assets=[], orders=[])
+ monkeypatch.setattr(mod, "book_report", lambda **k: canned)
+ monkeypatch.setattr(mod, "notify", lambda title, det=None: None)
+ monkeypatch.setattr(mod, "load_config",
+ lambda: dict(execution_enabled=False, min_order_usd=5.0, disaster_sl_pct=0.3))
+ monkeypatch.setattr(sys, "argv", ["book_execute.py"])
+ mod._run() # nessun KeyError
+ assert "FEED STANTIO" not in capsys.readouterr().out
+
+
+def test_il_gate_blocca_prima_di_creare_il_trader():
+ """Ordine dei controlli: il blocco per feed stantio deve stare PRIMA di DeribitTrader(),
+ altrimenti si aprirebbe comunque una sessione autenticata verso il conto reale."""
+ src = (ROOT / "scripts" / "live" / "book_execute.py").read_text()
+ i_gate = src.index("FEED STANTIO")
+ i_trader = src.index("DeribitTrader() if do_execute")
+ assert i_gate < i_trader, "il gate di staleness deve precedere la creazione del trader"
diff --git a/tests/test_telegram_daily.py b/tests/test_telegram_daily.py
new file mode 100644
index 0000000..135a68d
--- /dev/null
+++ b/tests/test_telegram_daily.py
@@ -0,0 +1,54 @@
+"""Lock del report giornaliero Telegram (in cron dal 2026-07-25).
+
+Gira in produzione dentro cron_daily.sh: se rompe, non deve rompere nulla d'altro e non deve
+MAI inviare ordini. I test bloccano proprio questo: e' sola lettura, tollera i pezzi mancanti,
+e non invia niente in dry-run.
+"""
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+import pytest
+
+ROOT = Path(__file__).resolve().parents[1]
+sys.path.insert(0, str(ROOT))
+sys.path.insert(0, str(ROOT / "scripts" / "live"))
+
+telegram_daily = pytest.importorskip("telegram_daily")
+
+
+def test_build_non_solleva_e_ha_le_sezioni():
+ txt = telegram_daily.build()
+ assert isinstance(txt, str) and len(txt) > 50
+ for sezione in ("Conto", "Perche' non opera", "Ordini", "Monitor in osservazione"):
+ assert sezione in txt, f"sezione mancante: {sezione}"
+
+
+def test_nessuna_scrittura_ne_ordine():
+ """Il modulo non deve importare l'esecutore ne' avere funzioni che inviano ordini."""
+ src = (ROOT / "scripts" / "live" / "telegram_daily.py").read_text()
+ for vietato in ("DeribitTrader", "book_execute", "--execute", "place_order", "buy(", "sell("):
+ assert vietato not in src, f"il report non deve poter operare: trovato {vietato!r}"
+
+
+def test_tollera_i_log_mancanti(monkeypatch, tmp_path):
+ """Se i log dei monitor non esistono, il report deve degradare, non esplodere."""
+ monkeypatch.setattr(telegram_daily, "ROOT", tmp_path)
+ righe = telegram_daily.monitors()
+ assert len(righe) == len(telegram_daily.GATES)
+ assert all(isinstance(r, str) and r.strip() for r in righe)
+ assert telegram_daily.last_trade().strip()
+
+
+def test_gate_coerenti_con_i_pre_registrati():
+ """Le date qui devono coincidere con i gate pre-registrati negli script di decisione."""
+ d = dict((n, x) for n, x, _, _ in telegram_daily.GATES)
+ assert str(d["STATARB-RESID"]) == "2026-09-27"
+ assert str(d["XSR01"]) == "2026-10-23"
+
+
+def test_convenzione_trend_e_documentata():
+ """La convenzione media-dei-segni (non frazione) e' la ragione per cui TP01 e' 0 con 1/3 su:
+ se sparisce dal codice, il report mentirebbe sul 'quanto manca'."""
+ assert "media dei SEGNI" in telegram_daily.trend_state.__doc__