better fib left start
This commit is contained in:
parent
5f39144dc8
commit
1c3a852a03
2 changed files with 135 additions and 29 deletions
|
|
@ -1216,3 +1216,6 @@ shares numbering, hide, delete and colour with other drawings, but it is
|
||||||
not a confluence level. The chart draws native price lines at 0, 23.6,
|
not a confluence level. The chart draws native price lines at 0, 23.6,
|
||||||
38.2, 50, 61.8, 78.6 and 100 between the two prices. 0% is the first
|
38.2, 50, 61.8, 78.6 and 100 between the two prices. 0% is the first
|
||||||
click.
|
click.
|
||||||
|
|
||||||
|
Levels no longer span the full chart: they start at the first click and run
|
||||||
|
to the right, with a vertical rail at the origin so the swing is obvious.
|
||||||
|
|
|
||||||
161
static/chart.js
161
static/chart.js
|
|
@ -72,6 +72,120 @@ class RthLinesPrimitive {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FibLevelsPrimitive {
|
||||||
|
constructor(locateTime) {
|
||||||
|
this._locate = locateTime;
|
||||||
|
this._fibs = [];
|
||||||
|
this._series = null;
|
||||||
|
this._requestUpdate = null;
|
||||||
|
this._items = [];
|
||||||
|
this._view = {
|
||||||
|
update: () => this._sync(),
|
||||||
|
renderer: () => this._renderer,
|
||||||
|
};
|
||||||
|
this._renderer = { draw: target => this._draw(target) };
|
||||||
|
}
|
||||||
|
|
||||||
|
attached(param) {
|
||||||
|
this._series = param.series;
|
||||||
|
this._requestUpdate = param.requestUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
detached() {
|
||||||
|
this._series = null;
|
||||||
|
this._requestUpdate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFibs(fibs) {
|
||||||
|
this._fibs = fibs || [];
|
||||||
|
if (this._requestUpdate) this._requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAllViews() {
|
||||||
|
this._view.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
paneViews() {
|
||||||
|
return [this._view];
|
||||||
|
}
|
||||||
|
|
||||||
|
_sync() {
|
||||||
|
const items = [];
|
||||||
|
if (!this._series) {
|
||||||
|
this._items = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const fib of this._fibs) {
|
||||||
|
if (fib.hidden) continue;
|
||||||
|
const x0 = this._locate(fib.anchor_t);
|
||||||
|
const y0 = this._series.priceToCoordinate(Number(fib.anchor_p));
|
||||||
|
const endP = Number(fib.anchor_p) + Number(fib.slope || 0)
|
||||||
|
* (Number(fib.last_t) - Number(fib.anchor_t));
|
||||||
|
const y1 = this._series.priceToCoordinate(endP);
|
||||||
|
const width = Math.max(1, Number(fib.line_width) || 2);
|
||||||
|
const color = fib.color || '#c8992f';
|
||||||
|
if (x0 != null && y0 != null && y1 != null) {
|
||||||
|
items.push({
|
||||||
|
kind: 'rail', x: x0, y0, y1, color, width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const level of ConfluenceChart.fibPrices(fib)) {
|
||||||
|
const y = this._series.priceToCoordinate(level.price);
|
||||||
|
if (y == null) continue;
|
||||||
|
const major = level.r === 0 || level.r === 0.5 || level.r === 0.618 || level.r === 1;
|
||||||
|
items.push({
|
||||||
|
kind: 'level',
|
||||||
|
x0,
|
||||||
|
y,
|
||||||
|
color,
|
||||||
|
width,
|
||||||
|
dashed: !major,
|
||||||
|
label: `${level.label} ${level.price.toFixed(2)}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
_draw(target) {
|
||||||
|
if (!this._items.length) return;
|
||||||
|
target.useMediaCoordinateSpace(scope => {
|
||||||
|
const ctx = scope.context;
|
||||||
|
const right = scope.mediaSize.width;
|
||||||
|
ctx.save();
|
||||||
|
ctx.font = '10px "IBM Plex Mono", ui-monospace, monospace';
|
||||||
|
for (const item of this._items) {
|
||||||
|
if (item.kind === 'rail') {
|
||||||
|
ctx.strokeStyle = item.color;
|
||||||
|
ctx.lineWidth = item.width;
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
ctx.globalAlpha = 0.85;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(item.x + 0.5, item.y0);
|
||||||
|
ctx.lineTo(item.x + 0.5, item.y1);
|
||||||
|
ctx.stroke();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const start = item.x0 == null ? 0 : Math.max(0, item.x0);
|
||||||
|
if (start > right) continue;
|
||||||
|
ctx.strokeStyle = item.color;
|
||||||
|
ctx.fillStyle = item.color;
|
||||||
|
ctx.lineWidth = item.width;
|
||||||
|
ctx.setLineDash(item.dashed ? [5, 4] : []);
|
||||||
|
ctx.globalAlpha = 0.9;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(start, item.y + 0.5);
|
||||||
|
ctx.lineTo(right, item.y + 0.5);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
ctx.fillText(item.label, right - ctx.measureText(item.label).width - 6, item.y - 4);
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ConfluenceChart {
|
class ConfluenceChart {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.chart = null;
|
this.chart = null;
|
||||||
|
|
@ -135,7 +249,7 @@ class ConfluenceChart {
|
||||||
this.rthPrimitive = null;
|
this.rthPrimitive = null;
|
||||||
this.rthEnabled = true;
|
this.rthEnabled = true;
|
||||||
this.fibs = [];
|
this.fibs = [];
|
||||||
this.fibLines = new Map();
|
this.fibPrimitive = null;
|
||||||
this.fibPreview = null;
|
this.fibPreview = null;
|
||||||
this.futureSpace = null;
|
this.futureSpace = null;
|
||||||
this.snapDot = null;
|
this.snapDot = null;
|
||||||
|
|
@ -327,6 +441,8 @@ class ConfluenceChart {
|
||||||
// something to label and so timeToCoordinate answers out there.
|
// something to label and so timeToCoordinate answers out there.
|
||||||
this.rthPrimitive = new RthLinesPrimitive();
|
this.rthPrimitive = new RthLinesPrimitive();
|
||||||
this.candles.attachPrimitive(this.rthPrimitive);
|
this.candles.attachPrimitive(this.rthPrimitive);
|
||||||
|
this.fibPrimitive = new FibLevelsPrimitive(time => this.coordinateAtTime(time));
|
||||||
|
this.candles.attachPrimitive(this.fibPrimitive);
|
||||||
this.futureSpace = this.chart.addSeries(LightweightCharts.LineSeries, {
|
this.futureSpace = this.chart.addSeries(LightweightCharts.LineSeries, {
|
||||||
priceScaleId: 'right',
|
priceScaleId: 'right',
|
||||||
color: 'rgba(0,0,0,0)',
|
color: 'rgba(0,0,0,0)',
|
||||||
|
|
@ -706,33 +822,7 @@ class ConfluenceChart {
|
||||||
}
|
}
|
||||||
|
|
||||||
syncFibLines() {
|
syncFibLines() {
|
||||||
if (!this.candles) return;
|
if (this.fibPrimitive) this.fibPrimitive.setFibs(this.fibs.filter(fib => !fib.hidden));
|
||||||
const wanted = new Set();
|
|
||||||
for (const fib of this.fibs) {
|
|
||||||
if (fib.hidden) continue;
|
|
||||||
for (const level of ConfluenceChart.fibPrices(fib)) {
|
|
||||||
const id = `${fib.id}:${level.r}`;
|
|
||||||
wanted.add(id);
|
|
||||||
const width = Math.max(1, Number(fib.line_width) || 2);
|
|
||||||
const major = level.r === 0 || level.r === 0.5 || level.r === 0.618 || level.r === 1;
|
|
||||||
const options = {
|
|
||||||
price: level.price,
|
|
||||||
color: fib.color || '#c8992f',
|
|
||||||
lineWidth: width,
|
|
||||||
lineStyle: major ? LightweightCharts.LineStyle.Solid : LightweightCharts.LineStyle.Dashed,
|
|
||||||
axisLabelVisible: true,
|
|
||||||
title: `${level.label} ${level.price.toFixed(2)}`,
|
|
||||||
};
|
|
||||||
const existing = this.fibLines.get(id);
|
|
||||||
if (existing) existing.applyOptions(options);
|
|
||||||
else this.fibLines.set(id, this.candles.createPriceLine(options));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const [id, line] of this.fibLines) {
|
|
||||||
if (wanted.has(id)) continue;
|
|
||||||
this.candles.removePriceLine(line);
|
|
||||||
this.fibLines.delete(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setComments(comments) {
|
setComments(comments) {
|
||||||
|
|
@ -1946,11 +2036,24 @@ class ConfluenceChart {
|
||||||
this.fibPreview.removeAttribute('hidden');
|
this.fibPreview.removeAttribute('hidden');
|
||||||
this.fibPreview.replaceChildren();
|
this.fibPreview.replaceChildren();
|
||||||
const width = this.overlayLayer?.clientWidth || this.chartEl.clientWidth;
|
const width = this.overlayLayer?.clientWidth || this.chartEl.clientWidth;
|
||||||
|
const originX = start.t == null ? 0 : (this.coordinateAtTime(start.t) ?? 0);
|
||||||
|
const yStart = this.candles.priceToCoordinate(start.p);
|
||||||
|
const yEnd = this.candles.priceToCoordinate(end.p);
|
||||||
|
if (yStart != null && yEnd != null) {
|
||||||
|
const rail = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
||||||
|
rail.setAttribute('x1', originX);
|
||||||
|
rail.setAttribute('x2', originX);
|
||||||
|
rail.setAttribute('y1', yStart);
|
||||||
|
rail.setAttribute('y2', yEnd);
|
||||||
|
rail.setAttribute('stroke', '#c8992f');
|
||||||
|
rail.setAttribute('stroke-width', 2);
|
||||||
|
this.fibPreview.appendChild(rail);
|
||||||
|
}
|
||||||
for (const item of ConfluenceChart.FIB_RATIOS) {
|
for (const item of ConfluenceChart.FIB_RATIOS) {
|
||||||
const y = this.candles.priceToCoordinate(start.p + (end.p - start.p) * item.r);
|
const y = this.candles.priceToCoordinate(start.p + (end.p - start.p) * item.r);
|
||||||
if (y == null) continue;
|
if (y == null) continue;
|
||||||
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
||||||
line.setAttribute('x1', 0);
|
line.setAttribute('x1', originX);
|
||||||
line.setAttribute('x2', width);
|
line.setAttribute('x2', width);
|
||||||
line.setAttribute('y1', y);
|
line.setAttribute('y1', y);
|
||||||
line.setAttribute('y2', y);
|
line.setAttribute('y2', y);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue