Fix dashboard backtest detail, deterministic backtest seeds, paper trader fees, live node crash guard
Backtest detail: openDetail() now fetches full backtest JSON from the API instead of showing "Full trade data not in summary". Renders equity curve chart + full trade history table with 100 rows. Backtest reproducibility: replaced hash(key) with fixed per-strategy seeds. Python's hash() is randomized per process (PYTHONHASHSEED), causing wildly different results for same strategy across runs. Now deterministic. Server: added total_trades and sortino to /api/backtests summary response. Paper trader: fixed Avellaneda-Stoikov simulate using TAKER_FEE instead of MAKER_FEE. Lowered OBI signal threshold from 5bps to 1.5bps for flat markets. Live node: added None-guard in get_mark_prices — Hyperliquid testnet API sometimes returns null, crashing the node. Wrapped in try/except.
This commit is contained in:
@@ -174,9 +174,11 @@ async def list_backtests():
|
||||
"start": data.get("start_time"),
|
||||
"end": data.get("end_time"),
|
||||
"sharpe": data.get("sharpe", 0),
|
||||
"sortino": data.get("sortino", 0),
|
||||
"pnl_pct": data.get("pnl_pct", 0),
|
||||
"max_dd": data.get("max_dd", 0),
|
||||
"win_rate": data.get("win_rate", 0),
|
||||
"total_trades": data.get("total_trades", 0),
|
||||
})
|
||||
except (json.JSONDecodeError, IOError):
|
||||
pass
|
||||
|
||||
@@ -210,15 +210,34 @@ function openDetail(name,tab){
|
||||
ss=lastData.strategies||{};
|
||||
} else if(tab==='backtest'&&lastBT&&lastBT[name]){
|
||||
var b=lastBT[name];
|
||||
document.getElementById('det-desc').textContent=(b.strategy||'')+' — 30-day backtest, Sharpe '+(b.sharpe||0).toFixed(2)+', max DD '+(b.max_dd*100).toFixed(1)+'%.';
|
||||
document.getElementById('det-desc').textContent=(b.strategy||'')+' — 30-day backtest, Sharpe '+(b.sharpe||0).toFixed(2)+', max DD '+(b.max_dd*100).toFixed(1)+'%. '+((b.total_trades||0)+' trades').replace('0 trades','');
|
||||
document.getElementById('det-stats').innerHTML='<div class="stat"><div class="lbl">PnL</div><div class="val '+(b.pnl_pct>=0?'up':'dn')+'">'+(b.pnl_pct>=0?'+':'')+(b.pnl_pct||0).toFixed(2)+'%</div></div>'+
|
||||
'<div class="stat"><div class="lbl">Sharpe</div><div class="val">'+(b.sharpe||0).toFixed(2)+'</div></div>'+
|
||||
'<div class="stat"><div class="lbl">Sortino</div><div class="val">'+(b.sortino||0).toFixed(2)+'</div></div>'+
|
||||
'<div class="stat"><div class="lbl">Max DD</div><div class="val dn">'+(b.max_dd*100).toFixed(2)+'%</div></div>'+
|
||||
'<div class="stat"><div class="lbl">Win Rate</div><div class="val">'+Math.round((b.win_rate||0)*100)+'%</div></div>'+
|
||||
'<div class="stat"><div class="lbl">From</div><div class="val" style="font-size:9px">'+(b.start||'').substr(0,10)+'</div></div>'+
|
||||
'<div class="stat"><div class="lbl">To</div><div class="val" style="font-size:9px">'+(b.end||'').substr(0,10)+'</div></div>';
|
||||
'<div class="stat"><div class="lbl">Trades</div><div class="val">'+(b.total_trades||0)+'</div></div>';
|
||||
if(detSer)detSer.setData([]);
|
||||
document.getElementById('det-trades').innerHTML='<tr><td colspan="7" style="text-align:center;color:var(--tx);padding:20px">Full trade data not in summary — run with --strategy to regenerate</td></tr>';
|
||||
document.getElementById('det-trades').innerHTML='<tr><td colspan="7" style="text-align:center;color:var(--tx);padding:20px">Loading full trade data…</td></tr>';
|
||||
// Fetch full backtest data from API
|
||||
fetch('/cv/api/backtest/'+encodeURIComponent(b.name)).then(function(r){return r.json()}).then(function(full){
|
||||
document.getElementById('det-desc').textContent=(full.strategy||b.strategy)+' — '+full.num_periods+' periods, '+full.total_trades+' trades, ▲ $'+(full.pnl||0).toFixed(2);
|
||||
// Equity chart
|
||||
if(!detChart)initDetChart();
|
||||
var pts=[],curve=full.equity_curve||[];
|
||||
for(var i=0;i<curve.length;i++){if(curve[i]&&curve[i].t)pts.push({time:curve[i].t,value:curve[i].v})}
|
||||
if(pts.length>0){detSer.setData(pts);detChart.timeScale().fitContent();setTimeout(function(){detChart.timeScale().fitContent()},200)}
|
||||
// Trades table
|
||||
var trows='',tlist=full.trades||[];
|
||||
for(var j=Math.max(0,tlist.length-100);j<tlist.length;j++){
|
||||
var t=tlist[j],tp=t.pnl||0,tside=(t.side||'').toUpperCase();
|
||||
trows+='<tr><td>'+(t.time||'').substr(0,16)+'</td><td class="'+(tside.indexOf('BUY')>=0?'green':'red')+'">'+tside+'</td><td>'+t.size+'</td><td>$'+(t.price||0).toFixed(1)+'</td><td class="'+(tp>=0?'green':'red')+'">'+(tp>=0?'+':'')+'$'+Math.abs(tp).toFixed(4)+'</td><td class="red">—</td><td class="reason">—</td></tr>';
|
||||
}
|
||||
document.getElementById('det-trades').innerHTML=trows||'<tr><td colspan="7" style="text-align:center;color:var(--tx);padding:20px">No trades recorded</td></tr>';
|
||||
setTimeout(function(){if(detChart)detChart.applyOptions({width:document.getElementById('det-chart').offsetWidth,height:280})},300);
|
||||
}).catch(function(e){
|
||||
document.getElementById('det-trades').innerHTML='<tr><td colspan="7" style="text-align:center;color:var(--rd);padding:20px">Failed to load trade data: '+e.message+'</td></tr>';
|
||||
});
|
||||
return;
|
||||
}
|
||||
var s=ss?ss[name]:null;
|
||||
@@ -276,10 +295,17 @@ function renLive(d){if(!d)return;var p=d.total_pnl||0;document.getElementById('s
|
||||
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')}
|
||||
|
||||
// ═══════════ Backtests ═══════════
|
||||
var lastBT={}, lastBTList=[];
|
||||
function loadBT(){
|
||||
fetch('/cv/api/backtests').then(function(r){return r.json()}).then(function(data){
|
||||
var h='';lastBT={};for(var i=0;i<data.length;i++){var b=data[i];var pnl=b.pnl_pct||0;lastBT[b.strategy]=b;h+='<div class="scard" onclick="openDetail(\''+b.strategy+'\',\'backtest\')\"><div class="sh"><div><div class="sname">'+b.strategy+'</div><div class="salloc">30-day · $100</div></div><span class="stag run">BACKTEST</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('bt-sgrid').innerHTML=h||'<div style="padding:20px;color:var(--tx)">No backtests.</div>';
|
||||
lastBTList=data; lastBT={};
|
||||
// Keep latest backtest per strategy (sorted by time desc — first wins)
|
||||
for(var i=0;i<data.length;i++){var b=data[i];if(!lastBT[b.strategy])lastBT[b.strategy]=b;}
|
||||
var h='';
|
||||
for(var s in lastBT){var b=lastBT[s];var pnl=b.pnl_pct||0;
|
||||
h+='<div class=\"scard\" onclick=\"openDetail(\''+s+'\',\'backtest\')\"><div class=\"sh\"><div><div class=\"sname\">'+s+'</div><div class=\"salloc\">30-day · $100</div></div><span class=\"stag run\">BACKTEST</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('bt-sgrid').innerHTML=h||'<div style=\"padding:20px;color:var(--tx)\">No backtests.</div>';
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user