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
This commit is contained in:
@@ -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)}
|
||||
<div class="panel show" id="pnl-live">
|
||||
<div class="stats-row" id="live-stats"></div>
|
||||
<div class="sgrid" id="live-sgrid"></div>
|
||||
<div class="chart-wrap main-chart" id="chart-live-wrap"><div id="chart-live"></div></div>
|
||||
</div>
|
||||
<div class="panel" id="pnl-paper">
|
||||
<div class="stats-row" id="paper-stats"></div>
|
||||
<div class="sgrid" id="paper-sgrid"></div>
|
||||
<div class="chart-wrap main-chart" id="chart-paper-wrap"><div id="chart-paper"></div></div>
|
||||
</div>
|
||||
<div class="panel" id="pnl-backtest">
|
||||
<div class="sgrid" id="bt-sgrid"></div>
|
||||
@@ -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;i<data.length;i++){
|
||||
var t=data[i].t||data[i].time||data[i][0];
|
||||
var v=data[i].v||data[i].value||data[i].equity||data[i][1];
|
||||
if(typeof t==='number'){
|
||||
if(t>1e12)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<data.length;i++){var b=data[i];if(!lastHist[b.strategy])lastHist[b.strategy]=b;}
|
||||
var h='';
|
||||
for(var s in lastHist){var b=lastHist[s];var pnl=b.pnl_pct||0;
|
||||
h+='<div class="scard" onclick="openHistDetail(\'+s+\')"><div class="sh"><div><div class="sname">'+s+'</div><div class="salloc">30d '+b.coin+' · Mainnet</div></div><span class="stag run">REAL DATA</span></div><div class="spnl '+(pnl>=0?'up':'dn')+'">'+(pnl>=0?'+':'')+pnl.toFixed(2)+'%</div><div class="smeta"><span>Sharpe: <b>'+b.sharpe.toFixed(2)+'</b></span><span>DD: <b class="red">'+(b.max_dd*100).toFixed(2)+'%</b></span><span>Win: <b>'+Math.round(b.win_rate*100)+'%</b></span></div></div>';
|
||||
h+='<div class="scard" data-strat="'+s+'" onclick="openHistDetail(this.dataset.strat)"><div class="sh"><div><div class="sname">'+s+'</div><div class="salloc">30d '+b.coin+' · Mainnet</div></div><span class="stag run">REAL DATA</span></div><div class="spnl '+(pnl>=0?'up':'dn')+'">'+(pnl>=0?'+':'')+pnl.toFixed(2)+'%</div><div class="smeta"><span>Sharpe: <b>'+b.sharpe.toFixed(2)+'</b></span><span>DD: <b class="red">'+(b.max_dd*100).toFixed(2)+'%</b></span><span>Win: <b>'+Math.round(b.win_rate*100)+'%</b></span></div></div>';
|
||||
}
|
||||
document.getElementById('hist-sgrid').innerHTML=h||'<div style="padding:20px;color:var(--tx)">No historical backtests. Run: python backtests/historical_runner.py --coin BTC --strategy all</div>';
|
||||
})
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user