feat: added background fill to first hurst indicator
This commit is contained in:
@ -314,15 +314,14 @@ constructor() {
|
||||
},
|
||||
},
|
||||
localization: {
|
||||
timeFormatter: (timestamp) => {
|
||||
return TimezoneConfig.formatDate(timestamp * 1000);
|
||||
},
|
||||
timeFormatter: (timestamp) => {
|
||||
return TimezoneConfig.formatDate(timestamp * 1000);
|
||||
},
|
||||
},
|
||||
handleScroll: {
|
||||
vertTouchDrag: false,
|
||||
},
|
||||
handleScroll: {
|
||||
vertTouchDrag: false,
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
// Setup price format selector change handler
|
||||
const priceSelect = document.getElementById("priceFormatSelect");
|
||||
if (priceSelect) {
|
||||
|
||||
@ -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)
|
||||
|
||||
Reference in New Issue
Block a user