feat: added background fill to first hurst indicator
This commit is contained in:
@ -314,15 +314,14 @@ constructor() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
localization: {
|
localization: {
|
||||||
timeFormatter: (timestamp) => {
|
timeFormatter: (timestamp) => {
|
||||||
return TimezoneConfig.formatDate(timestamp * 1000);
|
return TimezoneConfig.formatDate(timestamp * 1000);
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
handleScroll: {
|
||||||
|
vertTouchDrag: false,
|
||||||
},
|
},
|
||||||
handleScroll: {
|
});
|
||||||
vertTouchDrag: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup price format selector change handler
|
// Setup price format selector change handler
|
||||||
const priceSelect = document.getElementById("priceFormatSelect");
|
const priceSelect = document.getElementById("priceFormatSelect");
|
||||||
if (priceSelect) {
|
if (priceSelect) {
|
||||||
|
|||||||
@ -817,6 +817,123 @@ function saveUserPresets() {
|
|||||||
localStorage.setItem('indicator_presets', JSON.stringify(userPresets));
|
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) {
|
function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, lineStyleMap) {
|
||||||
// Recalculate with current TF candles (or use cached if they exist and are the correct length)
|
// Recalculate with current TF candles (or use cached if they exist and are the correct length)
|
||||||
let results = indicator.cachedResults;
|
let results = indicator.cachedResults;
|
||||||
@ -863,8 +980,11 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
|
|||||||
}
|
}
|
||||||
|
|
||||||
let plotsCreated = 0;
|
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) => {
|
meta.plots.forEach((plot, plotIdx) => {
|
||||||
if (isObjectResult) {
|
if (isObjectResult) {
|
||||||
const hasData = results.some(r => r && r[plot.id] !== undefined && r[plot.id] !== null);
|
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 plotColor = indicator.params[`_color_${plotIdx}`] || plot.color || '#2962ff';
|
||||||
|
|
||||||
const data = [];
|
const data = [];
|
||||||
let firstDataIndex = -1;
|
|
||||||
|
|
||||||
for (let i = 0; i < candles.length; i++) {
|
for (let i = 0; i < candles.length; i++) {
|
||||||
let value;
|
let value;
|
||||||
if (isObjectResult) {
|
if (isObjectResult) {
|
||||||
value = results[i]?.[plot.id];
|
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 {
|
} else {
|
||||||
value = results[i];
|
value = results[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value !== null && value !== undefined && typeof value === 'number' && Number.isFinite(value)) {
|
if (value !== null && value !== undefined && typeof value === 'number' && Number.isFinite(value)) {
|
||||||
if (firstDataIndex === -1) {
|
|
||||||
firstDataIndex = i;
|
|
||||||
}
|
|
||||||
data.push({
|
data.push({
|
||||||
time: candles[i].time,
|
time: candles[i].time,
|
||||||
value: value
|
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) return;
|
||||||
|
|
||||||
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}]`);
|
|
||||||
|
|
||||||
let series;
|
let series;
|
||||||
let plotLineStyle = lineStyle;
|
let plotLineStyle = lineStyle;
|
||||||
@ -947,23 +1064,18 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
|
|||||||
series.setData(data);
|
series.setData(data);
|
||||||
indicator.series.push(series);
|
indicator.series.push(series);
|
||||||
plotsCreated++;
|
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) {
|
if (meta.name === 'RSI' && indicator.series.length > 0) {
|
||||||
const mainSeries = indicator.series[0];
|
const mainSeries = indicator.series[0];
|
||||||
const overbought = indicator.params.overbought || 70;
|
const overbought = indicator.params.overbought || 70;
|
||||||
const oversold = indicator.params.oversold || 30;
|
const oversold = indicator.params.oversold || 30;
|
||||||
|
|
||||||
// Remove existing price lines first
|
|
||||||
while (indicator.bands && indicator.bands.length > 0) {
|
while (indicator.bands && indicator.bands.length > 0) {
|
||||||
try {
|
try { indicator.bands.pop(); } catch(e) {}
|
||||||
indicator.bands.pop();
|
|
||||||
} catch(e) {}
|
|
||||||
}
|
}
|
||||||
indicator.bands = indicator.bands || [];
|
indicator.bands = indicator.bands || [];
|
||||||
|
|
||||||
// Create overbought band line
|
|
||||||
indicator.bands.push(mainSeries.createPriceLine({
|
indicator.bands.push(mainSeries.createPriceLine({
|
||||||
price: overbought,
|
price: overbought,
|
||||||
color: '#787B86',
|
color: '#787B86',
|
||||||
@ -972,8 +1084,6 @@ function renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, li
|
|||||||
axisLabelVisible: false,
|
axisLabelVisible: false,
|
||||||
title: ''
|
title: ''
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Create oversold band line
|
|
||||||
indicator.bands.push(mainSeries.createPriceLine({
|
indicator.bands.push(mainSeries.createPriceLine({
|
||||||
price: oversold,
|
price: oversold,
|
||||||
color: '#787B86',
|
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)
|
// Completely redraw indicators (works for both overlay and pane)
|
||||||
|
|||||||
Reference in New Issue
Block a user