diff --git a/index.html b/index.html
index 4bc2320..55acc36 100644
--- a/index.html
+++ b/index.html
@@ -34,13 +34,24 @@
}
/* Hide scrollbar for clean UI */
- .no-scrollbar::-webkit-scrollbar {
- display: none;
- }
- .no-scrollbar {
- -ms-overflow-style: none;
- scrollbar-width: none;
- }
+ .no-scrollbar::-webkit-scrollbar {
+ display: none;
+ }
+ .no-scrollbar {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ }
+
+ /* Chart type button active state */
+ .chart-type-btn.active {
+ background-color: #2d3a4f;
+ color: #3b82f6;
+ }
+
+ /* Chart type button hover effect */
+ .chart-type-btn:hover {
+ background-color: #2d3a4f;
+ }
@@ -73,8 +84,37 @@
diff --git a/js/ui/chart.js b/js/ui/chart.js
index 891beef..f694a0d 100644
--- a/js/ui/chart.js
+++ b/js/ui/chart.js
@@ -3,55 +3,7 @@ import { calculateAllIndicatorSignals, calculateSummarySignal } from './signals-
import { calculateSignalMarkers } from './signal-markers.js';
import { updateIndicatorCandles } from './indicators-panel-new.js';
import { TimezoneConfig } from '../config/timezone.js';
-
-export class SeriesMarkersPrimitive {
- constructor(markers) {
- this._markers = markers || [];
- this._paneViews = [new MarkersPaneView(this)];
- }
-
- setMarkers(markers) {
- this._markers = markers;
- if (this._requestUpdate) {
- this._requestUpdate();
- }
- }
-
- attached(param) {
- this._chart = param.chart;
- this._series = param.series;
- this._requestUpdate = param.requestUpdate;
- this._requestUpdate();
- }
-
- detached() {
- this._chart = undefined;
- this._series = undefined;
- this._requestUpdate = undefined;
- }
-
- updateAllViews() {
- this._requestUpdate?.();
- }
-
- paneViews() {
- return this._paneViews;
- }
-}
-
-class MarkersPaneView {
- constructor(source) {
- this._source = source;
- }
-
- renderer() {
- return new MarkersRenderer(this._source);
- }
-
- zOrder() {
- return 'top';
- }
-}
+import { DrawingManager } from './drawing-tools.js';
class MarkersRenderer {
constructor(source) {
@@ -67,7 +19,6 @@ class MarkersRenderer {
const chart = this._source._chart;
const markers = this._source._markers;
- // Adjust coordinates to bitmap space based on pixel ratio
const ratio = scope.horizontalPixelRatio;
ctx.save();
@@ -76,10 +27,8 @@ class MarkersRenderer {
const timeCoordinate = chart.timeScale().timeToCoordinate(marker.time);
if (timeCoordinate === null) continue;
- // Figure out price coordinate
let price = marker.price || marker.value;
- // If price wasn't specified but we have the series data, grab the candle high/low
if (!price && window.dashboard && window.dashboard.allData) {
const data = window.dashboard.allData.get(window.dashboard.currentInterval);
if (data) {
@@ -132,6 +81,55 @@ class MarkersRenderer {
}
}
+class MarkersPaneView {
+ constructor(source) {
+ this._source = source;
+ }
+
+ renderer() {
+ return new MarkersRenderer(this._source);
+ }
+
+ zOrder() {
+ return 'top';
+ }
+}
+
+export class SeriesMarkersPrimitive {
+ constructor(markers) {
+ this._markers = markers || [];
+ this._paneViews = [new MarkersPaneView(this)];
+ }
+
+ setMarkers(markers) {
+ this._markers = markers;
+ if (this._requestUpdate) {
+ this._requestUpdate();
+ }
+ }
+
+ attached(param) {
+ this._chart = param.chart;
+ this._series = param.series;
+ this._requestUpdate = param.requestUpdate;
+ this._requestUpdate();
+ }
+
+ detached() {
+ this._chart = undefined;
+ this._series = undefined;
+ this._requestUpdate = undefined;
+ }
+
+ updateAllViews() {
+ this._requestUpdate?.();
+ }
+
+ paneViews() {
+ return this._paneViews;
+ }
+}
+
function formatDate(timestamp) {
return TimezoneConfig.formatDate(timestamp);
}
@@ -149,13 +147,12 @@ function throttle(func, limit) {
}
}
-import { DrawingManager } from './drawing-tools.js';
-
export class TradingDashboard {
constructor() {
this.chart = null;
this.candleSeries = null;
- // Load settings from local storage or defaults
+ this.currentChartType = localStorage.getItem('winterfail_chart_type') || 'candlestick';
+
this.symbol = localStorage.getItem('winterfail_symbol') || 'BTC';
this.currentInterval = localStorage.getItem('winterfail_interval') || '1d';
@@ -169,18 +166,17 @@ export class TradingDashboard {
this.lastCandleTimestamp = null;
this.simulationMarkers = [];
this.avgPriceSeries = null;
- this.dailyMAData = new Map(); // timestamp -> { ma44, ma125, price }
+ this.dailyMAData = new Map();
this.currentMouseTime = null;
this.drawingManager = null;
+ this.seriesMap = {};
- // Throttled versions of heavy functions
this.throttledOnVisibleRangeChange = throttle(this.onVisibleRangeChange.bind(this), 150);
this.init();
}
async loadDailyMAData() {
try {
- // Use 1d interval for this calculation
const interval = '1d';
let candles = this.allData.get(interval);
@@ -243,7 +239,6 @@ export class TradingDashboard {
this.chart.removeSeries(this.avgPriceSeries);
}
- // Recreate series to apply custom colors per point via LineSeries data
this.avgPriceSeries = this.chart.addSeries(LightweightCharts.LineSeries, {
lineWidth: 2,
lineStyle: LightweightCharts.LineStyle.Solid,
@@ -264,6 +259,7 @@ export class TradingDashboard {
init() {
this.createTimeframeButtons();
+ this.createChartTypeButtons();
this.initChart();
this.initEventListeners();
this.loadInitialData();
@@ -302,6 +298,32 @@ export class TradingDashboard {
});
}
+ createChartTypeButtons() {
+ const container = document.querySelector('.flex.space-x-1:not(#timeframeContainer)');
+ if (!container) return;
+
+ const chartTypes = [
+ { type: 'candlestick', icon: 'show_chart', name: 'Candlestick' },
+ { type: 'line', icon: 'insert_chart', name: 'Line' },
+ { type: 'bar', icon: 'scatter_plot', name: 'Bar' }
+ ];
+
+ container.innerHTML = '';
+ chartTypes.forEach(chartType => {
+ const btn = document.createElement('button');
+ btn.className = 'chart-type-btn w-10 h-10 flex items-center justify-center text-gray-400 hover:text-white hover:bg-[#2d3a4f] rounded transition-colors';
+ btn.dataset.chartType = chartType.type;
+ btn.innerHTML = `
${chartType.icon}`;
+ btn.title = chartType.name;
+ if (chartType.type === this.currentChartType) {
+ btn.style.backgroundColor = '#2d3a4f';
+ btn.classList.add('text-blue-400');
+ }
+ btn.addEventListener('click', () => this.switchChartType(chartType.type));
+ container.appendChild(btn);
+ });
+ }
+
initChart() {
const chartContainer = document.getElementById('chart');
@@ -324,7 +346,6 @@ export class TradingDashboard {
borderColor: COLORS.tvBorder,
autoScale: true,
mode: 0,
- // Explicitly enable pinch/scale behavior on the price scale
scaleMargins: {
top: 0.1,
bottom: 0.1,
@@ -340,30 +361,28 @@ export class TradingDashboard {
return TimezoneConfig.formatTickMark(time);
},
},
- localization: {
- timeFormatter: (timestamp) => {
- return TimezoneConfig.formatDate(timestamp * 1000);
- },
- },
- handleScroll: {
- mouseWheel: true,
- pressedMouseMove: true,
- horzTouchDrag: true,
- vertTouchDrag: true, // Enabled to allow chart-internal vertical scrolling
+ localization: {
+ timeFormatter: (timestamp) => {
+ return TimezoneConfig.formatDate(timestamp * 1000);
+ },
},
- handleScale: {
- axisPressedMouseMove: true,
+ handleScroll: {
mouseWheel: true,
- pinch: true, // This enables pinch-to-zoom on touch devices
- },
+ pressedMouseMove: true,
+ horzTouchDrag: true,
+ vertTouchDrag: true,
+ },
+ handleScale: {
+ axisPressedMouseMove: true,
+ mouseWheel: true,
+ pinch: true,
+ },
crosshair: {
mode: LightweightCharts.CrosshairMode.Normal,
},
});
- // Setup price format selector change handler
const priceInput = document.getElementById("priceFormatInput");
- // Load saved precision
let savedPrecision = parseInt(localStorage.getItem('winterfail_price_precision'));
if (isNaN(savedPrecision)) savedPrecision = 2;
@@ -386,87 +405,63 @@ export class TradingDashboard {
});
}
- // Load candle colors from storage or default
const savedUpColor = localStorage.getItem('winterfail_candle_up') || '#ff9800';
const savedDownColor = localStorage.getItem('winterfail_candle_down') || '#ff9800';
const candleUpInput = document.getElementById('candleUpColor');
const candleDownInput = document.getElementById('candleDownColor');
- if (candleUpInput) candleUpInput.value = savedUpColor;
- if (candleDownInput) candleDownInput.value = savedDownColor;
+ if (candleUpInput && this.currentChartType === 'candlestick') candleUpInput.value = savedUpColor;
+ if (candleDownInput && this.currentChartType === 'candlestick') candleDownInput.value = savedDownColor;
- // Calculate initial minMove based on saved precision
- const initialMinMove = savedPrecision === 0 ? 1 : Number((1 / Math.pow(10, savedPrecision)).toFixed(precision));
+ const initialMinMove = savedPrecision === 0 ? 1 : Number((1 / Math.pow(10, savedPrecision)).toFixed(savedPrecision));
- this.candleSeries = this.chart.addSeries(LightweightCharts.CandlestickSeries, {
- upColor: savedUpColor,
- downColor: savedDownColor,
- borderUpColor: savedUpColor,
- borderDownColor: savedDownColor,
- wickUpColor: savedUpColor,
- wickDownColor: savedDownColor,
- lastValueVisible: false,
- priceLineVisible: false,
- priceFormat: { type: 'price', precision: savedPrecision, minMove: initialMinMove }
- }, 0);
+ this.candleSeries = this.addSeriesByType(this.currentChartType);
+
+ if (this.currentChartType === 'line') {
+ this.candleSeries.setData([]);
+ }
- // Color change listeners
- if (candleUpInput) {
- candleUpInput.addEventListener('input', (e) => {
- const color = e.target.value;
- localStorage.setItem('winterfail_candle_up', color);
- this.candleSeries.applyOptions({
- upColor: color,
- borderUpColor: color,
- wickUpColor: color
+ if (this.currentChartType === 'candlestick' || this.currentChartType === 'bar') {
+ if (candleUpInput) {
+ candleUpInput.addEventListener('input', (e) => {
+ const color = e.target.value;
+ localStorage.setItem('winterfail_candle_up', color);
+ this.applyColorToChartType(color, 'up');
});
- });
+ }
+
+ if (candleDownInput) {
+ candleDownInput.addEventListener('input', (e) => {
+ const color = e.target.value;
+ localStorage.setItem('winterfail_candle_down', color);
+ this.applyColorToChartType(color, 'down');
+ });
+ }
+
+ if (this.candleSeries) {
+ this.currentPriceLine = this.candleSeries.createPriceLine({
+ price: 0,
+ color: '#26a69a',
+ lineWidth: 1,
+ lineStyle: LightweightCharts.LineStyle.Dotted,
+ axisLabelVisible: true,
+ title: '',
+ });
+ }
}
- if (candleDownInput) {
- candleDownInput.addEventListener('input', (e) => {
- const color = e.target.value;
- localStorage.setItem('winterfail_candle_down', color);
- this.candleSeries.applyOptions({
- downColor: color,
- borderDownColor: color,
- wickDownColor: color
- });
- });
- }
-
- this.avgPriceSeries = this.chart.addSeries(LightweightCharts.LineSeries, {
- color: '#00bcd4',
- lineWidth: 1,
- lineStyle: LightweightCharts.LineStyle.Solid,
- lastValueVisible: true,
- priceLineVisible: false,
- crosshairMarkerVisible: false,
- title: '',
- priceFormat: { type: 'price', precision: savedPrecision, minMove: initialMinMove }
-});
-
- this.currentPriceLine = this.candleSeries.createPriceLine({
- price: 0,
- color: '#26a69a',
- lineWidth: 1,
- lineStyle: LightweightCharts.LineStyle.Dotted,
- axisLabelVisible: true,
- title: '',
- });
-
+ this.addAvgPriceSeries();
+
this.initPriceScaleControls();
this.initNavigationControls();
- // Initialize Drawing Manager
this.drawingManager = new DrawingManager(this, chartContainer);
window.activateDrawingTool = (tool, event) => {
const e = event || window.event;
this.drawingManager.setTool(tool, e);
};
- // Setup price format selector change handler
document.addEventListener("DOMContentLoaded", () => {
const priceSelect = document.getElementById("priceFormatSelect");
if (priceSelect) {
@@ -481,7 +476,6 @@ export class TradingDashboard {
this.chart.timeScale().subscribeVisibleLogicalRangeChange(this.onVisibleRangeChange.bind(this));
- // Subscribe to crosshair movement for Best Moving Averages updates
this.chart.subscribeCrosshairMove(param => {
if (param.time) {
this.currentMouseTime = param.time;
@@ -492,7 +486,6 @@ export class TradingDashboard {
}
});
- // Hide indicators panel when clicking on chart
this.chart.subscribeClick(param => {
window.hideAllPanels?.();
});
@@ -520,7 +513,6 @@ export class TradingDashboard {
const btnSettings = document.getElementById('btnSettings');
const settingsPopup = document.getElementById('settingsPopup');
- // Settings Popup Toggle and Outside Click
if (btnSettings && settingsPopup) {
btnSettings.addEventListener('click', (e) => {
e.stopPropagation();
@@ -540,14 +532,12 @@ export class TradingDashboard {
}
}
- // Initialize state from storage
this.scaleState = {
autoScale: localStorage.getItem('winterfail_scale_auto') !== 'false',
invertScale: localStorage.getItem('winterfail_scale_invert') === 'true',
scaleMode: parseInt(localStorage.getItem('winterfail_scale_mode')) || 0
};
- // UI Helpers
const updateCheckmark = (id, active) => {
const el = document.getElementById(id);
if (el) el.textContent = active ? '✓' : '';
@@ -561,7 +551,6 @@ export class TradingDashboard {
updateCheckmark('modePercentCheck', this.scaleState.scaleMode === 2);
updateCheckmark('modeIndexedCheck', this.scaleState.scaleMode === 3);
- // Apply state to chart
this.candleSeries.priceScale().applyOptions({
autoScale: this.scaleState.autoScale,
invertScale: this.scaleState.invertScale,
@@ -583,14 +572,12 @@ export class TradingDashboard {
updateUI();
};
- // Add keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return;
if (e.key.toLowerCase() === 'a') {
window.toggleScaleOption('autoScale');
} else if (e.key.toLowerCase() === 'l') {
- // Toggle between Normal (0) and Log (1)
const newMode = this.scaleState.scaleMode === 1 ? 0 : 1;
window.setScaleMode(newMode);
}
@@ -649,6 +636,8 @@ export class TradingDashboard {
}
initEventListeners() {
+ this.initChartTypeListeners();
+
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return;
@@ -669,18 +658,27 @@ export class TradingDashboard {
} else if (e.key === 'ArrowUp') {
this.navigateToRecent();
}
-});
+ });
+ }
+
+ initChartTypeListeners() {
+ document.addEventListener('click', (e) => {
+ const btn = e.target.closest('.chart-type-btn');
+ if (!btn) return;
+
+ const chartType = btn.dataset.chartType;
+ if (chartType) {
+ this.switchChartType(chartType);
+ }
+ });
}
clearIndicatorCaches(clearSignalState = false) {
const activeIndicators = window.getActiveIndicators?.() || [];
activeIndicators.forEach(indicator => {
- // Always clear calculation caches
indicator.cachedResults = null;
indicator.cachedMeta = null;
- // Only clear signal state if explicitly requested (e.g., timeframe change)
- // Do not clear on new candle completion - preserve signal change tracking
if (clearSignalState) {
indicator.lastSignalTimestamp = null;
indicator.lastSignalType = null;
@@ -709,7 +707,7 @@ export class TradingDashboard {
const response = await fetch(`${window.APP_CONFIG.API_BASE_URL}/candles?symbol=BTC&interval=${this.currentInterval}&limit=${limit}`);
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 => ({
time: Math.floor(new Date(c.time).getTime() / 1000),
open: parseFloat(c.open),
@@ -723,7 +721,10 @@ if (data.candles && data.candles.length > 0) {
const mergedData = this.mergeData(existingData, chartData);
this.allData.set(this.currentInterval, mergedData);
- this.candleSeries.setData(mergedData);
+ if (!this.candleSeries) {
+ console.error('[Chart] Candle series not initialized');
+ return;
+ }
if (fitToContent) {
this.chart.timeScale().scrollToRealTime();
@@ -731,6 +732,28 @@ if (data.candles && data.candles.length > 0) {
this.chart.timeScale().setVisibleLogicalRange(visibleRange);
}
+ if ((this.currentChartType === 'candlestick' || this.currentChartType === 'bar') &&
+ mergedData.length > 0 &&
+ mergedData[0].hasOwnProperty('open')) {
+ this.candleSeries.setData(mergedData);
+ } else if (this.currentChartType === 'line' &&
+ mergedData.length > 0 &&
+ mergedData[0].hasOwnProperty('close')) {
+ const closePrices = mergedData.map(c => ({
+ time: c.time,
+ value: c.close
+ }));
+ this.candleSeries.setData(closePrices);
+ } else if (mergedData.length > 0 && mergedData[0].hasOwnProperty('value')) {
+ this.candleSeries.setData(mergedData);
+ } else if (mergedData.length > 0) {
+ const closePrices = mergedData.map(c => ({
+ time: c.time,
+ value: c.close || c.value
+ }));
+ this.candleSeries.setData(closePrices);
+ }
+
const latest = mergedData[mergedData.length - 1];
this.updateStats(latest);
}
@@ -743,14 +766,19 @@ if (data.candles && data.candles.length > 0) {
}
}
-async loadNewData() {
+ async loadNewData() {
if (!this.hasInitialLoad || this.isLoading) return;
try {
const response = await fetch(`${window.APP_CONFIG.API_BASE_URL}/candles?symbol=BTC&interval=${this.currentInterval}&limit=50`);
const data = await response.json();
- if (data.candles && data.candles.length > 0) {
+ if (!this.candleSeries) {
+ console.error('[Chart] Candle series not initialized');
+ return;
+ }
+
+ if (data.candles && data.candles.length > 0) {
const atEdge = this.isAtRightEdge();
const currentSeriesData = this.candleSeries.data();
@@ -769,43 +797,53 @@ async loadNewData() {
const latest = chartData[chartData.length - 1];
- // Check if new candle detected
const isNewCandle = this.lastCandleTimestamp !== null && latest.time > this.lastCandleTimestamp;
if (isNewCandle) {
console.log(`[NewData Load] New candle detected: ${this.lastCandleTimestamp} -> ${latest.time}`);
- // Clear indicator caches but preserve signal state
this.clearIndicatorCaches(false);
}
this.lastCandleTimestamp = latest.time;
- chartData.forEach(candle => {
- if (candle.time >= lastTimestamp &&
- !Number.isNaN(candle.time) &&
- !Number.isNaN(candle.open) &&
- !Number.isNaN(candle.high) &&
- !Number.isNaN(candle.low) &&
- !Number.isNaN(candle.close)) {
- this.candleSeries.update(candle);
- }
- });
+ if (this.currentChartType === 'candlestick' || this.currentChartType === 'bar') {
+ chartData.forEach(candle => {
+ if (candle.time >= lastTimestamp &&
+ !Number.isNaN(candle.time) &&
+ !Number.isNaN(candle.open) &&
+ !Number.isNaN(candle.high) &&
+ !Number.isNaN(candle.low) &&
+ !Number.isNaN(candle.close)) {
+ this.candleSeries.update(candle);
+ }
+ });
+ } else if (this.currentChartType === 'line') {
+ const closePrices = chartData.map(c => ({
+ time: c.time,
+ value: c.close
+ }));
+
+ const existingData = this.candleSeries.data();
+ const existingTimeSet = new Set(existingData.map(d => d.time));
+
+ const newDataToAppend = closePrices.filter(c => !existingTimeSet.has(c.time));
+
+ if (newDataToAppend.length > 0) {
+ if (existingData.length === 0) {
+ this.candleSeries.setData(closePrices);
+ } else {
+ newDataToAppend.forEach(point => {
+ this.candleSeries.update(point);
+ });
+ }
+ }
+ }
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}`);
-
- // Auto-scrolling disabled per user request
- /*
- if (atEdge) {
- this.chart.timeScale().scrollToRealTime();
- }
- */
-
this.updateStats(latest);
- //console.log('[Chart] Calling drawIndicatorsOnChart after new data');
window.drawIndicatorsOnChart?.();
window.updateIndicatorCandles?.();
@@ -824,7 +862,7 @@ async loadNewData() {
return Array.from(dataMap.values()).sort((a, b) => a.time - b.time);
}
-onVisibleRangeChange() {
+ onVisibleRangeChange() {
if (!this.hasInitialLoad || this.isLoading) {
return;
}
@@ -859,7 +897,6 @@ onVisibleRangeChange() {
}
}
- // Recalculate indicators when data changes
if (data.length !== allData?.length) {
console.log(`[VisibleRange] Chart data (${data.length}) vs dataset (${allData?.length || 0}) differ, redrawing indicators...`);
}
@@ -867,7 +904,7 @@ onVisibleRangeChange() {
this.loadSignals().catch(e => console.error('Error loading signals:', e));
}
-async loadHistoricalData(beforeTime, limit = 1000) {
+ async loadHistoricalData(beforeTime, limit = 1000) {
if (this.isLoading) {
return;
}
@@ -899,17 +936,39 @@ async loadHistoricalData(beforeTime, limit = 1000) {
}));
const existingData = this.allData.get(this.currentInterval) || [];
- const mergedData = this.mergeData(existingData, chartData);
- this.allData.set(this.currentInterval, mergedData);
+ const mergedData = this.mergeData(existingData, chartData);
+ this.allData.set(this.currentInterval, mergedData);
+
+ console.log(`[Historical] SUCCESS: Added ${chartData.length} candles`);
+ console.log(`[Historical] Total candles in dataset: ${mergedData.length}`);
+ console.log(`[Historical] Oldest: ${new Date(mergedData[0]?.time * 1000).toLocaleDateString()}`);
+ console.log(`[Historical] Newest: ${new Date(mergedData[mergedData.length - 1]?.time * 1000).toLocaleDateString()}`);
+
+ if (this.currentChartType === 'candlestick' || this.currentChartType === 'bar') {
+ if (mergedData.length > 0 && mergedData[0].hasOwnProperty('open')) {
+ this.candleSeries.setData(mergedData);
+ } else {
+ const ohlcData = mergedData.map(c => ({
+ time: c.time,
+ open: c.value,
+ high: c.value,
+ low: c.value,
+ close: c.value
+ }));
+ this.candleSeries.setData(ohlcData);
+ }
+ } else {
+ if (mergedData.length > 0 && mergedData[0].hasOwnProperty('close')) {
+ const closePrices = mergedData.map(c => ({
+ time: c.time,
+ value: c.close
+ }));
+ this.candleSeries.setData(closePrices);
+ } else if (mergedData.length > 0) {
+ this.candleSeries.setData(mergedData);
+ }
+ }
- console.log(`[Historical] SUCCESS: Added ${chartData.length} candles`);
- console.log(`[Historical] Total candles in dataset: ${mergedData.length}`);
- console.log(`[Historical] Oldest: ${new Date(mergedData[0]?.time * 1000).toLocaleDateString()}`);
- console.log(`[Historical] Newest: ${new Date(mergedData[mergedData.length - 1]?.time * 1000).toLocaleDateString()}`);
-
- this.candleSeries.setData(mergedData);
-
- // Recalculate indicators and signals with the expanded dataset
console.log(`[Historical] Recalculating indicators...`);
window.drawIndicatorsOnChart?.();
await this.loadSignals();
@@ -925,7 +984,7 @@ async loadHistoricalData(beforeTime, limit = 1000) {
}
}
-async loadTA() {
+ async loadTA() {
if (!this.hasInitialLoad) {
const time = new Date().toLocaleTimeString();
document.getElementById('taContent').innerHTML = `
Loading technical analysis... ${time}
`;
@@ -950,7 +1009,7 @@ async loadTA() {
}
}
-async loadSignals() {
+ async loadSignals() {
try {
this.indicatorSignals = calculateAllIndicatorSignals();
this.summarySignal = calculateSummarySignal(this.indicatorSignals);
@@ -968,18 +1027,14 @@ async loadSignals() {
let markers = calculateSignalMarkers(candles);
- // Merge simulation markers if present
if (this.simulationMarkers && this.simulationMarkers.length > 0) {
markers = [...markers, ...this.simulationMarkers];
}
- // CRITICAL: Filter out any markers with invalid timestamps before passing to chart
markers = markers.filter(m => m && m.time !== null && m.time !== undefined && !isNaN(m.time));
- // Re-sort combined markers by time
markers.sort((a, b) => a.time - b.time);
- // Use custom primitive for markers in v5
try {
if (!this.markerPrimitive) {
this.markerPrimitive = new SeriesMarkersPrimitive();
@@ -1012,7 +1067,6 @@ async loadSignals() {
const signalColor = indSignal.signal === 'buy' ? '#26a69a' : indSignal.signal === 'sell' ? '#ef5350' : '#787b86';
const lastSignalDate = indSignal.lastSignalDate ? formatDate(indSignal.lastSignalDate * 1000) : '-';
- // Format params as "MA(44)" style
let paramsStr = '';
if (indSignal.params !== null && indSignal.params !== undefined) {
paramsStr = `(${indSignal.params})`;
@@ -1031,16 +1085,13 @@ async loadSignals() {
const summaryBadge = '';
- // Best Moving Averages Logic (1D based)
let displayMA = { ma44: null, ma125: null, price: null, time: null };
if (this.currentMouseTime && this.dailyMAData.size > 0) {
- // Find the 1D candle that includes this mouse time
const dayTimestamp = Math.floor(this.currentMouseTime / 86400) * 86400;
if (this.dailyMAData.has(dayTimestamp)) {
displayMA = { ...this.dailyMAData.get(dayTimestamp), time: dayTimestamp };
} else {
- // Fallback to latest if specific day not found
const keys = Array.from(this.dailyMAData.keys()).sort((a, b) => b - a);
const latestKey = keys[0];
displayMA = { ...this.dailyMAData.get(latestKey), time: latestKey };
@@ -1089,27 +1140,27 @@ async loadSignals() {
-