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:
@@ -14,24 +14,33 @@ trade chiuso. Il cuscino richiesto e' DERIVATO da config/live.json e src/live/bo
|
||||
che `usde_convert` applica a ogni piano — un sorvegliante che ridichiara il proprio bersaglio non
|
||||
sta controllando niente (P1, 5 occorrenze nel progetto).
|
||||
|
||||
TRE STATI, TRE AZIONI (P5: distinguere anche quando l'azione e' la stessa):
|
||||
* OK — slack >= PREAVVISO_FRAC x cuscino: niente.
|
||||
* PREAVVISO — 0 <= slack < 10% del cuscino: ⚠️ alla transizione. E' latenza comprata apposta:
|
||||
un'ora in perdita non deve trovare il cuscino gia' scoperto.
|
||||
* SCOPERTO — slack < 0: 🚨 alla transizione, e RICONVERSIONE AUTOMATICA (decisione
|
||||
dell'operatore, 10/09): lancia `usde_convert --quota q --esegui` con
|
||||
q = usde.quota_ripristino(MARGINE_RIPRISTINO), cioe' la quota che lascia il cuscino
|
||||
piu' un 20% di margine, cosi' non si torna sul bordo alla prima ora in perdita.
|
||||
Vende USDE contro USDC (~6 bps di spread, fee 0).
|
||||
QUATTRO STATI SULLO SLACK (= equity USDC − cuscino), in frazioni del cuscino lette da config
|
||||
(`usde.bande_cuscino`: preavviso 0,10 · margine 0,20 · riacquisto 0,40 — P5, P1):
|
||||
* SCOPERTO — slack < 0: 🚨 alla transizione, e VENDITA automatica di USDE fino alla quota di
|
||||
ripristino q* = usde.quota_ripristino() = 1 − 0,30 × 1,20 = 0,64 (cuscino + 20%).
|
||||
* PREAVVISO — 0 <= slack < 10% del cuscino: ⚠️ alla transizione. Latenza comprata apposta.
|
||||
* OK — 10% <= slack <= 40%: niente.
|
||||
* ECCEDENTE — slack > 40% del cuscino: ACQUISTO automatico di USDE fino allo stesso q* (issue #7,
|
||||
decisione dell'operatore 10/09: «riportarla a quota in autonomia»). E' cio' che
|
||||
riporta la quota a bersaglio dopo un bonifico (+0,7 × importo di slack) o dopo un
|
||||
tratto di guadagni del libro, senza che nessuno se ne ricordi. 📌 alla transizione.
|
||||
* BLIND — conto non leggibile: ⚠️ alla transizione. "Non vedo" non e' "va tutto bene".
|
||||
ISTERESI: vendita sotto 0, acquisto sopra 0,40, entrambe verso 0,20. Per oscillare lo slack deve
|
||||
muoversi di ±0,20 × cuscino = ±6% dell'equity, e siccome slack = 0,7·USDC − 0,3·USDE, servono
|
||||
±8,6% di equity USDC (~$380 oggi); un giro costa ~6 bps sull'importo mosso. Soglie DICHIARATE, non ottimizzate (M8). Il vecchio `quota_target` 0,70
|
||||
lasciava slack ZERO per costruzione: a quella quota ogni ora in perdita avrebbe venduto.
|
||||
|
||||
LE GUARDIE DELLA RICONVERSIONE (tutte dichiarate qui):
|
||||
* `execution_enabled` in config/live.json deve essere true — lo stesso interruttore del libro:
|
||||
disarmare il libro disarma anche questo, senza un secondo posto da ricordare.
|
||||
* NON si riconverte sotto `depeg_warn`: vendere USDE sotto la pari cristallizza la perdita del
|
||||
collaterale; li' decide l'operatore, e l'allarme lo dice.
|
||||
* al massimo UN tentativo ogni RIPRISTINO_MIN_ORE (6h): un tentativo fallito che si ripete ogni
|
||||
ora e' spam, e un sorvegliante che si ripete si impara a ignorare (P14).
|
||||
* NON si converte sotto `depeg_warn`, in nessun verso: vendere USDE sotto la pari cristallizza
|
||||
la perdita del collaterale, comprarne mentre scivola e' peggio; li' decide l'operatore.
|
||||
* al massimo UN tentativo ogni RIPRISTINO_MIN_ORE (6h), in qualunque verso: un tentativo che si
|
||||
ripete ogni ora e' spam, e un sorvegliante che si ripete si impara a ignorare (P14).
|
||||
* dopo un tentativo FALLITO da ECCEDENTE si riprova dopo RITENTATIVO_FALLITO_ORE (24h): USDC
|
||||
fermo non e' un rischio, e un guasto persistente (tetto del venue tornato, book illeggibile)
|
||||
diventerebbe 4 Telegram al giorno per sempre (revisione fable 10/09). Da SCOPERTO il ritmo
|
||||
resta 6h: li' il fallimento e' un rischio che costa 0,05%/giorno.
|
||||
* `usde_convert` porta le proprie guardie (banda di prezzo, book leggibile, tetto HARD sulla
|
||||
quota): questo script NON le duplica, le eredita lanciando lo script vero (P15: la regola si
|
||||
prova contro il codice che la esegue).
|
||||
@@ -39,12 +48,6 @@ LE GUARDIE DELLA RICONVERSIONE (tutte dichiarate qui):
|
||||
* niente lock: un `usde_convert --esegui` lanciato a mano nello stesso minuto del cron (:53)
|
||||
puo' vendere due volte verso bersagli diversi. Limite dichiarato (D5), non riparato.
|
||||
|
||||
LIMITE DICHIARATO — IL RIPRISTINO E' UN RATCHET VERSO IL BASSO. Dopo una riconversione la quota
|
||||
resta a `quota_ripristino` (~0,64) e nessuno la riporta a `usde.quota_target` (0,70): ricomprare
|
||||
USDE e' una decisione dell'operatore (`usde_convert --quota 0.70`), non un automatismo di questo
|
||||
script. E a 0,70 lo slack e' ZERO per costruzione (§1: «coincidenza per costruzione, zero slack»),
|
||||
quindi PREAVVISO e' lo stato NORMALE del conto a quota piena — allerta una volta, alla transizione.
|
||||
|
||||
TRASPORTO (lezione del debito §5.2): il marcatore "gia' detto" e' l'esito di `notify` scritto nel
|
||||
record (`allertato`); se l'invio fallisce la transizione si ripete al giro dopo invece di andare
|
||||
persa per l'episodio intero.
|
||||
@@ -72,12 +75,11 @@ from src.live.notifier import notify # noqa: E402
|
||||
|
||||
STATE = ROOT / "data" / "live" / "cuscino_watch.jsonl"
|
||||
CONVERT = ROOT / "scripts" / "live" / "usde_convert.py"
|
||||
PREAVVISO_FRAC = 0.10 # sotto il 10% di slack (in frazione del cuscino) si preavvisa
|
||||
MARGINE_RIPRISTINO = 0.20 # la riconversione lascia cuscino x 1,20: DOPPIO del preavviso, cosi'
|
||||
# dopo il ripristino lo stato e' OK e non PREAVVISO per costruzione
|
||||
# (revisione fable 10/09: a 0,10 == PREAVVISO_FRAC il floor di
|
||||
# usde_convert lasciava il conto sotto la soglia)
|
||||
RIPRISTINO_MIN_ORE = 6.0 # un tentativo al massimo ogni 6 ore
|
||||
# Le bande (preavviso / margine / riacquisto) NON stanno qui: si leggono da config via
|
||||
# usde.bande_cuscino() a ogni giro (P1). Il margine e' il DOPPIO del preavviso per costruzione
|
||||
# dell'ordine imposto (revisione fable 10/09: a margine == preavviso ogni 🚨 era seguito da un ⚠️).
|
||||
RIPRISTINO_MIN_ORE = 6.0 # un tentativo di conversione (in qualunque verso) ogni 6 ore
|
||||
RITENTATIVO_FALLITO_ORE = 24.0 # da ECCEDENTE, dopo un fallimento: un tentativo al giorno
|
||||
TIMEOUT_CONVERT_S = 900 # usde_convert spazia gli ordini di 12s: 60 ordini = 12 min
|
||||
|
||||
|
||||
@@ -93,19 +95,31 @@ def leggi(path: Path = STATE) -> list[dict]:
|
||||
|
||||
|
||||
def giudica(eq_usdc: float | None, equity_tot: float | None, cuscino: float | None,
|
||||
preavviso_frac: float = PREAVVISO_FRAC) -> dict:
|
||||
"""PURA. -> stato, slack, soglia di preavviso. `None` in ingresso = BLIND."""
|
||||
bande: dict | None = None) -> dict:
|
||||
"""PURA. -> stato, slack, soglie in dollari. `None` in ingresso = BLIND. `bande` = le frazioni
|
||||
del cuscino (default: config, via usde.bande_cuscino)."""
|
||||
if eq_usdc is None or equity_tot is None or cuscino is None:
|
||||
return dict(stato="BLIND", slack=None, preavviso_usd=None)
|
||||
return dict(stato="BLIND", slack=None, preavviso_usd=None, riacquisto_usd=None)
|
||||
try:
|
||||
b = bande or U.bande_cuscino()
|
||||
except Exception as e: # noqa: BLE001 — config rotta = "non vedo", non un crash
|
||||
return dict(stato="BLIND", slack=None, preavviso_usd=None, riacquisto_usd=None,
|
||||
motivo=f"bande del cuscino non leggibili: {e}")
|
||||
slack = eq_usdc - cuscino
|
||||
preavviso = preavviso_frac * cuscino
|
||||
preavviso, riacquisto = b["preavviso"] * cuscino, b["riacquisto"] * cuscino
|
||||
if slack < 0:
|
||||
stato = "SCOPERTO"
|
||||
elif slack < preavviso:
|
||||
stato = "PREAVVISO"
|
||||
elif slack > riacquisto:
|
||||
stato = "ECCEDENTE"
|
||||
else:
|
||||
stato = "OK"
|
||||
return dict(stato=stato, slack=round(slack, 2), preavviso_usd=round(preavviso, 2))
|
||||
return dict(stato=stato, slack=round(slack, 2), preavviso_usd=round(preavviso, 2),
|
||||
riacquisto_usd=round(riacquisto, 2))
|
||||
|
||||
|
||||
AZIONE = {"SCOPERTO": "vendita", "ECCEDENTE": "acquisto"} # gli stati che convertono
|
||||
|
||||
|
||||
def transizione(prev: dict | None, stato: str) -> bool:
|
||||
@@ -120,21 +134,27 @@ def transizione(prev: dict | None, stato: str) -> bool:
|
||||
|
||||
def puo_riconvertire(prev_records: list[dict], now_ms: int, execution_enabled: bool,
|
||||
px: float | None, depeg_warn: float,
|
||||
min_ore: float = RIPRISTINO_MIN_ORE) -> tuple[bool, str]:
|
||||
"""PURA. Le guardie della riconversione automatica. -> (si', motivo se no)."""
|
||||
min_ore: float = RIPRISTINO_MIN_ORE, stato: str = "SCOPERTO",
|
||||
dopo_fallito_ore: float = RITENTATIVO_FALLITO_ORE) -> tuple[bool, str]:
|
||||
"""PURA. Le guardie della conversione automatica. -> (si', motivo se no). ⚠️ il motivo finisce
|
||||
in un'allerta: niente '<' (Telegram HTML) — l'escape nel sink lo copre, ma qui non serve."""
|
||||
if not execution_enabled:
|
||||
return False, "execution_enabled=false in config/live.json (il libro e' disarmato: anche questo)"
|
||||
if px is None:
|
||||
return False, "prezzo USDE non leggibile: non si vende al buio (P5)"
|
||||
if px < depeg_warn:
|
||||
return False, f"USDE {px:.4f} sotto depeg_warn {depeg_warn}: vendere ora cristallizza la perdita — decide l'operatore"
|
||||
return False, f"USDE {px:.4f} sotto depeg_warn {depeg_warn}: ne' vendere (cristallizza) ne' comprare (scivola) — decide l'operatore"
|
||||
# contano solo i tentativi VERI (subprocess lanciato): un record con `tentata=False` — prezzo
|
||||
# illeggibile, --secco, interruttore spento — non consuma il budget (revisione fable 10/09)
|
||||
ultimo = [r for r in prev_records if (r.get("riconversione") or {}).get("tentata")]
|
||||
if ultimo:
|
||||
ore = (now_ms - int(ultimo[-1]["ts"])) / 3_600_000
|
||||
if ore < min_ore:
|
||||
return False, f"ultimo tentativo {ore:.1f}h fa (< {min_ore:.0f}h): non si insiste ogni ora"
|
||||
fallito = not (ultimo[-1].get("riconversione") or {}).get("ok")
|
||||
soglia = dopo_fallito_ore if (fallito and stato == "ECCEDENTE") else min_ore
|
||||
if ore < soglia:
|
||||
return False, (f"ultimo tentativo {ore:.1f}h fa, sotto le {soglia:.0f}h"
|
||||
+ (" (era FALLITO: da ECCEDENTE si riprova una volta al giorno)" if soglia == dopo_fallito_ore else "")
|
||||
+ ": non si insiste")
|
||||
return True, ""
|
||||
|
||||
|
||||
@@ -194,54 +214,72 @@ def main() -> int:
|
||||
usd, _ = U.valuta(eq_usde, px)
|
||||
equity_tot = eq_usdc + usd
|
||||
cuscino, come = U.cuscino_richiesto_usd(equity_tot)
|
||||
g = giudica(eq_usdc, equity_tot, cuscino)
|
||||
try:
|
||||
bande = U.bande_cuscino(c)
|
||||
except Exception as e: # noqa: BLE001 — si registra e si allerta, non si crasha (P5)
|
||||
bande, motivo_blind = None, f"config usde: {e}"
|
||||
g = giudica(eq_usdc, equity_tot, cuscino, bande)
|
||||
stato = g["stato"]
|
||||
if stato == "BLIND" and g.get("motivo"):
|
||||
motivo_blind = motivo_blind or g["motivo"]
|
||||
|
||||
rec = dict(ts=now_ms, data=now.strftime("%Y-%m-%dT%H:%M:%SZ"), stato=stato,
|
||||
motivo_blind=motivo_blind, eq_usdc=eq_usdc, eq_usde=eq_usde, px=px,
|
||||
px_fonte=px_fonte, equity_tot=equity_tot, cuscino=cuscino, cuscino_come=come,
|
||||
slack=g["slack"], preavviso_usd=g["preavviso_usd"], allertato=None,
|
||||
riconversione=None, secco=secco)
|
||||
slack=g["slack"], preavviso_usd=g["preavviso_usd"], riacquisto_usd=g["riacquisto_usd"],
|
||||
bande=bande, allertato=None, allerta_errore=None, riconversione=None, secco=secco)
|
||||
|
||||
# --- riconversione automatica: solo SCOPERTO, solo se le guardie lo permettono ---
|
||||
if stato == "SCOPERTO":
|
||||
ok, perche = puo_riconvertire(records, now_ms, _execution_enabled(), px, c["depeg_warn"])
|
||||
quota = U.quota_ripristino(MARGINE_RIPRISTINO)
|
||||
# --- conversione automatica: SCOPERTO vende, ECCEDENTE compra, stesso bersaglio q* ---
|
||||
if stato in AZIONE:
|
||||
ok, perche = puo_riconvertire(records, now_ms, _execution_enabled(), px, c["depeg_warn"], stato=stato)
|
||||
quota = U.quota_ripristino(bande["margine"])
|
||||
if not ok:
|
||||
rec["riconversione"] = dict(tentata=False, quota=quota, motivo=perche)
|
||||
rec["riconversione"] = dict(tentata=False, verso=AZIONE[stato], quota=quota, motivo=perche)
|
||||
elif secco:
|
||||
rec["riconversione"] = dict(tentata=False, quota=quota,
|
||||
motivo=f"--secco: avrei lanciato usde_convert --quota {quota:.4f} --esegui")
|
||||
rec["riconversione"] = dict(tentata=False, verso=AZIONE[stato], quota=quota,
|
||||
motivo=f"--secco: avrei lanciato usde_convert --quota {quota:.4f} --esegui ({AZIONE[stato]})")
|
||||
else:
|
||||
esito = riconverti(quota)
|
||||
rec["riconversione"] = dict(tentata=True, quota=quota, **esito)
|
||||
rec["riconversione"] = dict(tentata=True, verso=AZIONE[stato], quota=quota, **esito)
|
||||
|
||||
# --- allarmi: alla transizione, marcatore scritto con l'esito dell'invio ---
|
||||
# ⚠️ Telegram e' in parse_mode HTML: un '<' nudo nel testo fa fallire l'invio (successo al
|
||||
# primo giro vero, 13:53Z del 10/09: «(< 10% del cuscino)»). Qui non si scrive mai '<'.
|
||||
def _esito_azione(r: dict) -> str:
|
||||
if r.get("tentata"):
|
||||
return (f"{r['verso']} " + ("ESEGUITA" if r.get("ok") else "FALLITA")
|
||||
+ f" (quota bersaglio {r['quota']:.0%})")
|
||||
return f"{r.get('verso', 'conversione')} NON tentata: {r.get('motivo')}"
|
||||
|
||||
if transizione(prev, stato):
|
||||
if secco:
|
||||
rec["allertato"] = False
|
||||
elif stato == "SCOPERTO":
|
||||
r = rec["riconversione"] or {}
|
||||
azione = ("riconversione " + ("ESEGUITA" if r.get("ok") else "FALLITA")
|
||||
+ f" (quota -> {r.get('quota', 0):.0%})" if r.get("tentata")
|
||||
else f"NON riconvertito: {r.get('motivo')}")
|
||||
rec["allertato"] = notify("🚨 CUSCINO USDC SCOPERTO", {
|
||||
"USDC equity": f"${eq_usdc:,.2f}", "cuscino richiesto": f"${cuscino:,.2f} ({come})",
|
||||
"slack": f"${g['slack']:+,.2f}", "azione": azione,
|
||||
"slack": f"${g['slack']:+,.2f}", "azione": _esito_azione(rec["riconversione"] or {}),
|
||||
"nota": "un USDC negativo costa 0,05%/giorno; il libro si regola in USDC"}, tentativi=3)
|
||||
elif stato == "PREAVVISO":
|
||||
rec["allertato"] = notify("⚠️ cuscino USDC in esaurimento", {
|
||||
"USDC equity": f"${eq_usdc:,.2f}", "cuscino richiesto": f"${cuscino:,.2f}",
|
||||
"slack": f"${g['slack']:+,.2f} (< {PREAVVISO_FRAC:.0%} del cuscino)",
|
||||
"nota": f"sotto zero riconverte da solo a quota {U.quota_ripristino(MARGINE_RIPRISTINO):.0%}"})
|
||||
"slack": f"${g['slack']:+,.2f} (sotto il {bande['preavviso']:.0%} del cuscino)",
|
||||
"nota": f"sotto zero vende USDE da solo fino a quota {U.quota_ripristino(bande['margine']):.0%}"})
|
||||
elif stato == "ECCEDENTE":
|
||||
rec["allertato"] = notify("📌 cuscino USDC eccedente: riacquisto USDE", {
|
||||
"USDC equity": f"${eq_usdc:,.2f}", "cuscino richiesto": f"${cuscino:,.2f}",
|
||||
"slack": f"${g['slack']:+,.2f} (sopra il {bande['riacquisto']:.0%} del cuscino)",
|
||||
"azione": _esito_azione(rec["riconversione"] or {})})
|
||||
elif stato == "BLIND":
|
||||
rec["allertato"] = notify("⚠️ cuscino_watch BLIND", {"motivo": motivo_blind or "?",
|
||||
"nota": "'non vedo' non e' 'va tutto bene' (P5)"})
|
||||
elif stato == "SCOPERTO" and rec["riconversione"] and rec["riconversione"].get("tentata") and not secco:
|
||||
elif rec["riconversione"] and rec["riconversione"].get("tentata") and not secco:
|
||||
# non e' una transizione ma e' un ordine mandato: si dice sempre
|
||||
r = rec["riconversione"]
|
||||
rec["allertato"] = notify("📌 cuscino USDC: riconversione " + ("eseguita" if r["ok"] else "FALLITA"),
|
||||
rec["allertato"] = notify(f"📌 cuscino USDC: {r['verso']} " + ("eseguita" if r["ok"] else "FALLITA"),
|
||||
{"quota bersaglio": f"{r['quota']:.0%}", "esito": " | ".join(r["coda"][-3:])})
|
||||
if rec["allertato"] is False and not secco:
|
||||
from src.live.notifier import ultimo_errore
|
||||
rec["allerta_errore"] = ultimo_errore() # P3: si registra dove si ingoia
|
||||
|
||||
# --secco e' secco ANCHE sul registro: un giro a mano non deve diventare il `prev` del cron,
|
||||
# ne' far sembrare vivo un monitor fermo (revisione fable 10/09)
|
||||
@@ -261,22 +299,24 @@ def main() -> int:
|
||||
print(f" conto : USDC equity ${eq_usdc:,.2f} + USDE {eq_usde:,.2f} @ {px if px else 'n/d'}"
|
||||
f" ({px_fonte}) = ${equity_tot:,.2f}")
|
||||
print(f" cuscino : ${cuscino:,.2f} richiesti ({come})")
|
||||
print(f" slack : ${g['slack']:+,.2f} (preavviso sotto ${g['preavviso_usd']:,.2f})")
|
||||
print(f" slack : ${g['slack']:+,.2f} (preavviso sotto ${g['preavviso_usd']:,.2f},"
|
||||
f" riacquisto sopra ${g['riacquisto_usd']:,.2f}, bersaglio quota {U.quota_ripristino(bande['margine']):.0%})")
|
||||
print(f" stato : {stato}")
|
||||
if rec["riconversione"]:
|
||||
r = rec["riconversione"]
|
||||
print(f" riconvers. : {'TENTATA' if r.get('tentata') else 'NON tentata'} — quota {r['quota']:.4f}"
|
||||
print(f" {r.get('verso', 'conversione'):<12}: {'TENTATA' if r.get('tentata') else 'NON tentata'} — quota {r['quota']:.4f}"
|
||||
+ (f" — {r.get('motivo')}" if r.get("motivo") else ""))
|
||||
for ln in r.get("coda", []):
|
||||
print(f" {ln}")
|
||||
if rec["allertato"] is not None:
|
||||
print(f" allerta : {'inviata' if rec['allertato'] else 'NON inviata (si ripete al giro dopo)'}")
|
||||
print(f" allerta : {'inviata' if rec['allertato'] else 'NON inviata (si ripete al giro dopo)'}"
|
||||
+ (f" — {rec['allerta_errore']}" if rec.get("allerta_errore") else ""))
|
||||
return 0
|
||||
|
||||
|
||||
USO = """uso: cuscino_watch.py [--quiet] [--secco]
|
||||
|
||||
cuscino_watch.py — sorveglianza ORARIA del cuscino USDC di regolamento; sotto zero riconverte.
|
||||
cuscino_watch.py — sorveglianza ORARIA del cuscino USDC; sotto zero vende USDE, sopra la banda ricompra.
|
||||
uv run python scripts/live/cuscino_watch.py # un giro: legge, giudica, agisce
|
||||
uv run python scripts/live/cuscino_watch.py --quiet # stampa solo se cambia qualcosa
|
||||
uv run python scripts/live/cuscino_watch.py --secco # nessun ordine, nessun Telegram
|
||||
|
||||
Reference in New Issue
Block a user