Fix: Track actual historical crossovers and force indicator redraw
Issue 1: Track actual crossovers from history - Added calculateHistoricalCrossovers() function - Scans candle history backwards to find most recent crossover - Updates lastSignalTimestamp when BUY→SELL or SELL→BUY crossover is detected - Date now reflects when the price actually crossed the MA line Issue 2: Force full redraw of indicator series - Changed updateIndicatorCandles() to remove and recreate all series - Instead of using setData() which may not work reliably - Full redraw ensures overlay indicators (MA) refresh correctly - Now indicator lines update properly when new candles finish Both implementations ensure: 1. Date shows actual crossover time (when candle crossed MA) 2. Indicator lines redraw on new candle completion 3. Works for both overlay (MA, BB, HTS) and pane (RSI, MACD, etc.) indicators
This commit is contained in:
@ -813,59 +813,31 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update existing indicator series with new data (for real-time updates)
|
// Completely redraw indicators (works for both overlay and pane)
|
||||||
export function updateIndicatorCandles() {
|
export function updateIndicatorCandles() {
|
||||||
if (!window.dashboard || !window.dashboard.chart) return;
|
console.log('[UpdateIndicators] Removing and recreating all indicator series');
|
||||||
|
|
||||||
|
// Remove all existing series
|
||||||
const activeIndicators = getActiveIndicators();
|
const activeIndicators = getActiveIndicators();
|
||||||
const currentInterval = window.dashboard.currentInterval;
|
|
||||||
const candles = window.dashboard?.allData?.get(currentInterval);
|
|
||||||
|
|
||||||
if (!candles || candles.length === 0) return;
|
|
||||||
|
|
||||||
activeIndicators.forEach(indicator => {
|
activeIndicators.forEach(indicator => {
|
||||||
if (!indicator.visible || indicator.series.length === 0) return;
|
indicator.series?.forEach(s => {
|
||||||
|
try {
|
||||||
const IndicatorClass = IndicatorRegistry[indicator.type];
|
window.dashboard.chart.removeSeries(s);
|
||||||
if (!IndicatorClass) return;
|
} catch(e) {
|
||||||
|
console.warn('[UpdateIndicators] Error removing series:', e);
|
||||||
const instance = new IndicatorClass(indicator);
|
|
||||||
const results = instance.calculate(candles);
|
|
||||||
|
|
||||||
if (!results || results.length === 0) return;
|
|
||||||
|
|
||||||
const meta = instance.getMetadata();
|
|
||||||
|
|
||||||
// Update each plot series
|
|
||||||
meta.plots.forEach((plot, plotIdx) => {
|
|
||||||
const series = indicator.series[plotIdx];
|
|
||||||
if (!series) return;
|
|
||||||
|
|
||||||
const plotColor = indicator.params[`_color_${plotIdx}`] || plot.color || '#2962ff';
|
|
||||||
const lineWidth = indicator.params._lineWidth || 2;
|
|
||||||
|
|
||||||
// Build complete data array
|
|
||||||
const data = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < candles.length; i++) {
|
|
||||||
const value = results[i]?.[plot.id];
|
|
||||||
if (value !== null && value !== undefined) {
|
|
||||||
data.push({
|
|
||||||
time: candles[i].time,
|
|
||||||
value: value,
|
|
||||||
color: plotColor,
|
|
||||||
lineWidth: lineWidth
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.length > 0) {
|
|
||||||
series.setData(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
indicator.series = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[UpdateIndicators] Updated ${activeIndicators.length} indicator series`);
|
// Clear pane mappings
|
||||||
|
indicatorPanes.clear();
|
||||||
|
nextPaneIndex = 1;
|
||||||
|
|
||||||
|
// Now call drawIndicatorsOnChart to recreate everything
|
||||||
|
drawIndicatorsOnChart();
|
||||||
|
|
||||||
|
console.log(`[UpdateIndicators] Recreated ${activeIndicators.length} indicators`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chart drawing
|
// Chart drawing
|
||||||
|
|||||||
@ -87,6 +87,71 @@ export function calculateSummarySignal(signals) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate historical crossovers for all indicators based on full candle history
|
||||||
|
* Finds the last time each indicator crossed from BUY to SELL or SELL to BUY
|
||||||
|
*/
|
||||||
|
function calculateHistoricalCrossovers(activeIndicators, candles) {
|
||||||
|
activeIndicators.forEach(indicator => {
|
||||||
|
const indicatorType = indicator.type || indicator.indicatorType;
|
||||||
|
const SignalFunction = getSignalFunction(indicatorType);
|
||||||
|
|
||||||
|
if (!SignalFunction) return;
|
||||||
|
|
||||||
|
// Recalculate indicator values for all candles
|
||||||
|
const IndicatorClass = IndicatorRegistry[indicatorType];
|
||||||
|
if (!IndicatorClass) return;
|
||||||
|
|
||||||
|
const instance = new IndicatorClass(indicator);
|
||||||
|
const results = instance.calculate(candles);
|
||||||
|
|
||||||
|
if (!results || results.length === 0) return;
|
||||||
|
|
||||||
|
// Track the last crossover timestamp
|
||||||
|
let lastCrossoverTimestamp = indicator.lastSignalTimestamp || null;
|
||||||
|
let crossoverCount = 0;
|
||||||
|
|
||||||
|
// Iterate through candles to find crossovers (from newest to oldest)
|
||||||
|
// We start from the end and go backwards to find the most recent crossover
|
||||||
|
for (let i = candles.length - 2; i >= 0; i--) {
|
||||||
|
// Skip if we don't have data for these candles
|
||||||
|
if (!results[i] || !results[i + 1]) continue;
|
||||||
|
|
||||||
|
const candle = candles[i];
|
||||||
|
const prevCandle = candles[i + 1];
|
||||||
|
|
||||||
|
const valuesThis = typeof results[i] === 'object' ? results[i] : { ma: results[i] };
|
||||||
|
const valuesPrev = typeof results[i + 1] === 'object' ? results[i + 1] : { ma: results[i + 1] };
|
||||||
|
|
||||||
|
const closeThis = candles[i].close;
|
||||||
|
const closePrev = candles[i + 1].close;
|
||||||
|
const maThis = valuesThis.ma;
|
||||||
|
const maPrev = valuesPrev.ma;
|
||||||
|
|
||||||
|
// Check for BUY→SELL crossover (was above, now below)
|
||||||
|
if (closePrev > maPrev && closeThis < maThis) {
|
||||||
|
console.log(`[HistoricalCross] ${indicatorType} BUY→SELL crossover at candle ${i}, time: ${candle.time}`);
|
||||||
|
lastCrossoverTimestamp = candle.time;
|
||||||
|
crossoverCount++;
|
||||||
|
break; // Found most recent crossover
|
||||||
|
}
|
||||||
|
// Check for SELL→BUY crossover (was below, now above)
|
||||||
|
else if (closePrev < maPrev && closeThis > maThis) {
|
||||||
|
console.log(`[HistoricalCross] ${indicatorType} SELL→BUY crossover at candle ${i}, time: ${candle.time}`);
|
||||||
|
lastCrossoverTimestamp = candle.time;
|
||||||
|
crossoverCount++;
|
||||||
|
break; // Found most recent crossover
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crossoverCount > 0) {
|
||||||
|
console.log(`[HistoricalCross] ${indicatorType}: Found crossover at ${new Date(lastCrossoverTimestamp * 1000).toLocaleString()}`);
|
||||||
|
// Update the indicator's lastSignalTimestamp
|
||||||
|
indicator.lastSignalTimestamp = lastCrossoverTimestamp;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate signals for all active indicators
|
* Calculate signals for all active indicators
|
||||||
* @returns {Array} Array of indicator signals
|
* @returns {Array} Array of indicator signals
|
||||||
@ -110,6 +175,9 @@ export function calculateAllIndicatorSignals() {
|
|||||||
|
|
||||||
const signals = [];
|
const signals = [];
|
||||||
|
|
||||||
|
// Calculate crossovers for all indicators based on historical data
|
||||||
|
calculateHistoricalCrossovers(activeIndicators, candles);
|
||||||
|
|
||||||
for (const indicator of activeIndicators) {
|
for (const indicator of activeIndicators) {
|
||||||
const IndicatorClass = IndicatorRegistry[indicator.type];
|
const IndicatorClass = IndicatorRegistry[indicator.type];
|
||||||
if (!IndicatorClass) {
|
if (!IndicatorClass) {
|
||||||
|
|||||||
Reference in New Issue
Block a user