cuscino_watch: riacquisto automatico di USDE con isteresi (ECCEDENTE), bande in config, escape HTML nel sink di notify
Decisione dell'operatore 10/09 («riportarla a quota in autonomia»). Verificato da revisione fable (10 segnalazioni, 9 applicate; loop numerico su usde_convert.piano: nessuna sequenza vendita→acquisto). - quarto stato ECCEDENTE: slack > cuscino_riacquisto_frac (0,40) x cuscino => acquisto di USDE fino allo stesso bersaglio della vendita, q* = 1 - 0,30 x (1 + cuscino_margine_frac 0,20) = 0,64. Isteresi [0 ; 0,40] x cuscino con bersaglio unico 0,20: per oscillare servono +-8,6% di equity USDC (~$380); un bonifico alza lo slack di 0,7 x importo e sopra ~9% dell'equity ricompra da solo. - le tre frazioni (preavviso/margine/riacquisto) in config/live.json sezione usde, lette da usde.bande_cuscino() che verifica l'ordine; quota_target 0,70 TOLTO (slack zero per costruzione). - notifier.notify: html.escape su titolo e valori — la prima allerta vera (13:53Z) era andata persa per un '<' nudo; allerta_errore registrato nel record (P3). - da ECCEDENTE un fallimento si ritenta ogni 24h (non 4 Telegram/giorno per sempre); bande fuori ordine => BLIND con motivo, non un traceback ingoiato dal cron. - CLAUDE.md §1/§4/§5.17, config _nota_usde/_nota_cuscino, diario. 0,64 e' una DERIVAZIONE dell'autore, non la quota decisa il 30/08 (0,70): dichiarato, da confermare. - test: 1070 (+8 netti). Fixes #7 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kqvff47UBGeYfj1QeN4zE
This commit is contained in:
@@ -87,9 +87,15 @@ def notify(title: str, data: dict | None = None, tentativi: int = 1) -> bool:
|
||||
ver = f" <code>v{APP_VERSION}</code>"
|
||||
except Exception:
|
||||
ver = ""
|
||||
lines = [f"<b>{title}</b>{ver}"]
|
||||
# Telegram e' in parse_mode=HTML: un '<' o '&' nudo in un valore fa fallire l'INTERO invio
|
||||
# (HTTP 400, "can't parse entities"). Successo il 2026-09-10 13:53Z: la prima allerta di
|
||||
# cuscino_watch conteneva «(< 10% del cuscino)» ed e' andata persa. L'escape sta QUI, nel sink,
|
||||
# perche' nessuno dei 45 chiamanti passa tag di proposito (verificato con grep) e un chiamante
|
||||
# nuovo non deve ricordarsene. `send()` resta grezzo per chi costruisce il proprio HTML.
|
||||
import html
|
||||
lines = [f"<b>{html.escape(str(title), quote=False)}</b>{ver}"]
|
||||
for k, v in (data or {}).items():
|
||||
lines.append(f" {k}: {v}")
|
||||
lines.append(f" {html.escape(str(k), quote=False)}: {html.escape(str(v), quote=False)}")
|
||||
return send("\n".join(lines), tentativi=tentativi)
|
||||
|
||||
|
||||
|
||||
+22
-3
@@ -36,6 +36,10 @@ _DEFAULTS = {
|
||||
"venue_cap_misurato": "2026-08-31",
|
||||
"depeg_warn": 0.99,
|
||||
"depeg_crit": 0.95,
|
||||
# bande del cuscino USDC (frazioni del cuscino richiesto), issue #7 — vedi `_nota_cuscino`
|
||||
"cuscino_preavviso_frac": 0.10,
|
||||
"cuscino_margine_frac": 0.20,
|
||||
"cuscino_riacquisto_frac": 0.40,
|
||||
}
|
||||
|
||||
|
||||
@@ -123,9 +127,24 @@ def cuscino_richiesto_usd(equity_tot: float) -> tuple[float, str]:
|
||||
return equity_tot * f, f"{come} su ${equity_tot:,.2f}"
|
||||
|
||||
|
||||
def quota_ripristino(margine_frac: float) -> float:
|
||||
def bande_cuscino(c: dict | None = None) -> dict:
|
||||
"""PURA sulla config. Le tre frazioni del cuscino che governano cuscino_watch, con l'ordine
|
||||
verificato: 0 < preavviso < margine < riacquisto. Fuori ordine la banda di isteresi non
|
||||
esiste (vendita e acquisto si inseguirebbero): si solleva, non si indovina."""
|
||||
c = c or cfg()
|
||||
b = dict(preavviso=float(c["cuscino_preavviso_frac"]), margine=float(c["cuscino_margine_frac"]),
|
||||
riacquisto=float(c["cuscino_riacquisto_frac"]))
|
||||
if not (0.0 < b["preavviso"] < b["margine"] < b["riacquisto"]):
|
||||
raise ValueError(f"bande del cuscino fuori ordine (serve 0 < preavviso < margine < riacquisto): {b}")
|
||||
return b
|
||||
|
||||
|
||||
def quota_ripristino(margine_frac: float | None = None) -> float:
|
||||
"""PURA sulla config. Quota USDE che lascia il cuscino PIU' un margine (frazione del cuscino):
|
||||
e' il bersaglio della riconversione automatica quando il cuscino e' scoperto. Il margine serve
|
||||
a non tornare sul bordo alla prima ora in perdita (P6: la persistenza e' latenza)."""
|
||||
e' il bersaglio UNICO di ogni conversione automatica — vendita quando il cuscino e' scoperto,
|
||||
acquisto quando lo slack eccede la banda. Il margine serve a non tornare sul bordo alla prima
|
||||
ora in perdita (P6: la persistenza e' latenza). Default: `cuscino_margine_frac` di config."""
|
||||
if margine_frac is None:
|
||||
margine_frac = bande_cuscino()["margine"]
|
||||
f, _ = frazione_cuscino()
|
||||
return max(0.0, 1.0 - f * (1.0 + margine_frac))
|
||||
|
||||
Reference in New Issue
Block a user