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:
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -590,4 +593,51 @@
|
|||||||
.presets-list::-webkit-scrollbar-thumb {
|
.presets-list::-webkit-scrollbar-thumb {
|
||||||
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;
|
||||||
}
|
}
|
||||||
@ -1353,125 +1353,126 @@
|
|||||||
<!-- 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-section-indicators" id="indicatorPanel">
|
<div class="sidebar-tab-panel active" id="tab-indicators">
|
||||||
<div class="sidebar-section-header" style="padding: 8px 12px;">
|
<div class="sidebar-section-indicators" id="indicatorPanel">
|
||||||
<span>📊</span> Indicators
|
<div class="sidebar-section-header" style="padding: 8px 12px;">
|
||||||
</div>
|
<span>📊</span> Indicators
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Strategy Selection -->
|
|
||||||
<div class="sidebar-section">
|
|
||||||
<div class="sidebar-section-header">
|
|
||||||
<span>📋</span> Select Strategy
|
|
||||||
</div>
|
|
||||||
<div class="sidebar-section-content" id="strategyList">
|
|
||||||
<div class="loading-strategies" style="text-align: center; color: var(--tv-text-secondary); padding: 20px;">
|
|
||||||
Loading strategies...
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Configuration -->
|
<!-- Strategies Tab -->
|
||||||
<div class="sidebar-section">
|
<div class="sidebar-tab-panel" id="tab-strategies">
|
||||||
<div class="sidebar-section-header">
|
<!-- Strategy Selection -->
|
||||||
<span>⚙️</span> Configuration
|
<div class="sidebar-section">
|
||||||
</div>
|
<div class="sidebar-section-header">
|
||||||
<div class="sidebar-section-content">
|
<span>📋</span> Select Strategy
|
||||||
<div class="config-group">
|
|
||||||
<label class="config-label">Start Date</label>
|
|
||||||
<input type="datetime-local" id="simStartDate" class="config-input">
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sidebar-section-content" id="strategyList">
|
||||||
<div class="config-group">
|
<div class="loading-strategies" style="text-align: center; color: var(--tv-text-secondary); padding: 20px;">
|
||||||
<label class="config-label">Confirmation TF (Optional)</label>
|
Loading strategies...
|
||||||
<select id="simSecondaryTF" class="config-input">
|
|
||||||
<option value="">None</option>
|
|
||||||
<option value="1h">1h</option>
|
|
||||||
<option value="4h">4h</option>
|
|
||||||
<option value="1d" selected>1d</option>
|
|
||||||
<option value="1w">1w</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="config-group">
|
|
||||||
<label class="config-label">Risk % per Trade</label>
|
|
||||||
<input type="number" id="simRiskPercent" class="config-input" value="2" min="0.1" max="100" step="0.1">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="config-group">
|
|
||||||
<label class="config-label">Stop Loss %</label>
|
|
||||||
<input type="number" id="simStopLoss" class="config-input" value="2" min="0.1" max="20" step="0.1">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Dynamic Strategy Parameters -->
|
|
||||||
<div id="strategyParams"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Run Button -->
|
|
||||||
<button class="action-btn primary" onclick="runSimulation()" id="runSimBtn" disabled>
|
|
||||||
▶ Run Simulation
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Results Section -->
|
|
||||||
<div class="sidebar-section" id="resultsSection" style="display: none;">
|
|
||||||
<div class="sidebar-section-header">
|
|
||||||
<span>📊</span> Results
|
|
||||||
</div>
|
|
||||||
<div class="sidebar-section-content">
|
|
||||||
<!-- Equity Sparkline -->
|
|
||||||
<div class="equity-sparkline" id="equitySparkline"></div>
|
|
||||||
|
|
||||||
<!-- Stats -->
|
|
||||||
<div class="results-summary">
|
|
||||||
<div class="result-stat">
|
|
||||||
<div class="result-stat-value" id="simTrades">--</div>
|
|
||||||
<div class="result-stat-label">Trades</div>
|
|
||||||
</div>
|
|
||||||
<div class="result-stat">
|
|
||||||
<div class="result-stat-value" id="simWinRate">--</div>
|
|
||||||
<div class="result-stat-label">Win Rate</div>
|
|
||||||
</div>
|
|
||||||
<div class="result-stat">
|
|
||||||
<div class="result-stat-value" id="simPnL">--</div>
|
|
||||||
<div class="result-stat-label">Total P&L</div>
|
|
||||||
</div>
|
|
||||||
<div class="result-stat">
|
|
||||||
<div class="result-stat-value" id="simProfitFactor">--</div>
|
|
||||||
<div class="result-stat-label">Profit Factor</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
|
||||||
<button class="action-btn secondary" onclick="showSimulationMarkers()">
|
|
||||||
📍 Plot on Chart
|
|
||||||
</button>
|
|
||||||
<button class="action-btn secondary" onclick="saveSimulation()">
|
|
||||||
💾 Save Simulation
|
|
||||||
</button>
|
|
||||||
<button class="action-btn success" onclick="showExportDialog()">
|
|
||||||
📥 Export Report
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<!-- Configuration -->
|
||||||
<!-- Saved Simulations -->
|
<div class="sidebar-section">
|
||||||
<div class="sidebar-section">
|
<div class="sidebar-section-header">
|
||||||
<div class="sidebar-section-header">
|
<span>⚙️</span> Configuration
|
||||||
<span>💾</span> Saved Simulations
|
</div>
|
||||||
|
<div class="sidebar-section-content">
|
||||||
|
<div class="config-group">
|
||||||
|
<label class="config-label">Start Date</label>
|
||||||
|
<input type="datetime-local" id="simStartDate" class="config-input">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-group">
|
||||||
|
<label class="config-label">Confirmation TF (Optional)</label>
|
||||||
|
<select id="simSecondaryTF" class="config-input">
|
||||||
|
<option value="">None</option>
|
||||||
|
<option value="1h">1h</option>
|
||||||
|
<option value="4h">4h</option>
|
||||||
|
<option value="1d" selected>1d</option>
|
||||||
|
<option value="1w">1w</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-group">
|
||||||
|
<label class="config-label">Risk % per Trade</label>
|
||||||
|
<input type="number" id="simRiskPercent" class="config-input" value="2" min="0.1" max="100" step="0.1">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-group">
|
||||||
|
<label class="config-label">Stop Loss %</label>
|
||||||
|
<input type="number" id="simStopLoss" class="config-input" value="2" min="0.1" max="20" step="0.1">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="strategyParams"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-section-content" id="savedSimulations">
|
|
||||||
<div style="text-align: center; color: var(--tv-text-secondary); padding: 10px; font-size: 12px;">
|
<!-- Run Button -->
|
||||||
No saved simulations
|
<button class="action-btn primary" onclick="runSimulation()" id="runSimBtn" disabled>
|
||||||
|
▶ Run Simulation
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Results Section -->
|
||||||
|
<div class="sidebar-section" id="resultsSection" style="display: none;">
|
||||||
|
<div class="sidebar-section-header">
|
||||||
|
<span>📊</span> Results
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-section-content">
|
||||||
|
<div class="equity-sparkline" id="equitySparkline"></div>
|
||||||
|
|
||||||
|
<div class="results-summary">
|
||||||
|
<div class="result-stat">
|
||||||
|
<div class="result-stat-value" id="simTrades">--</div>
|
||||||
|
<div class="result-stat-label">Trades</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-stat">
|
||||||
|
<div class="result-stat-value" id="simWinRate">--</div>
|
||||||
|
<div class="result-stat-label">Win Rate</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-stat">
|
||||||
|
<div class="result-stat-value" id="simPnL">--</div>
|
||||||
|
<div class="result-stat-label">Total P&L</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-stat">
|
||||||
|
<div class="result-stat-value" id="simProfitFactor">--</div>
|
||||||
|
<div class="result-stat-label">Profit Factor</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="action-btn secondary" onclick="showSimulationMarkers()">
|
||||||
|
📍 Plot on Chart
|
||||||
|
</button>
|
||||||
|
<button class="action-btn secondary" onclick="saveSimulation()">
|
||||||
|
💾 Save Simulation
|
||||||
|
</button>
|
||||||
|
<button class="action-btn success" onclick="showExportDialog()">
|
||||||
|
📥 Export Report
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Saved Simulations -->
|
||||||
|
<div class="sidebar-section">
|
||||||
|
<div class="sidebar-section-header">
|
||||||
|
<span>💾</span> Saved Simulations
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-section-content" id="savedSimulations">
|
||||||
|
<div style="text-align: center; color: var(--tv-text-secondary); padding: 10px; font-size: 12px;">
|
||||||
|
No saved simulations
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -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);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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?.();
|
||||||
|
|||||||
@ -193,21 +193,19 @@ 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 ? '' : `
|
<button class="indicator-btn favorite" data-type="${indicator.type}" title="Add to favorites">
|
||||||
<button class="indicator-btn favorite" data-type="${indicator.type}" title="Add to favorites">
|
${userPresets.favorites?.includes(indicator.type) ? '★' : '☆'}
|
||||||
${userPresets.favorites?.includes(indicator.type) ? '★' : '☆'}
|
</button>
|
||||||
</button>
|
`}
|
||||||
`}
|
</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;
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user