Upgrade Lightweight Charts from v2.1.0 to v5.1.0
- Update series creation to v5 API (addSeries with type parameter) - Migrate watermarks to createTextWatermark plugin - Migrate markers to createSeriesMarkers primitive - Replace getBarSpacing() with timeScale().options().barSpacing - Update crosshair handler: seriesPrices → seriesData - Move scaleMargins from series options to price scale - Fix chart sync infinite loop with time-based debounce (50ms) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6a7a1c2b45
commit
5c37fb00b6
|
|
@ -13,6 +13,8 @@ class Charts {
|
||||||
/* A list of bound charts this is necessary for maintaining a dynamic
|
/* A list of bound charts this is necessary for maintaining a dynamic
|
||||||
number of charts with their position and zoom factors bound.*/
|
number of charts with their position and zoom factors bound.*/
|
||||||
this.bound_charts=[];
|
this.bound_charts=[];
|
||||||
|
// Debounce timestamp to prevent infinite loop in chart synchronization
|
||||||
|
this._lastSyncTime = 0;
|
||||||
// Only the main chart is created by default.
|
// Only the main chart is created by default.
|
||||||
this.create_main_chart();
|
this.create_main_chart();
|
||||||
}
|
}
|
||||||
|
|
@ -26,8 +28,8 @@ class Charts {
|
||||||
// Display the trading pair as a watermark overlaying the chart.
|
// Display the trading pair as a watermark overlaying the chart.
|
||||||
this.addWatermark(this.chart_1, this.trading_pair);
|
this.addWatermark(this.chart_1, this.trading_pair);
|
||||||
|
|
||||||
// - Create the candle stick series for our chart
|
// - Create the candle stick series for our chart (v5 API)
|
||||||
this.candleSeries = this.chart_1.addCandlestickSeries();
|
this.candleSeries = this.chart_1.addSeries(LightweightCharts.CandlestickSeries);
|
||||||
|
|
||||||
// Initialize the candlestick series if price_history is available
|
// Initialize the candlestick series if price_history is available
|
||||||
if (this.price_history && this.price_history.length > 0) {
|
if (this.price_history && this.price_history.length > 0) {
|
||||||
|
|
@ -78,9 +80,9 @@ class Charts {
|
||||||
this.addWatermark(this.chart5, 'Patterns');
|
this.addWatermark(this.chart5, 'Patterns');
|
||||||
this.bind_charts(this.chart5);
|
this.bind_charts(this.chart5);
|
||||||
|
|
||||||
// Sync time scale with main chart so timestamps align
|
// Sync time scale with main chart so timestamps align (v5 API)
|
||||||
if (this.chart_1) {
|
if (this.chart_1) {
|
||||||
let barSpacing = this.chart_1.timeScale().getBarSpacing();
|
let barSpacing = this.chart_1.timeScale().options().barSpacing;
|
||||||
let scrollPosition = this.chart_1.timeScale().scrollPosition();
|
let scrollPosition = this.chart_1.timeScale().scrollPosition();
|
||||||
this.chart5.timeScale().applyOptions({ rightOffset: scrollPosition, barSpacing: barSpacing });
|
this.chart5.timeScale().applyOptions({ rightOffset: scrollPosition, barSpacing: barSpacing });
|
||||||
}
|
}
|
||||||
|
|
@ -137,16 +139,20 @@ class Charts {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
addWatermark(chart,text){
|
addWatermark(chart, text){
|
||||||
chart.applyOptions({
|
// v5 API: watermarks are now created via createTextWatermark plugin
|
||||||
watermark: {visible: true,
|
LightweightCharts.createTextWatermark(chart.panes()[0], {
|
||||||
color: '#DBC29E',
|
horzAlign: 'center',
|
||||||
text: text,
|
vertAlign: 'center',
|
||||||
fontSize: 30,
|
lines: [
|
||||||
fontFamily: 'Roboto',
|
{
|
||||||
fontStyle: 'bold',
|
text: text,
|
||||||
vertAlign: 'center'
|
color: '#DBC29E',
|
||||||
}
|
fontSize: 30,
|
||||||
|
fontFamily: 'Roboto',
|
||||||
|
fontStyle: 'bold',
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,8 +187,11 @@ class Charts {
|
||||||
bind2charts(){
|
bind2charts(){
|
||||||
//On change in chart 1 change chart 2
|
//On change in chart 1 change chart 2
|
||||||
let syncHandler1 = (e) => {
|
let syncHandler1 = (e) => {
|
||||||
// Get the barSpacing(zoom) and position of 1st chart.
|
const now = Date.now();
|
||||||
let barSpacing1 = this.bound_charts[0].timeScale().getBarSpacing();
|
if (now - this._lastSyncTime < 50) return;
|
||||||
|
this._lastSyncTime = now;
|
||||||
|
// Get the barSpacing(zoom) and position of 1st chart (v5 API: options().barSpacing)
|
||||||
|
let barSpacing1 = this.bound_charts[0].timeScale().options().barSpacing;
|
||||||
let scrollPosition1 = this.bound_charts[0].timeScale().scrollPosition();
|
let scrollPosition1 = this.bound_charts[0].timeScale().scrollPosition();
|
||||||
// Apply barSpacing(zoom) and position to 2nd chart.
|
// Apply barSpacing(zoom) and position to 2nd chart.
|
||||||
this.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
|
this.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
|
||||||
|
|
@ -191,8 +200,11 @@ class Charts {
|
||||||
|
|
||||||
//On change in chart 2 change chart 1
|
//On change in chart 2 change chart 1
|
||||||
let syncHandler2 = (e) => {
|
let syncHandler2 = (e) => {
|
||||||
// Get the barSpacing(zoom) and position of chart 2
|
const now = Date.now();
|
||||||
let barSpacing2 = this.bound_charts[1].timeScale().getBarSpacing();
|
if (now - this._lastSyncTime < 50) return;
|
||||||
|
this._lastSyncTime = now;
|
||||||
|
// Get the barSpacing(zoom) and position of chart 2 (v5 API: options().barSpacing)
|
||||||
|
let barSpacing2 = this.bound_charts[1].timeScale().options().barSpacing;
|
||||||
let scrollPosition2 = this.bound_charts[1].timeScale().scrollPosition();
|
let scrollPosition2 = this.bound_charts[1].timeScale().scrollPosition();
|
||||||
// Apply barSpacing(zoom) and position to chart 1
|
// Apply barSpacing(zoom) and position to chart 1
|
||||||
this.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
|
this.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
|
||||||
|
|
@ -203,8 +215,11 @@ class Charts {
|
||||||
|
|
||||||
//On change to chart 1 change chart 2 and 3
|
//On change to chart 1 change chart 2 and 3
|
||||||
let syncHandler = (e) => {
|
let syncHandler = (e) => {
|
||||||
// Get the barSpacing(zoom) and position of chart 1
|
const now = Date.now();
|
||||||
let barSpacing1 = this.bound_charts[0].timeScale().getBarSpacing();
|
if (now - this._lastSyncTime < 50) return;
|
||||||
|
this._lastSyncTime = now;
|
||||||
|
// Get the barSpacing(zoom) and position of chart 1 (v5 API)
|
||||||
|
let barSpacing1 = this.bound_charts[0].timeScale().options().barSpacing;
|
||||||
let scrollPosition1 = this.bound_charts[0].timeScale().scrollPosition();
|
let scrollPosition1 = this.bound_charts[0].timeScale().scrollPosition();
|
||||||
// Apply barSpacing(zoom) and position to new chart
|
// Apply barSpacing(zoom) and position to new chart
|
||||||
this.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
|
this.bound_charts[1].timeScale().applyOptions({ rightOffset: scrollPosition1, barSpacing: barSpacing1 });
|
||||||
|
|
@ -214,8 +229,11 @@ class Charts {
|
||||||
|
|
||||||
//On change to chart 2 change chart 1 and 3
|
//On change to chart 2 change chart 1 and 3
|
||||||
let syncHandler2 = (e) => {
|
let syncHandler2 = (e) => {
|
||||||
// Get the barSpacing(zoom) and position of chart 2
|
const now = Date.now();
|
||||||
let barSpacing2 = this.bound_charts[1].timeScale().getBarSpacing();
|
if (now - this._lastSyncTime < 50) return;
|
||||||
|
this._lastSyncTime = now;
|
||||||
|
// Get the barSpacing(zoom) and position of chart 2 (v5 API)
|
||||||
|
let barSpacing2 = this.bound_charts[1].timeScale().options().barSpacing;
|
||||||
let scrollPosition2 = this.bound_charts[1].timeScale().scrollPosition();
|
let scrollPosition2 = this.bound_charts[1].timeScale().scrollPosition();
|
||||||
// Apply barSpacing(zoom) and position to chart 1 and 3
|
// Apply barSpacing(zoom) and position to chart 1 and 3
|
||||||
this.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
|
this.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
|
||||||
|
|
@ -225,8 +243,11 @@ class Charts {
|
||||||
|
|
||||||
//On change to chart 3 change chart 1 and 2
|
//On change to chart 3 change chart 1 and 2
|
||||||
let syncHandler3 = (e) => {
|
let syncHandler3 = (e) => {
|
||||||
// Get the barSpacing(zoom) and position of new chart
|
const now = Date.now();
|
||||||
let barSpacing2 = this.bound_charts[2].timeScale().getBarSpacing();
|
if (now - this._lastSyncTime < 50) return;
|
||||||
|
this._lastSyncTime = now;
|
||||||
|
// Get the barSpacing(zoom) and position of new chart (v5 API)
|
||||||
|
let barSpacing2 = this.bound_charts[2].timeScale().options().barSpacing;
|
||||||
let scrollPosition2 = this.bound_charts[2].timeScale().scrollPosition();
|
let scrollPosition2 = this.bound_charts[2].timeScale().scrollPosition();
|
||||||
// Apply barSpacing(zoom) and position to parent chart
|
// Apply barSpacing(zoom) and position to parent chart
|
||||||
this.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
|
this.bound_charts[0].timeScale().applyOptions({ rightOffset: scrollPosition2, barSpacing: barSpacing2 });
|
||||||
|
|
@ -236,10 +257,13 @@ class Charts {
|
||||||
}
|
}
|
||||||
|
|
||||||
bind4charts(){
|
bind4charts(){
|
||||||
// Sync all 4 charts together
|
// Sync all 4 charts together (v5 API: options().barSpacing)
|
||||||
let syncFromChart = (sourceIndex) => {
|
let syncFromChart = (sourceIndex) => {
|
||||||
return (e) => {
|
return (e) => {
|
||||||
let barSpacing = this.bound_charts[sourceIndex].timeScale().getBarSpacing();
|
const now = Date.now();
|
||||||
|
if (now - this._lastSyncTime < 50) return;
|
||||||
|
this._lastSyncTime = now;
|
||||||
|
let barSpacing = this.bound_charts[sourceIndex].timeScale().options().barSpacing;
|
||||||
let scrollPosition = this.bound_charts[sourceIndex].timeScale().scrollPosition();
|
let scrollPosition = this.bound_charts[sourceIndex].timeScale().scrollPosition();
|
||||||
for (let i = 0; i < 4; i++) {
|
for (let i = 0; i < 4; i++) {
|
||||||
if (i !== sourceIndex) {
|
if (i !== sourceIndex) {
|
||||||
|
|
@ -255,10 +279,13 @@ class Charts {
|
||||||
}
|
}
|
||||||
|
|
||||||
bind5charts(){
|
bind5charts(){
|
||||||
// Sync all 5 charts together (main + RSI + MACD + %B + Patterns)
|
// Sync all 5 charts together (main + RSI + MACD + %B + Patterns) (v5 API)
|
||||||
let syncFromChart = (sourceIndex) => {
|
let syncFromChart = (sourceIndex) => {
|
||||||
return (e) => {
|
return (e) => {
|
||||||
let barSpacing = this.bound_charts[sourceIndex].timeScale().getBarSpacing();
|
const now = Date.now();
|
||||||
|
if (now - this._lastSyncTime < 50) return;
|
||||||
|
this._lastSyncTime = now;
|
||||||
|
let barSpacing = this.bound_charts[sourceIndex].timeScale().options().barSpacing;
|
||||||
let scrollPosition = this.bound_charts[sourceIndex].timeScale().scrollPosition();
|
let scrollPosition = this.bound_charts[sourceIndex].timeScale().scrollPosition();
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
if (i !== sourceIndex) {
|
if (i !== sourceIndex) {
|
||||||
|
|
@ -339,14 +366,20 @@ class Charts {
|
||||||
|
|
||||||
console.log(`Setting ${markers.length} trade markers on chart`);
|
console.log(`Setting ${markers.length} trade markers on chart`);
|
||||||
|
|
||||||
// Set markers on the candlestick series
|
// v5 API: use createSeriesMarkers instead of series.setMarkers
|
||||||
this.candleSeries.setMarkers(markers);
|
// Clear existing markers primitive if it exists
|
||||||
|
if (this.markersPrimitive) {
|
||||||
|
this.markersPrimitive.setMarkers([]);
|
||||||
|
}
|
||||||
|
// Create new markers primitive
|
||||||
|
this.markersPrimitive = LightweightCharts.createSeriesMarkers(this.candleSeries, markers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear all trade markers from chart
|
// Clear all trade markers from chart
|
||||||
clearTradeMarkers() {
|
clearTradeMarkers() {
|
||||||
if (this.candleSeries) {
|
// v5 API: clear markers via the markers primitive
|
||||||
this.candleSeries.setMarkers([]);
|
if (this.markersPrimitive) {
|
||||||
|
this.markersPrimitive.setMarkers([]);
|
||||||
console.log('Trade markers cleared');
|
console.log('Trade markers cleared');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,12 @@ class Indicator_Output {
|
||||||
this.legend[name].style.left = 3 + 'px';
|
this.legend[name].style.left = 3 + 'px';
|
||||||
this.legend[name].style.top = 3 + 'px';
|
this.legend[name].style.top = 3 + 'px';
|
||||||
// subscribe set legend text to crosshair moves
|
// subscribe set legend text to crosshair moves
|
||||||
|
// v5 API: seriesPrices renamed to seriesData, returns full data item
|
||||||
chart.subscribeCrosshairMove((param) => {
|
chart.subscribeCrosshairMove((param) => {
|
||||||
this.set_legend_text(param.seriesPrices.get(lineSeries), name);
|
const data = param.seriesData.get(lineSeries);
|
||||||
|
// Extract value from data item (could be {value} or {close} etc)
|
||||||
|
const priceValue = data ? (data.value !== undefined ? data.value : data.close) : undefined;
|
||||||
|
this.set_legend_text(priceValue, name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,21 +92,27 @@ class Indicator {
|
||||||
}
|
}
|
||||||
|
|
||||||
addHist(name, chart, color = '#26a69a') {
|
addHist(name, chart, color = '#26a69a') {
|
||||||
this.hist[name] = chart.addHistogramSeries({
|
// v5 API: use addSeries with HistogramSeries
|
||||||
|
this.hist[name] = chart.addSeries(LightweightCharts.HistogramSeries, {
|
||||||
color: color,
|
color: color,
|
||||||
priceFormat: {
|
priceFormat: {
|
||||||
type: 'price',
|
type: 'price',
|
||||||
},
|
},
|
||||||
priceScaleId: 'volume_ps',
|
priceScaleId: 'volume_ps',
|
||||||
|
});
|
||||||
|
// v5: scaleMargins must be set on the price scale, not series options
|
||||||
|
// Volume should only take up bottom 30% of the chart
|
||||||
|
chart.priceScale('volume_ps').applyOptions({
|
||||||
scaleMargins: {
|
scaleMargins: {
|
||||||
top: 0,
|
top: 0.7,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addLine(name, chart, color, lineWidth) {
|
addLine(name, chart, color, lineWidth) {
|
||||||
this.lines[name] = chart.addLineSeries({
|
// v5 API: use addSeries with LineSeries
|
||||||
|
this.lines[name] = chart.addSeries(LightweightCharts.LineSeries, {
|
||||||
color: color,
|
color: color,
|
||||||
lineWidth: lineWidth
|
lineWidth: lineWidth
|
||||||
});
|
});
|
||||||
|
|
@ -599,8 +609,8 @@ class CandlestickPattern extends Indicator {
|
||||||
}
|
}
|
||||||
|
|
||||||
addPatternHist(name, chart) {
|
addPatternHist(name, chart) {
|
||||||
// Create histogram series for pattern signals
|
// v5 API: use addSeries with HistogramSeries
|
||||||
this.hist[name] = chart.addHistogramSeries({
|
this.hist[name] = chart.addSeries(LightweightCharts.HistogramSeries, {
|
||||||
priceFormat: {
|
priceFormat: {
|
||||||
type: 'price',
|
type: 'price',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue