Add tab system to right sidebar with Indicators and Strategies

- Add two-tab navigation (Indicators, Strategies) in right sidebar
- Move all strategy-related content to Strategies tab
- Implement sidebar collapse/expand functionality
- Add indicator visibility toggle (eye button)
- Fix bug where wrong interval data was deleted on TF switch
- Add localStorage persistence for sidebar state and active tab
- Ensure indicators recalculate when TF changes
This commit is contained in:
DiTus
2026-02-26 14:56:03 +01:00
parent 6e21be6523
commit 5f84215acd
6 changed files with 319 additions and 143 deletions

View File

@ -171,12 +171,15 @@
} }
.indicator-desc { .indicator-desc {
display: none; font-size: 11px;
color: var(--tv-text-secondary);
margin-left: 8px;
} }
.indicator-actions { .indicator-actions {
display: flex; display: flex;
gap: 4px; gap: 4px;
margin-left: auto;
} }
.indicator-btn { .indicator-btn {
@ -213,13 +216,13 @@
display: block; display: block;
} }
.indicator-desc { .indicator-desc {
display: block; display: inline;
font-size: 10px; font-size: 11px;
color: var(--tv-text-secondary); color: var(--tv-text-secondary);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: 150px; max-width: 120px;
} }
} }
@ -591,3 +594,50 @@
background: var(--tv-border); background: var(--tv-border);
border-radius: 2px; border-radius: 2px;
} }
/* Sidebar Tabs */
.sidebar-tabs {
display: flex;
gap: 4px;
flex: 1;
margin-right: 8px;
}
.sidebar-tab {
flex: 1;
background: transparent;
border: none;
color: var(--tv-text-secondary);
font-size: 11px;
padding: 6px 8px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.sidebar-tab:hover {
background: var(--tv-hover);
color: var(--tv-text);
}
.sidebar-tab.active {
background: rgba(41, 98, 255, 0.15);
color: var(--tv-blue);
font-weight: 600;
}
/* Sidebar Tab Panels */
.sidebar-tab-panel {
display: none;
animation: fadeIn 0.2s ease;
}
.sidebar-tab-panel.active {
display: block;
}
/* Collapsed sidebar adjustments */
.right-sidebar.collapsed .sidebar-tabs {
display: none;
}

View File

@ -1353,21 +1353,25 @@
<!-- Right Sidebar - Tools Panel --> <!-- Right Sidebar - Tools Panel -->
<div class="right-sidebar collapsed" id="rightSidebar"> <div class="right-sidebar collapsed" id="rightSidebar">
<div class="sidebar-header"> <div class="sidebar-header">
<div class="sidebar-title"> <div class="sidebar-tabs">
<span>🔧</span> <button class="sidebar-tab active" data-tab="indicators">📊 Indicators</button>
<span class="sidebar-title-text">Tools</span> <button class="sidebar-tab" data-tab="strategies">📋 Strategies</button>
</div> </div>
<button class="sidebar-toggle" onclick="toggleSidebar()"></button> <button class="sidebar-toggle" onclick="toggleSidebar()"></button>
</div> </div>
<div class="sidebar-content"> <div class="sidebar-content">
<!-- Indicators Panel --> <!-- Indicators Tab -->
<div class="sidebar-tab-panel active" id="tab-indicators">
<div class="sidebar-section-indicators" id="indicatorPanel"> <div class="sidebar-section-indicators" id="indicatorPanel">
<div class="sidebar-section-header" style="padding: 8px 12px;"> <div class="sidebar-section-header" style="padding: 8px 12px;">
<span>📊</span> Indicators <span>📊</span> Indicators
</div> </div>
</div> </div>
</div>
<!-- Strategies Tab -->
<div class="sidebar-tab-panel" id="tab-strategies">
<!-- Strategy Selection --> <!-- Strategy Selection -->
<div class="sidebar-section"> <div class="sidebar-section">
<div class="sidebar-section-header"> <div class="sidebar-section-header">
@ -1412,7 +1416,6 @@
<input type="number" id="simStopLoss" class="config-input" value="2" min="0.1" max="20" step="0.1"> <input type="number" id="simStopLoss" class="config-input" value="2" min="0.1" max="20" step="0.1">
</div> </div>
<!-- Dynamic Strategy Parameters -->
<div id="strategyParams"></div> <div id="strategyParams"></div>
</div> </div>
</div> </div>
@ -1428,10 +1431,8 @@
<span>📊</span> Results <span>📊</span> Results
</div> </div>
<div class="sidebar-section-content"> <div class="sidebar-section-content">
<!-- Equity Sparkline -->
<div class="equity-sparkline" id="equitySparkline"></div> <div class="equity-sparkline" id="equitySparkline"></div>
<!-- Stats -->
<div class="results-summary"> <div class="results-summary">
<div class="result-stat"> <div class="result-stat">
<div class="result-stat-value" id="simTrades">--</div> <div class="result-stat-value" id="simTrades">--</div>
@ -1451,7 +1452,6 @@
</div> </div>
</div> </div>
<!-- Action Buttons -->
<button class="action-btn secondary" onclick="showSimulationMarkers()"> <button class="action-btn secondary" onclick="showSimulationMarkers()">
📍 Plot on Chart 📍 Plot on Chart
</button> </button>
@ -1478,6 +1478,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Load Indicators Panel functions for global access --> <!-- Load Indicators Panel functions for global access -->
<script> <script>

View File

@ -1,5 +1,5 @@
import { TradingDashboard, refreshTA, openAIAnalysis } from './ui/chart.js'; import { TradingDashboard, refreshTA, openAIAnalysis } from './ui/chart.js';
import { restoreSidebarState, toggleSidebar } from './ui/sidebar.js'; import { restoreSidebarState, toggleSidebar, initSidebarTabs, restoreSidebarTabState } from './ui/sidebar.js';
import { SimulationStorage } from './ui/storage.js'; import { SimulationStorage } from './ui/storage.js';
import { showExportDialog, closeExportDialog, performExport, exportSavedSimulation } from './ui/export.js'; import { showExportDialog, closeExportDialog, performExport, exportSavedSimulation } from './ui/export.js';
import { import {
@ -70,7 +70,6 @@ window.renderIndicatorList = function() {
// Export init function for global access // Export init function for global access
window.initIndicatorPanel = initIndicatorPanel; window.initIndicatorPanel = initIndicatorPanel;
window.initIndicatorPanel = initIndicatorPanel;
window.addIndicator = addIndicator; window.addIndicator = addIndicator;
window.toggleIndicator = addIndicator; window.toggleIndicator = addIndicator;
@ -81,6 +80,8 @@ window.IndicatorRegistry = IndicatorRegistry;
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
window.dashboard = new TradingDashboard(); window.dashboard = new TradingDashboard();
restoreSidebarState(); restoreSidebarState();
restoreSidebarTabState();
initSidebarTabs();
setDefaultStartDate(); setDefaultStartDate();
updateTimeframeDisplay(); updateTimeframeDisplay();
renderSavedSimulations(); renderSavedSimulations();
@ -93,6 +94,24 @@ document.addEventListener('DOMContentLoaded', async () => {
const originalSwitchTimeframe = window.dashboard.switchTimeframe.bind(window.dashboard); const originalSwitchTimeframe = window.dashboard.switchTimeframe.bind(window.dashboard);
window.dashboard.switchTimeframe = function(interval) { window.dashboard.switchTimeframe = function(interval) {
originalSwitchTimeframe(interval); originalSwitchTimeframe(interval);
setTimeout(() => drawIndicatorsOnChart(), 500);
// Force redraw indicators after TF switch with multiple attempts
setTimeout(() => {
if (window.drawIndicatorsOnChart) {
window.drawIndicatorsOnChart();
}
}, 100);
setTimeout(() => {
if (window.drawIndicatorsOnChart) {
window.drawIndicatorsOnChart();
}
}, 300);
setTimeout(() => {
if (window.drawIndicatorsOnChart) {
window.drawIndicatorsOnChart();
}
}, 500);
}; };
}); });

View File

@ -303,7 +303,7 @@ async loadInitialData() {
const response = await fetch(`/api/v1/candles?symbol=BTC&interval=${this.currentInterval}&limit=${limit}`); const response = await fetch(`/api/v1/candles?symbol=BTC&interval=${this.currentInterval}&limit=${limit}`);
const data = await response.json(); const data = await response.json();
if (data.candles && data.candles.length > 0) { if (data.candles && data.candles.length > 0) {
const chartData = data.candles.reverse().map(c => ({ const chartData = data.candles.reverse().map(c => ({
time: Math.floor(new Date(c.time).getTime() / 1000), time: Math.floor(new Date(c.time).getTime() / 1000),
open: parseFloat(c.open), open: parseFloat(c.open),
@ -328,6 +328,11 @@ async loadInitialData() {
const latest = mergedData[mergedData.length - 1]; const latest = mergedData[mergedData.length - 1];
this.updateStats(latest); this.updateStats(latest);
} }
// Always try to redraw indicators after candles are set
if (window.drawIndicatorsOnChart) {
window.drawIndicatorsOnChart();
}
} catch (error) { } catch (error) {
console.error('Error loading data:', error); console.error('Error loading data:', error);
} finally { } finally {
@ -342,7 +347,7 @@ async loadInitialData() {
const response = await fetch(`/api/v1/candles?symbol=BTC&interval=${this.currentInterval}&limit=50`); const response = await fetch(`/api/v1/candles?symbol=BTC&interval=${this.currentInterval}&limit=50`);
const data = await response.json(); const data = await response.json();
if (data.candles && data.candles.length > 0) { if (data.candles && data.candles.length > 0) {
const atEdge = this.isAtRightEdge(); const atEdge = this.isAtRightEdge();
const currentSeriesData = this.candleSeries.data(); const currentSeriesData = this.candleSeries.data();
@ -374,6 +379,11 @@ async loadInitialData() {
const latest = chartData[chartData.length - 1]; const latest = chartData[chartData.length - 1];
this.updateStats(latest); this.updateStats(latest);
// Redraw indicators when new data loads
if (window.drawIndicatorsOnChart) {
window.drawIndicatorsOnChart();
}
} }
} catch (error) { } catch (error) {
console.error('Error loading new data:', error); console.error('Error loading new data:', error);
@ -586,6 +596,7 @@ async loadTA() {
switchTimeframe(interval) { switchTimeframe(interval) {
if (!this.intervals.includes(interval) || interval === this.currentInterval) return; if (!this.intervals.includes(interval) || interval === this.currentInterval) return;
const oldInterval = this.currentInterval;
this.currentInterval = interval; this.currentInterval = interval;
this.hasInitialLoad = false; this.hasInitialLoad = false;
@ -593,9 +604,9 @@ async loadTA() {
btn.classList.toggle('active', btn.dataset.interval === interval); btn.classList.toggle('active', btn.dataset.interval === interval);
}); });
this.allData.delete(interval); // Clear old interval data, not new interval
this.allData.delete(oldInterval);
this.loadInitialData(); this.loadInitialData();
// Don't reload TA on timeframe switch - let user refresh manually
window.clearSimulationResults?.(); window.clearSimulationResults?.();
window.updateTimeframeDisplay?.(); window.updateTimeframeDisplay?.();

View File

@ -193,14 +193,11 @@ export function renderIndicatorPanel() {
} }
function renderIndicatorItem(indicator, isFavorite) { function renderIndicatorItem(indicator, isFavorite) {
const colorDots = '';
return ` return `
<div class="indicator-item ${isFavorite ? 'favorite' : ''}" data-type="${indicator.type}"> <div class="indicator-item ${isFavorite ? 'favorite' : ''}" data-type="${indicator.type}">
<div class="indicator-item-main"> <div class="indicator-item-main">
<span class="indicator-name">${indicator.name}</span> <span class="indicator-name">${indicator.name}</span>
<span class="indicator-desc">${indicator.description || ''}</span> <span class="indicator-desc">${indicator.description || ''}</span>
</div>
<div class="indicator-actions"> <div class="indicator-actions">
<button class="indicator-btn add" data-type="${indicator.type}" title="Add to chart">+</button> <button class="indicator-btn add" data-type="${indicator.type}" title="Add to chart">+</button>
${isFavorite ? '' : ` ${isFavorite ? '' : `
@ -210,6 +207,7 @@ function renderIndicatorItem(indicator, isFavorite) {
`} `}
</div> </div>
</div> </div>
</div>
`; `;
} }
@ -238,7 +236,7 @@ function renderActiveIndicator(indicator) {
<button class="indicator-btn favorite" onclick="event.stopPropagation(); window.toggleFavorite && window.toggleFavorite('${indicator.type}')" title="Add to favorites"> <button class="indicator-btn favorite" onclick="event.stopPropagation(); window.toggleFavorite && window.toggleFavorite('${indicator.type}')" title="Add to favorites">
${isFavorite ? '★' : '☆'} ${isFavorite ? '★' : '☆'}
</button> </button>
<button class="indicator-btn expand ${isExpanded ? 'rotated' : ''}" data-id="${indicator.id}" title="Show settings"> <button class="indicator-btn expand ${isExpanded ? 'rotated' : ''}" data-id="${indicator.id}" onclick="event.stopPropagation(); window.toggleIndicatorExpand && window.toggleIndicatorExpand('${indicator.id}')" title="Show settings">
${isExpanded ? '▼' : '▶'} ${isExpanded ? '▼' : '▶'}
</button> </button>
</div> </div>
@ -640,7 +638,21 @@ function saveUserPresets() {
} }
function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, lineStyleMap) { function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, lineStyleMap) {
console.log(`renderIndicatorOnPane for ${indicator.id}, candles.length=${candles.length}, paneIndex=${paneIndex}`);
// Recalculate with current TF candles
const results = instance.calculate(candles); const results = instance.calculate(candles);
console.log(`Calculated results for ${indicator.id}:`, results?.length || 0, 'values');
// Clear previous series for this indicator
if (indicator.series && indicator.series.length > 0) {
indicator.series.forEach(s => {
try {
window.dashboard.chart.removeSeries(s);
console.log(`Removed series for ${indicator.id}`);
} catch(e) { console.error('Error removing series:', e); }
});
}
indicator.series = []; indicator.series = [];
const lineStyle = lineStyleMap[indicator.params._lineType] || LightweightCharts.LineStyle.Solid; const lineStyle = lineStyleMap[indicator.params._lineType] || LightweightCharts.LineStyle.Solid;
@ -649,10 +661,14 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
const firstNonNull = results?.find(r => r !== null && r !== undefined); const firstNonNull = results?.find(r => r !== null && r !== undefined);
const isObjectResult = firstNonNull && typeof firstNonNull === 'object'; const isObjectResult = firstNonNull && typeof firstNonNull === 'object';
let plotsCreated = 0;
meta.plots.forEach((plot, plotIdx) => { meta.plots.forEach((plot, plotIdx) => {
if (isObjectResult) { if (isObjectResult) {
const hasData = results.some(r => r && r[plot.id] !== undefined && r[plot.id] !== null); const hasData = results.some(r => r && r[plot.id] !== undefined && r[plot.id] !== null);
if (!hasData) return; if (!hasData) {
console.log(`No data for plot ${plot.id} in ${indicator.id}`);
return;
}
} }
const plotColor = indicator.params[`_color_${plotIdx}`] || plot.color || '#2962ff'; const plotColor = indicator.params[`_color_${plotIdx}`] || plot.color || '#2962ff';
@ -674,6 +690,7 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
} }
} }
console.log(`Plot ${plot.id} has ${data.length} data points`);
if (data.length === 0) return; if (data.length === 0) return;
let series; let series;
@ -716,6 +733,8 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
series.setData(data); series.setData(data);
indicator.series.push(series); indicator.series.push(series);
plotsCreated++;
console.log(`Created series for ${indicator.id}, plot=${plot.id}, total series now=${indicator.series.length}`);
// Create horizontal band lines for RSI // Create horizontal band lines for RSI
if (meta.name === 'RSI' && indicator.series.length > 0) { if (meta.name === 'RSI' && indicator.series.length > 0) {
@ -756,17 +775,28 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
// Chart drawing // Chart drawing
export function drawIndicatorsOnChart() { export function drawIndicatorsOnChart() {
if (!window.dashboard || !window.dashboard.chart) return; if (!window.dashboard || !window.dashboard.chart) {
return;
}
const currentInterval = window.dashboard.currentInterval;
const candles = window.dashboard.allData.get(currentInterval);
if (!candles || candles.length === 0) {
return;
}
// Log: Ensure we're using the correct interval candles
console.log(`drawIndicatorsOnChart for interval=${currentInterval}, candles=${candles.length}`);
// First, remove all existing series
activeIndicators.forEach(ind => { activeIndicators.forEach(ind => {
ind.series?.forEach(s => { ind.series?.forEach(s => {
try { window.dashboard.chart.removeSeries(s); } catch(e) {} try { window.dashboard.chart.removeSeries(s); } catch(e) {}
}); });
ind.series = [];
}); });
const candles = window.dashboard.allData.get(window.dashboard.currentInterval);
if (!candles || candles.length === 0) return;
const lineStyleMap = { const lineStyleMap = {
'solid': LightweightCharts.LineStyle.Solid, 'solid': LightweightCharts.LineStyle.Solid,
'dotted': LightweightCharts.LineStyle.Dotted, 'dotted': LightweightCharts.LineStyle.Dotted,
@ -779,7 +809,12 @@ export function drawIndicatorsOnChart() {
const overlayIndicators = []; const overlayIndicators = [];
const paneIndicators = []; const paneIndicators = [];
// Process all indicators, filtering by visibility
activeIndicators.forEach(ind => { activeIndicators.forEach(ind => {
if (ind.visible === false) {
return;
}
const IndicatorClass = IR?.[ind.type]; const IndicatorClass = IR?.[ind.type];
if (!IndicatorClass) return; if (!IndicatorClass) return;
@ -793,37 +828,37 @@ export function drawIndicatorsOnChart() {
} }
}); });
console.log('Rendering indicators:', { overlayCount: overlayIndicators.length, paneCount: paneIndicators.length });
// Calculate heights based on VISIBLE indicators only
const totalPanes = 1 + paneIndicators.length; const totalPanes = 1 + paneIndicators.length;
const mainPaneHeight = paneIndicators.length > 0 ? 60 : 100; const mainPaneHeight = paneIndicators.length > 0 ? 60 : 100;
const paneHeight = paneIndicators.length > 0 ? Math.floor(40 / paneIndicators.length) : 0; const paneHeight = paneIndicators.length > 0 ? Math.floor(40 / paneIndicators.length) : 0;
window.dashboard.chart.panes()[0]?.setHeight(mainPaneHeight); window.dashboard.chart.panes()[0]?.setHeight(mainPaneHeight);
let totalSeriesCreated = 0;
overlayIndicators.forEach(({ indicator, meta, instance }) => { overlayIndicators.forEach(({ indicator, meta, instance }) => {
if (indicator.visible === false) { const oldLen = indicator.series.length;
indicator.series = [];
return;
}
renderIndicatorOnPane(indicator, meta, instance, candles, 0, lineStyleMap); renderIndicatorOnPane(indicator, meta, instance, candles, 0, lineStyleMap);
totalSeriesCreated += indicator.series.length - oldLen;
}); });
paneIndicators.forEach(({ indicator, meta, instance }, idx) => { paneIndicators.forEach(({ indicator, meta, instance }, idx) => {
if (indicator.visible === false) {
indicator.series = [];
return;
}
const paneIndex = nextPaneIndex++; const paneIndex = nextPaneIndex++;
indicatorPanes.set(indicator.id, paneIndex); indicatorPanes.set(indicator.id, paneIndex);
const oldLen = indicator.series.length;
renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, lineStyleMap); renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, lineStyleMap);
totalSeriesCreated += indicator.series.length - oldLen;
const pane = window.dashboard.chart.panes()[paneIndex]; const pane = window.dashboard.chart.panes()[paneIndex];
if (pane) { if (pane) {
pane.setHeight(paneHeight); pane.setHeight(paneHeight);
} }
}); });
console.log(`drawIndicatorsOnChart complete - created ${totalSeriesCreated} series for ${overlayIndicators.length + paneIndicators.length} visible indicators`);
} }
function resetIndicator(id) { function resetIndicator(id) {
@ -849,14 +884,31 @@ function removeIndicator(id) {
removeIndicatorById(id); removeIndicatorById(id);
} }
function toggleIndicatorVisibility(id) {
const indicator = activeIndicators.find(a => a.id === id);
if (!indicator) {
return;
}
indicator.visible = indicator.visible === false;
// Full redraw to ensure all indicators render correctly
if (typeof drawIndicatorsOnChart === 'function') {
drawIndicatorsOnChart();
}
renderIndicatorPanel();
}
// Export functions for module access // Export functions for module access
export { addIndicator, removeIndicatorById }; export { addIndicator, removeIndicatorById, toggleIndicatorVisibility };
// Legacy compatibility functions // Legacy compatibility functions
window.renderIndicatorList = renderIndicatorPanel; window.renderIndicatorList = renderIndicatorPanel;
window.resetIndicator = resetIndicator; window.resetIndicator = resetIndicator;
window.removeIndicator = removeIndicator; window.removeIndicator = removeIndicator;
window.toggleIndicator = addIndicator; window.toggleIndicator = addIndicator;
window.toggleIndicatorVisibility = toggleIndicatorVisibility;
window.showIndicatorConfig = function(id) { window.showIndicatorConfig = function(id) {
const ind = activeIndicators.find(a => a.id === id); const ind = activeIndicators.find(a => a.id === id);
if (ind) configuringId = id; if (ind) configuringId = id;

View File

@ -22,3 +22,46 @@ export function restoreSidebarState() {
sidebar.classList.add('collapsed'); sidebar.classList.add('collapsed');
} }
} }
// Tab Management
let activeTab = 'indicators';
export function initSidebarTabs() {
const tabs = document.querySelectorAll('.sidebar-tab');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
switchTab(tab.dataset.tab);
});
});
}
export function switchTab(tabId) {
activeTab = tabId;
localStorage.setItem('sidebar_active_tab', tabId);
document.querySelectorAll('.sidebar-tab').forEach(tab => {
tab.classList.toggle('active', tab.dataset.tab === tabId);
});
document.querySelectorAll('.sidebar-tab-panel').forEach(panel => {
panel.classList.toggle('active', panel.id === `tab-${tabId}`);
});
if (tabId === 'indicators') {
setTimeout(() => {
if (window.drawIndicatorsOnChart) {
window.drawIndicatorsOnChart();
}
}, 50);
}
}
export function getActiveTab() {
return activeTab;
}
export function restoreSidebarTabState() {
const savedTab = localStorage.getItem('sidebar_active_tab') || 'indicators';
switchTab(savedTab);
}