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='StrategiaLatoEntry realeMercato realePnL non realizz.Barre→TP%Età'; + const t=E('table');t.innerHTML='StrategiaLatoIngresso (UTC)In posizioneEntry realeMercato realePnL 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)StrategiaMotivoPnLsimesito';