hyperprop-charting-library 0.1.119 → 0.1.120
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hyperprop-charting-library.cjs +200 -7
- package/dist/hyperprop-charting-library.js +200 -7
- package/dist/index.cjs +200 -7
- package/dist/index.js +200 -7
- package/package.json +1 -1
|
@@ -435,6 +435,60 @@ var computeStdDevSeries = (data, length, source) => {
|
|
|
435
435
|
}
|
|
436
436
|
return result;
|
|
437
437
|
};
|
|
438
|
+
var VWAP_SESSION_ANCHOR_MS = 22 * 36e5;
|
|
439
|
+
var vwapSessionDayOf = (ms) => Math.floor((ms - VWAP_SESSION_ANCHOR_MS) / 864e5);
|
|
440
|
+
var computeVwapSeries = (data, band) => {
|
|
441
|
+
const result = new Array(data.length).fill(null);
|
|
442
|
+
let session = Number.NaN;
|
|
443
|
+
let cumPv = 0;
|
|
444
|
+
let cumPv2 = 0;
|
|
445
|
+
let cumV = 0;
|
|
446
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
447
|
+
const point = data[i];
|
|
448
|
+
if (!point) continue;
|
|
449
|
+
const day = vwapSessionDayOf(point.time.getTime());
|
|
450
|
+
if (day !== session) {
|
|
451
|
+
session = day;
|
|
452
|
+
cumPv = 0;
|
|
453
|
+
cumPv2 = 0;
|
|
454
|
+
cumV = 0;
|
|
455
|
+
}
|
|
456
|
+
const typical = (point.h + point.l + point.c) / 3;
|
|
457
|
+
const volume = Math.max(0, point.v ?? 0);
|
|
458
|
+
cumPv += typical * volume;
|
|
459
|
+
cumPv2 += typical * typical * volume;
|
|
460
|
+
cumV += volume;
|
|
461
|
+
if (cumV <= 0) continue;
|
|
462
|
+
const vwap = cumPv / cumV;
|
|
463
|
+
if (band === "vwap") {
|
|
464
|
+
result[i] = vwap;
|
|
465
|
+
} else {
|
|
466
|
+
const variance = Math.max(0, cumPv2 / cumV - vwap * vwap);
|
|
467
|
+
result[i] = Math.sqrt(variance);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
for (let i = 1; i < data.length; i += 1) {
|
|
471
|
+
const point = data[i];
|
|
472
|
+
const prev = data[i - 1];
|
|
473
|
+
const next = data[i + 1];
|
|
474
|
+
if (!point || !prev || !next) continue;
|
|
475
|
+
const day = vwapSessionDayOf(point.time.getTime());
|
|
476
|
+
if (day !== vwapSessionDayOf(prev.time.getTime()) && day === vwapSessionDayOf(next.time.getTime())) {
|
|
477
|
+
result[i] = null;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return result;
|
|
481
|
+
};
|
|
482
|
+
var computeBollingerSeries = (data, length, multiplier, source, band) => {
|
|
483
|
+
const basis = computeSmaSeries(data, length, source);
|
|
484
|
+
if (band === "basis") return basis;
|
|
485
|
+
const dev = computeStdDevSeries(data, length, source);
|
|
486
|
+
return basis.map((value, idx) => {
|
|
487
|
+
const sigma = dev[idx];
|
|
488
|
+
if (value == null || sigma == null) return null;
|
|
489
|
+
return band === "upper" ? value + multiplier * sigma : value - multiplier * sigma;
|
|
490
|
+
});
|
|
491
|
+
};
|
|
438
492
|
var computeRsiSeries = (data, length) => {
|
|
439
493
|
const result = new Array(data.length).fill(null);
|
|
440
494
|
if (data.length < 2) return result;
|
|
@@ -561,6 +615,39 @@ var drawOverlaySeries = (ctx, renderContext, values, color, width) => {
|
|
|
561
615
|
}
|
|
562
616
|
ctx.restore();
|
|
563
617
|
};
|
|
618
|
+
var fillBetweenSeries = (ctx, renderContext, upper, lower, color, opacity) => {
|
|
619
|
+
if (!renderContext.yFromPrice || opacity <= 0) return;
|
|
620
|
+
const yFromPrice = renderContext.yFromPrice;
|
|
621
|
+
ctx.save();
|
|
622
|
+
ctx.globalAlpha = Math.min(1, Math.max(0, opacity));
|
|
623
|
+
ctx.fillStyle = color;
|
|
624
|
+
let runStart = -1;
|
|
625
|
+
const flushRun = (endIndexExclusive) => {
|
|
626
|
+
if (runStart < 0) return;
|
|
627
|
+
ctx.beginPath();
|
|
628
|
+
for (let i = runStart; i < endIndexExclusive; i += 1) {
|
|
629
|
+
const x = renderContext.xFromIndex(i);
|
|
630
|
+
const y = yFromPrice(upper[i]);
|
|
631
|
+
if (i === runStart) ctx.moveTo(x, y);
|
|
632
|
+
else ctx.lineTo(x, y);
|
|
633
|
+
}
|
|
634
|
+
for (let i = endIndexExclusive - 1; i >= runStart; i -= 1) {
|
|
635
|
+
ctx.lineTo(renderContext.xFromIndex(i), yFromPrice(lower[i]));
|
|
636
|
+
}
|
|
637
|
+
ctx.closePath();
|
|
638
|
+
ctx.fill();
|
|
639
|
+
runStart = -1;
|
|
640
|
+
};
|
|
641
|
+
for (let index = renderContext.startIndex; index <= renderContext.endIndex; index += 1) {
|
|
642
|
+
const up = upper[index];
|
|
643
|
+
const lo = lower[index];
|
|
644
|
+
const valid = Number.isFinite(up ?? Number.NaN) && Number.isFinite(lo ?? Number.NaN);
|
|
645
|
+
if (valid && runStart < 0) runStart = index;
|
|
646
|
+
if (!valid) flushRun(index);
|
|
647
|
+
}
|
|
648
|
+
flushRun(renderContext.endIndex + 1);
|
|
649
|
+
ctx.restore();
|
|
650
|
+
};
|
|
564
651
|
var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride, maxOverride, guideLines, options = {}) => {
|
|
565
652
|
const visible = [];
|
|
566
653
|
for (let index = renderContext.startIndex; index <= renderContext.endIndex; index += 1) {
|
|
@@ -822,6 +909,89 @@ var BUILTIN_HMA_INDICATOR = {
|
|
|
822
909
|
drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#14b8a6", Number(inputs.width) || 2);
|
|
823
910
|
}
|
|
824
911
|
};
|
|
912
|
+
var BUILTIN_VWAP_INDICATOR = {
|
|
913
|
+
id: "vwap",
|
|
914
|
+
name: "VWAP",
|
|
915
|
+
pane: "overlay",
|
|
916
|
+
defaultInputs: {
|
|
917
|
+
color: "#f59e0b",
|
|
918
|
+
width: 2,
|
|
919
|
+
showBands: false,
|
|
920
|
+
bandMultiplier: 1,
|
|
921
|
+
bandColor: "#94a3b8",
|
|
922
|
+
bandFillOpacity: 0.06
|
|
923
|
+
},
|
|
924
|
+
draw: (ctx, renderContext, inputs) => {
|
|
925
|
+
const vwap = withCachedSeries(
|
|
926
|
+
"vwap|line",
|
|
927
|
+
renderContext.data,
|
|
928
|
+
() => computeVwapSeries(renderContext.data, "vwap")
|
|
929
|
+
);
|
|
930
|
+
if (inputs.showBands) {
|
|
931
|
+
const sigma = withCachedSeries(
|
|
932
|
+
"vwap|sigma",
|
|
933
|
+
renderContext.data,
|
|
934
|
+
() => computeVwapSeries(renderContext.data, "sigma")
|
|
935
|
+
);
|
|
936
|
+
const multiplier = Math.max(0.1, Number(inputs.bandMultiplier) || 1);
|
|
937
|
+
const upper = vwap.map((value, idx) => {
|
|
938
|
+
const s = sigma[idx];
|
|
939
|
+
return value == null || s == null ? null : value + multiplier * s;
|
|
940
|
+
});
|
|
941
|
+
const lower = vwap.map((value, idx) => {
|
|
942
|
+
const s = sigma[idx];
|
|
943
|
+
return value == null || s == null ? null : value - multiplier * s;
|
|
944
|
+
});
|
|
945
|
+
const bandColor = inputs.bandColor ?? "#94a3b8";
|
|
946
|
+
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.bandFillOpacity) || 0.06);
|
|
947
|
+
drawOverlaySeries(ctx, renderContext, upper, bandColor, 1);
|
|
948
|
+
drawOverlaySeries(ctx, renderContext, lower, bandColor, 1);
|
|
949
|
+
}
|
|
950
|
+
drawOverlaySeries(ctx, renderContext, vwap, inputs.color ?? "#f59e0b", Number(inputs.width) || 2);
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
var BUILTIN_BOLLINGER_INDICATOR = {
|
|
954
|
+
id: "bollinger",
|
|
955
|
+
name: "BB",
|
|
956
|
+
pane: "overlay",
|
|
957
|
+
// Basis is pink so it stays distinguishable from the (orange) VWAP line
|
|
958
|
+
// when both overlays are active.
|
|
959
|
+
defaultInputs: {
|
|
960
|
+
length: 20,
|
|
961
|
+
multiplier: 2,
|
|
962
|
+
source: "close",
|
|
963
|
+
basisColor: "#ec4899",
|
|
964
|
+
bandColor: "#3b82f6",
|
|
965
|
+
width: 1.5,
|
|
966
|
+
fillOpacity: 0.05
|
|
967
|
+
},
|
|
968
|
+
draw: (ctx, renderContext, inputs) => {
|
|
969
|
+
const length = clampIndicatorLength(inputs.length, 20);
|
|
970
|
+
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 2);
|
|
971
|
+
const source = inputs.source ?? "close";
|
|
972
|
+
const basis = withCachedSeries(
|
|
973
|
+
`bb|basis|${length}|${source}`,
|
|
974
|
+
renderContext.data,
|
|
975
|
+
() => computeBollingerSeries(renderContext.data, length, multiplier, source, "basis")
|
|
976
|
+
);
|
|
977
|
+
const upper = withCachedSeries(
|
|
978
|
+
`bb|upper|${length}|${multiplier}|${source}`,
|
|
979
|
+
renderContext.data,
|
|
980
|
+
() => computeBollingerSeries(renderContext.data, length, multiplier, source, "upper")
|
|
981
|
+
);
|
|
982
|
+
const lower = withCachedSeries(
|
|
983
|
+
`bb|lower|${length}|${multiplier}|${source}`,
|
|
984
|
+
renderContext.data,
|
|
985
|
+
() => computeBollingerSeries(renderContext.data, length, multiplier, source, "lower")
|
|
986
|
+
);
|
|
987
|
+
const bandColor = inputs.bandColor ?? "#3b82f6";
|
|
988
|
+
const width = Number(inputs.width) || 1.5;
|
|
989
|
+
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.fillOpacity) || 0.05);
|
|
990
|
+
drawOverlaySeries(ctx, renderContext, upper, bandColor, width);
|
|
991
|
+
drawOverlaySeries(ctx, renderContext, lower, bandColor, width);
|
|
992
|
+
drawOverlaySeries(ctx, renderContext, basis, inputs.basisColor ?? "#f59e0b", width);
|
|
993
|
+
}
|
|
994
|
+
};
|
|
825
995
|
var BUILTIN_STDDEV_INDICATOR = {
|
|
826
996
|
id: "stddev",
|
|
827
997
|
name: "StdDev",
|
|
@@ -905,6 +1075,8 @@ var BUILTIN_INDICATORS = [
|
|
|
905
1075
|
BUILTIN_VWMA_INDICATOR,
|
|
906
1076
|
BUILTIN_RMA_INDICATOR,
|
|
907
1077
|
BUILTIN_HMA_INDICATOR,
|
|
1078
|
+
BUILTIN_VWAP_INDICATOR,
|
|
1079
|
+
BUILTIN_BOLLINGER_INDICATOR,
|
|
908
1080
|
BUILTIN_STDDEV_INDICATOR,
|
|
909
1081
|
BUILTIN_ATR_INDICATOR
|
|
910
1082
|
];
|
|
@@ -2757,15 +2929,31 @@ function createChart(element, options = {}) {
|
|
|
2757
2929
|
const pct = diff / base * 100;
|
|
2758
2930
|
const ticks = tick > 0 ? Math.round(diff / tick) : 0;
|
|
2759
2931
|
const signed = (value, text) => `${value < 0 ? "\u2212" : value > 0 ? "+" : ""}${text}`;
|
|
2760
|
-
const
|
|
2932
|
+
const labelLines = [
|
|
2933
|
+
tick > 0 ? `${signed(diff, formatPrice(Math.abs(diff)))} (${signed(pct, `${Math.abs(pct).toFixed(2)}%`)}) ${signed(ticks, String(Math.abs(ticks)))}` : `${signed(diff, formatPrice(Math.abs(diff)))} (${signed(pct, `${Math.abs(pct).toFixed(2)}%`)})`
|
|
2934
|
+
];
|
|
2935
|
+
const barSpan = Math.abs(Math.round(p1.index) - Math.round(p0.index));
|
|
2936
|
+
if (barSpan > 0) {
|
|
2937
|
+
const t0 = getTimeForIndex(Math.round(p0.index));
|
|
2938
|
+
const t1 = getTimeForIndex(Math.round(p1.index));
|
|
2939
|
+
const spanMs = t0 && t1 ? Math.abs(t1.getTime() - t0.getTime()) : 0;
|
|
2940
|
+
labelLines.push(spanMs > 0 ? `${barSpan} bars, ${formatDuration(spanMs)}` : `${barSpan} bars`);
|
|
2941
|
+
}
|
|
2942
|
+
const pointValue = Number(drawing.pointValue);
|
|
2943
|
+
if (Number.isFinite(pointValue) && pointValue > 0) {
|
|
2944
|
+
const dollars = diff * pointValue;
|
|
2945
|
+
const money = Math.abs(dollars).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
2946
|
+
labelLines.push(`${signed(dollars, `$${money}`)} / contract`);
|
|
2947
|
+
}
|
|
2761
2948
|
const prevFont = ctx.font;
|
|
2762
2949
|
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2763
2950
|
const padding = 6;
|
|
2764
|
-
const
|
|
2951
|
+
const lineHeight = 15;
|
|
2952
|
+
const textW = Math.max(...labelLines.map((line) => ctx.measureText(line).width));
|
|
2765
2953
|
const pillW = textW + padding * 2;
|
|
2766
|
-
const pillH =
|
|
2954
|
+
const pillH = labelLines.length * lineHeight + padding;
|
|
2767
2955
|
const pillX = midX - pillW / 2;
|
|
2768
|
-
const pillY = botY + 6;
|
|
2956
|
+
const pillY = botY + 6 + pillH <= fullChartBottom - 2 ? botY + 6 : Math.max(chartTop + 2, topY - 6 - pillH);
|
|
2769
2957
|
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2770
2958
|
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2771
2959
|
ctx.save();
|
|
@@ -2776,7 +2964,9 @@ function createChart(element, options = {}) {
|
|
|
2776
2964
|
ctx.fillStyle = drawing.color;
|
|
2777
2965
|
ctx.textAlign = "center";
|
|
2778
2966
|
ctx.textBaseline = "middle";
|
|
2779
|
-
|
|
2967
|
+
labelLines.forEach((line, lineIndex) => {
|
|
2968
|
+
ctx.fillText(line, midX, pillY + padding / 2 + lineIndex * lineHeight + lineHeight / 2);
|
|
2969
|
+
});
|
|
2780
2970
|
ctx.font = prevFont;
|
|
2781
2971
|
if (drawing.label) {
|
|
2782
2972
|
drawDrawingLabel(drawing.label, midX, topY - 4, drawing.color);
|
|
@@ -3552,8 +3742,9 @@ function createChart(element, options = {}) {
|
|
|
3552
3742
|
}
|
|
3553
3743
|
return typeof value === "string" && !/^#(?:[0-9a-fA-F]{3}){1,2}$/.test(value.trim());
|
|
3554
3744
|
};
|
|
3745
|
+
const LEGEND_EXCLUDED_INPUT_KEYS = /* @__PURE__ */ new Set(["width", "bandMultiplier", "bandFillOpacity", "fillOpacity"]);
|
|
3555
3746
|
const labelEntries = activeOverlayIndicators.map(({ indicator, plugin }) => {
|
|
3556
|
-
const inputValues = Object.entries(indicator.inputs).filter(([, value]) => isLegendInputValue(value)).slice(0, 2).map(([, value]) => String(value));
|
|
3747
|
+
const inputValues = Object.entries(indicator.inputs).filter(([key, value]) => !LEGEND_EXCLUDED_INPUT_KEYS.has(key) && typeof value !== "boolean" && isLegendInputValue(value)).slice(0, 2).map(([, value]) => String(value));
|
|
3557
3748
|
if (labels.showIndicatorNames && labels.showIndicatorValues && inputValues.length > 0) {
|
|
3558
3749
|
return `${plugin.name} ${inputValues.join(" ")}`;
|
|
3559
3750
|
}
|
|
@@ -4531,7 +4722,9 @@ function createChart(element, options = {}) {
|
|
|
4531
4722
|
points: [point, normalizeDrawingPoint(point.index + width2, point.price - priceOffset)],
|
|
4532
4723
|
color: defaults.color ?? "#2962ff",
|
|
4533
4724
|
style: defaults.style ?? "solid",
|
|
4534
|
-
width: defaults.width ?? 1
|
|
4725
|
+
width: defaults.width ?? 1,
|
|
4726
|
+
// pointValue enables the dollar-P&L line in the measure label.
|
|
4727
|
+
...defaults.pointValue === void 0 ? {} : { pointValue: defaults.pointValue }
|
|
4535
4728
|
})
|
|
4536
4729
|
);
|
|
4537
4730
|
emitDrawingsChange();
|
|
@@ -409,6 +409,60 @@ var computeStdDevSeries = (data, length, source) => {
|
|
|
409
409
|
}
|
|
410
410
|
return result;
|
|
411
411
|
};
|
|
412
|
+
var VWAP_SESSION_ANCHOR_MS = 22 * 36e5;
|
|
413
|
+
var vwapSessionDayOf = (ms) => Math.floor((ms - VWAP_SESSION_ANCHOR_MS) / 864e5);
|
|
414
|
+
var computeVwapSeries = (data, band) => {
|
|
415
|
+
const result = new Array(data.length).fill(null);
|
|
416
|
+
let session = Number.NaN;
|
|
417
|
+
let cumPv = 0;
|
|
418
|
+
let cumPv2 = 0;
|
|
419
|
+
let cumV = 0;
|
|
420
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
421
|
+
const point = data[i];
|
|
422
|
+
if (!point) continue;
|
|
423
|
+
const day = vwapSessionDayOf(point.time.getTime());
|
|
424
|
+
if (day !== session) {
|
|
425
|
+
session = day;
|
|
426
|
+
cumPv = 0;
|
|
427
|
+
cumPv2 = 0;
|
|
428
|
+
cumV = 0;
|
|
429
|
+
}
|
|
430
|
+
const typical = (point.h + point.l + point.c) / 3;
|
|
431
|
+
const volume = Math.max(0, point.v ?? 0);
|
|
432
|
+
cumPv += typical * volume;
|
|
433
|
+
cumPv2 += typical * typical * volume;
|
|
434
|
+
cumV += volume;
|
|
435
|
+
if (cumV <= 0) continue;
|
|
436
|
+
const vwap = cumPv / cumV;
|
|
437
|
+
if (band === "vwap") {
|
|
438
|
+
result[i] = vwap;
|
|
439
|
+
} else {
|
|
440
|
+
const variance = Math.max(0, cumPv2 / cumV - vwap * vwap);
|
|
441
|
+
result[i] = Math.sqrt(variance);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
for (let i = 1; i < data.length; i += 1) {
|
|
445
|
+
const point = data[i];
|
|
446
|
+
const prev = data[i - 1];
|
|
447
|
+
const next = data[i + 1];
|
|
448
|
+
if (!point || !prev || !next) continue;
|
|
449
|
+
const day = vwapSessionDayOf(point.time.getTime());
|
|
450
|
+
if (day !== vwapSessionDayOf(prev.time.getTime()) && day === vwapSessionDayOf(next.time.getTime())) {
|
|
451
|
+
result[i] = null;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return result;
|
|
455
|
+
};
|
|
456
|
+
var computeBollingerSeries = (data, length, multiplier, source, band) => {
|
|
457
|
+
const basis = computeSmaSeries(data, length, source);
|
|
458
|
+
if (band === "basis") return basis;
|
|
459
|
+
const dev = computeStdDevSeries(data, length, source);
|
|
460
|
+
return basis.map((value, idx) => {
|
|
461
|
+
const sigma = dev[idx];
|
|
462
|
+
if (value == null || sigma == null) return null;
|
|
463
|
+
return band === "upper" ? value + multiplier * sigma : value - multiplier * sigma;
|
|
464
|
+
});
|
|
465
|
+
};
|
|
412
466
|
var computeRsiSeries = (data, length) => {
|
|
413
467
|
const result = new Array(data.length).fill(null);
|
|
414
468
|
if (data.length < 2) return result;
|
|
@@ -535,6 +589,39 @@ var drawOverlaySeries = (ctx, renderContext, values, color, width) => {
|
|
|
535
589
|
}
|
|
536
590
|
ctx.restore();
|
|
537
591
|
};
|
|
592
|
+
var fillBetweenSeries = (ctx, renderContext, upper, lower, color, opacity) => {
|
|
593
|
+
if (!renderContext.yFromPrice || opacity <= 0) return;
|
|
594
|
+
const yFromPrice = renderContext.yFromPrice;
|
|
595
|
+
ctx.save();
|
|
596
|
+
ctx.globalAlpha = Math.min(1, Math.max(0, opacity));
|
|
597
|
+
ctx.fillStyle = color;
|
|
598
|
+
let runStart = -1;
|
|
599
|
+
const flushRun = (endIndexExclusive) => {
|
|
600
|
+
if (runStart < 0) return;
|
|
601
|
+
ctx.beginPath();
|
|
602
|
+
for (let i = runStart; i < endIndexExclusive; i += 1) {
|
|
603
|
+
const x = renderContext.xFromIndex(i);
|
|
604
|
+
const y = yFromPrice(upper[i]);
|
|
605
|
+
if (i === runStart) ctx.moveTo(x, y);
|
|
606
|
+
else ctx.lineTo(x, y);
|
|
607
|
+
}
|
|
608
|
+
for (let i = endIndexExclusive - 1; i >= runStart; i -= 1) {
|
|
609
|
+
ctx.lineTo(renderContext.xFromIndex(i), yFromPrice(lower[i]));
|
|
610
|
+
}
|
|
611
|
+
ctx.closePath();
|
|
612
|
+
ctx.fill();
|
|
613
|
+
runStart = -1;
|
|
614
|
+
};
|
|
615
|
+
for (let index = renderContext.startIndex; index <= renderContext.endIndex; index += 1) {
|
|
616
|
+
const up = upper[index];
|
|
617
|
+
const lo = lower[index];
|
|
618
|
+
const valid = Number.isFinite(up ?? Number.NaN) && Number.isFinite(lo ?? Number.NaN);
|
|
619
|
+
if (valid && runStart < 0) runStart = index;
|
|
620
|
+
if (!valid) flushRun(index);
|
|
621
|
+
}
|
|
622
|
+
flushRun(renderContext.endIndex + 1);
|
|
623
|
+
ctx.restore();
|
|
624
|
+
};
|
|
538
625
|
var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride, maxOverride, guideLines, options = {}) => {
|
|
539
626
|
const visible = [];
|
|
540
627
|
for (let index = renderContext.startIndex; index <= renderContext.endIndex; index += 1) {
|
|
@@ -796,6 +883,89 @@ var BUILTIN_HMA_INDICATOR = {
|
|
|
796
883
|
drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#14b8a6", Number(inputs.width) || 2);
|
|
797
884
|
}
|
|
798
885
|
};
|
|
886
|
+
var BUILTIN_VWAP_INDICATOR = {
|
|
887
|
+
id: "vwap",
|
|
888
|
+
name: "VWAP",
|
|
889
|
+
pane: "overlay",
|
|
890
|
+
defaultInputs: {
|
|
891
|
+
color: "#f59e0b",
|
|
892
|
+
width: 2,
|
|
893
|
+
showBands: false,
|
|
894
|
+
bandMultiplier: 1,
|
|
895
|
+
bandColor: "#94a3b8",
|
|
896
|
+
bandFillOpacity: 0.06
|
|
897
|
+
},
|
|
898
|
+
draw: (ctx, renderContext, inputs) => {
|
|
899
|
+
const vwap = withCachedSeries(
|
|
900
|
+
"vwap|line",
|
|
901
|
+
renderContext.data,
|
|
902
|
+
() => computeVwapSeries(renderContext.data, "vwap")
|
|
903
|
+
);
|
|
904
|
+
if (inputs.showBands) {
|
|
905
|
+
const sigma = withCachedSeries(
|
|
906
|
+
"vwap|sigma",
|
|
907
|
+
renderContext.data,
|
|
908
|
+
() => computeVwapSeries(renderContext.data, "sigma")
|
|
909
|
+
);
|
|
910
|
+
const multiplier = Math.max(0.1, Number(inputs.bandMultiplier) || 1);
|
|
911
|
+
const upper = vwap.map((value, idx) => {
|
|
912
|
+
const s = sigma[idx];
|
|
913
|
+
return value == null || s == null ? null : value + multiplier * s;
|
|
914
|
+
});
|
|
915
|
+
const lower = vwap.map((value, idx) => {
|
|
916
|
+
const s = sigma[idx];
|
|
917
|
+
return value == null || s == null ? null : value - multiplier * s;
|
|
918
|
+
});
|
|
919
|
+
const bandColor = inputs.bandColor ?? "#94a3b8";
|
|
920
|
+
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.bandFillOpacity) || 0.06);
|
|
921
|
+
drawOverlaySeries(ctx, renderContext, upper, bandColor, 1);
|
|
922
|
+
drawOverlaySeries(ctx, renderContext, lower, bandColor, 1);
|
|
923
|
+
}
|
|
924
|
+
drawOverlaySeries(ctx, renderContext, vwap, inputs.color ?? "#f59e0b", Number(inputs.width) || 2);
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
var BUILTIN_BOLLINGER_INDICATOR = {
|
|
928
|
+
id: "bollinger",
|
|
929
|
+
name: "BB",
|
|
930
|
+
pane: "overlay",
|
|
931
|
+
// Basis is pink so it stays distinguishable from the (orange) VWAP line
|
|
932
|
+
// when both overlays are active.
|
|
933
|
+
defaultInputs: {
|
|
934
|
+
length: 20,
|
|
935
|
+
multiplier: 2,
|
|
936
|
+
source: "close",
|
|
937
|
+
basisColor: "#ec4899",
|
|
938
|
+
bandColor: "#3b82f6",
|
|
939
|
+
width: 1.5,
|
|
940
|
+
fillOpacity: 0.05
|
|
941
|
+
},
|
|
942
|
+
draw: (ctx, renderContext, inputs) => {
|
|
943
|
+
const length = clampIndicatorLength(inputs.length, 20);
|
|
944
|
+
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 2);
|
|
945
|
+
const source = inputs.source ?? "close";
|
|
946
|
+
const basis = withCachedSeries(
|
|
947
|
+
`bb|basis|${length}|${source}`,
|
|
948
|
+
renderContext.data,
|
|
949
|
+
() => computeBollingerSeries(renderContext.data, length, multiplier, source, "basis")
|
|
950
|
+
);
|
|
951
|
+
const upper = withCachedSeries(
|
|
952
|
+
`bb|upper|${length}|${multiplier}|${source}`,
|
|
953
|
+
renderContext.data,
|
|
954
|
+
() => computeBollingerSeries(renderContext.data, length, multiplier, source, "upper")
|
|
955
|
+
);
|
|
956
|
+
const lower = withCachedSeries(
|
|
957
|
+
`bb|lower|${length}|${multiplier}|${source}`,
|
|
958
|
+
renderContext.data,
|
|
959
|
+
() => computeBollingerSeries(renderContext.data, length, multiplier, source, "lower")
|
|
960
|
+
);
|
|
961
|
+
const bandColor = inputs.bandColor ?? "#3b82f6";
|
|
962
|
+
const width = Number(inputs.width) || 1.5;
|
|
963
|
+
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.fillOpacity) || 0.05);
|
|
964
|
+
drawOverlaySeries(ctx, renderContext, upper, bandColor, width);
|
|
965
|
+
drawOverlaySeries(ctx, renderContext, lower, bandColor, width);
|
|
966
|
+
drawOverlaySeries(ctx, renderContext, basis, inputs.basisColor ?? "#f59e0b", width);
|
|
967
|
+
}
|
|
968
|
+
};
|
|
799
969
|
var BUILTIN_STDDEV_INDICATOR = {
|
|
800
970
|
id: "stddev",
|
|
801
971
|
name: "StdDev",
|
|
@@ -879,6 +1049,8 @@ var BUILTIN_INDICATORS = [
|
|
|
879
1049
|
BUILTIN_VWMA_INDICATOR,
|
|
880
1050
|
BUILTIN_RMA_INDICATOR,
|
|
881
1051
|
BUILTIN_HMA_INDICATOR,
|
|
1052
|
+
BUILTIN_VWAP_INDICATOR,
|
|
1053
|
+
BUILTIN_BOLLINGER_INDICATOR,
|
|
882
1054
|
BUILTIN_STDDEV_INDICATOR,
|
|
883
1055
|
BUILTIN_ATR_INDICATOR
|
|
884
1056
|
];
|
|
@@ -2731,15 +2903,31 @@ function createChart(element, options = {}) {
|
|
|
2731
2903
|
const pct = diff / base * 100;
|
|
2732
2904
|
const ticks = tick > 0 ? Math.round(diff / tick) : 0;
|
|
2733
2905
|
const signed = (value, text) => `${value < 0 ? "\u2212" : value > 0 ? "+" : ""}${text}`;
|
|
2734
|
-
const
|
|
2906
|
+
const labelLines = [
|
|
2907
|
+
tick > 0 ? `${signed(diff, formatPrice(Math.abs(diff)))} (${signed(pct, `${Math.abs(pct).toFixed(2)}%`)}) ${signed(ticks, String(Math.abs(ticks)))}` : `${signed(diff, formatPrice(Math.abs(diff)))} (${signed(pct, `${Math.abs(pct).toFixed(2)}%`)})`
|
|
2908
|
+
];
|
|
2909
|
+
const barSpan = Math.abs(Math.round(p1.index) - Math.round(p0.index));
|
|
2910
|
+
if (barSpan > 0) {
|
|
2911
|
+
const t0 = getTimeForIndex(Math.round(p0.index));
|
|
2912
|
+
const t1 = getTimeForIndex(Math.round(p1.index));
|
|
2913
|
+
const spanMs = t0 && t1 ? Math.abs(t1.getTime() - t0.getTime()) : 0;
|
|
2914
|
+
labelLines.push(spanMs > 0 ? `${barSpan} bars, ${formatDuration(spanMs)}` : `${barSpan} bars`);
|
|
2915
|
+
}
|
|
2916
|
+
const pointValue = Number(drawing.pointValue);
|
|
2917
|
+
if (Number.isFinite(pointValue) && pointValue > 0) {
|
|
2918
|
+
const dollars = diff * pointValue;
|
|
2919
|
+
const money = Math.abs(dollars).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
2920
|
+
labelLines.push(`${signed(dollars, `$${money}`)} / contract`);
|
|
2921
|
+
}
|
|
2735
2922
|
const prevFont = ctx.font;
|
|
2736
2923
|
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2737
2924
|
const padding = 6;
|
|
2738
|
-
const
|
|
2925
|
+
const lineHeight = 15;
|
|
2926
|
+
const textW = Math.max(...labelLines.map((line) => ctx.measureText(line).width));
|
|
2739
2927
|
const pillW = textW + padding * 2;
|
|
2740
|
-
const pillH =
|
|
2928
|
+
const pillH = labelLines.length * lineHeight + padding;
|
|
2741
2929
|
const pillX = midX - pillW / 2;
|
|
2742
|
-
const pillY = botY + 6;
|
|
2930
|
+
const pillY = botY + 6 + pillH <= fullChartBottom - 2 ? botY + 6 : Math.max(chartTop + 2, topY - 6 - pillH);
|
|
2743
2931
|
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2744
2932
|
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2745
2933
|
ctx.save();
|
|
@@ -2750,7 +2938,9 @@ function createChart(element, options = {}) {
|
|
|
2750
2938
|
ctx.fillStyle = drawing.color;
|
|
2751
2939
|
ctx.textAlign = "center";
|
|
2752
2940
|
ctx.textBaseline = "middle";
|
|
2753
|
-
|
|
2941
|
+
labelLines.forEach((line, lineIndex) => {
|
|
2942
|
+
ctx.fillText(line, midX, pillY + padding / 2 + lineIndex * lineHeight + lineHeight / 2);
|
|
2943
|
+
});
|
|
2754
2944
|
ctx.font = prevFont;
|
|
2755
2945
|
if (drawing.label) {
|
|
2756
2946
|
drawDrawingLabel(drawing.label, midX, topY - 4, drawing.color);
|
|
@@ -3526,8 +3716,9 @@ function createChart(element, options = {}) {
|
|
|
3526
3716
|
}
|
|
3527
3717
|
return typeof value === "string" && !/^#(?:[0-9a-fA-F]{3}){1,2}$/.test(value.trim());
|
|
3528
3718
|
};
|
|
3719
|
+
const LEGEND_EXCLUDED_INPUT_KEYS = /* @__PURE__ */ new Set(["width", "bandMultiplier", "bandFillOpacity", "fillOpacity"]);
|
|
3529
3720
|
const labelEntries = activeOverlayIndicators.map(({ indicator, plugin }) => {
|
|
3530
|
-
const inputValues = Object.entries(indicator.inputs).filter(([, value]) => isLegendInputValue(value)).slice(0, 2).map(([, value]) => String(value));
|
|
3721
|
+
const inputValues = Object.entries(indicator.inputs).filter(([key, value]) => !LEGEND_EXCLUDED_INPUT_KEYS.has(key) && typeof value !== "boolean" && isLegendInputValue(value)).slice(0, 2).map(([, value]) => String(value));
|
|
3531
3722
|
if (labels.showIndicatorNames && labels.showIndicatorValues && inputValues.length > 0) {
|
|
3532
3723
|
return `${plugin.name} ${inputValues.join(" ")}`;
|
|
3533
3724
|
}
|
|
@@ -4505,7 +4696,9 @@ function createChart(element, options = {}) {
|
|
|
4505
4696
|
points: [point, normalizeDrawingPoint(point.index + width2, point.price - priceOffset)],
|
|
4506
4697
|
color: defaults.color ?? "#2962ff",
|
|
4507
4698
|
style: defaults.style ?? "solid",
|
|
4508
|
-
width: defaults.width ?? 1
|
|
4699
|
+
width: defaults.width ?? 1,
|
|
4700
|
+
// pointValue enables the dollar-P&L line in the measure label.
|
|
4701
|
+
...defaults.pointValue === void 0 ? {} : { pointValue: defaults.pointValue }
|
|
4509
4702
|
})
|
|
4510
4703
|
);
|
|
4511
4704
|
emitDrawingsChange();
|
package/dist/index.cjs
CHANGED
|
@@ -435,6 +435,60 @@ var computeStdDevSeries = (data, length, source) => {
|
|
|
435
435
|
}
|
|
436
436
|
return result;
|
|
437
437
|
};
|
|
438
|
+
var VWAP_SESSION_ANCHOR_MS = 22 * 36e5;
|
|
439
|
+
var vwapSessionDayOf = (ms) => Math.floor((ms - VWAP_SESSION_ANCHOR_MS) / 864e5);
|
|
440
|
+
var computeVwapSeries = (data, band) => {
|
|
441
|
+
const result = new Array(data.length).fill(null);
|
|
442
|
+
let session = Number.NaN;
|
|
443
|
+
let cumPv = 0;
|
|
444
|
+
let cumPv2 = 0;
|
|
445
|
+
let cumV = 0;
|
|
446
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
447
|
+
const point = data[i];
|
|
448
|
+
if (!point) continue;
|
|
449
|
+
const day = vwapSessionDayOf(point.time.getTime());
|
|
450
|
+
if (day !== session) {
|
|
451
|
+
session = day;
|
|
452
|
+
cumPv = 0;
|
|
453
|
+
cumPv2 = 0;
|
|
454
|
+
cumV = 0;
|
|
455
|
+
}
|
|
456
|
+
const typical = (point.h + point.l + point.c) / 3;
|
|
457
|
+
const volume = Math.max(0, point.v ?? 0);
|
|
458
|
+
cumPv += typical * volume;
|
|
459
|
+
cumPv2 += typical * typical * volume;
|
|
460
|
+
cumV += volume;
|
|
461
|
+
if (cumV <= 0) continue;
|
|
462
|
+
const vwap = cumPv / cumV;
|
|
463
|
+
if (band === "vwap") {
|
|
464
|
+
result[i] = vwap;
|
|
465
|
+
} else {
|
|
466
|
+
const variance = Math.max(0, cumPv2 / cumV - vwap * vwap);
|
|
467
|
+
result[i] = Math.sqrt(variance);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
for (let i = 1; i < data.length; i += 1) {
|
|
471
|
+
const point = data[i];
|
|
472
|
+
const prev = data[i - 1];
|
|
473
|
+
const next = data[i + 1];
|
|
474
|
+
if (!point || !prev || !next) continue;
|
|
475
|
+
const day = vwapSessionDayOf(point.time.getTime());
|
|
476
|
+
if (day !== vwapSessionDayOf(prev.time.getTime()) && day === vwapSessionDayOf(next.time.getTime())) {
|
|
477
|
+
result[i] = null;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return result;
|
|
481
|
+
};
|
|
482
|
+
var computeBollingerSeries = (data, length, multiplier, source, band) => {
|
|
483
|
+
const basis = computeSmaSeries(data, length, source);
|
|
484
|
+
if (band === "basis") return basis;
|
|
485
|
+
const dev = computeStdDevSeries(data, length, source);
|
|
486
|
+
return basis.map((value, idx) => {
|
|
487
|
+
const sigma = dev[idx];
|
|
488
|
+
if (value == null || sigma == null) return null;
|
|
489
|
+
return band === "upper" ? value + multiplier * sigma : value - multiplier * sigma;
|
|
490
|
+
});
|
|
491
|
+
};
|
|
438
492
|
var computeRsiSeries = (data, length) => {
|
|
439
493
|
const result = new Array(data.length).fill(null);
|
|
440
494
|
if (data.length < 2) return result;
|
|
@@ -561,6 +615,39 @@ var drawOverlaySeries = (ctx, renderContext, values, color, width) => {
|
|
|
561
615
|
}
|
|
562
616
|
ctx.restore();
|
|
563
617
|
};
|
|
618
|
+
var fillBetweenSeries = (ctx, renderContext, upper, lower, color, opacity) => {
|
|
619
|
+
if (!renderContext.yFromPrice || opacity <= 0) return;
|
|
620
|
+
const yFromPrice = renderContext.yFromPrice;
|
|
621
|
+
ctx.save();
|
|
622
|
+
ctx.globalAlpha = Math.min(1, Math.max(0, opacity));
|
|
623
|
+
ctx.fillStyle = color;
|
|
624
|
+
let runStart = -1;
|
|
625
|
+
const flushRun = (endIndexExclusive) => {
|
|
626
|
+
if (runStart < 0) return;
|
|
627
|
+
ctx.beginPath();
|
|
628
|
+
for (let i = runStart; i < endIndexExclusive; i += 1) {
|
|
629
|
+
const x = renderContext.xFromIndex(i);
|
|
630
|
+
const y = yFromPrice(upper[i]);
|
|
631
|
+
if (i === runStart) ctx.moveTo(x, y);
|
|
632
|
+
else ctx.lineTo(x, y);
|
|
633
|
+
}
|
|
634
|
+
for (let i = endIndexExclusive - 1; i >= runStart; i -= 1) {
|
|
635
|
+
ctx.lineTo(renderContext.xFromIndex(i), yFromPrice(lower[i]));
|
|
636
|
+
}
|
|
637
|
+
ctx.closePath();
|
|
638
|
+
ctx.fill();
|
|
639
|
+
runStart = -1;
|
|
640
|
+
};
|
|
641
|
+
for (let index = renderContext.startIndex; index <= renderContext.endIndex; index += 1) {
|
|
642
|
+
const up = upper[index];
|
|
643
|
+
const lo = lower[index];
|
|
644
|
+
const valid = Number.isFinite(up ?? Number.NaN) && Number.isFinite(lo ?? Number.NaN);
|
|
645
|
+
if (valid && runStart < 0) runStart = index;
|
|
646
|
+
if (!valid) flushRun(index);
|
|
647
|
+
}
|
|
648
|
+
flushRun(renderContext.endIndex + 1);
|
|
649
|
+
ctx.restore();
|
|
650
|
+
};
|
|
564
651
|
var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride, maxOverride, guideLines, options = {}) => {
|
|
565
652
|
const visible = [];
|
|
566
653
|
for (let index = renderContext.startIndex; index <= renderContext.endIndex; index += 1) {
|
|
@@ -822,6 +909,89 @@ var BUILTIN_HMA_INDICATOR = {
|
|
|
822
909
|
drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#14b8a6", Number(inputs.width) || 2);
|
|
823
910
|
}
|
|
824
911
|
};
|
|
912
|
+
var BUILTIN_VWAP_INDICATOR = {
|
|
913
|
+
id: "vwap",
|
|
914
|
+
name: "VWAP",
|
|
915
|
+
pane: "overlay",
|
|
916
|
+
defaultInputs: {
|
|
917
|
+
color: "#f59e0b",
|
|
918
|
+
width: 2,
|
|
919
|
+
showBands: false,
|
|
920
|
+
bandMultiplier: 1,
|
|
921
|
+
bandColor: "#94a3b8",
|
|
922
|
+
bandFillOpacity: 0.06
|
|
923
|
+
},
|
|
924
|
+
draw: (ctx, renderContext, inputs) => {
|
|
925
|
+
const vwap = withCachedSeries(
|
|
926
|
+
"vwap|line",
|
|
927
|
+
renderContext.data,
|
|
928
|
+
() => computeVwapSeries(renderContext.data, "vwap")
|
|
929
|
+
);
|
|
930
|
+
if (inputs.showBands) {
|
|
931
|
+
const sigma = withCachedSeries(
|
|
932
|
+
"vwap|sigma",
|
|
933
|
+
renderContext.data,
|
|
934
|
+
() => computeVwapSeries(renderContext.data, "sigma")
|
|
935
|
+
);
|
|
936
|
+
const multiplier = Math.max(0.1, Number(inputs.bandMultiplier) || 1);
|
|
937
|
+
const upper = vwap.map((value, idx) => {
|
|
938
|
+
const s = sigma[idx];
|
|
939
|
+
return value == null || s == null ? null : value + multiplier * s;
|
|
940
|
+
});
|
|
941
|
+
const lower = vwap.map((value, idx) => {
|
|
942
|
+
const s = sigma[idx];
|
|
943
|
+
return value == null || s == null ? null : value - multiplier * s;
|
|
944
|
+
});
|
|
945
|
+
const bandColor = inputs.bandColor ?? "#94a3b8";
|
|
946
|
+
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.bandFillOpacity) || 0.06);
|
|
947
|
+
drawOverlaySeries(ctx, renderContext, upper, bandColor, 1);
|
|
948
|
+
drawOverlaySeries(ctx, renderContext, lower, bandColor, 1);
|
|
949
|
+
}
|
|
950
|
+
drawOverlaySeries(ctx, renderContext, vwap, inputs.color ?? "#f59e0b", Number(inputs.width) || 2);
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
var BUILTIN_BOLLINGER_INDICATOR = {
|
|
954
|
+
id: "bollinger",
|
|
955
|
+
name: "BB",
|
|
956
|
+
pane: "overlay",
|
|
957
|
+
// Basis is pink so it stays distinguishable from the (orange) VWAP line
|
|
958
|
+
// when both overlays are active.
|
|
959
|
+
defaultInputs: {
|
|
960
|
+
length: 20,
|
|
961
|
+
multiplier: 2,
|
|
962
|
+
source: "close",
|
|
963
|
+
basisColor: "#ec4899",
|
|
964
|
+
bandColor: "#3b82f6",
|
|
965
|
+
width: 1.5,
|
|
966
|
+
fillOpacity: 0.05
|
|
967
|
+
},
|
|
968
|
+
draw: (ctx, renderContext, inputs) => {
|
|
969
|
+
const length = clampIndicatorLength(inputs.length, 20);
|
|
970
|
+
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 2);
|
|
971
|
+
const source = inputs.source ?? "close";
|
|
972
|
+
const basis = withCachedSeries(
|
|
973
|
+
`bb|basis|${length}|${source}`,
|
|
974
|
+
renderContext.data,
|
|
975
|
+
() => computeBollingerSeries(renderContext.data, length, multiplier, source, "basis")
|
|
976
|
+
);
|
|
977
|
+
const upper = withCachedSeries(
|
|
978
|
+
`bb|upper|${length}|${multiplier}|${source}`,
|
|
979
|
+
renderContext.data,
|
|
980
|
+
() => computeBollingerSeries(renderContext.data, length, multiplier, source, "upper")
|
|
981
|
+
);
|
|
982
|
+
const lower = withCachedSeries(
|
|
983
|
+
`bb|lower|${length}|${multiplier}|${source}`,
|
|
984
|
+
renderContext.data,
|
|
985
|
+
() => computeBollingerSeries(renderContext.data, length, multiplier, source, "lower")
|
|
986
|
+
);
|
|
987
|
+
const bandColor = inputs.bandColor ?? "#3b82f6";
|
|
988
|
+
const width = Number(inputs.width) || 1.5;
|
|
989
|
+
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.fillOpacity) || 0.05);
|
|
990
|
+
drawOverlaySeries(ctx, renderContext, upper, bandColor, width);
|
|
991
|
+
drawOverlaySeries(ctx, renderContext, lower, bandColor, width);
|
|
992
|
+
drawOverlaySeries(ctx, renderContext, basis, inputs.basisColor ?? "#f59e0b", width);
|
|
993
|
+
}
|
|
994
|
+
};
|
|
825
995
|
var BUILTIN_STDDEV_INDICATOR = {
|
|
826
996
|
id: "stddev",
|
|
827
997
|
name: "StdDev",
|
|
@@ -905,6 +1075,8 @@ var BUILTIN_INDICATORS = [
|
|
|
905
1075
|
BUILTIN_VWMA_INDICATOR,
|
|
906
1076
|
BUILTIN_RMA_INDICATOR,
|
|
907
1077
|
BUILTIN_HMA_INDICATOR,
|
|
1078
|
+
BUILTIN_VWAP_INDICATOR,
|
|
1079
|
+
BUILTIN_BOLLINGER_INDICATOR,
|
|
908
1080
|
BUILTIN_STDDEV_INDICATOR,
|
|
909
1081
|
BUILTIN_ATR_INDICATOR
|
|
910
1082
|
];
|
|
@@ -2757,15 +2929,31 @@ function createChart(element, options = {}) {
|
|
|
2757
2929
|
const pct = diff / base * 100;
|
|
2758
2930
|
const ticks = tick > 0 ? Math.round(diff / tick) : 0;
|
|
2759
2931
|
const signed = (value, text) => `${value < 0 ? "\u2212" : value > 0 ? "+" : ""}${text}`;
|
|
2760
|
-
const
|
|
2932
|
+
const labelLines = [
|
|
2933
|
+
tick > 0 ? `${signed(diff, formatPrice(Math.abs(diff)))} (${signed(pct, `${Math.abs(pct).toFixed(2)}%`)}) ${signed(ticks, String(Math.abs(ticks)))}` : `${signed(diff, formatPrice(Math.abs(diff)))} (${signed(pct, `${Math.abs(pct).toFixed(2)}%`)})`
|
|
2934
|
+
];
|
|
2935
|
+
const barSpan = Math.abs(Math.round(p1.index) - Math.round(p0.index));
|
|
2936
|
+
if (barSpan > 0) {
|
|
2937
|
+
const t0 = getTimeForIndex(Math.round(p0.index));
|
|
2938
|
+
const t1 = getTimeForIndex(Math.round(p1.index));
|
|
2939
|
+
const spanMs = t0 && t1 ? Math.abs(t1.getTime() - t0.getTime()) : 0;
|
|
2940
|
+
labelLines.push(spanMs > 0 ? `${barSpan} bars, ${formatDuration(spanMs)}` : `${barSpan} bars`);
|
|
2941
|
+
}
|
|
2942
|
+
const pointValue = Number(drawing.pointValue);
|
|
2943
|
+
if (Number.isFinite(pointValue) && pointValue > 0) {
|
|
2944
|
+
const dollars = diff * pointValue;
|
|
2945
|
+
const money = Math.abs(dollars).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
2946
|
+
labelLines.push(`${signed(dollars, `$${money}`)} / contract`);
|
|
2947
|
+
}
|
|
2761
2948
|
const prevFont = ctx.font;
|
|
2762
2949
|
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2763
2950
|
const padding = 6;
|
|
2764
|
-
const
|
|
2951
|
+
const lineHeight = 15;
|
|
2952
|
+
const textW = Math.max(...labelLines.map((line) => ctx.measureText(line).width));
|
|
2765
2953
|
const pillW = textW + padding * 2;
|
|
2766
|
-
const pillH =
|
|
2954
|
+
const pillH = labelLines.length * lineHeight + padding;
|
|
2767
2955
|
const pillX = midX - pillW / 2;
|
|
2768
|
-
const pillY = botY + 6;
|
|
2956
|
+
const pillY = botY + 6 + pillH <= fullChartBottom - 2 ? botY + 6 : Math.max(chartTop + 2, topY - 6 - pillH);
|
|
2769
2957
|
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2770
2958
|
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2771
2959
|
ctx.save();
|
|
@@ -2776,7 +2964,9 @@ function createChart(element, options = {}) {
|
|
|
2776
2964
|
ctx.fillStyle = drawing.color;
|
|
2777
2965
|
ctx.textAlign = "center";
|
|
2778
2966
|
ctx.textBaseline = "middle";
|
|
2779
|
-
|
|
2967
|
+
labelLines.forEach((line, lineIndex) => {
|
|
2968
|
+
ctx.fillText(line, midX, pillY + padding / 2 + lineIndex * lineHeight + lineHeight / 2);
|
|
2969
|
+
});
|
|
2780
2970
|
ctx.font = prevFont;
|
|
2781
2971
|
if (drawing.label) {
|
|
2782
2972
|
drawDrawingLabel(drawing.label, midX, topY - 4, drawing.color);
|
|
@@ -3552,8 +3742,9 @@ function createChart(element, options = {}) {
|
|
|
3552
3742
|
}
|
|
3553
3743
|
return typeof value === "string" && !/^#(?:[0-9a-fA-F]{3}){1,2}$/.test(value.trim());
|
|
3554
3744
|
};
|
|
3745
|
+
const LEGEND_EXCLUDED_INPUT_KEYS = /* @__PURE__ */ new Set(["width", "bandMultiplier", "bandFillOpacity", "fillOpacity"]);
|
|
3555
3746
|
const labelEntries = activeOverlayIndicators.map(({ indicator, plugin }) => {
|
|
3556
|
-
const inputValues = Object.entries(indicator.inputs).filter(([, value]) => isLegendInputValue(value)).slice(0, 2).map(([, value]) => String(value));
|
|
3747
|
+
const inputValues = Object.entries(indicator.inputs).filter(([key, value]) => !LEGEND_EXCLUDED_INPUT_KEYS.has(key) && typeof value !== "boolean" && isLegendInputValue(value)).slice(0, 2).map(([, value]) => String(value));
|
|
3557
3748
|
if (labels.showIndicatorNames && labels.showIndicatorValues && inputValues.length > 0) {
|
|
3558
3749
|
return `${plugin.name} ${inputValues.join(" ")}`;
|
|
3559
3750
|
}
|
|
@@ -4531,7 +4722,9 @@ function createChart(element, options = {}) {
|
|
|
4531
4722
|
points: [point, normalizeDrawingPoint(point.index + width2, point.price - priceOffset)],
|
|
4532
4723
|
color: defaults.color ?? "#2962ff",
|
|
4533
4724
|
style: defaults.style ?? "solid",
|
|
4534
|
-
width: defaults.width ?? 1
|
|
4725
|
+
width: defaults.width ?? 1,
|
|
4726
|
+
// pointValue enables the dollar-P&L line in the measure label.
|
|
4727
|
+
...defaults.pointValue === void 0 ? {} : { pointValue: defaults.pointValue }
|
|
4535
4728
|
})
|
|
4536
4729
|
);
|
|
4537
4730
|
emitDrawingsChange();
|
package/dist/index.js
CHANGED
|
@@ -409,6 +409,60 @@ var computeStdDevSeries = (data, length, source) => {
|
|
|
409
409
|
}
|
|
410
410
|
return result;
|
|
411
411
|
};
|
|
412
|
+
var VWAP_SESSION_ANCHOR_MS = 22 * 36e5;
|
|
413
|
+
var vwapSessionDayOf = (ms) => Math.floor((ms - VWAP_SESSION_ANCHOR_MS) / 864e5);
|
|
414
|
+
var computeVwapSeries = (data, band) => {
|
|
415
|
+
const result = new Array(data.length).fill(null);
|
|
416
|
+
let session = Number.NaN;
|
|
417
|
+
let cumPv = 0;
|
|
418
|
+
let cumPv2 = 0;
|
|
419
|
+
let cumV = 0;
|
|
420
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
421
|
+
const point = data[i];
|
|
422
|
+
if (!point) continue;
|
|
423
|
+
const day = vwapSessionDayOf(point.time.getTime());
|
|
424
|
+
if (day !== session) {
|
|
425
|
+
session = day;
|
|
426
|
+
cumPv = 0;
|
|
427
|
+
cumPv2 = 0;
|
|
428
|
+
cumV = 0;
|
|
429
|
+
}
|
|
430
|
+
const typical = (point.h + point.l + point.c) / 3;
|
|
431
|
+
const volume = Math.max(0, point.v ?? 0);
|
|
432
|
+
cumPv += typical * volume;
|
|
433
|
+
cumPv2 += typical * typical * volume;
|
|
434
|
+
cumV += volume;
|
|
435
|
+
if (cumV <= 0) continue;
|
|
436
|
+
const vwap = cumPv / cumV;
|
|
437
|
+
if (band === "vwap") {
|
|
438
|
+
result[i] = vwap;
|
|
439
|
+
} else {
|
|
440
|
+
const variance = Math.max(0, cumPv2 / cumV - vwap * vwap);
|
|
441
|
+
result[i] = Math.sqrt(variance);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
for (let i = 1; i < data.length; i += 1) {
|
|
445
|
+
const point = data[i];
|
|
446
|
+
const prev = data[i - 1];
|
|
447
|
+
const next = data[i + 1];
|
|
448
|
+
if (!point || !prev || !next) continue;
|
|
449
|
+
const day = vwapSessionDayOf(point.time.getTime());
|
|
450
|
+
if (day !== vwapSessionDayOf(prev.time.getTime()) && day === vwapSessionDayOf(next.time.getTime())) {
|
|
451
|
+
result[i] = null;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return result;
|
|
455
|
+
};
|
|
456
|
+
var computeBollingerSeries = (data, length, multiplier, source, band) => {
|
|
457
|
+
const basis = computeSmaSeries(data, length, source);
|
|
458
|
+
if (band === "basis") return basis;
|
|
459
|
+
const dev = computeStdDevSeries(data, length, source);
|
|
460
|
+
return basis.map((value, idx) => {
|
|
461
|
+
const sigma = dev[idx];
|
|
462
|
+
if (value == null || sigma == null) return null;
|
|
463
|
+
return band === "upper" ? value + multiplier * sigma : value - multiplier * sigma;
|
|
464
|
+
});
|
|
465
|
+
};
|
|
412
466
|
var computeRsiSeries = (data, length) => {
|
|
413
467
|
const result = new Array(data.length).fill(null);
|
|
414
468
|
if (data.length < 2) return result;
|
|
@@ -535,6 +589,39 @@ var drawOverlaySeries = (ctx, renderContext, values, color, width) => {
|
|
|
535
589
|
}
|
|
536
590
|
ctx.restore();
|
|
537
591
|
};
|
|
592
|
+
var fillBetweenSeries = (ctx, renderContext, upper, lower, color, opacity) => {
|
|
593
|
+
if (!renderContext.yFromPrice || opacity <= 0) return;
|
|
594
|
+
const yFromPrice = renderContext.yFromPrice;
|
|
595
|
+
ctx.save();
|
|
596
|
+
ctx.globalAlpha = Math.min(1, Math.max(0, opacity));
|
|
597
|
+
ctx.fillStyle = color;
|
|
598
|
+
let runStart = -1;
|
|
599
|
+
const flushRun = (endIndexExclusive) => {
|
|
600
|
+
if (runStart < 0) return;
|
|
601
|
+
ctx.beginPath();
|
|
602
|
+
for (let i = runStart; i < endIndexExclusive; i += 1) {
|
|
603
|
+
const x = renderContext.xFromIndex(i);
|
|
604
|
+
const y = yFromPrice(upper[i]);
|
|
605
|
+
if (i === runStart) ctx.moveTo(x, y);
|
|
606
|
+
else ctx.lineTo(x, y);
|
|
607
|
+
}
|
|
608
|
+
for (let i = endIndexExclusive - 1; i >= runStart; i -= 1) {
|
|
609
|
+
ctx.lineTo(renderContext.xFromIndex(i), yFromPrice(lower[i]));
|
|
610
|
+
}
|
|
611
|
+
ctx.closePath();
|
|
612
|
+
ctx.fill();
|
|
613
|
+
runStart = -1;
|
|
614
|
+
};
|
|
615
|
+
for (let index = renderContext.startIndex; index <= renderContext.endIndex; index += 1) {
|
|
616
|
+
const up = upper[index];
|
|
617
|
+
const lo = lower[index];
|
|
618
|
+
const valid = Number.isFinite(up ?? Number.NaN) && Number.isFinite(lo ?? Number.NaN);
|
|
619
|
+
if (valid && runStart < 0) runStart = index;
|
|
620
|
+
if (!valid) flushRun(index);
|
|
621
|
+
}
|
|
622
|
+
flushRun(renderContext.endIndex + 1);
|
|
623
|
+
ctx.restore();
|
|
624
|
+
};
|
|
538
625
|
var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride, maxOverride, guideLines, options = {}) => {
|
|
539
626
|
const visible = [];
|
|
540
627
|
for (let index = renderContext.startIndex; index <= renderContext.endIndex; index += 1) {
|
|
@@ -796,6 +883,89 @@ var BUILTIN_HMA_INDICATOR = {
|
|
|
796
883
|
drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#14b8a6", Number(inputs.width) || 2);
|
|
797
884
|
}
|
|
798
885
|
};
|
|
886
|
+
var BUILTIN_VWAP_INDICATOR = {
|
|
887
|
+
id: "vwap",
|
|
888
|
+
name: "VWAP",
|
|
889
|
+
pane: "overlay",
|
|
890
|
+
defaultInputs: {
|
|
891
|
+
color: "#f59e0b",
|
|
892
|
+
width: 2,
|
|
893
|
+
showBands: false,
|
|
894
|
+
bandMultiplier: 1,
|
|
895
|
+
bandColor: "#94a3b8",
|
|
896
|
+
bandFillOpacity: 0.06
|
|
897
|
+
},
|
|
898
|
+
draw: (ctx, renderContext, inputs) => {
|
|
899
|
+
const vwap = withCachedSeries(
|
|
900
|
+
"vwap|line",
|
|
901
|
+
renderContext.data,
|
|
902
|
+
() => computeVwapSeries(renderContext.data, "vwap")
|
|
903
|
+
);
|
|
904
|
+
if (inputs.showBands) {
|
|
905
|
+
const sigma = withCachedSeries(
|
|
906
|
+
"vwap|sigma",
|
|
907
|
+
renderContext.data,
|
|
908
|
+
() => computeVwapSeries(renderContext.data, "sigma")
|
|
909
|
+
);
|
|
910
|
+
const multiplier = Math.max(0.1, Number(inputs.bandMultiplier) || 1);
|
|
911
|
+
const upper = vwap.map((value, idx) => {
|
|
912
|
+
const s = sigma[idx];
|
|
913
|
+
return value == null || s == null ? null : value + multiplier * s;
|
|
914
|
+
});
|
|
915
|
+
const lower = vwap.map((value, idx) => {
|
|
916
|
+
const s = sigma[idx];
|
|
917
|
+
return value == null || s == null ? null : value - multiplier * s;
|
|
918
|
+
});
|
|
919
|
+
const bandColor = inputs.bandColor ?? "#94a3b8";
|
|
920
|
+
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.bandFillOpacity) || 0.06);
|
|
921
|
+
drawOverlaySeries(ctx, renderContext, upper, bandColor, 1);
|
|
922
|
+
drawOverlaySeries(ctx, renderContext, lower, bandColor, 1);
|
|
923
|
+
}
|
|
924
|
+
drawOverlaySeries(ctx, renderContext, vwap, inputs.color ?? "#f59e0b", Number(inputs.width) || 2);
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
var BUILTIN_BOLLINGER_INDICATOR = {
|
|
928
|
+
id: "bollinger",
|
|
929
|
+
name: "BB",
|
|
930
|
+
pane: "overlay",
|
|
931
|
+
// Basis is pink so it stays distinguishable from the (orange) VWAP line
|
|
932
|
+
// when both overlays are active.
|
|
933
|
+
defaultInputs: {
|
|
934
|
+
length: 20,
|
|
935
|
+
multiplier: 2,
|
|
936
|
+
source: "close",
|
|
937
|
+
basisColor: "#ec4899",
|
|
938
|
+
bandColor: "#3b82f6",
|
|
939
|
+
width: 1.5,
|
|
940
|
+
fillOpacity: 0.05
|
|
941
|
+
},
|
|
942
|
+
draw: (ctx, renderContext, inputs) => {
|
|
943
|
+
const length = clampIndicatorLength(inputs.length, 20);
|
|
944
|
+
const multiplier = Math.max(0.1, Number(inputs.multiplier) || 2);
|
|
945
|
+
const source = inputs.source ?? "close";
|
|
946
|
+
const basis = withCachedSeries(
|
|
947
|
+
`bb|basis|${length}|${source}`,
|
|
948
|
+
renderContext.data,
|
|
949
|
+
() => computeBollingerSeries(renderContext.data, length, multiplier, source, "basis")
|
|
950
|
+
);
|
|
951
|
+
const upper = withCachedSeries(
|
|
952
|
+
`bb|upper|${length}|${multiplier}|${source}`,
|
|
953
|
+
renderContext.data,
|
|
954
|
+
() => computeBollingerSeries(renderContext.data, length, multiplier, source, "upper")
|
|
955
|
+
);
|
|
956
|
+
const lower = withCachedSeries(
|
|
957
|
+
`bb|lower|${length}|${multiplier}|${source}`,
|
|
958
|
+
renderContext.data,
|
|
959
|
+
() => computeBollingerSeries(renderContext.data, length, multiplier, source, "lower")
|
|
960
|
+
);
|
|
961
|
+
const bandColor = inputs.bandColor ?? "#3b82f6";
|
|
962
|
+
const width = Number(inputs.width) || 1.5;
|
|
963
|
+
fillBetweenSeries(ctx, renderContext, upper, lower, bandColor, Number(inputs.fillOpacity) || 0.05);
|
|
964
|
+
drawOverlaySeries(ctx, renderContext, upper, bandColor, width);
|
|
965
|
+
drawOverlaySeries(ctx, renderContext, lower, bandColor, width);
|
|
966
|
+
drawOverlaySeries(ctx, renderContext, basis, inputs.basisColor ?? "#f59e0b", width);
|
|
967
|
+
}
|
|
968
|
+
};
|
|
799
969
|
var BUILTIN_STDDEV_INDICATOR = {
|
|
800
970
|
id: "stddev",
|
|
801
971
|
name: "StdDev",
|
|
@@ -879,6 +1049,8 @@ var BUILTIN_INDICATORS = [
|
|
|
879
1049
|
BUILTIN_VWMA_INDICATOR,
|
|
880
1050
|
BUILTIN_RMA_INDICATOR,
|
|
881
1051
|
BUILTIN_HMA_INDICATOR,
|
|
1052
|
+
BUILTIN_VWAP_INDICATOR,
|
|
1053
|
+
BUILTIN_BOLLINGER_INDICATOR,
|
|
882
1054
|
BUILTIN_STDDEV_INDICATOR,
|
|
883
1055
|
BUILTIN_ATR_INDICATOR
|
|
884
1056
|
];
|
|
@@ -2731,15 +2903,31 @@ function createChart(element, options = {}) {
|
|
|
2731
2903
|
const pct = diff / base * 100;
|
|
2732
2904
|
const ticks = tick > 0 ? Math.round(diff / tick) : 0;
|
|
2733
2905
|
const signed = (value, text) => `${value < 0 ? "\u2212" : value > 0 ? "+" : ""}${text}`;
|
|
2734
|
-
const
|
|
2906
|
+
const labelLines = [
|
|
2907
|
+
tick > 0 ? `${signed(diff, formatPrice(Math.abs(diff)))} (${signed(pct, `${Math.abs(pct).toFixed(2)}%`)}) ${signed(ticks, String(Math.abs(ticks)))}` : `${signed(diff, formatPrice(Math.abs(diff)))} (${signed(pct, `${Math.abs(pct).toFixed(2)}%`)})`
|
|
2908
|
+
];
|
|
2909
|
+
const barSpan = Math.abs(Math.round(p1.index) - Math.round(p0.index));
|
|
2910
|
+
if (barSpan > 0) {
|
|
2911
|
+
const t0 = getTimeForIndex(Math.round(p0.index));
|
|
2912
|
+
const t1 = getTimeForIndex(Math.round(p1.index));
|
|
2913
|
+
const spanMs = t0 && t1 ? Math.abs(t1.getTime() - t0.getTime()) : 0;
|
|
2914
|
+
labelLines.push(spanMs > 0 ? `${barSpan} bars, ${formatDuration(spanMs)}` : `${barSpan} bars`);
|
|
2915
|
+
}
|
|
2916
|
+
const pointValue = Number(drawing.pointValue);
|
|
2917
|
+
if (Number.isFinite(pointValue) && pointValue > 0) {
|
|
2918
|
+
const dollars = diff * pointValue;
|
|
2919
|
+
const money = Math.abs(dollars).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
2920
|
+
labelLines.push(`${signed(dollars, `$${money}`)} / contract`);
|
|
2921
|
+
}
|
|
2735
2922
|
const prevFont = ctx.font;
|
|
2736
2923
|
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2737
2924
|
const padding = 6;
|
|
2738
|
-
const
|
|
2925
|
+
const lineHeight = 15;
|
|
2926
|
+
const textW = Math.max(...labelLines.map((line) => ctx.measureText(line).width));
|
|
2739
2927
|
const pillW = textW + padding * 2;
|
|
2740
|
-
const pillH =
|
|
2928
|
+
const pillH = labelLines.length * lineHeight + padding;
|
|
2741
2929
|
const pillX = midX - pillW / 2;
|
|
2742
|
-
const pillY = botY + 6;
|
|
2930
|
+
const pillY = botY + 6 + pillH <= fullChartBottom - 2 ? botY + 6 : Math.max(chartTop + 2, topY - 6 - pillH);
|
|
2743
2931
|
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2744
2932
|
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2745
2933
|
ctx.save();
|
|
@@ -2750,7 +2938,9 @@ function createChart(element, options = {}) {
|
|
|
2750
2938
|
ctx.fillStyle = drawing.color;
|
|
2751
2939
|
ctx.textAlign = "center";
|
|
2752
2940
|
ctx.textBaseline = "middle";
|
|
2753
|
-
|
|
2941
|
+
labelLines.forEach((line, lineIndex) => {
|
|
2942
|
+
ctx.fillText(line, midX, pillY + padding / 2 + lineIndex * lineHeight + lineHeight / 2);
|
|
2943
|
+
});
|
|
2754
2944
|
ctx.font = prevFont;
|
|
2755
2945
|
if (drawing.label) {
|
|
2756
2946
|
drawDrawingLabel(drawing.label, midX, topY - 4, drawing.color);
|
|
@@ -3526,8 +3716,9 @@ function createChart(element, options = {}) {
|
|
|
3526
3716
|
}
|
|
3527
3717
|
return typeof value === "string" && !/^#(?:[0-9a-fA-F]{3}){1,2}$/.test(value.trim());
|
|
3528
3718
|
};
|
|
3719
|
+
const LEGEND_EXCLUDED_INPUT_KEYS = /* @__PURE__ */ new Set(["width", "bandMultiplier", "bandFillOpacity", "fillOpacity"]);
|
|
3529
3720
|
const labelEntries = activeOverlayIndicators.map(({ indicator, plugin }) => {
|
|
3530
|
-
const inputValues = Object.entries(indicator.inputs).filter(([, value]) => isLegendInputValue(value)).slice(0, 2).map(([, value]) => String(value));
|
|
3721
|
+
const inputValues = Object.entries(indicator.inputs).filter(([key, value]) => !LEGEND_EXCLUDED_INPUT_KEYS.has(key) && typeof value !== "boolean" && isLegendInputValue(value)).slice(0, 2).map(([, value]) => String(value));
|
|
3531
3722
|
if (labels.showIndicatorNames && labels.showIndicatorValues && inputValues.length > 0) {
|
|
3532
3723
|
return `${plugin.name} ${inputValues.join(" ")}`;
|
|
3533
3724
|
}
|
|
@@ -4505,7 +4696,9 @@ function createChart(element, options = {}) {
|
|
|
4505
4696
|
points: [point, normalizeDrawingPoint(point.index + width2, point.price - priceOffset)],
|
|
4506
4697
|
color: defaults.color ?? "#2962ff",
|
|
4507
4698
|
style: defaults.style ?? "solid",
|
|
4508
|
-
width: defaults.width ?? 1
|
|
4699
|
+
width: defaults.width ?? 1,
|
|
4700
|
+
// pointValue enables the dollar-P&L line in the measure label.
|
|
4701
|
+
...defaults.pointValue === void 0 ? {} : { pointValue: defaults.pointValue }
|
|
4509
4702
|
})
|
|
4510
4703
|
);
|
|
4511
4704
|
emitDrawingsChange();
|