import { getStrategy, registerStrategy } from '../strategies/index.js'; import { PingPongStrategy } from '../strategies/ping-pong.js'; // Register available strategies registerStrategy('ping_pong', PingPongStrategy); let activeIndicators = []; function formatDisplayDate(timestamp) { if (!timestamp) return ''; const date = new Date(timestamp); const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); const year = date.getFullYear(); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); return `${day}/${month}/${year} ${hours}:${minutes}`; } export function initStrategyPanel() { window.renderStrategyPanel = renderStrategyPanel; renderStrategyPanel(); // Listen for indicator changes to update the signal selection list const originalAddIndicator = window.addIndicator; window.addIndicator = function(...args) { const res = originalAddIndicator.apply(this, args); setTimeout(renderStrategyPanel, 100); return res; }; const originalRemoveIndicator = window.removeIndicatorById; window.removeIndicatorById = function(...args) { const res = originalRemoveIndicator.apply(this, args); setTimeout(renderStrategyPanel, 100); return res; }; } export function renderStrategyPanel() { const container = document.getElementById('strategyPanel'); if (!container) return; activeIndicators = window.getActiveIndicators?.() || []; // For now, we only have Ping-Pong. Later we can add a strategy selector. const currentStrategyId = 'ping_pong'; const strategy = getStrategy(currentStrategyId); if (!strategy) { container.innerHTML = ``; return; } container.innerHTML = ` `; // Attach strategy specific listeners (like disabling dropdowns when auto-detect is on) if (strategy.attachListeners) { strategy.attachListeners(); } document.getElementById('runSimulationBtn').addEventListener('click', () => { strategy.runSimulation(activeIndicators, displayResults); }); } // Keep the display logic here so all strategies can use the same rendering for results let equitySeries = null; let equityChart = null; let posSeries = null; let posSizeChart = null; let tradeMarkers = []; function displayResults(trades, equityData, config, endPrice, avgPriceData, posSizeData) { const resultsDiv = document.getElementById('simulationResults'); resultsDiv.style.display = 'block'; if (window.dashboard) { window.dashboard.setAvgPriceData(avgPriceData); } const entryTrades = trades.filter(t => t.recordType === 'entry').length; const exitTrades = trades.filter(t => t.recordType === 'exit').length; const profitableTrades = trades.filter(t => t.recordType === 'exit' && t.pnl > 0).length; const winRate = exitTrades > 0 ? (profitableTrades / exitTrades * 100).toFixed(1) : 0; const startPrice = equityData.usd[0].value / equityData.btc[0].value; const startBtc = config.capital / startPrice; const finalUsd = equityData.usd[equityData.usd.length - 1].value; const finalBtc = finalUsd / endPrice; const totalPnlUsd = finalUsd - config.capital; const roi = (totalPnlUsd / config.capital * 100).toFixed(2); const roiBtc = ((finalBtc - startBtc) / startBtc * 100).toFixed(2); resultsDiv.innerHTML = ` `; // Create Charts const initCharts = () => { const equityContainer = document.getElementById('equityChart'); if (equityContainer) { equityContainer.innerHTML = ''; equityChart = LightweightCharts.createChart(equityContainer, { layout: { background: { color: '#131722' }, textColor: '#d1d4dc' }, grid: { vertLines: { visible: false }, horzLines: { color: '#2a2e39' } }, rightPriceScale: { borderColor: '#2a2e39', autoScale: true }, timeScale: { borderColor: '#2a2e39', visible: true, timeVisible: true, secondsVisible: false, tickMarkFormatter: (time, tickMarkType, locale) => { return window.TimezoneConfig ? window.TimezoneConfig.formatTickMark(time) : new Date(time * 1000).toLocaleDateString(); }, }, localization: { timeFormatter: (timestamp) => { return window.TimezoneConfig ? window.TimezoneConfig.formatDate(timestamp * 1000) : new Date(timestamp * 1000).toLocaleString(); }, }, handleScroll: true, handleScale: true }); equitySeries = equityChart.addSeries(LightweightCharts.AreaSeries, { lineColor: totalPnlUsd >= 0 ? '#26a69a' : '#ef5350', topColor: totalPnlUsd >= 0 ? 'rgba(38, 166, 154, 0.4)' : 'rgba(239, 83, 80, 0.4)', bottomColor: 'rgba(0, 0, 0, 0)', lineWidth: 2, }); equitySeries.setData(equityData['usd']); equityChart.timeScale().fitContent(); } const posSizeContainer = document.getElementById('posSizeChart'); if (posSizeContainer) { posSizeContainer.innerHTML = ''; posSizeChart = LightweightCharts.createChart(posSizeContainer, { layout: { background: { color: '#131722' }, textColor: '#d1d4dc' }, grid: { vertLines: { visible: false }, horzLines: { color: '#2a2e39' } }, rightPriceScale: { borderColor: '#2a2e39', autoScale: true }, timeScale: { borderColor: '#2a2e39', visible: true, timeVisible: true, secondsVisible: false, tickMarkFormatter: (time, tickMarkType, locale) => { return window.TimezoneConfig ? window.TimezoneConfig.formatTickMark(time) : new Date(time * 1000).toLocaleDateString(); }, }, localization: { timeFormatter: (timestamp) => { return window.TimezoneConfig ? window.TimezoneConfig.formatDate(timestamp * 1000) : new Date(timestamp * 1000).toLocaleString(); }, }, handleScroll: true, handleScale: true }); posSeries = posSizeChart.addSeries(LightweightCharts.AreaSeries, { lineColor: '#00bcd4', topColor: 'rgba(0, 188, 212, 0.4)', bottomColor: 'rgba(0, 0, 0, 0)', lineWidth: 2, }); posSeries.setData(posSizeData['usd']); posSizeChart.timeScale().fitContent(); const label = document.getElementById('posSizeLabel'); if (label) label.textContent = 'Position Size (USD)'; } if (equityChart && posSizeChart) { let isSyncing = false; const syncCharts = (source, target) => { if (isSyncing) return; isSyncing = true; const range = source.timeScale().getVisibleRange(); target.timeScale().setVisibleRange(range); isSyncing = false; }; equityChart.timeScale().subscribeVisibleTimeRangeChange(() => syncCharts(equityChart, posSizeChart)); posSizeChart.timeScale().subscribeVisibleTimeRangeChange(() => syncCharts(posSizeChart, equityChart)); } const syncToMain = (param) => { if (!param.time || !window.dashboard || !window.dashboard.chart) return; const timeScale = window.dashboard.chart.timeScale(); const currentRange = timeScale.getVisibleRange(); if (!currentRange) return; const width = currentRange.to - currentRange.from; const halfWidth = width / 2; timeScale.setVisibleRange({ from: param.time - halfWidth, to: param.time + halfWidth }); }; if (equityChart) equityChart.subscribeClick(syncToMain); if (posSizeChart) posSizeChart.subscribeClick(syncToMain); }; setTimeout(initCharts, 100); resultsDiv.querySelectorAll('.toggle-btn').forEach(btn => { btn.addEventListener('click', (e) => { const unit = btn.dataset.unit; resultsDiv.querySelectorAll(`.toggle-btn`).forEach(b => { if (b.dataset.unit === unit) b.classList.add('active'); else b.classList.remove('active'); }); if (equitySeries) { equitySeries.setData(equityData[unit]); equityChart.timeScale().fitContent(); } if (posSeries) { posSeries.setData(posSizeData[unit]); posSizeChart.timeScale().fitContent(); const label = document.getElementById('posSizeLabel'); if (label) label.textContent = `Position Size (${unit.toUpperCase()})`; } }); }); document.getElementById('toggleTradeMarkers').addEventListener('click', () => { toggleSimulationMarkers(trades); }); document.getElementById('clearSim').addEventListener('click', () => { resultsDiv.style.display = 'none'; clearSimulationMarkers(); if (window.dashboard) { window.dashboard.clearAvgPriceData(); } }); } function toggleSimulationMarkers(trades) { if (tradeMarkers.length > 0) { clearSimulationMarkers(); document.getElementById('toggleTradeMarkers').textContent = 'Show Markers'; return; } 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})`; if (t.recordType === 'entry') { markers.push({ time: t.time, position: t.type === 'long' ? 'belowBar' : 'aboveBar', color: t.type === 'long' ? '#2962ff' : '#9c27b0', shape: t.type === 'long' ? 'arrowUp' : 'arrowDown', text: `Entry ${t.type.toUpperCase()}${sizeStr}` }); } if (t.recordType === 'exit') { markers.push({ time: t.time, position: t.type === 'long' ? 'aboveBar' : 'belowBar', color: t.pnl >= 0 ? '#26a69a' : '#ef5350', shape: t.type === 'long' ? 'arrowDown' : 'arrowUp', text: `Exit ${t.reason}${sizeStr}` }); } }); markers.sort((a, b) => a.time - b.time); if (window.dashboard) { window.dashboard.setSimulationMarkers(markers); tradeMarkers = markers; document.getElementById('toggleTradeMarkers').textContent = 'Hide Markers'; } } function clearSimulationMarkers() { if (window.dashboard) { window.dashboard.clearSimulationMarkers(); tradeMarkers = []; } } window.clearSimulationResults = function() { const resultsDiv = document.getElementById('simulationResults'); if (resultsDiv) resultsDiv.style.display = 'none'; clearSimulationMarkers(); };