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:
ramseshk
2026-08-04 07:07:15 +00:00
parent 1c83fd378e
commit e4de21192a
17 changed files with 43315 additions and 16 deletions
+2
View File
@@ -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
+32 -6
View File
@@ -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&hellip;</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 &middot; $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 &middot; $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>';
})
}