const HTS_COLORS = { fastHigh: '#00bcd4', fastLow: '#00bcd4', slowHigh: '#f44336', slowLow: '#f44336', bullishZone: 'rgba(38, 166, 154, 0.1)', bearishZone: 'rgba(239, 83, 80, 0.1)', channelRegion: 'rgba(41, 98, 255, 0.05)' }; let HTSOverlays = []; export class HTSVisualizer { constructor(chart, candles) { this.chart = chart; this.candles = candles; this.overlays = []; } clear() { this.overlays.forEach(overlay => { try { this.chart.removeSeries(overlay.series); } catch (e) { } }); this.overlays = []; } addHTSChannels(htsData, isAutoHTS = false) { this.clear(); if (!htsData || htsData.length === 0) return; const alpha = isAutoHTS ? 0.3 : 0.3; const lineWidth = isAutoHTS ? 1 : 2; const fastHighSeries = this.chart.addSeries(LightweightCharts.LineSeries, { color: `rgba(0, 188, 212, ${alpha})`, lineWidth: lineWidth, lastValueVisible: false, title: 'HTS Fast High' + (isAutoHTS ? ' (Auto)' : ''), priceLineVisible: false, crosshairMarkerVisible: false }); const fastLowSeries = this.chart.addSeries(LightweightCharts.LineSeries, { color: `rgba(0, 188, 212, ${alpha})`, lineWidth: lineWidth, lastValueVisible: false, title: 'HTS Fast Low' + (isAutoHTS ? ' (Auto)' : ''), priceLineVisible: false, crosshairMarkerVisible: false }); const slowHighSeries = this.chart.addSeries(LightweightCharts.LineSeries, { color: `rgba(244, 67, 54, ${alpha})`, lineWidth: lineWidth + 1, lastValueVisible: false, title: 'HTS Slow High' + (isAutoHTS ? ' (Auto)' : ''), priceLineVisible: false, crosshairMarkerVisible: false }); const slowLowSeries = this.chart.addSeries(LightweightCharts.LineSeries, { color: `rgba(244, 67, 54, ${alpha})`, lineWidth: lineWidth + 1, lastValueVisible: false, title: 'HTS Slow Low' + (isAutoHTS ? ' (Auto)' : ''), priceLineVisible: false, crosshairMarkerVisible: false }); const fastHighData = htsData.map(h => ({ time: h.time, value: h.fastHigh })); const fastLowData = htsData.map(h => ({ time: h.time, value: h.fastLow })); const slowHighData = htsData.map(h => ({ time: h.time, value: h.slowHigh })); const slowLowData = htsData.map(h => ({ time: h.time, value: h.slowLow })); fastHighSeries.setData(fastHighData); fastLowSeries.setData(fastLowData); slowHighSeries.setData(slowHighData); slowLowSeries.setData(slowLowData); this.overlays.push( { series: fastHighSeries, name: 'fastHigh' }, { series: fastLowSeries, name: 'fastLow' }, { series: slowHighSeries, name: 'slowHigh' }, { series: slowLowSeries, name: 'slowLow' } ); return { fastHigh: fastHighSeries, fastLow: fastLowSeries, slowHigh: slowHighSeries, slowLow: slowLowSeries }; } addTrendZones(htsData) { if (!htsData || htsData.length < 2) return; const trendZones = []; let currentZone = null; for (let i = 1; i < htsData.length; i++) { const prev = htsData[i - 1]; const curr = htsData[i]; const prevBullish = prev.fastLow > prev.slowLow && prev.fastHigh > prev.slowHigh; const currBullish = curr.fastLow > curr.slowLow && curr.fastHigh > curr.slowHigh; const prevBearish = prev.fastLow < prev.slowLow && prev.fastHigh < prev.slowHigh; const currBearish = curr.fastLow < curr.slowLow && curr.fastHigh < curr.slowHigh; if (currBullish && !prevBullish) { currentZone = { type: 'bullish', start: curr.time }; } else if (currBearish && !prevBearish) { currentZone = { type: 'bearish', start: curr.time }; } else if (!currBullish && !currBearish && currentZone) { currentZone.end = prev.time; trendZones.push({ ...currentZone }); currentZone = null; } } if (currentZone) { currentZone.end = htsData[htsData.length - 1].time; trendZones.push(currentZone); } trendZones.forEach(zone => { const zoneSeries = this.chart.addSeries(LightweightCharts.AreaSeries, { topColor: zone.type === 'bullish' ? 'rgba(38, 166, 154, 0.02)' : 'rgba(239, 83, 80, 0.02)', bottomColor: zone.type === 'bullish' ? 'rgba(38, 166, 154, 0.02)' : 'rgba(239, 83, 80, 0.02)', lineColor: 'transparent', lastValueVisible: false, priceLineVisible: false, }); if (this.candles && this.candles.length > 0) { const maxPrice = Math.max(...this.candles.map(c => c.high)) * 2; const minPrice = Math.min(...this.candles.map(c => c.low)) * 0.5; const startTime = zone.start || (this.candles[0]?.time); const endTime = zone.end || (this.candles[this.candles.length - 1]?.time); zoneSeries.setData([ { time: startTime, value: minPrice }, { time: startTime, value: maxPrice }, { time: endTime, value: maxPrice }, { time: endTime, value: minPrice } ]); } this.overlays.push({ series: zoneSeries, name: `trendZone_${zone.type}_${zone.start}` }); }); } addCrossoverMarkers(htsData) { if (!htsData || htsData.length < 2) return; const markers = []; for (let i = 1; i < htsData.length; i++) { const prev = htsData[i - 1]; const curr = htsData[i]; if (!prev || !curr) continue; const price = curr.price; const prevFastLow = prev.fastLow; const currFastLow = curr.fastLow; const prevFastHigh = prev.fastHigh; const currFastHigh = curr.fastHigh; const prevSlowLow = prev.slowLow; const currSlowLow = curr.slowLow; const prevSlowHigh = prev.slowHigh; const currSlowHigh = curr.slowHigh; if (prevFastLow <= prevSlowLow && currFastLow > currSlowLow && price > currSlowLow) { markers.push({ time: curr.time, position: 'belowBar', color: '#26a69a', shape: 'arrowUp', text: 'BUY', size: 1.2 }); } if (prevFastHigh >= prevSlowHigh && currFastHigh < currSlowHigh && price < currSlowHigh) { markers.push({ time: curr.time, position: 'aboveBar', color: '#ef5350', shape: 'arrowDown', text: 'SELL', size: 1.2 }); } } const candleSeries = this.candleData?.series; if (candleSeries) { try { if (typeof candleSeries.setMarkers === 'function') { candleSeries.setMarkers(markers); } else if (typeof SeriesMarkersPrimitive !== 'undefined') { if (!this.markerPrimitive) { this.markerPrimitive = new SeriesMarkersPrimitive(); candleSeries.attachPrimitive(this.markerPrimitive); } this.markerPrimitive.setMarkers(markers); } } catch (e) { console.warn('[HTS] Error setting markers:', e); } } return markers; } } export function addHTSVisualization(chart, candleSeries, htsData, candles, isAutoHTS = false) { const visualizer = new HTSVisualizer(chart, candles); visualizer.candleData = { series: candleSeries }; visualizer.addHTSChannels(htsData, isAutoHTS); // Disable trend zones to avoid visual clutter // visualizer.addTrendZones(htsData); if (window.showCrossoverMarkers !== false) { setTimeout(() => { try { visualizer.addCrossoverMarkers(htsData); } catch (e) { console.warn('Crossover markers skipped (API limitation):', e.message); } }, 100); } return visualizer; }