feat(dashboard): trade attivi con data/ora ingresso + tempo in posizione

Nuove colonne 'Ingresso (UTC)' (entry_time) e 'In posizione' (durata dall'ingresso,
formattata m/h/g) con flag stantio. Sostituisce la colonna Eta (staleness) col
tempo di holding effettivo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-13 22:24:22 +00:00
parent 7ee5b1767f
commit 3ac8a46e6a
+17 -3
View File
@@ -213,6 +213,14 @@ def build_state() -> dict:
# SEMPRE prezzi REALI (entry reale di esecuzione vs mark reale USDC): il feed # 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. # di decisione SIM (testnet inverse) è dislocato e non va mostrato come prezzo.
executed = st.get("real_in_position") executed = st.get("real_in_position")
et = st.get("entry_time", "")
held = None
if et:
try:
t0 = datetime.fromisoformat(et.replace("Z", "+00:00"))
held = round((now - t0).total_seconds() / 60.0, 1)
except Exception:
held = None
row = { row = {
"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",
@@ -220,6 +228,7 @@ def build_state() -> dict:
"tp": st.get("tp", 0.0), "age_min": round(_age_min(sp), 1), "tp": st.get("tp", 0.0), "age_min": round(_age_min(sp), 1),
"stale": _age_min(sp) > 15, "pair": is_pair, "stale": _age_min(sp) > 15, "pair": is_pair,
"real": bool(executed), "real": bool(executed),
"entry_time": et, "held_min": held,
} }
if is_pair: if is_pair:
a_, b_ = wid.split("__")[1].split("_") # es. ETH_SOL a_, b_ = wid.split("__")[1].split("_") # es. ETH_SOL
@@ -605,16 +614,21 @@ 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 reale</th><th>Mercato reale</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>Ingresso (UTC)</th><th>In posizione</th><th>Entry reale</th><th>Mercato reale</th><th>PnL non realizz.</th><th>Barre</th><th>→TP%</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}${p.pair?' ratio':''}</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!=null?' ('+p.unreal_pct+'%)':''}</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 mkt=p.pair?(p.mark!=null?`${p.mark} / ${p.mark_b}`:''):(p.mark||''); const mkt=p.pair?(p.mark!=null?`${p.mark} / ${p.mark_b}`:''):(p.mark||'');
const ing=p.entry_time?p.entry_time.slice(5,16).replace('T',' '):'';
const held=fmtDur(p.held_min);
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>'}${p.pair?' <span class=tag>'+p.leg_a+'/'+p.leg_b+'</span>':''}</td><td>${side}</td>
<td class=mut>${ing}</td><td class="${p.stale?'stale':''}">${held}${p.stale?'':''}</td>
<td>${p.entry}</td><td>${mkt}</td><td>${ue}</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>${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);} t.append(tr);});wrap.append(t);}
function fmtDur(m){if(m==null)return '';m=Math.round(m);if(m<60)return m+'m';
const h=Math.floor(m/60),mm=m%60;if(h<24)return h+'h '+mm+'m';
const d=Math.floor(h/24);return d+'g '+(h%24)+'h';}
function renderClosed(c){const wrap=document.getElementById('closed');wrap.innerHTML=''; function renderClosed(c){const wrap=document.getElementById('closed');wrap.innerHTML='';
if(!c.length){wrap.innerHTML='<div class=mut>nessun trade chiuso</div>';return;} if(!c.length){wrap.innerHTML='<div class=mut>nessun trade chiuso</div>';return;}
const t=E('table');t.innerHTML='<tr><th>Ora (UTC)</th><th>Strategia</th><th>Motivo</th><th>PnL</th><th>sim</th><th>esito</th></tr>'; const t=E('table');t.innerHTML='<tr><th>Ora (UTC)</th><th>Strategia</th><th>Motivo</th><th>PnL</th><th>sim</th><th>esito</th></tr>';