From 22af19c7f933e1b4be49e965b06f03483925b0cc Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Sat, 13 Jun 2026 21:12:42 +0000 Subject: [PATCH] feat(dashboard): badge strategie RITIRATE (staleness) + descrizioni per strategia MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le fade 1h sostituite dallo swap 15m sono marcate RITIRATA→15m (rilevate per status.json stantio >30min, ordinate in fondo, riga barrata). Descrizioni concise per ogni strategia (da strategie_attive.html) come tooltip + sezione legenda. Fix rilevazione paper: per directory (portfolio_paper_stats), non per chiave 'weights' (TR01/TSM01/XS01 non ce l'hanno) -> ora i 4 multi-asset sono paper. Co-Authored-By: Claude Fable 5 --- src/live/dashboard.py | 69 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/src/live/dashboard.py b/src/live/dashboard.py index d1a60fe..c6a69bc 100644 --- a/src/live/dashboard.py +++ b/src/live/dashboard.py @@ -30,6 +30,42 @@ LEDGER = PROJECT_ROOT / "data" / "portfolios" / "PORT06" FAMILY = [("MR01", "FADE"), ("MR02", "FADE"), ("MR07", "FADE"), ("DIP01", "HONEST"), ("PR01", "PAIRS"), ("SH01", "SHAPE"), ("TR01", "HONEST"), ("ROT02", "HONEST"), ("TSM01", "TSM"), ("XS01", "XSEC")] +RETIRED_MIN = 30 # status.json non aggiornato da >30min = worker non piu' nel + # config attivo (es. le fade 1h sostituite dal 15m allo swap) + +# Descrizioni concise (fonte: docs/report/strategie_attive.html / make_strategy_doc.py) +DESCRIPTIONS = { + "MR01": "Bollinger Fade — quando il close esce dalla banda ±2.5σ (SMA50) entra CONTRO il " + "movimento (short sopra/long sotto); TP alla media, SL 2·ATR, max 24 barre. Mean-reversion.", + "MR02": "Donchian Fade — fada la rottura degli estremi del canale a 20 barre (max/min recenti); " + "TP al centro del canale. Stessa tesi di MR01 con trigger diverso.", + "MR07": "Return Reversal — fada il rendimento di barra estremo (z-score ±3.5); exit in multipli " + "di ATR. La fade più selettiva (esposizione ~8% del tempo).", + "DIP01": "Dip Buy — compra il dip quando lo z del prezzo incrocia sotto −2.5; TP alla SMA50, " + "EXIT-16 sul SL, max 24 barre. Unico sleeve BTC con round-trip reali su testnet.", + "PR01": "Pairs Reversion — market-neutral: quando |z| del log-ratio fra due asset ≥2 compra la " + "gamba debole/shorta la forte, chiude a |z|≤0.75 o 72 barre. Scorrelato dal mercato (~0.05). " + "Anche ETH/BTC a 15m (flat-skip). Fee su 2 gambe, senza stop → size ridotta.", + "SH01": "Shape-ML — una LogisticRegression legge 17 feature di forma in walk-forward e predice il " + "segno del rendimento a 12 barre. Win-rate ~50%: l'edge è nell'asimmetria. Diversificatore, no stop.", + "TR01": "Basket Trend (4h) — long quando EMA20>EMA100 su paniere equal-weight di 5 asset, flat " + "altrimenti. Trend-following difensivo che cattura i trend lunghi che le fade non prendono.", + "ROT02": "Dual Momentum (1d) — ogni giorno tiene le top-3 per momentum 60g (se positive), gross 0.45, " + "tutto cash se BTC str: + for code, _ in FAMILY: + if wid.startswith(code): + return code + return "?" + + _MARK_CACHE: dict = {"ts": 0.0, "marks": {}} @@ -127,17 +163,23 @@ def build_state() -> dict: continue wid = d.name st = json.loads(sp.read_text()) - paper_only = ("weights" in st) # multi-asset stats (no real exec) + paper_only = (d.parent.name == "portfolio_paper_stats") # multi-asset (no real exec) pnl, n, wins, closes = _worker_trades(wid) closed.extend(closes) cap = st.get("capital", 0.0) rc = st.get("real_capital") + # ritirata = status.json non aggiornato da >30min (non piu' tickato dal runner = + # fuori dal config attivo, es. le fade 1h sostituite dal 15m). I paper/stats + # tickano sempre -> mai ritirati. + retired = (not paper_only) and _age_min(sp) > RETIRED_MIN strategies.append({ - "id": _short(wid), "family": _family_of(wid), + "id": _short(wid), "family": _family_of(wid), "code": _code_of(wid), "capital": round(cap, 2), "real_capital": round(rc, 2) if rc is not None else None, "realized_pnl": round(pnl, 2), "n_trades": n, "wins": wins, "win_rate": round(wins / n * 100, 0) if n else 0.0, "in_position": bool(st.get("in_position")), "paper": paper_only, + "retired": retired, "tf": wid.split("__")[-1], + "desc": DESCRIPTIONS.get(_code_of(wid), ""), }) if st.get("in_position") and not paper_only: is_pair = "entry_a" in st @@ -190,7 +232,9 @@ def build_state() -> dict: "curve": curve, }, "fam_pnl": {k: round(v, 2) for k, v in sorted(fam_pnl.items())}, - "strategies": sorted(strategies, key=lambda s: (s["family"], s["id"])), + "descriptions": DESCRIPTIONS, + # ritirate in fondo, poi per famiglia/id + "strategies": sorted(strategies, key=lambda s: (s["retired"], s["family"], s["id"])), "active": sorted(active, key=lambda a: a.get("unreal_eur", 0)), "closed": closed[:50], "n_active": len(active), @@ -241,6 +285,10 @@ HTML = r""" .dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:5px} .pill{font-size:11px;padding:1px 7px;border-radius:10px}.long{background:#0f3d2e;color:var(--grn)}.short{background:#3d1418;color:var(--red)} .stale{color:#d29922}.muted-row td{color:var(--mut)} + .retired-row td{color:#6e7681;opacity:.7}.retired-row .id{text-decoration:line-through} + .badge-ret{background:#3d1418;color:#f85149}.badge-paper{background:#21262d;color:var(--mut)} + .desc{font-size:11px;color:var(--mut);margin-top:2px;max-width:520px} + .dl b{color:var(--acc)}.dl div{padding:5px 0;border-bottom:1px solid var(--bd)} canvas{width:100%!important;height:220px!important}

PORT06 — Dashboard live

@@ -250,6 +298,7 @@ HTML = r"""

PnL per strategia (realizzato, netto fee)

Trade attivi — stato in tempo reale

Trade chiusi (ultimi 50)

+

Descrizioni strategie

"""