243 lines
8.6 KiB
JavaScript
243 lines
8.6 KiB
JavaScript
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;
|
|
} |