diff --git a/src/api/dashboard/static/js/ui/strategy-panel.js b/src/api/dashboard/static/js/ui/strategy-panel.js index fa48af6..35297b1 100644 --- a/src/api/dashboard/static/js/ui/strategy-panel.js +++ b/src/api/dashboard/static/js/ui/strategy-panel.js @@ -177,9 +177,9 @@ async function runSimulation() { // Simulation loop let balance = config.capital; let equity = [{ time: simCandles[0].time, value: balance }]; - let totalSize = 0; // Total position size in USD + let totalQty = 0; // Total position quantity in BTC let avgPrice = 0; - let trades = []; // { type, entryTime, exitTime, entryPrice, exitPrice, pnl, reason } + let trades = []; // { type, recordType, time, entryPrice, exitPrice, pnl, reason, currentUsd, currentQty } const PARTIAL_EXIT_PCT = 0.15; // 15% partial exit @@ -189,7 +189,7 @@ async function runSimulation() { let actionTakenInThisCandle = false; // 1. Check TP for total position - if (totalSize > 0) { + if (totalQty > 0) { let isTP = false; let exitPrice = price; @@ -206,12 +206,14 @@ async function runSimulation() { } if (isTP) { - const amountToClose = totalSize * PARTIAL_EXIT_PCT; + const qtyToClose = totalQty * PARTIAL_EXIT_PCT; const pnl = config.direction === 'long' - ? (exitPrice - avgPrice) / avgPrice * amountToClose * config.leverage - : (avgPrice - exitPrice) / avgPrice * amountToClose * config.leverage; + ? (exitPrice - avgPrice) * qtyToClose + : (avgPrice - exitPrice) * qtyToClose; balance += pnl; + totalQty -= qtyToClose; + trades.push({ type: config.direction, recordType: 'exit', @@ -219,15 +221,16 @@ async function runSimulation() { entryPrice: avgPrice, exitPrice: exitPrice, pnl: pnl, - reason: 'TP (Partial)' + reason: 'TP (Partial)', + currentUsd: totalQty * price, + currentQty: totalQty }); - totalSize -= amountToClose; actionTakenInThisCandle = true; } } // 2. Check Close Signals (Partial Exit) - Only if TP didn't trigger - if (!actionTakenInThisCandle && totalSize > 0 && config.closeIndicators.length > 0) { + if (!actionTakenInThisCandle && totalQty > 0 && config.closeIndicators.length > 0) { const hasCloseSignal = config.closeIndicators.some(id => { const sig = indicatorSignals[id][i]; if (!sig) return false; @@ -235,12 +238,14 @@ async function runSimulation() { }); if (hasCloseSignal) { - const amountToClose = totalSize * PARTIAL_EXIT_PCT; + const qtyToClose = totalQty * PARTIAL_EXIT_PCT; const pnl = config.direction === 'long' - ? (price - avgPrice) / avgPrice * amountToClose * config.leverage - : (avgPrice - price) / avgPrice * amountToClose * config.leverage; + ? (price - avgPrice) * qtyToClose + : (avgPrice - price) * qtyToClose; balance += pnl; + totalQty -= qtyToClose; + trades.push({ type: config.direction, recordType: 'exit', @@ -248,9 +253,10 @@ async function runSimulation() { entryPrice: avgPrice, exitPrice: price, pnl: pnl, - reason: 'Signal (Partial)' + reason: 'Signal (Partial)', + currentUsd: totalQty * price, + currentQty: totalQty }); - totalSize -= amountToClose; actionTakenInThisCandle = true; } } @@ -263,24 +269,29 @@ async function runSimulation() { return config.direction === 'long' ? sig.type === 'buy' : sig.type === 'sell'; }); - if (hasOpenSignal && balance >= config.posSize) { - const newSize = totalSize + config.posSize; - avgPrice = ((totalSize * avgPrice) + (config.posSize * price)) / newSize; - totalSize = newSize; + if (hasOpenSignal) { + const entryUsd = config.posSize * config.leverage; + const entryQty = entryUsd / price; + + const newQty = totalQty + entryQty; + avgPrice = ((totalQty * avgPrice) + (entryQty * price)) / newQty; + totalQty = newQty; trades.push({ type: config.direction, recordType: 'entry', time: candle.time, entryPrice: price, - reason: 'Entry' + reason: 'Entry', + currentUsd: totalQty * price, + currentQty: totalQty }); } } // Calculate current equity (realized balance + unrealized PnL) - const unrealizedPnl = totalSize > 0 - ? (config.direction === 'long' ? (price - avgPrice) / avgPrice : (avgPrice - price) / avgPrice) * totalSize * config.leverage + const unrealizedPnl = totalQty > 0 + ? (config.direction === 'long' ? (price - avgPrice) : (avgPrice - price)) * totalQty : 0; equity.push({ time: candle.time, value: balance + unrealizedPnl }); @@ -392,6 +403,10 @@ function toggleSimulationMarkers(trades) { const markers = []; trades.forEach(t => { + const usdVal = t.currentUsd !== undefined ? `$${t.currentUsd.toFixed(0)}` : '0'; + const qtyVal = t.currentQty !== undefined ? `${t.currentQty.toFixed(4)} BTC` : '0'; + const sizeStr = ` (${usdVal} / ${qtyVal})`; + // Entry marker if (t.recordType === 'entry') { markers.push({ @@ -399,7 +414,7 @@ function toggleSimulationMarkers(trades) { position: t.type === 'long' ? 'belowBar' : 'aboveBar', color: t.type === 'long' ? '#2962ff' : '#9c27b0', shape: t.type === 'long' ? 'arrowUp' : 'arrowDown', - text: `Entry ${t.type.toUpperCase()}` + text: `Entry ${t.type.toUpperCase()}${sizeStr}` }); } @@ -410,7 +425,7 @@ function toggleSimulationMarkers(trades) { position: t.type === 'long' ? 'aboveBar' : 'belowBar', color: t.pnl >= 0 ? '#26a69a' : '#ef5350', shape: t.type === 'long' ? 'arrowDown' : 'arrowUp', - text: `Exit ${t.reason} ($${t.pnl.toFixed(2)})` + text: `Exit ${t.reason}${sizeStr}` }); } });