feat(dashboard): solo dati REALI nei trade attivi + valore mercato reale (anche pairs) + attivi sotto il grafico

- entry/mark/unrealized SEMPRE reali (entry di esecuzione vs mark USDC live); il
  feed sim/decisione (testnet inverse dislocato) non e' piu' mostrato come prezzo
- pairs: valore di mercato reale di ENTRAMBE le gambe + PnL non realizzato reale a
  2 gambe (prima 'pairs (z)' senza valore)
- tabella attivi spostata subito sotto il grafico equity

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-13 22:21:22 +00:00
parent d5a83075f8
commit 7ee5b1767f
+44 -33
View File
@@ -210,46 +210,58 @@ def build_state() -> dict:
}) })
if st.get("in_position") and not paper_only: if st.get("in_position") and not paper_only:
is_pair = "entry_a" in st is_pair = "entry_a" in st
asset = wid.split("__")[1] # SEMPRE prezzi REALI (entry reale di esecuzione vs mark reale USDC): il feed
# entry REALE se eseguito (il mark live è il prezzo reale USDC: confrontare # di decisione SIM (testnet inverse) è dislocato e non va mostrato come prezzo.
# con l'entry SIM mischierebbe le basi — il feed di decisione testnet inverse
# è dislocato dal lineare, vedi xex_divergence_research). Fallback al sim.
executed = st.get("real_in_position") executed = st.get("real_in_position")
if is_pair: row = {
real_e, sim_e = (st.get("real_entry_a") if executed else None), st.get("entry_a") or 0.0
else:
real_e, sim_e = (st.get("real_entry_price") if executed else None), st.get("entry_price") or 0.0
entry = real_e or sim_e
if not is_pair:
open_assets.add(asset)
active.append({
"id": _short(wid), "family": _family_of(wid), "id": _short(wid), "family": _family_of(wid),
"dir": "LONG" if st.get("direction", 0) > 0 else "SHORT", "dir": "LONG" if st.get("direction", 0) > 0 else "SHORT",
"entry": round(entry, 4), "sim_entry": round(sim_e, 4), "bars": st.get("bars_held", 0), "max_bars": st.get("max_bars", 0),
"bars": st.get("bars_held", 0), "tp": st.get("tp", 0.0), "age_min": round(_age_min(sp), 1),
"max_bars": st.get("max_bars", 0), "stale": _age_min(sp) > 15, "pair": is_pair,
"tp": st.get("tp", 0.0), "sl": st.get("sl", 0.0), "real": bool(executed),
"age_min": round(_age_min(sp), 1), }
"stale": _age_min(sp) > 15, if is_pair:
"asset": None if is_pair else asset, a_, b_ = wid.split("__")[1].split("_") # es. ETH_SOL
"pair": is_pair, open_assets.update({a_, b_})
"notional": st.get("real_entry_notional") or round(cap * 0.5 * 3, 1), row.update({
"real": bool(st.get("real_in_position")), "asset": None, "leg_a": a_, "leg_b": b_,
"entry_a": st.get("real_entry_a") if executed else st.get("entry_a"),
"entry_b": st.get("real_entry_b") if executed else st.get("entry_b"),
"notional_a": st.get("real_notional_a") or 0.0,
"notional_b": st.get("real_notional_b") or 0.0,
"dirnum": 1 if st.get("direction", 0) > 0 else -1,
"entry": round(st.get("real_entry_a") or st.get("entry_a") or 0.0, 4),
}) })
else:
asset = wid.split("__")[1]
open_assets.add(asset)
entry = (st.get("real_entry_price") if executed else None) or st.get("entry_price") or 0.0
row.update({"asset": asset, "entry": round(entry, 4),
"notional": st.get("real_entry_notional") or round(cap * 0.5 * 3, 1)})
active.append(row)
# PnL non realizzato live per i trade attivi single-leg (mark best-effort) # valore di mercato REALE + PnL non realizzato reale (mark USDC live, best-effort)
marks = _marks(open_assets) marks = _marks(open_assets)
for a in active: for a in active:
if a["asset"] and marks.get(a["asset"]) and a["entry"]: if not a["pair"] and a.get("asset") and marks.get(a["asset"]) and a["entry"]:
mark = marks[a["asset"]] mark = marks[a["asset"]]
sign = 1 if a["dir"] == "LONG" else -1 sign = 1 if a["dir"] == "LONG" else -1
pct = sign * (mark - a["entry"]) / a["entry"] pct = sign * (mark - a["entry"]) / a["entry"]
a["mark"] = round(mark, 4) a["mark"] = round(mark, 4)
a["unreal_pct"] = round(pct * 100, 2) a["unreal_pct"] = round(pct * 100, 2)
a["unreal_eur"] = round(a["notional"] * pct, 2) a["unreal_eur"] = round(a["notional"] * pct, 2)
# distanza al TP (%)
if a["tp"]: if a["tp"]:
a["to_tp_pct"] = round(abs(a["tp"] - mark) / mark * 100, 2) a["to_tp_pct"] = round(abs(a["tp"] - mark) / mark * 100, 2)
elif a["pair"]:
ma, mb = marks.get(a["leg_a"]), marks.get(a["leg_b"])
if ma and mb and a["entry_a"] and a["entry_b"]:
s = a["dirnum"] # +1 long_ratio: long A / short B
gain_a = a["notional_a"] * s * (ma - a["entry_a"]) / a["entry_a"]
gain_b = a["notional_b"] * (-s) * (mb - a["entry_b"]) / a["entry_b"]
a["mark"] = round(ma, 4)
a["mark_b"] = round(mb, 4)
a["unreal_eur"] = round(gain_a + gain_b, 2)
# curve EQUITY per FAMIGLIA: PnL cumulato di ogni famiglia su asse-tempo comune # curve EQUITY per FAMIGLIA: PnL cumulato di ogni famiglia su asse-tempo comune
# (ad ogni CLOSE di una qualsiasi famiglia, il cumulato di TUTTE avanza step-wise). # (ad ogni CLOSE di una qualsiasi famiglia, il cumulato di TUTTE avanza step-wise).
@@ -427,10 +439,10 @@ HTML = r"""<!doctype html><html lang="it"><head><meta charset="utf-8">
<div class="card sec" id="eqcard"><div class="eqhead"><h2 style="margin:0">Equity</h2> <div class="card sec" id="eqcard"><div class="eqhead"><h2 style="margin:0">Equity</h2>
<div><span class="big" id="eqval">—</span> <span class="pc" id="eqpc"></span></div></div> <div><span class="big" id="eqval">—</span> <span class="pc" id="eqpc"></span></div></div>
<div class="chartbox eq"><canvas id="eq"></canvas></div></div> <div class="chartbox eq"><canvas id="eq"></canvas></div></div>
<div class="card sec"><h2>Trade attivi — stato in tempo reale</h2><div id="active"></div></div>
<div class="card sec"><h2>Equity per famiglia <span class="mut" style="font-size:12px">(PnL cumulato realizzato)</span></h2> <div class="card sec"><h2>Equity per famiglia <span class="mut" style="font-size:12px">(PnL cumulato realizzato)</span></h2>
<div class="chartbox" style="height:240px"><canvas id="famchart"></canvas></div></div> <div class="chartbox" style="height:240px"><canvas id="famchart"></canvas></div></div>
<div class="card sec"><h2>Strategie REALI per famiglia <span class="mut" style="font-size:12px">(realizzato, netto fee)</span></h2><div id="strat"></div></div> <div class="card sec"><h2>Strategie REALI per famiglia <span class="mut" style="font-size:12px">(realizzato, netto fee)</span></h2><div id="strat"></div></div>
<div class="card sec"><h2>Trade attivi — stato in tempo reale</h2><div id="active"></div></div>
<div class="card sec"><h2>Trade chiusi (ultimi 50)</h2><div id="closed"></div></div> <div class="card sec"><h2>Trade chiusi (ultimi 50)</h2><div id="closed"></div></div>
<div class="paperzone"> <div class="paperzone">
@@ -593,14 +605,13 @@ function renderDescs(d){const wrap=document.getElementById('descs');if(!wrap)ret
wrap.append(box);} wrap.append(box);}
function renderActive(a){const wrap=document.getElementById('active');wrap.innerHTML=''; function renderActive(a){const wrap=document.getElementById('active');wrap.innerHTML='';
if(!a.length){wrap.innerHTML='<div class=mut>nessuna posizione aperta</div>';return;} if(!a.length){wrap.innerHTML='<div class=mut>nessuna posizione aperta</div>';return;}
const t=E('table');t.innerHTML='<tr><th>Strategia</th><th>Lato</th><th>Entry</th><th>Mark</th><th>PnL non realizz.</th><th>Barre</th><th>→TP%</th><th>Età</th></tr>'; const t=E('table');t.innerHTML='<tr><th>Strategia</th><th>Lato</th><th>Entry reale</th><th>Mercato reale</th><th>PnL non realizz.</th><th>Barre</th><th>→TP%</th><th>Età</th></tr>';
a.forEach(p=>{const tr=E('tr'); a.forEach(p=>{const tr=E('tr');
const side=`<span class="pill ${p.dir=='LONG'?'long':'short'}">${p.dir}</span>`; const side=`<span class="pill ${p.dir=='LONG'?'long':'short'}">${p.dir}${p.pair?' ratio':''}</span>`;
const ue=p.unreal_eur!=null?`<span class="${cls(p.unreal_eur)}">${eur(p.unreal_eur)} (${p.unreal_pct}%)</span>`:(p.pair?'<span class=mut>pairs (z)</span>':'<span class=mut>—</span>'); const ue=p.unreal_eur!=null?`<span class="${cls(p.unreal_eur)}">${eur(p.unreal_eur)}${p.unreal_pct!=null?' ('+p.unreal_pct+'%)':''}</span>`:'<span class=mut>—</span>';
const gap=p.sim_entry&&Math.abs(p.sim_entry-p.entry)>0.01; const mkt=p.pair?(p.mark!=null?`${p.mark} / ${p.mark_b}`:''):(p.mark||'');
const ecell=gap?`<td title="entry reale ${p.entry} · feed sim/decisione ${p.sim_entry} (testnet inverse dislocato)">${p.entry} <span class=stale style="font-size:10px">⚠sim ${p.sim_entry}</span></td>`:`<td>${p.entry}</td>`; tr.innerHTML=`<td>${p.id}${p.real?'':' <span class=tag>sim</span>'}${p.pair?' <span class=tag>'+p.leg_a+'/'+p.leg_b+'</span>':''}</td><td>${side}</td>
tr.innerHTML=`<td>${p.id}${p.real?'':' <span class=tag>sim</span>'}</td><td>${side}</td> <td>${p.entry}</td><td>${mkt}</td><td>${ue}</td>
${ecell}<td>${p.mark||''}</td><td>${ue}</td>
<td>${p.bars}/${p.max_bars||''}</td><td class=mut>${p.to_tp_pct!=null?p.to_tp_pct:''}</td> <td>${p.bars}/${p.max_bars||''}</td><td class=mut>${p.to_tp_pct!=null?p.to_tp_pct:''}</td>
<td class="${p.stale?'stale':'mut'}">${p.age_min}m${p.stale?'':''}</td>`; <td class="${p.stale?'stale':'mut'}">${p.age_min}m${p.stale?'':''}</td>`;
t.append(tr);});wrap.append(t);} t.append(tr);});wrap.append(t);}