From c2e5b23b89a3eb600ac5c4745d98ec03db491663 Mon Sep 17 00:00:00 2001 From: BTC Bot Date: Mon, 16 Feb 2026 10:32:48 +0100 Subject: [PATCH] Fix sidebar toggle button visibility when collapsed; add hover effect --- src/api/dashboard/static/index.html | 1460 ++++++++++++++++++++++----- 1 file changed, 1191 insertions(+), 269 deletions(-) diff --git a/src/api/dashboard/static/index.html b/src/api/dashboard/static/index.html index c2b31aa..8743ac1 100644 --- a/src/api/dashboard/static/index.html +++ b/src/api/dashboard/static/index.html @@ -159,10 +159,18 @@ .stat-value.negative { color: var(--tv-red); } .main-container { + flex: 1; + display: flex; + flex-direction: row; + overflow: hidden; + } + + .chart-area { flex: 1; display: flex; flex-direction: column; overflow: hidden; + min-width: 0; } .chart-wrapper { @@ -182,6 +190,337 @@ background: var(--tv-panel-bg); border-top: 1px solid var(--tv-border); display: flex; + } + + /* Right Sidebar - Strategy Simulation */ + .right-sidebar { + width: 350px; + background: var(--tv-panel-bg); + border-left: 1px solid var(--tv-border); + display: flex; + flex-direction: column; + overflow: hidden; + transition: width 0.3s ease; + } + + .right-sidebar.collapsed { + width: 44px; + min-width: 44px; + } + + .sidebar-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border-bottom: 1px solid var(--tv-border); + background: var(--tv-bg); + min-height: 48px; + } + + .right-sidebar.collapsed .sidebar-header { + justify-content: center; + padding: 12px 4px; + } + + .sidebar-title { + font-size: 14px; + font-weight: 600; + display: flex; + align-items: center; + gap: 8px; + white-space: nowrap; + } + + .right-sidebar.collapsed .sidebar-title { + display: none; + } + + .sidebar-toggle { + background: transparent; + border: none; + color: var(--tv-text); + cursor: pointer; + padding: 4px; + font-size: 16px; + transition: transform 0.3s ease; + min-width: 24px; + min-height: 24px; + display: flex; + align-items: center; + justify-content: center; + } + + .sidebar-toggle:hover { + background: var(--tv-hover); + border-radius: 4px; + } + + .right-sidebar.collapsed .sidebar-toggle { + transform: rotate(180deg); + } + + .sidebar-content { + flex: 1; + overflow-y: auto; + padding: 12px; + } + + .right-sidebar.collapsed .sidebar-content { + display: none; + } + + .sidebar-section { + margin-bottom: 16px; + border: 1px solid var(--tv-border); + border-radius: 6px; + overflow: hidden; + } + + .sidebar-section-header { + background: var(--tv-bg); + padding: 10px 12px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 1px solid var(--tv-border); + display: flex; + align-items: center; + gap: 6px; + } + + .sidebar-section-content { + padding: 12px; + } + + /* Strategy Config Form */ + .config-group { + margin-bottom: 12px; + } + + .config-label { + display: block; + font-size: 11px; + color: var(--tv-text-secondary); + text-transform: uppercase; + margin-bottom: 4px; + } + + .config-input { + width: 100%; + background: var(--tv-bg); + border: 1px solid var(--tv-border); + border-radius: 4px; + padding: 8px; + color: var(--tv-text); + font-size: 13px; + } + + .config-input:focus { + outline: none; + border-color: var(--tv-blue); + } + + /* Strategy List */ + .strategy-item { + display: flex; + align-items: center; + gap: 8px; + padding: 10px; + border-bottom: 1px solid var(--tv-border); + cursor: pointer; + transition: background 0.2s; + } + + .strategy-item:last-child { + border-bottom: none; + } + + .strategy-item:hover { + background: var(--tv-hover); + } + + .strategy-item.selected { + background: rgba(41, 98, 255, 0.1); + border-left: 3px solid var(--tv-blue); + } + + .strategy-radio { + width: 16px; + height: 16px; + accent-color: var(--tv-blue); + } + + .strategy-name { + flex: 1; + font-size: 13px; + } + + .strategy-info { + color: var(--tv-text-secondary); + font-size: 14px; + cursor: help; + } + + /* Results Panel */ + .results-summary { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + margin-bottom: 12px; + } + + .result-stat { + background: var(--tv-bg); + padding: 8px; + border-radius: 4px; + text-align: center; + } + + .result-stat-value { + font-size: 18px; + font-weight: 600; + } + + .result-stat-label { + font-size: 10px; + color: var(--tv-text-secondary); + text-transform: uppercase; + } + + /* Equity Sparkline */ + .equity-sparkline { + height: 60px; + background: var(--tv-bg); + border-radius: 4px; + margin-bottom: 12px; + position: relative; + } + + /* Action Buttons */ + .action-btn { + width: 100%; + padding: 10px; + border: none; + border-radius: 4px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: opacity 0.2s; + margin-bottom: 8px; + } + + .action-btn:hover { + opacity: 0.9; + } + + .action-btn.primary { + background: var(--tv-blue); + color: white; + } + + .action-btn.secondary { + background: var(--tv-bg); + color: var(--tv-text); + border: 1px solid var(--tv-border); + } + + .action-btn.success { + background: var(--tv-green); + color: white; + } + + /* Saved Simulations List */ + .saved-sim-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px; + background: var(--tv-bg); + border-radius: 4px; + margin-bottom: 6px; + font-size: 12px; + } + + .saved-sim-name { + flex: 1; + cursor: pointer; + } + + .saved-sim-name:hover { + color: var(--tv-blue); + } + + .saved-sim-actions { + display: flex; + gap: 4px; + } + + .sim-action-btn { + background: transparent; + border: none; + color: var(--tv-text-secondary); + cursor: pointer; + padding: 2px 4px; + font-size: 12px; + } + + .sim-action-btn:hover { + color: var(--tv-text); + } + + /* Export Dialog */ + .export-dialog { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: var(--tv-panel-bg); + border: 1px solid var(--tv-border); + border-radius: 8px; + padding: 20px; + z-index: 10000; + min-width: 300px; + box-shadow: 0 4px 20px rgba(0,0,0,0.5); + } + + .export-dialog-title { + font-size: 16px; + font-weight: 600; + margin-bottom: 16px; + } + + .export-options { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 16px; + } + + .export-option { + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + background: var(--tv-bg); + border-radius: 4px; + cursor: pointer; + } + + .export-option:hover { + background: var(--tv-hover); + } + + .dialog-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.7); + z-index: 9999; + } flex-direction: column; min-height: 200px; max-height: 400px; @@ -605,28 +944,166 @@
-
-
-
- -
-
-
- Technical Analysis - 1D +
+
+
+
+ +
+
+
+ Technical Analysis + 1D +
+
+ -- + + +
-
- -- - - +
+
Loading technical analysis...
-
-
Loading technical analysis...
+
+ + +
@@ -1395,77 +1872,7 @@
-
-
Strategy Simulation
- -
- -
- - -
- - -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
Loading strategies...
-
- - - - - -
`; - - // Load strategies after simulation panel is rendered - setTimeout(() => { - loadStrategies(); - setDefaultStartDate(); - }, 0); } updateStats(candle) { @@ -1510,186 +1917,7 @@ const geminiUrl = `https://gemini.google.com/app?prompt=${encodeURIComponent(prompt)}`; window.open(geminiUrl, '_blank'); } - - // Load strategies on page load - async function loadStrategies() { - try { - console.log('Fetching strategies from API...'); - - // Add timeout to fetch - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout - - const response = await fetch('/api/v1/strategies', { - signal: controller.signal - }); - clearTimeout(timeoutId); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - console.log('Strategies loaded:', data); - - if (!data.strategies) { - throw new Error('Invalid response format: missing strategies array'); - } - - window.availableStrategies = data.strategies; - renderStrategies(data.strategies); - } catch (error) { - console.error('Error loading strategies:', error); - - let errorMessage = error.message; - if (error.name === 'AbortError') { - errorMessage = 'Request timeout - API server not responding'; - } else if (error.message.includes('Failed to fetch')) { - errorMessage = 'Cannot connect to API server - is it running?'; - } - - document.getElementById('strategyList').innerHTML = - `
- ${errorMessage}
- Check console (F12) for details -
`; - } - } - - // Render strategy list - function renderStrategies(strategies) { - const container = document.getElementById('strategyList'); - - if (!strategies || strategies.length === 0) { - container.innerHTML = '
No strategies available
'; - return; - } - - container.innerHTML = strategies.map((s, index) => ` -
- - - โ“˜ -
- `).join(''); - - // Enable run button - document.getElementById('runSimBtn').disabled = false; - } - - // Run simulation (Client-Side) - async function runSimulation() { - const selectedStrategy = document.querySelector('input[name="strategy"]:checked'); - if (!selectedStrategy) { - alert('Please select a strategy'); - return; - } - - const strategyId = selectedStrategy.value; - const strategy = (window.availableStrategies || []).find(s => s.id === strategyId); - const startDateInput = document.getElementById('simStartDate').value; - if (!startDateInput) { - alert('Please select a start date'); - return; - } - - const runBtn = document.getElementById('runSimBtn'); - const simResults = document.getElementById('simResults'); - runBtn.disabled = true; - runBtn.textContent = 'Downloading...'; - simResults.style.display = 'none'; - try { - const interval = window.dashboard?.currentInterval || '1d'; - const secondaryTF = document.getElementById('simSecondaryTF').value; - const riskPercent = parseFloat(document.getElementById('simRiskPercent').value) / 100; - const stopLossPercent = parseFloat(document.getElementById('simStopLoss').value) / 100; - - const start = new Date(startDateInput).toISOString(); - - // 1. Fetch Bulk Candles - const timeframes = [interval]; - if (secondaryTF && !timeframes.includes(secondaryTF)) timeframes.push(secondaryTF); - - // For MA strategies, we might need more data before start date for calculation - const fetchStart = new Date(new Date(start).getTime() - (200 * 24 * 60 * 60 * 1000)).toISOString(); - - const query = new URLSearchParams({ symbol: 'BTC', start: fetchStart }); - timeframes.forEach(tf => query.append('timeframes', tf)); - const response = await fetch(`/api/v1/candles/bulk?${query.toString()}`); - const candlesMap = await response.json(); - - if (!candlesMap[interval] || candlesMap[interval].length === 0) { - throw new Error('No data found for the selected period'); - } - - runBtn.textContent = 'Calculating...'; - - // 2. Setup Strategy Config - const strategyConfig = { - id: strategyId, - timeframes: { primary: interval, secondary: secondaryTF ? [secondaryTF] : [] }, - indicators: (strategy?.required_indicators || []).map(name => ({ - name: name, - type: name.startsWith('ma') ? 'sma' : name, - params: { period: parseInt(name.replace(/\D/g, '')) || 44 }, - timeframe: interval // primary by default - })) - }; - - // Add secondary TF indicators if needed (example: MA44 on secondary TF) - if (secondaryTF) { - strategyConfig.indicators.push({ - name: `ma44_${secondaryTF}`, - type: 'sma', - params: { period: 44 }, - timeframe: secondaryTF - }); - } - - // 3. Run Engine - const riskConfig = { - positionSizing: { method: 'percent', value: riskPercent }, - stopLoss: { enabled: true, method: 'percent', value: stopLossPercent } - }; - - const engine = new ClientStrategyEngine(); - const results = engine.run(candlesMap, strategyConfig, riskConfig, start); - - if (results.error) throw new Error(results.error); - - // 4. Store Results for plotting - window.lastSimulationResults = results; - - // 5. Display Results - displayBacktestResults({ results }); - - console.log(`Simulation complete: ${results.total_trades} trades found.`); - - } catch (error) { - console.error('Simulation error:', error); - alert('Simulation error: ' + error.message); - } finally { - runBtn.disabled = false; - runBtn.textContent = 'Run Simulation'; - } - } - - // Display backtest results in the UI - function displayBacktestResults(backtest) { - const results = backtest.results; - - document.getElementById('simTrades').textContent = results.total_trades || '0'; - document.getElementById('simWinRate').textContent = (results.win_rate || 0).toFixed(1) + '%'; - - const pnlElement = document.getElementById('simPnL'); - const pnl = results.total_pnl || 0; - pnlElement.textContent = (pnl >= 0 ? '+' : '') + '$' + pnl.toFixed(2); - pnlElement.className = 'sim-value ' + (pnl >= 0 ? 'positive' : 'negative'); - - document.getElementById('simResults').style.display = 'block'; - } // Setup marker tooltips on chart hover function setupMarkerTooltips(trades) { @@ -1858,13 +2086,707 @@ if (startDateInput) { const sevenDaysAgo = new Date(); sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); - // Format as datetime-local: YYYY-MM-DDTHH:mm startDateInput.value = sevenDaysAgo.toISOString().slice(0, 16); } } + // ========================================== + // SIMULATION STORAGE SERVICE (localStorage) + // ========================================== + const SimulationStorage = { + STORAGE_KEY: 'btc_bot_simulations', + + getAll() { + try { + const data = localStorage.getItem(this.STORAGE_KEY); + return data ? JSON.parse(data) : []; + } catch (e) { + console.error('Error reading simulations:', e); + return []; + } + }, + + save(simulation) { + try { + const simulations = this.getAll(); + simulation.id = simulation.id || 'sim_' + Date.now(); + simulation.createdAt = new Date().toISOString(); + simulations.push(simulation); + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(simulations)); + return simulation.id; + } catch (e) { + console.error('Error saving simulation:', e); + return null; + } + }, + + delete(id) { + try { + let simulations = this.getAll(); + simulations = simulations.filter(s => s.id !== id); + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(simulations)); + return true; + } catch (e) { + console.error('Error deleting simulation:', e); + return false; + } + }, + + get(id) { + return this.getAll().find(s => s.id === id); + }, + + clear() { + localStorage.removeItem(this.STORAGE_KEY); + } + }; + + // ========================================== + // SIDEBAR FUNCTIONS + // ========================================== + function toggleSidebar() { + const sidebar = document.getElementById('rightSidebar'); + sidebar.classList.toggle('collapsed'); + localStorage.setItem('sidebar_collapsed', sidebar.classList.contains('collapsed')); + } + + function restoreSidebarState() { + const collapsed = localStorage.getItem('sidebar_collapsed') === 'true'; + if (collapsed) { + document.getElementById('rightSidebar').classList.add('collapsed'); + } + } + + // ========================================== + // STRATEGY MANAGEMENT + // ========================================== + let currentStrategy = null; + let strategyParameters = {}; + + // Strategy parameter definitions + const StrategyParams = { + ma44_strategy: [ + { name: 'maPeriod', label: 'MA Period', type: 'number', default: 44, min: 5, max: 500 } + ], + ma125_strategy: [ + { name: 'maPeriod', label: 'MA Period', type: 'number', default: 125, min: 5, max: 500 } + ] + }; + + function renderStrategies(strategies) { + const container = document.getElementById('strategyList'); + + if (!strategies || strategies.length === 0) { + container.innerHTML = '
No strategies available
'; + return; + } + + container.innerHTML = strategies.map((s, index) => ` +
+ + ${s.name} + โ“˜ +
+ `).join(''); + + // Select first strategy by default + if (strategies.length > 0) { + selectStrategy(strategies[0].id); + } + + document.getElementById('runSimBtn').disabled = false; + } + + function selectStrategy(strategyId) { + // Update UI + document.querySelectorAll('.strategy-item').forEach(item => { + item.classList.toggle('selected', item.dataset.strategyId === strategyId); + const radio = item.querySelector('input[type="radio"]'); + if (radio) radio.checked = item.dataset.strategyId === strategyId; + }); + + currentStrategy = strategyId; + + // Render parameters + renderStrategyParams(strategyId); + } + + function renderStrategyParams(strategyId) { + const container = document.getElementById('strategyParams'); + const params = StrategyParams[strategyId] || []; + + if (params.length === 0) { + container.innerHTML = ''; + return; + } + + container.innerHTML = params.map(param => ` +
+ + +
+ `).join(''); + } + + function getStrategyConfig() { + const strategyId = currentStrategy; + if (!strategyId) return null; + + const params = {}; + const paramDefs = StrategyParams[strategyId] || []; + + paramDefs.forEach(def => { + const input = document.getElementById(`param_${def.name}`); + if (input) { + params[def.name] = def.type === 'number' ? parseFloat(input.value) : input.value; + } + }); + + return { + id: strategyId, + params: params + }; + } + + // ========================================== + // ENHANCED SIMULATION FUNCTIONS + // ========================================== + async function runSimulation() { + const strategyConfig = getStrategyConfig(); + if (!strategyConfig) { + alert('Please select a strategy'); + return; + } + + const startDateInput = document.getElementById('simStartDate').value; + if (!startDateInput) { + alert('Please select a start date'); + return; + } + + const runBtn = document.getElementById('runSimBtn'); + runBtn.disabled = true; + runBtn.textContent = 'โณ Running...'; + + try { + const start = new Date(startDateInput); + const fetchStart = new Date(start.getTime() - 200 * 24 * 60 * 60 * 1000); + const interval = document.getElementById('simTimeframe').value; + const secondaryTF = document.getElementById('simSecondaryTF').value; + const riskPercent = parseFloat(document.getElementById('simRiskPercent').value); + const stopLossPercent = parseFloat(document.getElementById('simStopLoss').value); + + // Fetch data + const response = await fetch(`/api/v1/candles/bulk?symbol=BTC&start=${fetchStart.toISOString()}&timeframes=${interval}&timeframes=${secondaryTF}`); + const data = await response.json(); + + if (!data.candles || !data.candles[interval]) { + throw new Error('No candle data available'); + } + + const candlesMap = { + [interval]: data.candles[interval].map(c => ({ + time: Math.floor(new Date(c.time).getTime() / 1000), + open: parseFloat(c.open), + high: parseFloat(c.high), + low: parseFloat(c.low), + close: parseFloat(c.close) + })) + }; + + // Build strategy config + const engineConfig = { + id: strategyConfig.id, + timeframes: { primary: interval, secondary: secondaryTF ? [secondaryTF] : [] }, + indicators: [] + }; + + // Add indicator based on strategy + if (strategyConfig.id === 'ma44_strategy') { + engineConfig.indicators.push({ + name: 'ma44', + type: 'sma', + params: { period: strategyConfig.params.maPeriod || 44 }, + timeframe: interval + }); + } else if (strategyConfig.id === 'ma125_strategy') { + engineConfig.indicators.push({ + name: 'ma125', + type: 'sma', + params: { period: strategyConfig.params.maPeriod || 125 }, + timeframe: interval + }); + } + + // Run engine + const riskConfig = { + positionSizing: { method: 'percent', value: riskPercent }, + stopLoss: { enabled: true, method: 'percent', value: stopLossPercent } + }; + + const engine = new ClientStrategyEngine(); + const results = engine.run(candlesMap, engineConfig, riskConfig, start); + + if (results.error) throw new Error(results.error); + + // Add metadata + window.lastSimulationResults = { + ...results, + config: { + strategyId: strategyConfig.id, + strategyName: window.availableStrategies.find(s => s.id === strategyConfig.id)?.name || strategyConfig.id, + timeframe: interval, + secondaryTimeframe: secondaryTF, + startDate: startDateInput, + riskPercent: riskPercent, + stopLossPercent: stopLossPercent, + params: strategyConfig.params + }, + runAt: new Date().toISOString() + }; + + // Display results + displayEnhancedResults(window.lastSimulationResults); + + // Show results section + document.getElementById('resultsSection').style.display = 'block'; + + } catch (error) { + console.error('Simulation error:', error); + alert('Simulation error: ' + error.message); + } finally { + runBtn.disabled = false; + runBtn.textContent = 'โ–ถ Run Simulation'; + } + } + + function displayEnhancedResults(simulation) { + const results = simulation.results || simulation; + + document.getElementById('simTrades').textContent = results.total_trades || '0'; + document.getElementById('simWinRate').textContent = (results.win_rate || 0).toFixed(1) + '%'; + + const pnl = results.total_pnl || 0; + const pnlElement = document.getElementById('simPnL'); + pnlElement.textContent = (pnl >= 0 ? '+' : '') + '$' + pnl.toFixed(2); + pnlElement.style.color = pnl >= 0 ? '#4caf50' : '#f44336'; + + // Calculate profit factor + let grossProfit = 0; + let grossLoss = 0; + (results.trades || []).forEach(trade => { + if (trade.pnl > 0) grossProfit += trade.pnl; + else grossLoss += Math.abs(trade.pnl); + }); + const profitFactor = grossLoss > 0 ? (grossProfit / grossLoss).toFixed(2) : grossProfit > 0 ? 'โˆž' : '0'; + document.getElementById('simProfitFactor').textContent = profitFactor; + + // Draw equity sparkline + drawEquitySparkline(results); + } + + function drawEquitySparkline(results) { + const container = document.getElementById('equitySparkline'); + if (!container || !results.trades || results.trades.length === 0) { + container.innerHTML = '
No trades
'; + return; + } + + // Build equity curve + let equity = 1000; + const equityData = [{ time: results.trades[0].entryTime, equity: equity }]; + + results.trades.forEach(trade => { + equity += trade.pnl; + equityData.push({ time: trade.exitTime, equity: equity }); + }); + + // Store for later use + window.lastSimulationResults.equity_curve = equityData; + + // Draw simple sparkline using canvas + container.innerHTML = ''; + const canvas = document.getElementById('sparklineCanvas'); + const ctx = canvas.getContext('2d'); + + const minEquity = Math.min(...equityData.map(d => d.equity)); + const maxEquity = Math.max(...equityData.map(d => d.equity)); + const range = maxEquity - minEquity || 1; + + ctx.strokeStyle = equityData[equityData.length - 1].equity >= equityData[0].equity ? '#4caf50' : '#f44336'; + ctx.lineWidth = 2; + ctx.beginPath(); + + equityData.forEach((point, i) => { + const x = (i / (equityData.length - 1)) * canvas.width; + const y = canvas.height - ((point.equity - minEquity) / range) * canvas.height; + + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + }); + + ctx.stroke(); + + // Add start/end labels + ctx.fillStyle = '#888'; + ctx.font = '9px sans-serif'; + ctx.fillText('$' + equityData[0].equity.toFixed(0), 2, canvas.height - 2); + ctx.fillText('$' + equityData[equityData.length - 1].equity.toFixed(0), canvas.width - 30, 10); + } + + // ========================================== + // SAVE SIMULATION + // ========================================== + function saveSimulation() { + if (!window.lastSimulationResults) { + alert('Please run a simulation first'); + return; + } + + const defaultName = generateSimulationName(window.lastSimulationResults.config); + const name = prompt('Save simulation as:', defaultName); + + if (!name || name.trim() === '') return; + + const simulation = { + name: name.trim(), + config: window.lastSimulationResults.config, + results: { + total_trades: window.lastSimulationResults.total_trades, + win_rate: window.lastSimulationResults.win_rate, + total_pnl: window.lastSimulationResults.total_pnl, + trades: window.lastSimulationResults.trades, + equity_curve: window.lastSimulationResults.equity_curve + } + }; + + const id = SimulationStorage.save(simulation); + if (id) { + renderSavedSimulations(); + alert('Simulation saved successfully!'); + } else { + alert('Error saving simulation'); + } + } + + function generateSimulationName(config) { + if (!config) return 'Unnamed Simulation'; + + const start = new Date(config.startDate); + const now = new Date(); + const duration = now - start; + const oneDay = 24 * 60 * 60 * 1000; + + let dateStr; + if (duration < oneDay) { + dateStr = start.toISOString().slice(0, 16).replace('T', ' '); + } else { + dateStr = start.toISOString().slice(0, 10); + } + + return `${config.strategyName}_${config.timeframe}_${dateStr}`; + } + + function renderSavedSimulations() { + const container = document.getElementById('savedSimulations'); + const simulations = SimulationStorage.getAll(); + + if (simulations.length === 0) { + container.innerHTML = '
No saved simulations
'; + return; + } + + container.innerHTML = simulations.map(sim => ` +
+ + ${sim.name.length > 25 ? sim.name.slice(0, 25) + '...' : sim.name} + +
+ + + +
+
+ `).join(''); + } + + function loadSavedSimulation(id) { + const sim = SimulationStorage.get(id); + if (!sim) { + alert('Simulation not found'); + return; + } + + // Restore config + if (sim.config) { + document.getElementById('simTimeframe').value = sim.config.timeframe || '37m'; + document.getElementById('simSecondaryTF').value = sim.config.secondaryTimeframe || ''; + document.getElementById('simStartDate').value = sim.config.startDate || ''; + document.getElementById('simRiskPercent').value = sim.config.riskPercent || 2; + document.getElementById('simStopLoss').value = sim.config.stopLossPercent || 2; + + // Select strategy + if (sim.config.strategyId) { + selectStrategy(sim.config.strategyId); + + // Restore params + if (sim.config.params) { + Object.entries(sim.config.params).forEach(([key, value]) => { + const input = document.getElementById(`param_${key}`); + if (input) input.value = value; + }); + } + } + } + + // Restore results + window.lastSimulationResults = sim; + displayEnhancedResults(sim.results); + document.getElementById('resultsSection').style.display = 'block'; + } + + function deleteSavedSimulation(id) { + if (!confirm('Are you sure you want to delete this simulation?')) return; + + if (SimulationStorage.delete(id)) { + renderSavedSimulations(); + } + } + + // ========================================== + // EXPORT FUNCTIONS + // ========================================== + function showExportDialog() { + if (!window.lastSimulationResults) { + alert('Please run a simulation first'); + return; + } + + // Create overlay + const overlay = document.createElement('div'); + overlay.className = 'dialog-overlay'; + overlay.onclick = () => closeExportDialog(); + document.body.appendChild(overlay); + + // Create dialog + const dialog = document.createElement('div'); + dialog.className = 'export-dialog'; + dialog.id = 'exportDialog'; + dialog.innerHTML = ` +
๐Ÿ“ฅ Export Simulation Report
+
+ + + +
+
+ + +
+ `; + document.body.appendChild(dialog); + } + + function closeExportDialog() { + const overlay = document.querySelector('.dialog-overlay'); + const dialog = document.getElementById('exportDialog'); + if (overlay) overlay.remove(); + if (dialog) dialog.remove(); + } + + function performExport() { + const format = document.querySelector('input[name="exportFormat"]:checked').value; + const sim = window.lastSimulationResults; + const config = sim.config || {}; + const dateStr = new Date().toISOString().slice(0, 10); + const baseFilename = generateSimulationName(config).replace(/[^a-zA-Z0-9_-]/g, '_'); + + if (format === 'csv' || format === 'both') { + exportToCSV(sim, `${baseFilename}.csv`); + } + + if (format === 'json' || format === 'both') { + exportToJSON(sim, `${baseFilename}.json`); + } + + closeExportDialog(); + } + + function exportToCSV(simulation, filename) { + const results = simulation.results || simulation; + const config = simulation.config || {}; + + let csv = 'Trade #,Entry Time,Exit Time,Entry Price,Exit Price,Size,P&L ($),P&L (%),Type\n'; + + (results.trades || []).forEach((trade, i) => { + csv += `${i + 1},${trade.entryTime},${trade.exitTime},${trade.entryPrice},${trade.exitPrice},${trade.size},${trade.pnl},${trade.pnlPct},${trade.type}\n`; + }); + + csv += '\n'; + csv += 'Summary\n'; + csv += `Strategy,${config.strategyName || 'Unknown'}\n`; + csv += `Timeframe,${config.timeframe || 'Unknown'}\n`; + csv += `Start Date,${config.startDate || 'Unknown'}\n`; + csv += `Total Trades,${results.total_trades || 0}\n`; + csv += `Win Rate (%),${(results.win_rate || 0).toFixed(2)}\n`; + csv += `Total P&L ($),${(results.total_pnl || 0).toFixed(2)}\n`; + csv += `Risk % per Trade,${config.riskPercent || 2}\n`; + csv += `Stop Loss %,${config.stopLossPercent || 2}\n`; + + downloadFile(csv, filename, 'text/csv'); + } + + function exportToJSON(simulation, filename) { + const exportData = { + metadata: { + exported_at: new Date().toISOString(), + version: '1.0' + }, + configuration: simulation.config || {}, + results: { + summary: { + total_trades: simulation.total_trades || simulation.results?.total_trades || 0, + win_rate: simulation.win_rate || simulation.results?.win_rate || 0, + total_pnl: simulation.total_pnl || simulation.results?.total_pnl || 0 + }, + trades: simulation.trades || simulation.results?.trades || [], + equity_curve: simulation.equity_curve || [] + } + }; + + downloadFile(JSON.stringify(exportData, null, 2), filename, 'application/json'); + } + + function exportSavedSimulation(id) { + const sim = SimulationStorage.get(id); + if (!sim) { + alert('Simulation not found'); + return; + } + + window.lastSimulationResults = sim; + showExportDialog(); + } + + function downloadFile(content, filename, mimeType) { + const blob = new Blob([content], { type: mimeType }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + } + + // ========================================== + // ENHANCED MARKER FUNCTIONS + // ========================================== + let tradeLineSeries = []; + + function showSimulationMarkers() { + if (!window.lastSimulationResults || !window.dashboard) return; + + const trades = window.lastSimulationResults.trades || window.lastSimulationResults.results?.trades || []; + const markers = []; + + // Clear existing trade lines + clearSimulationMarkers(); + + trades.forEach(trade => { + const entryTime = Math.floor(new Date(trade.entryTime).getTime() / 1000); + const exitTime = Math.floor(new Date(trade.exitTime).getTime() / 1000); + const pnlSymbol = trade.pnl > 0 ? '+' : ''; + + // Entry Marker - Buy signal (blue) + markers.push({ + time: entryTime, + position: 'belowBar', + color: '#2196f3', + shape: 'arrowUp', + text: 'BUY', + size: 1 + }); + + // Exit Marker - Sell signal (green/red based on P&L) + markers.push({ + time: exitTime, + position: 'aboveBar', + color: trade.pnl > 0 ? '#4caf50' : '#f44336', + shape: 'arrowDown', + text: `SELL ${pnlSymbol}${trade.pnlPct.toFixed(1)}%`, + size: 1 + }); + + // Create line connecting entry to exit + const lineSeries = window.dashboard.chart.addLineSeries({ + color: '#2196f3', + lineWidth: 1, + lastValueVisible: false, + title: '', + priceLineVisible: false, + crosshairMarkerVisible: false + }); + + lineSeries.setData([ + { time: entryTime, value: trade.entryPrice }, + { time: exitTime, value: trade.exitPrice } + ]); + + tradeLineSeries.push(lineSeries); + }); + + // Sort markers by time + markers.sort((a, b) => a.time - b.time); + + // Apply markers + window.dashboard.candleSeries.setMarkers(markers); + + console.log(`Plotted ${trades.length} trades with connection lines`); + } + + function clearSimulationMarkers() { + if (window.dashboard) { + window.dashboard.candleSeries.setMarkers([]); + + tradeLineSeries.forEach(series => { + try { + window.dashboard.chart.removeSeries(series); + } catch (e) { + // Series might already be removed + } + }); + tradeLineSeries = []; + } + } + + // ========================================== + // INITIALIZATION + // ========================================== document.addEventListener('DOMContentLoaded', () => { window.dashboard = new TradingDashboard(); + restoreSidebarState(); + setDefaultStartDate(); + renderSavedSimulations(); + + // Load strategies + loadStrategies(); });