feat: added background fill to first hurst indicator

This commit is contained in:
DiTus
2026-03-19 23:19:38 +01:00
parent a9be584c0e
commit 5efd652456
2 changed files with 149 additions and 30 deletions

View File

@ -817,6 +817,123 @@ function saveUserPresets() {
localStorage.setItem('indicator_presets', JSON.stringify(userPresets));
}
// Custom Primitive for filling area between two lines
class SeriesAreaFillPrimitive {
constructor(data, color) {
this._data = data || [];
this._color = color || 'rgba(128, 128, 128, 0.05)';
this._paneViews = [new SeriesAreaFillPaneView(this)];
}
setData(data) {
this._data = data;
this._requestUpdate?.();
}
setColor(color) {
this._color = color;
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 SeriesAreaFillPaneView {
constructor(source) {
this._source = source;
}
renderer() {
return new SeriesAreaFillRenderer(this._source);
}
}
class SeriesAreaFillRenderer {
constructor(source) {
this._source = source;
}
draw(target) {
if (!this._source._chart || !this._source._series || this._source._data.length === 0) return;
target.useBitmapCoordinateSpace((scope) => {
const ctx = scope.context;
const series = this._source._series;
const chart = this._source._chart;
const data = this._source._data;
const color = this._source._color;
const ratio = scope.horizontalPixelRatio;
ctx.save();
ctx.beginPath();
let started = false;
// Draw top line (upper) forward
for (let i = 0; i < data.length; i++) {
const point = data[i];
const timeCoordinate = chart.timeScale().timeToCoordinate(point.time);
if (timeCoordinate === null) continue;
const upperY = series.priceToCoordinate(point.upper);
if (upperY === null) continue;
const x = timeCoordinate * ratio;
const y = upperY * ratio;
if (!started) {
ctx.moveTo(x, y);
started = true;
} else {
ctx.lineTo(x, y);
}
}
// Draw bottom line (lower) backward
for (let i = data.length - 1; i >= 0; i--) {
const point = data[i];
const timeCoordinate = chart.timeScale().timeToCoordinate(point.time);
if (timeCoordinate === null) continue;
const lowerY = series.priceToCoordinate(point.lower);
if (lowerY === null) continue;
const x = timeCoordinate * ratio;
const y = lowerY * ratio;
ctx.lineTo(x, y);
}
if (started) {
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
ctx.restore();
});
}
}
function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, lineStyleMap) {
// Recalculate with current TF candles (or use cached if they exist and are the correct length)
let results = indicator.cachedResults;
@ -863,8 +980,11 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
}
let plotsCreated = 0;
let dataPointsAdded = 0;
// Special logic for Hurst fill
let hurstFillData = [];
const isFirstHurst = indicator.type === 'hurst' && activeIndicators.filter(ind => ind.type === 'hurst')[0].id === indicator.id;
meta.plots.forEach((plot, plotIdx) => {
if (isObjectResult) {
const hasData = results.some(r => r && r[plot.id] !== undefined && r[plot.id] !== null);
@ -874,20 +994,24 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
const plotColor = indicator.params[`_color_${plotIdx}`] || plot.color || '#2962ff';
const data = [];
let firstDataIndex = -1;
for (let i = 0; i < candles.length; i++) {
let value;
if (isObjectResult) {
value = results[i]?.[plot.id];
// Collect fill data if this is Hurst
if (isFirstHurst && results[i]) {
// Ensure we only add once per index
if (!hurstFillData[i]) hurstFillData[i] = { time: candles[i].time };
if (plot.id === 'upper') hurstFillData[i].upper = value;
if (plot.id === 'lower') hurstFillData[i].lower = value;
}
} else {
value = results[i];
}
if (value !== null && value !== undefined && typeof value === 'number' && Number.isFinite(value)) {
if (firstDataIndex === -1) {
firstDataIndex = i;
}
data.push({
time: candles[i].time,
value: value
@ -895,14 +1019,7 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
}
}
console.log(`[renderIndicatorOnPane] ${indicator.name} plot ${plotIdx}: ${data.length} data points created, first data at index ${firstDataIndex}/${candles.length}`);
if (data.length === 0) {
console.log(`[renderIndicatorOnPane] ${indicator.name} plot ${plotIdx}: No data to render`);
return;
}
console.log(`[renderIndicatorOnPane] ${indicator.name} plot ${plotIdx}: Creating series with ${data.length} data points [${data[0].time} to ${data[data.length - 1].time}]`);
if (data.length === 0) return;
let series;
let plotLineStyle = lineStyle;
@ -947,23 +1064,18 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
series.setData(data);
indicator.series.push(series);
plotsCreated++;
console.log(`Created series for ${indicator.id}, plot=${plot.id}, total series now=${indicator.series.length}`);
// Create horizontal band lines for RSI
// Attach RSI bands
if (meta.name === 'RSI' && indicator.series.length > 0) {
const mainSeries = indicator.series[0];
const overbought = indicator.params.overbought || 70;
const oversold = indicator.params.oversold || 30;
// Remove existing price lines first
while (indicator.bands && indicator.bands.length > 0) {
try {
indicator.bands.pop();
} catch(e) {}
try { indicator.bands.pop(); } catch(e) {}
}
indicator.bands = indicator.bands || [];
// Create overbought band line
indicator.bands.push(mainSeries.createPriceLine({
price: overbought,
color: '#787B86',
@ -972,8 +1084,6 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
axisLabelVisible: false,
title: ''
}));
// Create oversold band line
indicator.bands.push(mainSeries.createPriceLine({
price: oversold,
color: '#787B86',
@ -984,6 +1094,16 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
}));
}
});
// Attach Hurst Fill Primitive
if (isFirstHurst && hurstFillData.length > 0 && indicator.series.length > 0) {
// Filter out incomplete data points
const validFillData = hurstFillData.filter(d => d && d.time && d.upper !== undefined && d.lower !== undefined);
// Attach to the first series (usually upper or lower band)
const fillPrimitive = new SeriesAreaFillPrimitive(validFillData, 'rgba(128, 128, 128, 0.05)');
indicator.series[0].attachPrimitive(fillPrimitive);
}
}
// Completely redraw indicators (works for both overlay and pane)