added centralized timezone config with UI selector in hamburger menu
This commit is contained in:
@ -523,7 +523,77 @@
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* Price Scale Controls */
|
||||
/* Chart Settings Menu */
|
||||
.chart-settings-btn {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: rgba(42, 46, 57, 0.9);
|
||||
border: 1px solid #363c4e;
|
||||
color: #d1d4dc;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 2px;
|
||||
transition: all 0.2s;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.chart-settings-btn:hover {
|
||||
background: #363c4e;
|
||||
border-color: #4a4f5e;
|
||||
}
|
||||
|
||||
/* Settings Popup */
|
||||
.chart-settings-popup {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 26px;
|
||||
background: #1e222d;
|
||||
border: 1px solid #363c4e;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
z-index: 100;
|
||||
min-width: 200px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chart-settings-popup.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.settings-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #787b86;
|
||||
margin-bottom: 4px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.settings-select {
|
||||
width: 100%;
|
||||
background: #131722;
|
||||
border: 1px solid #363c4e;
|
||||
color: #d1d4dc;
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.settings-select:focus {
|
||||
outline: none;
|
||||
border-color: #2962ff;
|
||||
}
|
||||
.price-scale-controls {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
@ -1355,8 +1425,26 @@
|
||||
<div class="chart-wrapper" id="chartWrapper">
|
||||
<div id="chart"></div>
|
||||
<div class="price-scale-controls" id="priceScaleControls">
|
||||
<button class="chart-settings-btn" id="btnSettings" title="Settings (S)">☰</button>
|
||||
<button class="ps-control-btn auto-scale active" id="btnAutoScale" title="Auto Scale (A)">A</button>
|
||||
<button class="ps-control-btn log-scale" id="btnLogScale" title="Logarithmic Scale">L</button>
|
||||
<div class="chart-settings-popup" id="settingsPopup">
|
||||
<div class="settings-group">
|
||||
<label class="settings-label">Timezone</label>
|
||||
<select class="settings-select" id="timezoneSelect">
|
||||
<option value="UTC">UTC</option>
|
||||
<option value="Europe/London">London (GMT/BST)</option>
|
||||
<option value="Europe/Paris">Central Europe (CET/CEST)</option>
|
||||
<option value="Europe/Warsaw" selected>Warsaw (CET/CEST)</option>
|
||||
<option value="America/New_York">New York (EST/EDT)</option>
|
||||
<option value="America/Chicago">Chicago (CST/CDT)</option>
|
||||
<option value="America/Los_Angeles">Los Angeles (PST/PDT)</option>
|
||||
<option value="Asia/Tokyo">Tokyo (JST)</option>
|
||||
<option value="Asia/Shanghai">Shanghai (CST)</option>
|
||||
<option value="Australia/Sydney">Sydney (AEST/AEDT)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-controls" id="navControls">
|
||||
<button class="nav-btn" id="navLeft" title="Navigate Left (←)">‹</button>
|
||||
|
||||
@ -9,12 +9,14 @@ import {
|
||||
removeIndicatorById
|
||||
} from './ui/indicators-panel-new.js';
|
||||
import { IndicatorRegistry } from './indicators/index.js';
|
||||
import { TimezoneConfig } from './config/timezone.js';
|
||||
|
||||
window.dashboard = null;
|
||||
|
||||
window.toggleSidebar = toggleSidebar;
|
||||
window.refreshTA = refreshTA;
|
||||
window.openAIAnalysis = openAIAnalysis;
|
||||
window.TimezoneConfig = TimezoneConfig;
|
||||
window.renderIndicatorList = function() {
|
||||
// This function is no longer needed for sidebar indicators
|
||||
};
|
||||
@ -35,6 +37,39 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
toggleBtn.addEventListener('click', toggleSidebar);
|
||||
}
|
||||
|
||||
// Initialize timezone selector
|
||||
const timezoneSelect = document.getElementById('timezoneSelect');
|
||||
const settingsPopup = document.getElementById('settingsPopup');
|
||||
const settingsBtn = document.getElementById('btnSettings');
|
||||
|
||||
if (timezoneSelect) {
|
||||
timezoneSelect.value = TimezoneConfig.getTimezone();
|
||||
timezoneSelect.addEventListener('change', (e) => {
|
||||
TimezoneConfig.setTimezone(e.target.value);
|
||||
settingsPopup.classList.remove('show');
|
||||
// Redraw chart and indicators
|
||||
if (window.dashboard) {
|
||||
window.drawIndicatorsOnChart?.();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle settings popup
|
||||
if (settingsBtn && settingsPopup) {
|
||||
settingsBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
settingsPopup.classList.toggle('show');
|
||||
});
|
||||
|
||||
settingsPopup.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
document.addEventListener('click', () => {
|
||||
settingsPopup.classList.remove('show');
|
||||
});
|
||||
}
|
||||
|
||||
window.dashboard = new TradingDashboard();
|
||||
restoreSidebarState();
|
||||
restoreSidebarTabState();
|
||||
|
||||
68
src/api/dashboard/static/js/config/timezone.js
Normal file
68
src/api/dashboard/static/js/config/timezone.js
Normal file
@ -0,0 +1,68 @@
|
||||
const TimezoneConfig = {
|
||||
timezone: localStorage.getItem('timezone') || 'Europe/Warsaw',
|
||||
|
||||
availableTimezones: [
|
||||
{ value: 'UTC', label: 'UTC', offset: 0 },
|
||||
{ value: 'Europe/London', label: 'London (GMT/BST)', offset: 0 },
|
||||
{ value: 'Europe/Paris', label: 'Central Europe (CET/CEST)', offset: 1 },
|
||||
{ value: 'Europe/Warsaw', label: 'Warsaw (CET/CEST)', offset: 1 },
|
||||
{ value: 'America/New_York', label: 'New York (EST/EDT)', offset: -5 },
|
||||
{ value: 'America/Chicago', label: 'Chicago (CST/CDT)', offset: -6 },
|
||||
{ value: 'America/Los_Angeles', label: 'Los Angeles (PST/PDT)', offset: -8 },
|
||||
{ value: 'Asia/Tokyo', label: 'Tokyo (JST)', offset: 9 },
|
||||
{ value: 'Asia/Shanghai', label: 'Shanghai (CST)', offset: 8 },
|
||||
{ value: 'Australia/Sydney', label: 'Sydney (AEST/AEDT)', offset: 10 },
|
||||
],
|
||||
|
||||
setTimezone(tz) {
|
||||
this.timezone = tz;
|
||||
localStorage.setItem('timezone', tz);
|
||||
document.dispatchEvent(new CustomEvent('timezone-changed', { detail: tz }));
|
||||
},
|
||||
|
||||
getTimezone() {
|
||||
return this.timezone;
|
||||
},
|
||||
|
||||
getOffsetHours(tz = this.timezone) {
|
||||
const now = new Date();
|
||||
const tzDate = new Date(now.toLocaleString('en-US', { timeZone: tz }));
|
||||
const utcDate = new Date(now.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||
return (tzDate - utcDate) / 3600000;
|
||||
},
|
||||
|
||||
formatDate(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
const tz = this.timezone;
|
||||
|
||||
const options = {
|
||||
timeZone: tz,
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||||
hour12: false
|
||||
};
|
||||
|
||||
const formatter = new Intl.DateTimeFormat('en-GB', options);
|
||||
const parts = formatter.formatToParts(date);
|
||||
const get = (type) => parts.find(p => p.type === type).value;
|
||||
|
||||
return `${get('day')}/${get('month')}/${get('year').slice(-2)} ${get('hour')}:${get('minute')}`;
|
||||
},
|
||||
|
||||
formatTickMark(timestamp) {
|
||||
const date = new Date(timestamp * 1000);
|
||||
const tz = this.timezone;
|
||||
|
||||
const options = {
|
||||
timeZone: tz,
|
||||
month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit',
|
||||
hour12: false
|
||||
};
|
||||
|
||||
const formatter = new Intl.DateTimeFormat('en-US', options);
|
||||
return formatter.format(date).replace(',', '');
|
||||
}
|
||||
};
|
||||
|
||||
export { TimezoneConfig };
|
||||
@ -1,15 +1,10 @@
|
||||
import { INTERVALS, COLORS } from '../core/index.js';
|
||||
import { calculateAllIndicatorSignals, calculateSummarySignal } from './signals-calculator.js';
|
||||
import { updateIndicatorCandles } from './indicators-panel-new.js';
|
||||
import { TimezoneConfig } from '../config/timezone.js';
|
||||
|
||||
function formatDate(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
const day = String(date.getUTCDate()).padStart(2, '0');
|
||||
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
||||
const year = String(date.getUTCFullYear()).slice(-2);
|
||||
const hours = String(date.getUTCHours()).padStart(2, '0');
|
||||
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
||||
return `${day}/${month}/${year} ${hours}:${minutes}`;
|
||||
return TimezoneConfig.formatDate(timestamp);
|
||||
}
|
||||
|
||||
export class TradingDashboard {
|
||||
@ -98,8 +93,7 @@ timeScale: {
|
||||
rightOffset: 12,
|
||||
barSpacing: 10,
|
||||
tickMarkFormatter: (time, tickMarkType, locale) => {
|
||||
const date = new Date((time + 3600) * 1000);
|
||||
return `${String(date.getUTCMonth() + 1).padStart(2, '0')}/${String(date.getUTCDate()).padStart(2, '0')} ${String(date.getUTCHours()).padStart(2, '0')}:${String(date.getUTCMinutes()).padStart(2, '0')}`;
|
||||
return TimezoneConfig.formatTickMark(time);
|
||||
},
|
||||
},
|
||||
handleScroll: {
|
||||
@ -419,7 +413,7 @@ async loadNewData() {
|
||||
const existingData = this.allData.get(this.currentInterval) || [];
|
||||
this.allData.set(this.currentInterval, this.mergeData(existingData, chartData));
|
||||
|
||||
console.log(`[NewData Load] Added ${chartData.length} new candles, total in dataset: ${this.allData.get(this.currentInterval).length}`);
|
||||
//console.log(`[NewData Load] Added ${chartData.length} new candles, total in dataset: ${this.allData.get(this.currentInterval).length}`);
|
||||
|
||||
if (atEdge) {
|
||||
this.chart.timeScale().scrollToRealTime();
|
||||
@ -427,7 +421,7 @@ async loadNewData() {
|
||||
|
||||
this.updateStats(latest);
|
||||
|
||||
console.log('[Chart] Calling drawIndicatorsOnChart after new data');
|
||||
//console.log('[Chart] Calling drawIndicatorsOnChart after new data');
|
||||
window.drawIndicatorsOnChart?.();
|
||||
window.updateIndicatorCandles?.();
|
||||
|
||||
|
||||
@ -854,22 +854,22 @@ export function drawIndicatorsOnChart() {
|
||||
const candles = window.dashboard?.allData?.get(currentInterval);
|
||||
|
||||
if (!candles || candles.length === 0) {
|
||||
console.log('[Indicators] No candles available');
|
||||
//console.log('[Indicators] No candles available');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[Indicators] ========== drawIndicatorsOnChart START ==========`);
|
||||
console.log(`[Indicators] Candles from allData: ${candles.length}`);
|
||||
console.log(`[Indicators] First candle time: ${candles[0]?.time} (${new Date(candles[0]?.time * 1000).toLocaleDateString()})`);
|
||||
console.log(`[Indicators] Last candle time: ${candles[candles.length - 1]?.time} (${new Date(candles[candles.length - 1]?.time * 1000).toLocaleDateString()})`);
|
||||
// console.log(`[Indicators] ========== drawIndicatorsOnChart START ==========`);
|
||||
// console.log(`[Indicators] Candles from allData: ${candles.length}`);
|
||||
// console.log(`[Indicators] First candle time: ${candles[0]?.time} (${new Date(candles[0]?.time * 1000).toLocaleDateString()})`);
|
||||
// console.log(`[Indicators] Last candle time: ${candles[candles.length - 1]?.time} (${new Date(candles[candles.length - 1]?.time * 1000).toLocaleDateString()})`);
|
||||
|
||||
const oldestTime = candles[0]?.time;
|
||||
const newestTime = candles[candles.length - 1]?.time;
|
||||
const oldestDate = oldestTime ? new Date(oldestTime * 1000).toLocaleDateString() : 'N/A';
|
||||
const newestDate = newestTime ? new Date(newestTime * 1000).toLocaleDateString() : 'N/A';
|
||||
|
||||
console.log(`[Indicators] ========== Redrawing ==========`);
|
||||
console.log(`[Indicators] Candles: ${candles.length} | Time range: ${oldestDate} (${oldestTime}) to ${newestDate} (${newestTime})`);
|
||||
//console.log(`[Indicators] ========== Redrawing ==========`);
|
||||
// console.log(`[Indicators] Candles: ${candles.length} | Time range: ${oldestDate} (${oldestTime}) to ${newestDate} (${newestTime})`);
|
||||
|
||||
const activeIndicators = getActiveIndicators();
|
||||
|
||||
@ -929,14 +929,14 @@ export function drawIndicatorsOnChart() {
|
||||
|
||||
window.dashboard.chart.panes()[0]?.setHeight(mainPaneHeight);
|
||||
|
||||
console.log(`[Indicators] ========== Rendering Indicators ==========`);
|
||||
console.log(`[Indicators] Input candles: ${candles.length} | Panel count: ${totalPanes}`);
|
||||
//console.log(`[Indicators] ========== Rendering Indicators ==========`);
|
||||
//console.log(`[Indicators] Input candles: ${candles.length} | Panel count: ${totalPanes}`);
|
||||
|
||||
overlayIndicators.forEach(({ indicator, meta, instance }) => {
|
||||
console.log(`[Indicators] Processing overlay: ${indicator.name}`);
|
||||
console.log(`[Indicators] Before renderIndicatorOnPane: indicator.cachedResults length = ${indicator.cachedResults?.length || 0}`);
|
||||
//console.log(`[Indicators] Processing overlay: ${indicator.name}`);
|
||||
//console.log(`[Indicators] Before renderIndicatorOnPane: indicator.cachedResults length = ${indicator.cachedResults?.length || 0}`);
|
||||
renderIndicatorOnPane(indicator, meta, instance, candles, 0, lineStyleMap);
|
||||
console.log(`[Indicators] After renderIndicatorOnPane: indicator.cachedResults length = ${indicator.cachedResults?.length || 0}`);
|
||||
//console.log(`[Indicators] After renderIndicatorOnPane: indicator.cachedResults length = ${indicator.cachedResults?.length || 0}`);
|
||||
});
|
||||
|
||||
paneIndicators.forEach(({ indicator, meta, instance }, idx) => {
|
||||
@ -947,10 +947,10 @@ export function drawIndicatorsOnChart() {
|
||||
indicatorPanes.set(indicator.id, paneIndex);
|
||||
}
|
||||
|
||||
console.log(`[Indicators] Processing pane: ${indicator.name} (pane ${paneIndex})`);
|
||||
console.log(`[Indicators] Before renderIndicatorOnPane: indicator.cachedResults length = ${indicator.cachedResults?.length || 0}`);
|
||||
//console.log(`[Indicators] Processing pane: ${indicator.name} (pane ${paneIndex})`);
|
||||
//console.log(`[Indicators] Before renderIndicatorOnPane: indicator.cachedResults length = ${indicator.cachedResults?.length || 0}`);
|
||||
renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, lineStyleMap);
|
||||
console.log(`[Indicators] After renderIndicatorOnPane: indicator.cachedResults length = ${indicator.cachedResults?.length || 0}`);
|
||||
//console.log(`[Indicators] After renderIndicatorOnPane: indicator.cachedResults length = ${indicator.cachedResults?.length || 0}`);
|
||||
|
||||
const pane = window.dashboard.chart.panes()[paneIndex];
|
||||
if (pane) {
|
||||
@ -959,27 +959,10 @@ export function drawIndicatorsOnChart() {
|
||||
parseInt(localStorage.getItem(`pane_height_${indicator.type}`)) ||
|
||||
120;
|
||||
pane.setHeight(storedHeight);
|
||||
|
||||
// Subscribe to pane height changes to save to localStorage
|
||||
const originalHeight = storedHeight;
|
||||
const paneElement = pane.getHTMLElement && pane.getHTMLElement();
|
||||
if (paneElement) {
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const newHeight = Math.round(entry.contentRect.height);
|
||||
if (newHeight !== originalHeight && newHeight > 50) {
|
||||
indicator.paneHeight = newHeight;
|
||||
localStorage.setItem(`pane_height_${indicator.type}`, newHeight);
|
||||
console.log(`[Indicators] Saved pane height for ${indicator.type}: ${newHeight}px`);
|
||||
}
|
||||
}
|
||||
});
|
||||
resizeObserver.observe(paneElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`[Indicators] ========== drawIndicatorsOnChart END ==========`);
|
||||
//console.log(`[Indicators] ========== drawIndicatorsOnChart END ==========`);
|
||||
} catch (error) {
|
||||
console.error('[Indicators] Error drawing indicators:', error);
|
||||
}
|
||||
|
||||
@ -173,16 +173,16 @@ export function calculateAllIndicatorSignals() {
|
||||
const activeIndicators = window.getActiveIndicators?.() || [];
|
||||
const candles = window.dashboard?.allData?.get(window.dashboard?.currentInterval);
|
||||
|
||||
console.log('[Signals] ========== calculateAllIndicatorSignals START ==========');
|
||||
//console.log('[Signals] ========== calculateAllIndicatorSignals START ==========');
|
||||
console.log('[Signals] Active indicators:', activeIndicators.length, 'Candles:', candles?.length || 0);
|
||||
|
||||
if (!candles || candles.length < 2) {
|
||||
console.log('[Signals] Insufficient candles available:', candles?.length || 0);
|
||||
//console.log('[Signals] Insufficient candles available:', candles?.length || 0);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!activeIndicators || activeIndicators.length === 0) {
|
||||
console.log('[Signals] No active indicators');
|
||||
//console.log('[Signals] No active indicators');
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -283,7 +283,7 @@ export function calculateAllIndicatorSignals() {
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[Signals] ========== calculateAllIndicatorSignals END ==========');
|
||||
//console.log('[Signals] ========== calculateAllIndicatorSignals END ==========');
|
||||
console.log('[Signals] Total signals calculated:', signals.length);
|
||||
return signals;
|
||||
}
|
||||
Reference in New Issue
Block a user