From 2538ccc456777781352a1f2da7bf4ac878629978 Mon Sep 17 00:00:00 2001 From: ramseshk Date: Tue, 4 Aug 2026 08:17:46 +0000 Subject: [PATCH] Add equity curve charts to Live and Paper main tabs Two LightweightCharts area-series charts added below strategy cards in Live and Paper tabs. Each chart renders equity_history from the WebSocket data stream, updating on every tick. Changes: - chart-live and chart-paper containers with 220px height - initMainCharts() creates chart instances + area series - pushEquity() converts equity points to chart data, auto-fits view - renLive() and renPaper() push equity_history to respective charts - .main-chart CSS for dark-theme background - init chain calls initMainCharts() after initDetChart() Live chart: 600 data points rendering on first load Paper chart: loads on tab activation, 600 points --- dashboard/static/index.html | 41 +++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/dashboard/static/index.html b/dashboard/static/index.html index 8eb9334..15df692 100644 --- a/dashboard/static/index.html +++ b/dashboard/static/index.html @@ -61,6 +61,7 @@ body{background:var(--bg);color:var(--hi);font-family:var(--f);min-height:100vh; .close-btn{background:none;border:1px solid var(--ln);color:var(--hi);padding:6px 14px;border-radius:6px;cursor:pointer;font-size:12px;font-family:var(--f);transition:all .15s} .close-btn:hover{background:var(--hr)} .detail-body{padding:20px} +.main-chart{width:100%;height:220px;margin:8px 0 0;border-radius:var(--ra);overflow:hidden;background:rgba(0,0,0,.25)} .detail-body .chart-wrap{width:100%;height:280px;margin-bottom:16px;border-radius:var(--ra);overflow:hidden} .detail-stats{display:grid;grid-template-columns:repeat(6,1fr);gap:8px;margin-bottom:16px} .detail-section{margin-bottom:20px} @@ -124,10 +125,12 @@ footer a{color:#3f3f4a;text-decoration:none}footer a:hover{color:var(--tx)}
+
+
@@ -197,6 +200,36 @@ var STRAT_COLORS=['#22c55e','#3b82f6','#a855f7','#f59e0b','#ef4444','#06b6d4','# // ═══════════ Chart for detail view ═══════════ var detChart=null, detSer=null; + +// ═══════════ Main area charts ═══════════ +var chartLive=null, serLive=null, chartPaper=null, serPaper=null; +function initMainCharts(){ + [{el:'chart-live',ch:'chartLive',sr:'serLive'},{el:'chart-paper',ch:'chartPaper',sr:'serPaper'}].forEach(function(c){ + var el=document.getElementById(c.el);if(!el)return; + el.style.width='100%';el.style.height='220px'; + window[c.ch]=LightweightCharts.createChart(el,{ + layout:{background:{color:'transparent'},textColor:'#a0a0b0'}, + grid:{vertLines:{color:'rgba(255,255,255,.02)'},horzLines:{color:'rgba(255,255,255,.03)'}}, + rightPriceScale:{borderColor:'rgba(255,255,255,.08)',autoScale:true}, + timeScale:{borderColor:'rgba(255,255,255,.08)',timeVisible:false}, + crosshair:{mode:0},width:el.offsetWidth,height:220 + }); + window[c.sr]=window[c.ch].addAreaSeries({lineColor:'#3b82f6',topColor:'rgba(59,130,246,.15)',bottomColor:'rgba(59,130,246,.02)',lineWidth:2}); + }); +} +function pushEquity(chart,ser,data){ + if(!chart||!ser||!data||!data.length)return; + var pts=[]; + for(var i=0;i1e12)t=Math.floor(t/1000); + pts.push({time:t,value:v}); + } + } + if(pts.length>0){ser.setData(pts);chart.timeScale().fitContent()} +} function initDetChart(){ var el=document.getElementById('det-chart'); if(!el)return; @@ -385,8 +418,8 @@ function connect(){ wsPaper.onmessage=function(e){try{lastPaper=JSON.parse(e.data)}catch(ex){return};if(currentTab==='paper')renPaper(lastPaper)}; } -function renLive(d){if(!d)return;var p=d.total_pnl||0;document.getElementById('stpnl').textContent=(p>=0?'+':'')+'$'+Math.abs(p).toFixed(2);document.getElementById('stpnl').className='pnl '+(p>=0?'up':'dn');document.getElementById('stpct').textContent='Testnet · Equity: $'+((d.base_equity||898)+p).toFixed(2);renCards('live-sgrid',d.strategies||{},d.base_equity||898,'live','live-stats')} -function renPaper(d){if(!d)return;var p=d.total_pnl||0;document.getElementById('stpnl').textContent=(p>=0?'+':'')+'$'+Math.abs(p).toFixed(2);document.getElementById('stpnl').className='pnl '+(p>=0?'up':'dn');document.getElementById('stpct').textContent='Paper · '+d.total_equity+' · Regime: '+(d.regime||'—');renCards('paper-sgrid',d.strategies||{},d.base_equity||100000,'paper','paper-stats')} +function renLive(d){if(!d)return;var p=d.total_pnl||0;document.getElementById('stpnl').textContent=(p>=0?'+':'')+'$'+Math.abs(p).toFixed(2);document.getElementById('stpnl').className='pnl '+(p>=0?'up':'dn');document.getElementById('stpct').textContent='Testnet · Equity: $'+((d.base_equity||898)+p).toFixed(2);renCards("live-sgrid",d.strategies||{},d.base_equity||898,"live","live-stats");if(d.equity_history&&chartLive)pushEquity(chartLive,serLive,d.equity_history)} +function renPaper(d){if(!d)return;var p=d.total_pnl||0;document.getElementById('stpnl').textContent=(p>=0?'+':'')+'$'+Math.abs(p).toFixed(2);document.getElementById('stpnl').className='pnl '+(p>=0?'up':'dn');document.getElementById('stpct').textContent='Paper · '+d.total_equity+' · Regime: '+(d.regime||'—');renCards("paper-sgrid",d.strategies||{},d.base_equity||100000,"paper","paper-stats");if(d.equity_history&&chartPaper)pushEquity(chartPaper,serPaper,d.equity_history)} // ═══════════ Backtests ═══════════ var lastBT={}, lastBTList=[]; @@ -411,7 +444,7 @@ function loadHistBT(){ for(var i=0;i
'+s+'
30d '+b.coin+' · Mainnet
REAL DATA
'+(pnl>=0?'+':'')+pnl.toFixed(2)+'%
Sharpe: '+b.sharpe.toFixed(2)+'DD: '+(b.max_dd*100).toFixed(2)+'%Win: '+Math.round(b.win_rate*100)+'%
'; + h+='
'+s+'
30d '+b.coin+' · Mainnet
REAL DATA
'+(pnl>=0?'+':'')+pnl.toFixed(2)+'%
Sharpe: '+b.sharpe.toFixed(2)+'DD: '+(b.max_dd*100).toFixed(2)+'%Win: '+Math.round(b.win_rate*100)+'%
'; } document.getElementById('hist-sgrid').innerHTML=h||'
No historical backtests. Run: python backtests/historical_runner.py --coin BTC --strategy all
'; }) @@ -433,7 +466,7 @@ function openHistDetail(strat){ } // ═══════════ Init ═══════════ -initDetChart();connect();loadBT(); +initDetChart();initMainCharts();connect();loadBT();loadHistBT(); // ═══════════ Risk Analytics ═══════════ function toggleRisk(){ var p=document.getElementById('risk-panel'),b=document.getElementById('risk-btn');