diff --git a/src/live/dashboard.py b/src/live/dashboard.py
index bd8e24c..2cc99e6 100644
--- a/src/live/dashboard.py
+++ b/src/live/dashboard.py
@@ -213,6 +213,14 @@ def build_state() -> dict:
# 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")
+ 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 = {
"id": _short(wid), "family": _family_of(wid),
"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),
"stale": _age_min(sp) > 15, "pair": is_pair,
"real": bool(executed),
+ "entry_time": et, "held_min": held,
}
if is_pair:
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);}
function renderActive(a){const wrap=document.getElementById('active');wrap.innerHTML='';
if(!a.length){wrap.innerHTML='
nessuna posizione aperta
';return;}
- const t=E('table');t.innerHTML='| Strategia | Lato | Entry reale | Mercato reale | PnL non realizz. | Barre | →TP% | Età |
';
+ const t=E('table');t.innerHTML='| Strategia | Lato | Ingresso (UTC) | In posizione | Entry reale | Mercato reale | PnL non realizz. | Barre | →TP% |
';
a.forEach(p=>{const tr=E('tr');
const side=`${p.dir}${p.pair?' ratio':''}`;
const ue=p.unreal_eur!=null?`${eur(p.unreal_eur)}${p.unreal_pct!=null?' ('+p.unreal_pct+'%)':''}`:'—';
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=`${p.id}${p.real?'':' sim'}${p.pair?' '+p.leg_a+'/'+p.leg_b+'':''} | ${side} |
+ ${ing} | ${held}${p.stale?' ⚠':''} |
${p.entry} | ${mkt} | ${ue} |
- ${p.bars}/${p.max_bars||'∞'} | ${p.to_tp_pct!=null?p.to_tp_pct:'—'} |
- ${p.age_min}m${p.stale?' ⚠':''} | `;
+ ${p.bars}/${p.max_bars||'∞'} | ${p.to_tp_pct!=null?p.to_tp_pct:'—'} | `;
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='';
if(!c.length){wrap.innerHTML='nessun trade chiuso
';return;}
const t=E('table');t.innerHTML='| Ora (UTC) | Strategia | Motivo | PnL | sim | esito |
';