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:
+44
-33
@@ -210,46 +210,58 @@ def build_state() -> dict:
|
||||
})
|
||||
if st.get("in_position") and not paper_only:
|
||||
is_pair = "entry_a" in st
|
||||
asset = wid.split("__")[1]
|
||||
# entry REALE se eseguito (il mark live è il prezzo reale USDC: confrontare
|
||||
# con l'entry SIM mischierebbe le basi — il feed di decisione testnet inverse
|
||||
# è dislocato dal lineare, vedi xex_divergence_research). Fallback al sim.
|
||||
# SEMPRE prezzi REALI (entry reale di esecuzione vs mark reale USDC): il feed
|
||||
# di decisione SIM (testnet inverse) è dislocato e non va mostrato come prezzo.
|
||||
executed = st.get("real_in_position")
|
||||
if is_pair:
|
||||
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({
|
||||
row = {
|
||||
"id": _short(wid), "family": _family_of(wid),
|
||||
"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),
|
||||
"tp": st.get("tp", 0.0), "sl": st.get("sl", 0.0),
|
||||
"age_min": round(_age_min(sp), 1),
|
||||
"stale": _age_min(sp) > 15,
|
||||
"asset": None if is_pair else asset,
|
||||
"pair": is_pair,
|
||||
"notional": st.get("real_entry_notional") or round(cap * 0.5 * 3, 1),
|
||||
"real": bool(st.get("real_in_position")),
|
||||
"bars": st.get("bars_held", 0), "max_bars": st.get("max_bars", 0),
|
||||
"tp": st.get("tp", 0.0), "age_min": round(_age_min(sp), 1),
|
||||
"stale": _age_min(sp) > 15, "pair": is_pair,
|
||||
"real": bool(executed),
|
||||
}
|
||||
if is_pair:
|
||||
a_, b_ = wid.split("__")[1].split("_") # es. ETH_SOL
|
||||
open_assets.update({a_, b_})
|
||||
row.update({
|
||||
"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)
|
||||
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"]]
|
||||
sign = 1 if a["dir"] == "LONG" else -1
|
||||
pct = sign * (mark - a["entry"]) / a["entry"]
|
||||
a["mark"] = round(mark, 4)
|
||||
a["unreal_pct"] = round(pct * 100, 2)
|
||||
a["unreal_eur"] = round(a["notional"] * pct, 2)
|
||||
# distanza al TP (%)
|
||||
if a["tp"]:
|
||||
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
|
||||
# (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><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="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="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>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="paperzone">
|
||||
@@ -593,14 +605,13 @@ function renderDescs(d){const wrap=document.getElementById('descs');if(!wrap)ret
|
||||
wrap.append(box);}
|
||||
function renderActive(a){const wrap=document.getElementById('active');wrap.innerHTML='';
|
||||
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');
|
||||
const side=`<span class="pill ${p.dir=='LONG'?'long':'short'}">${p.dir}</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 gap=p.sim_entry&&Math.abs(p.sim_entry-p.entry)>0.01;
|
||||
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>'}</td><td>${side}</td>
|
||||
${ecell}<td>${p.mark||'—'}</td><td>${ue}</td>
|
||||
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!=null?' ('+p.unreal_pct+'%)':''}</span>`:'<span class=mut>—</span>';
|
||||
const mkt=p.pair?(p.mark!=null?`${p.mark} / ${p.mark_b}`:'—'):(p.mark||'—');
|
||||
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>
|
||||
<td>${p.entry}</td><td>${mkt}</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 class="${p.stale?'stale':'mut'}">${p.age_min}m${p.stale?' ⚠':''}</td>`;
|
||||
t.append(tr);});wrap.append(t);}
|
||||
|
||||
Reference in New Issue
Block a user