Per-strategy equity curves + $100K paper capital
Paper trader now tracks individual equity history per strategy (strategy_equity dict with deque per strategy). Metrics file exports per-strategy data for dashboard rendering. Dashboard paper chart upgraded to 7 overlaid area series: - Each strategy gets its own colored curve (green, blue, purple, etc.) - 300px height for better visibility of multiple lines - Color palette distinguishes strategies at a glance $100K total capital: $10K per strategy × 7 + $30K reserve. Exeria Charts evaluated: excellent library (Benzinga award winner, Canvas/WebGL, exchange connectors) but requires npm+bundler — not suitable for single-file dashboard. Lightweight-charts remains the right choice for our architecture.
This commit is contained in:
@@ -100,7 +100,7 @@ footer{text-align:center;padding:20px;font-size:10px;color:#3f3f46}footer a{colo
|
|||||||
<!-- PAPER -->
|
<!-- PAPER -->
|
||||||
<div class="panel" id="pnl-paper">
|
<div class="panel" id="pnl-paper">
|
||||||
<div class="stats" id="paper-stats"></div>
|
<div class="stats" id="paper-stats"></div>
|
||||||
<div class="card"><h3>Equity Curve <span class="desc">real-time · Hyperliquid Mainnet (simulated fills)</span></h3><div class="chart-wrap" id="paper-chart"></div></div>
|
<div class="card"><h3>Equity Curve <span class="desc">per-strategy · Hyperliquid Mainnet (simulated)</span></h3><div class="chart-wrap" id="paper-chart" style="height:300px"></div></div>
|
||||||
<div class="grid" id="paper-grid"></div>
|
<div class="grid" id="paper-grid"></div>
|
||||||
<div class="card"><h3>Trade Log</h3><div class="tbl-scroll"><table><thead><tr><th>Time</th><th>Strategy</th><th>Side</th><th>Size</th><th>Price</th><th>Fee</th><th>PnL</th></tr></thead><tbody id="paper-tb"></tbody></table></div></div>
|
<div class="card"><h3>Trade Log</h3><div class="tbl-scroll"><table><thead><tr><th>Time</th><th>Strategy</th><th>Side</th><th>Size</th><th>Price</th><th>Fee</th><th>PnL</th></tr></thead><tbody id="paper-tb"></tbody></table></div></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,7 +117,20 @@ footer{text-align:center;padding:20px;font-size:10px;color:#3f3f46}footer a{colo
|
|||||||
var tab='live',lastData=null,lastPaper=null;
|
var tab='live',lastData=null,lastPaper=null;
|
||||||
function mkChart(el,w,h){var c=LightweightCharts.createChart(el,{layout:{background:{color:'transparent'},textColor:'#8b8b96'},grid:{vertLines:{color:'rgba(255,255,255,0.03)'},horzLines:{color:'rgba(255,255,255,0.03)'}},rightPriceScale:{borderColor:'rgba(255,255,255,0.06)'},timeScale:{borderColor:'rgba(255,255,255,0.06)',timeVisible:true},crosshair:{mode:0},width:w,height:h});return c}
|
function mkChart(el,w,h){var c=LightweightCharts.createChart(el,{layout:{background:{color:'transparent'},textColor:'#8b8b96'},grid:{vertLines:{color:'rgba(255,255,255,0.03)'},horzLines:{color:'rgba(255,255,255,0.03)'}},rightPriceScale:{borderColor:'rgba(255,255,255,0.06)'},timeScale:{borderColor:'rgba(255,255,255,0.06)',timeVisible:true},crosshair:{mode:0},width:w,height:h});return c}
|
||||||
var eqChart=mkChart(document.getElementById('eq-chart'),0,0);var eqSer=eqChart.addAreaSeries({lineColor:'#3b82f6',topColor:'rgba(59,130,246,0.15)',bottomColor:'rgba(59,130,246,0.02)',lineWidth:2});
|
var eqChart=mkChart(document.getElementById('eq-chart'),0,0);var eqSer=eqChart.addAreaSeries({lineColor:'#3b82f6',topColor:'rgba(59,130,246,0.15)',bottomColor:'rgba(59,130,246,0.02)',lineWidth:2});
|
||||||
var paperChart=mkChart(document.getElementById('paper-chart'),0,0);var paperSer=paperChart.addAreaSeries({lineColor:'#a855f7',topColor:'rgba(168,85,247,0.12)',bottomColor:'rgba(168,85,247,0.02)',lineWidth:2});
|
var paperChart=mkChart(document.getElementById('paper-chart'),0,0);
|
||||||
|
// Per-strategy equity series (7 strategies, 7 colors)
|
||||||
|
var STRAT_COLORS = ['#22c55e','#3b82f6','#a855f7','#f59e0b','#ef4444','#06b6d4','#ec4899'];
|
||||||
|
var paperStrataSeries = {};
|
||||||
|
function getStratSeries(name){
|
||||||
|
if(!paperStrataSeries[name]){
|
||||||
|
var idx = Object.keys(paperStrataSeries).length;
|
||||||
|
var color = STRAT_COLORS[idx % STRAT_COLORS.length];
|
||||||
|
paperStrataSeries[name] = paperChart.addAreaSeries({
|
||||||
|
lineColor: color, topColor: color+'26', bottomColor: color+'05', lineWidth: 1.5
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return paperStrataSeries[name];
|
||||||
|
}
|
||||||
var btChart=mkChart(document.getElementById('bt-chart'),0,0);var btSer=btChart.addAreaSeries({lineColor:'#a855f7',topColor:'rgba(168,85,247,0.12)',bottomColor:'rgba(168,85,247,0.02)',lineWidth:2});
|
var btChart=mkChart(document.getElementById('bt-chart'),0,0);var btSer=btChart.addAreaSeries({lineColor:'#a855f7',topColor:'rgba(168,85,247,0.12)',bottomColor:'rgba(168,85,247,0.02)',lineWidth:2});
|
||||||
|
|
||||||
function fitCharts(){
|
function fitCharts(){
|
||||||
@@ -196,8 +209,19 @@ function renLive(d){
|
|||||||
function renPaper(d){
|
function renPaper(d){
|
||||||
if(!d)return;
|
if(!d)return;
|
||||||
var pnl=d.total_pnl||0;document.getElementById('stpnl').textContent=(pnl>=0?'+':'')+'$'+Math.abs(pnl).toFixed(2);document.getElementById('stpnl').className='pnl '+(pnl>=0?'up':'dn');
|
var pnl=d.total_pnl||0;document.getElementById('stpnl').textContent=(pnl>=0?'+':'')+'$'+Math.abs(pnl).toFixed(2);document.getElementById('stpnl').className='pnl '+(pnl>=0?'up':'dn');
|
||||||
document.getElementById('stpct').textContent='Mainnet Paper · Equity: $'+((d.total_equity||5000)).toFixed(2)+' · BTC: $'+(d.btc_price||0).toLocaleString('en-US',{maximumFractionDigits:0});
|
document.getElementById('stpct').textContent='Mainnet Paper · Equity: $'+(d.total_equity||100000).toFixed(0)+' · BTC: $'+(d.btc_price||0).toLocaleString('en-US',{maximumFractionDigits:0});
|
||||||
renGrid('paper-grid',d.strategies||{},d.base_equity||5000,d.reserve||1000,'paper-stats','paper-tb',paperChart,paperSer,d.equity_history||[],d.trades||[]);
|
renGrid('paper-grid',d.strategies||{},d.base_equity||100000,d.reserve||30000,'paper-stats','paper-tb',paperChart,null,d.equity_history||[],d.trades||[]);
|
||||||
|
// Per-strategy equity curves
|
||||||
|
var seq = d.strategy_equity || {};
|
||||||
|
var keyz = Object.keys(seq);
|
||||||
|
for(var i=0;i<keyz.length;i++){
|
||||||
|
var name=keyz[i],pts=seq[name],ser=getStratSeries(name);
|
||||||
|
if(pts&&pts.length>0){
|
||||||
|
var arr=[];for(var j=0;j<pts.length;j++) if(pts[j]&&pts[j].t) arr.push({time:pts[j].t,value:pts[j].v});
|
||||||
|
ser.setData(arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paperChart.timeScale().fitContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleStrat(sid){var el=document.getElementById('strat-'+sid);if(!el)return;el.classList.toggle('open');setTimeout(fitCharts,200)}
|
function toggleStrat(sid){var el=document.getElementById('strat-'+sid);if(!el)return;el.classList.toggle('open');setTimeout(fitCharts,200)}
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ STRATEGIES = {
|
|||||||
|
|
||||||
trades_log: list[dict] = []
|
trades_log: list[dict] = []
|
||||||
equity_history: list[dict] = []
|
equity_history: list[dict] = []
|
||||||
|
strategy_equity: dict = {name: deque(maxlen=300) for name in STRATEGIES}
|
||||||
btc_prices: deque = deque(maxlen=120)
|
btc_prices: deque = deque(maxlen=120)
|
||||||
eth_prices: deque = deque(maxlen=120)
|
eth_prices: deque = deque(maxlen=120)
|
||||||
funding_rates: deque = deque(maxlen=100)
|
funding_rates: deque = deque(maxlen=100)
|
||||||
@@ -258,6 +259,8 @@ def simulate_fill(name: str, side: str, coin: str, price: float):
|
|||||||
|
|
||||||
cfg["trades_today"] += 1
|
cfg["trades_today"] += 1
|
||||||
cfg["pnl_pct"] = cfg["pnl"] / cfg["allocation"] * 100
|
cfg["pnl_pct"] = cfg["pnl"] / cfg["allocation"] * 100
|
||||||
|
# Track per-strategy equity
|
||||||
|
strategy_equity[name].append({"t": time.time(), "v": cfg["allocation"] + cfg["pnl"]})
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════ A-S Spread Capture ═══════════════════════
|
# ═══════════════════════ A-S Spread Capture ═══════════════════════
|
||||||
@@ -312,6 +315,7 @@ def simulate_avellaneda(btc_bid, btc_ask):
|
|||||||
cfg["fee_paid"] += fee
|
cfg["fee_paid"] += fee
|
||||||
cfg["trades_today"] += 1
|
cfg["trades_today"] += 1
|
||||||
cfg["pnl_pct"] = cfg["pnl"] / cfg["allocation"] * 100
|
cfg["pnl_pct"] = cfg["pnl"] / cfg["allocation"] * 100
|
||||||
|
strategy_equity["Avellaneda-Stoikov"].append({"t": time.time(), "v": cfg["allocation"] + cfg["pnl"]})
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════ Metrics ═══════════════════════
|
# ═══════════════════════ Metrics ═══════════════════════
|
||||||
@@ -332,6 +336,7 @@ def write_metrics():
|
|||||||
"total_pnl_pct": total_pnl_pct,
|
"total_pnl_pct": total_pnl_pct,
|
||||||
"reserve": RESERVE,
|
"reserve": RESERVE,
|
||||||
"equity_history": equity_history[-600:],
|
"equity_history": equity_history[-600:],
|
||||||
|
"strategy_equity": {k: list(v)[-300:] for k, v in strategy_equity.items()},
|
||||||
"strategies": STRATEGIES,
|
"strategies": STRATEGIES,
|
||||||
"trades": trades_log[-200:],
|
"trades": trades_log[-200:],
|
||||||
"status": "running",
|
"status": "running",
|
||||||
|
|||||||
Reference in New Issue
Block a user