hyperprop-charting-library 0.1.23 → 0.1.25

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/index.d.ts CHANGED
@@ -4,6 +4,8 @@ interface ChartOptions {
4
4
  backgroundColor?: string;
5
5
  axisColor?: string;
6
6
  axis?: AxisOptions;
7
+ xAxis?: AxisOptions;
8
+ yAxis?: AxisOptions;
7
9
  priceDecimals?: number;
8
10
  stabilizePriceLabels?: boolean;
9
11
  priceLabelMinIntegerDigits?: number;
@@ -22,6 +24,9 @@ interface ChartOptions {
22
24
  candleBodyWidthRatio?: number;
23
25
  candleMinWidth?: number;
24
26
  candleWickWidth?: number;
27
+ tickSize?: number;
28
+ candleColorMode?: "openClose" | "prevClose";
29
+ candleColorEpsilon?: number;
25
30
  autoScaleSmoothing?: number;
26
31
  autoScaleIgnoreLatestCandle?: boolean;
27
32
  doubleClickEnabled?: boolean;
@@ -42,6 +47,9 @@ interface IndicatorInstanceOptions<TInputs extends Record<string, unknown> = Rec
42
47
  visible?: boolean;
43
48
  pane?: IndicatorPane;
44
49
  paneHeightRatio?: number;
50
+ zIndex?: number;
51
+ excludeFromAutoscale?: boolean;
52
+ overlayScaleWeight?: number;
45
53
  inputs?: Partial<TInputs>;
46
54
  }
47
55
  interface IndicatorRenderContext {
@@ -65,6 +73,7 @@ interface IndicatorRenderContext {
65
73
  chartHeight: number;
66
74
  xFromIndex: (index: number) => number;
67
75
  yFromPrice: ((price: number) => number) | null;
76
+ getCandleDirectionByIndex: (index: number) => "up" | "down";
68
77
  candleSpacing: number;
69
78
  upColor: string;
70
79
  downColor: string;
@@ -77,6 +86,12 @@ interface IndicatorPlugin<TInputs extends Record<string, unknown> = Record<strin
77
86
  defaultInputs?: TInputs;
78
87
  draw: (ctx: CanvasRenderingContext2D, renderContext: IndicatorRenderContext, inputs: TInputs) => void;
79
88
  }
89
+ interface BuiltInIndicatorInfo {
90
+ id: string;
91
+ name: string;
92
+ pane: IndicatorPane;
93
+ paneHeightRatio?: number;
94
+ }
80
95
  interface DashPatternOptions {
81
96
  dotted: [number, number];
82
97
  dashed: [number, number];
@@ -268,6 +283,8 @@ interface ChartInstance {
268
283
  setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
269
284
  registerIndicator: (plugin: IndicatorPlugin<any>) => void;
270
285
  unregisterIndicator: (type: string) => void;
286
+ listBuiltInIndicators: () => BuiltInIndicatorInfo[];
287
+ getIndicators: () => IndicatorInstanceOptions[];
271
288
  addIndicator: (type: string, inputs?: Record<string, unknown>, options?: Partial<IndicatorInstanceOptions>) => string;
272
289
  updateIndicator: (id: string, patch: Partial<IndicatorInstanceOptions>) => void;
273
290
  removeIndicator: (id: string) => void;
@@ -285,4 +302,4 @@ interface OhlcDataPoint {
285
302
  }
286
303
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
287
304
 
288
- export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
305
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
package/dist/index.js CHANGED
@@ -109,6 +109,8 @@ var DEFAULT_OPTIONS = {
109
109
  backgroundColor: "#101114",
110
110
  axisColor: "#7f8289",
111
111
  axis: DEFAULT_AXIS_OPTIONS,
112
+ xAxis: DEFAULT_AXIS_OPTIONS,
113
+ yAxis: DEFAULT_AXIS_OPTIONS,
112
114
  priceDecimals: 2,
113
115
  stabilizePriceLabels: true,
114
116
  priceLabelMinIntegerDigits: 3,
@@ -127,6 +129,9 @@ var DEFAULT_OPTIONS = {
127
129
  candleBodyWidthRatio: 0.7,
128
130
  candleMinWidth: 0.5,
129
131
  candleWickWidth: 1,
132
+ tickSize: 0,
133
+ candleColorMode: "openClose",
134
+ candleColorEpsilon: -1,
130
135
  autoScaleSmoothing: 0.16,
131
136
  autoScaleIgnoreLatestCandle: true,
132
137
  doubleClickEnabled: true,
@@ -362,6 +367,35 @@ var computeAtrSeries = (data, length) => {
362
367
  }
363
368
  return result;
364
369
  };
370
+ var builtInSeriesCache = /* @__PURE__ */ new Map();
371
+ var getSeriesFingerprint = (data) => {
372
+ const length = data.length;
373
+ const last = length > 0 ? data[length - 1] : void 0;
374
+ const prev = length > 1 ? data[length - 2] : void 0;
375
+ if (!last) return "empty";
376
+ return [
377
+ length,
378
+ last.time.getTime(),
379
+ last.o,
380
+ last.h,
381
+ last.l,
382
+ last.c,
383
+ last.v ?? "",
384
+ prev?.time.getTime() ?? "",
385
+ prev?.c ?? "",
386
+ prev?.v ?? ""
387
+ ].join("|");
388
+ };
389
+ var withCachedSeries = (key, data, compute) => {
390
+ const fingerprint = getSeriesFingerprint(data);
391
+ const existing = builtInSeriesCache.get(key);
392
+ if (existing && existing.fingerprint === fingerprint) {
393
+ return existing.values;
394
+ }
395
+ const values = compute();
396
+ builtInSeriesCache.set(key, { fingerprint, values });
397
+ return values;
398
+ };
365
399
  var drawOverlaySeries = (ctx, renderContext, values, color, width) => {
366
400
  if (!renderContext.yFromPrice) return;
367
401
  const yFromPrice = renderContext.yFromPrice;
@@ -459,6 +493,8 @@ var BUILTIN_VOLUME_INDICATOR = {
459
493
  defaultInputs: {
460
494
  upOpacity: 0.7,
461
495
  downOpacity: 0.7,
496
+ upColor: "",
497
+ downColor: "",
462
498
  minBarWidth: 1,
463
499
  overlayHeightRatio: 0.22
464
500
  },
@@ -487,11 +523,13 @@ var BUILTIN_VOLUME_INDICATOR = {
487
523
  const xCenter = xFromIndex(index);
488
524
  const barX = Math.round(xCenter - barWidth / 2);
489
525
  const barY = Math.round(paneBottom - volumeHeight);
490
- const isUp = point.c >= point.o;
491
- const opacity = isUp ? upOpacity : downOpacity;
526
+ const direction = renderContext.getCandleDirectionByIndex(index);
527
+ const opacity = direction === "up" ? upOpacity : downOpacity;
528
+ const upBarColor = inputs.upColor && inputs.upColor.trim().length > 0 ? inputs.upColor : upColor;
529
+ const downBarColor = inputs.downColor && inputs.downColor.trim().length > 0 ? inputs.downColor : downColor;
492
530
  ctx.save();
493
531
  ctx.globalAlpha = opacity;
494
- ctx.fillStyle = isUp ? upColor : downColor;
532
+ ctx.fillStyle = direction === "up" ? upBarColor : downBarColor;
495
533
  ctx.fillRect(barX, barY, Math.max(1, Math.round(barWidth)), volumeHeight);
496
534
  ctx.restore();
497
535
  }
@@ -504,7 +542,11 @@ var BUILTIN_SMA_INDICATOR = {
504
542
  defaultInputs: { length: 20, source: "close", color: "#60a5fa", width: 2 },
505
543
  draw: (ctx, renderContext, inputs) => {
506
544
  const length = clampIndicatorLength(inputs.length, 20);
507
- const values = computeSmaSeries(renderContext.data, length, inputs.source ?? "close");
545
+ const values = withCachedSeries(
546
+ `sma|${length}|${inputs.source ?? "close"}`,
547
+ renderContext.data,
548
+ () => computeSmaSeries(renderContext.data, length, inputs.source ?? "close")
549
+ );
508
550
  drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#60a5fa", Number(inputs.width) || 2);
509
551
  }
510
552
  };
@@ -515,7 +557,11 @@ var BUILTIN_EMA_INDICATOR = {
515
557
  defaultInputs: { length: 20, source: "close", color: "#f59e0b", width: 2 },
516
558
  draw: (ctx, renderContext, inputs) => {
517
559
  const length = clampIndicatorLength(inputs.length, 20);
518
- const values = computeEmaSeries(renderContext.data, length, inputs.source ?? "close");
560
+ const values = withCachedSeries(
561
+ `ema|${length}|${inputs.source ?? "close"}`,
562
+ renderContext.data,
563
+ () => computeEmaSeries(renderContext.data, length, inputs.source ?? "close")
564
+ );
519
565
  drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#f59e0b", Number(inputs.width) || 2);
520
566
  }
521
567
  };
@@ -526,7 +572,11 @@ var BUILTIN_WMA_INDICATOR = {
526
572
  defaultInputs: { length: 20, source: "close", color: "#a78bfa", width: 2 },
527
573
  draw: (ctx, renderContext, inputs) => {
528
574
  const length = clampIndicatorLength(inputs.length, 20);
529
- const values = computeWmaSeries(renderContext.data, length, inputs.source ?? "close");
575
+ const values = withCachedSeries(
576
+ `wma|${length}|${inputs.source ?? "close"}`,
577
+ renderContext.data,
578
+ () => computeWmaSeries(renderContext.data, length, inputs.source ?? "close")
579
+ );
530
580
  drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#a78bfa", Number(inputs.width) || 2);
531
581
  }
532
582
  };
@@ -537,7 +587,11 @@ var BUILTIN_VWMA_INDICATOR = {
537
587
  defaultInputs: { length: 20, source: "close", color: "#ef4444", width: 2 },
538
588
  draw: (ctx, renderContext, inputs) => {
539
589
  const length = clampIndicatorLength(inputs.length, 20);
540
- const values = computeVwmaSeries(renderContext.data, length, inputs.source ?? "close");
590
+ const values = withCachedSeries(
591
+ `vwma|${length}|${inputs.source ?? "close"}`,
592
+ renderContext.data,
593
+ () => computeVwmaSeries(renderContext.data, length, inputs.source ?? "close")
594
+ );
541
595
  drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#ef4444", Number(inputs.width) || 2);
542
596
  }
543
597
  };
@@ -548,7 +602,11 @@ var BUILTIN_RMA_INDICATOR = {
548
602
  defaultInputs: { length: 14, source: "close", color: "#22c55e", width: 2 },
549
603
  draw: (ctx, renderContext, inputs) => {
550
604
  const length = clampIndicatorLength(inputs.length, 14);
551
- const values = computeRmaSeries(renderContext.data, length, inputs.source ?? "close");
605
+ const values = withCachedSeries(
606
+ `rma|${length}|${inputs.source ?? "close"}`,
607
+ renderContext.data,
608
+ () => computeRmaSeries(renderContext.data, length, inputs.source ?? "close")
609
+ );
552
610
  drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#22c55e", Number(inputs.width) || 2);
553
611
  }
554
612
  };
@@ -559,7 +617,11 @@ var BUILTIN_HMA_INDICATOR = {
559
617
  defaultInputs: { length: 21, source: "close", color: "#14b8a6", width: 2 },
560
618
  draw: (ctx, renderContext, inputs) => {
561
619
  const length = clampIndicatorLength(inputs.length, 21);
562
- const values = computeHmaSeries(renderContext.data, length, inputs.source ?? "close");
620
+ const values = withCachedSeries(
621
+ `hma|${length}|${inputs.source ?? "close"}`,
622
+ renderContext.data,
623
+ () => computeHmaSeries(renderContext.data, length, inputs.source ?? "close")
624
+ );
563
625
  drawOverlaySeries(ctx, renderContext, values, inputs.color ?? "#14b8a6", Number(inputs.width) || 2);
564
626
  }
565
627
  };
@@ -571,7 +633,11 @@ var BUILTIN_STDDEV_INDICATOR = {
571
633
  defaultInputs: { length: 20, source: "close", color: "#f97316", width: 2 },
572
634
  draw: (ctx, renderContext, inputs) => {
573
635
  const length = clampIndicatorLength(inputs.length, 20);
574
- const values = computeStdDevSeries(renderContext.data, length, inputs.source ?? "close");
636
+ const values = withCachedSeries(
637
+ `stddev|${length}|${inputs.source ?? "close"}`,
638
+ renderContext.data,
639
+ () => computeStdDevSeries(renderContext.data, length, inputs.source ?? "close")
640
+ );
575
641
  drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#f97316", Number(inputs.width) || 2);
576
642
  }
577
643
  };
@@ -583,7 +649,7 @@ var BUILTIN_ATR_INDICATOR = {
583
649
  defaultInputs: { length: 14, color: "#eab308", width: 2 },
584
650
  draw: (ctx, renderContext, inputs) => {
585
651
  const length = clampIndicatorLength(inputs.length, 14);
586
- const values = computeAtrSeries(renderContext.data, length);
652
+ const values = withCachedSeries(`atr|${length}`, renderContext.data, () => computeAtrSeries(renderContext.data, length));
587
653
  drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#eab308", Number(inputs.width) || 2);
588
654
  }
589
655
  };
@@ -595,7 +661,7 @@ var BUILTIN_RSI_INDICATOR = {
595
661
  defaultInputs: { length: 14, color: "#3b82f6", width: 2 },
596
662
  draw: (ctx, renderContext, inputs) => {
597
663
  const length = clampIndicatorLength(inputs.length, 14);
598
- const values = computeRsiSeries(renderContext.data, length);
664
+ const values = withCachedSeries(`rsi|${length}`, renderContext.data, () => computeRsiSeries(renderContext.data, length));
599
665
  drawSeparateSeries(
600
666
  ctx,
601
667
  renderContext,
@@ -629,6 +695,18 @@ function createChart(element, options = {}) {
629
695
  ...options.axis ?? {},
630
696
  ...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {}
631
697
  },
698
+ xAxis: {
699
+ ...DEFAULT_AXIS_OPTIONS,
700
+ ...options.axis ?? {},
701
+ ...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
702
+ ...options.xAxis ?? {}
703
+ },
704
+ yAxis: {
705
+ ...DEFAULT_AXIS_OPTIONS,
706
+ ...options.axis ?? {},
707
+ ...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
708
+ ...options.yAxis ?? {}
709
+ },
632
710
  crosshair: {
633
711
  ...DEFAULT_CROSSHAIR_OPTIONS,
634
712
  ...options.crosshair ?? {}
@@ -684,6 +762,9 @@ function createChart(element, options = {}) {
684
762
  visible: indicator.visible ?? true,
685
763
  pane: indicator.pane ?? plugin?.pane ?? "overlay",
686
764
  ...indicator.paneHeightRatio === void 0 ? {} : { paneHeightRatio: indicator.paneHeightRatio },
765
+ zIndex: Math.round(Number(indicator.zIndex) || 0),
766
+ excludeFromAutoscale: indicator.excludeFromAutoscale ?? true,
767
+ overlayScaleWeight: Math.min(1, Math.max(0, Number(indicator.overlayScaleWeight) || 0.25)),
687
768
  inputs: {
688
769
  ...defaults,
689
770
  ...indicator.inputs ?? {}
@@ -839,9 +920,63 @@ function createChart(element, options = {}) {
839
920
  }
840
921
  return { min: nextMin, max: nextMax };
841
922
  };
923
+ const getConfiguredPriceDecimals = () => clamp(Math.round(mergedOptions.priceDecimals), 0, 8);
924
+ const getConfiguredTickSize = () => {
925
+ const step = Number(mergedOptions.tickSize);
926
+ return Number.isFinite(step) && step > 0 ? step : 0;
927
+ };
928
+ const getTickSizeDecimals = () => {
929
+ const tickSize = getConfiguredTickSize();
930
+ if (tickSize <= 0) {
931
+ return 0;
932
+ }
933
+ let scaled = tickSize;
934
+ let decimals = 0;
935
+ while (decimals < 8 && Math.abs(Math.round(scaled) - scaled) > 1e-10) {
936
+ scaled *= 10;
937
+ decimals += 1;
938
+ }
939
+ return decimals;
940
+ };
941
+ const getDisplayPriceDecimals = () => {
942
+ const configured = getConfiguredPriceDecimals();
943
+ const tickDecimals = getTickSizeDecimals();
944
+ return Math.max(configured, tickDecimals);
945
+ };
946
+ const quantizeToTickSize = (price) => {
947
+ const tickSize = getConfiguredTickSize();
948
+ if (tickSize <= 0 || !Number.isFinite(price)) {
949
+ return price;
950
+ }
951
+ return Math.round(price / tickSize) * tickSize;
952
+ };
842
953
  const formatPrice = (price) => {
843
- const decimals = clamp(Math.round(mergedOptions.priceDecimals), 0, 8);
844
- return price.toFixed(decimals);
954
+ const rounded = quantizeToTickSize(price);
955
+ const decimals = getDisplayPriceDecimals();
956
+ return rounded.toFixed(decimals);
957
+ };
958
+ const roundToPricePrecision = (price) => {
959
+ const rounded = quantizeToTickSize(price);
960
+ const decimals = getDisplayPriceDecimals();
961
+ return Number(rounded.toFixed(decimals));
962
+ };
963
+ const getResolvedCandleColorEpsilon = () => {
964
+ const configured = mergedOptions.candleColorEpsilon;
965
+ if (configured >= 0) {
966
+ return configured;
967
+ }
968
+ const tickSize = getConfiguredTickSize();
969
+ if (tickSize > 0) {
970
+ return tickSize / 2;
971
+ }
972
+ const decimals = getDisplayPriceDecimals();
973
+ return decimals > 0 ? 0.5 / 10 ** decimals : 0;
974
+ };
975
+ const getDirectionFromDelta = (delta) => {
976
+ const epsilon = Math.max(0, getResolvedCandleColorEpsilon());
977
+ if (delta > epsilon) return 1;
978
+ if (delta < -epsilon) return -1;
979
+ return 0;
845
980
  };
846
981
  const getStabilizedPriceTemplate = () => {
847
982
  const explicitTemplate = mergedOptions.priceLabelWidthTemplate.trim();
@@ -869,14 +1004,33 @@ function createChart(element, options = {}) {
869
1004
  return Math.max(measured, templateWidth);
870
1005
  };
871
1006
  const parseData = (nextData) => {
872
- return nextData.map((point) => ({
873
- time: new Date(point.t),
874
- o: point.o,
875
- h: point.h,
876
- l: point.l,
877
- c: point.c,
878
- ...point.v === void 0 ? {} : { v: point.v }
879
- })).filter((point) => Number.isFinite(point.time.getTime())).sort((a, b) => a.time.getTime() - b.time.getTime());
1007
+ const dedupedByTime = /* @__PURE__ */ new Map();
1008
+ for (const point of nextData) {
1009
+ const time = new Date(point.t);
1010
+ const timeMs = time.getTime();
1011
+ if (!Number.isFinite(timeMs)) {
1012
+ continue;
1013
+ }
1014
+ const open = Number(point.o);
1015
+ const close = Number(point.c);
1016
+ const highInput = Number(point.h);
1017
+ const lowInput = Number(point.l);
1018
+ if (!Number.isFinite(open) || !Number.isFinite(close) || !Number.isFinite(highInput) || !Number.isFinite(lowInput)) {
1019
+ continue;
1020
+ }
1021
+ const normalizedHigh = Math.max(highInput, open, close);
1022
+ const normalizedLow = Math.min(lowInput, open, close);
1023
+ const volumeValue = point.v === void 0 ? void 0 : Number(point.v);
1024
+ dedupedByTime.set(timeMs, {
1025
+ time,
1026
+ o: open,
1027
+ h: normalizedHigh,
1028
+ l: normalizedLow,
1029
+ c: close,
1030
+ ...volumeValue === void 0 || !Number.isFinite(volumeValue) ? {} : { v: volumeValue }
1031
+ });
1032
+ }
1033
+ return Array.from(dedupedByTime.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
880
1034
  };
881
1035
  const getTimeStepMs = () => {
882
1036
  if (data.length < 2) {
@@ -953,6 +1107,26 @@ function createChart(element, options = {}) {
953
1107
  const upperDelta = Math.abs(upperPoint.time.getTime() - timeMs);
954
1108
  return lowerDelta <= upperDelta ? lower : upper;
955
1109
  };
1110
+ const getCandleDirectionByIndex = (index) => {
1111
+ const point = data[index];
1112
+ if (!point) {
1113
+ return "up";
1114
+ }
1115
+ const prevPoint = index > 0 ? data[index - 1] : void 0;
1116
+ const mode = mergedOptions.candleColorMode;
1117
+ const baseForMode = mode === "prevClose" && prevPoint ? prevPoint.c : point.o;
1118
+ let direction = getDirectionFromDelta(point.c - baseForMode);
1119
+ if (direction === 0 && mode === "prevClose") {
1120
+ direction = getDirectionFromDelta(point.c - point.o);
1121
+ }
1122
+ if (direction === 0 && prevPoint) {
1123
+ direction = getDirectionFromDelta(point.c - prevPoint.c);
1124
+ }
1125
+ if (direction === 0) {
1126
+ return point.c >= point.o ? "up" : "down";
1127
+ }
1128
+ return direction > 0 ? "up" : "down";
1129
+ };
956
1130
  const formatHoverTimeLabel = (time, mode) => {
957
1131
  if (mode === "time") {
958
1132
  return time.toLocaleTimeString(void 0, {
@@ -1327,6 +1501,10 @@ function createChart(element, options = {}) {
1327
1501
  canvas.height = Math.floor(height * pixelRatio);
1328
1502
  ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
1329
1503
  const axis = { ...DEFAULT_AXIS_OPTIONS, ...mergedOptions.axis ?? {} };
1504
+ const xAxis = { ...DEFAULT_AXIS_OPTIONS, ...mergedOptions.xAxis ?? {} };
1505
+ const yAxis = { ...DEFAULT_AXIS_OPTIONS, ...mergedOptions.yAxis ?? {} };
1506
+ const xAxisFontSize = Math.max(8, xAxis.fontSize);
1507
+ const yAxisFontSize = Math.max(8, yAxis.fontSize);
1330
1508
  ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
1331
1509
  ctx.fillStyle = mergedOptions.backgroundColor;
1332
1510
  ctx.fillRect(0, 0, width, height);
@@ -1341,6 +1519,9 @@ function createChart(element, options = {}) {
1341
1519
  const separatePaneSpacing = 6;
1342
1520
  const activeSeparateIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
1343
1521
  (value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "separate"
1522
+ ).sort((a, b) => a.indicator.zIndex - b.indicator.zIndex);
1523
+ const overlayIndicatorsForScale = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
1524
+ (value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "overlay"
1344
1525
  );
1345
1526
  const separatePaneHeightDefaults = activeSeparateIndicators.map(({ indicator, plugin }) => {
1346
1527
  const ratio = Math.min(0.45, Math.max(0.08, indicator.paneHeightRatio ?? plugin.paneHeightRatio ?? 0.22));
@@ -1392,8 +1573,47 @@ function createChart(element, options = {}) {
1392
1573
  }
1393
1574
  }
1394
1575
  }
1395
- const minPrice = Math.min(...priceSource.map((point) => point.l));
1396
- const maxPrice = Math.max(...priceSource.map((point) => point.h));
1576
+ let minPrice = Math.min(...priceSource.map((point) => point.l));
1577
+ let maxPrice = Math.max(...priceSource.map((point) => point.h));
1578
+ if (overlayIndicatorsForScale.length > 0) {
1579
+ for (const { indicator } of overlayIndicatorsForScale) {
1580
+ if (indicator.excludeFromAutoscale) {
1581
+ continue;
1582
+ }
1583
+ const type = indicator.type;
1584
+ const inputs = indicator.inputs;
1585
+ const source = inputs.source ?? "close";
1586
+ const length = clampIndicatorLength(inputs.length ?? 14, 14);
1587
+ let series = null;
1588
+ if (type === "sma") series = withCachedSeries(`sma|${length}|${source}`, data, () => computeSmaSeries(data, length, source));
1589
+ if (type === "ema") series = withCachedSeries(`ema|${length}|${source}`, data, () => computeEmaSeries(data, length, source));
1590
+ if (type === "wma") series = withCachedSeries(`wma|${length}|${source}`, data, () => computeWmaSeries(data, length, source));
1591
+ if (type === "vwma") series = withCachedSeries(`vwma|${length}|${source}`, data, () => computeVwmaSeries(data, length, source));
1592
+ if (type === "rma") series = withCachedSeries(`rma|${length}|${source}`, data, () => computeRmaSeries(data, length, source));
1593
+ if (type === "hma") series = withCachedSeries(`hma|${length}|${source}`, data, () => computeHmaSeries(data, length, source));
1594
+ if (!series) {
1595
+ continue;
1596
+ }
1597
+ const visibleValues = [];
1598
+ for (let idx = startIndex; idx <= endIndex; idx += 1) {
1599
+ const value = series[idx];
1600
+ if (Number.isFinite(value ?? Number.NaN)) {
1601
+ visibleValues.push(value);
1602
+ }
1603
+ }
1604
+ if (visibleValues.length === 0) {
1605
+ continue;
1606
+ }
1607
+ const seriesMin = Math.min(...visibleValues);
1608
+ const seriesMax = Math.max(...visibleValues);
1609
+ const weight = Math.min(1, Math.max(0, indicator.overlayScaleWeight));
1610
+ const currentMid = (minPrice + maxPrice) / 2;
1611
+ const weightedMin = currentMid + (seriesMin - currentMid) * weight;
1612
+ const weightedMax = currentMid + (seriesMax - currentMid) * weight;
1613
+ minPrice = Math.min(minPrice, weightedMin);
1614
+ maxPrice = Math.max(maxPrice, weightedMax);
1615
+ }
1616
+ }
1397
1617
  const priceRange = maxPrice - minPrice || 1;
1398
1618
  const autoMin = minPrice - priceRange * 0.08;
1399
1619
  const autoMax = maxPrice + priceRange * 0.08;
@@ -1538,8 +1758,8 @@ function createChart(element, options = {}) {
1538
1758
  const closeY = yFromPrice(point.c);
1539
1759
  const highY = yFromPrice(point.h);
1540
1760
  const lowY = yFromPrice(point.l);
1541
- const isUp = point.c >= point.o;
1542
- const candleColor = isUp ? mergedOptions.upColor : mergedOptions.downColor;
1761
+ const direction = getCandleDirectionByIndex(index);
1762
+ const candleColor = direction === "up" ? mergedOptions.upColor : mergedOptions.downColor;
1543
1763
  ctx.strokeStyle = candleColor;
1544
1764
  ctx.lineWidth = candleWickWidth;
1545
1765
  ctx.beginPath();
@@ -1553,7 +1773,7 @@ function createChart(element, options = {}) {
1553
1773
  }
1554
1774
  const activeOverlayIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
1555
1775
  (value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "overlay"
1556
- );
1776
+ ).sort((a, b) => a.indicator.zIndex - b.indicator.zIndex);
1557
1777
  if (activeOverlayIndicators.length > 0) {
1558
1778
  const xFromIndex = (index) => chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
1559
1779
  activeOverlayIndicators.forEach(({ indicator, plugin }) => {
@@ -1573,6 +1793,7 @@ function createChart(element, options = {}) {
1573
1793
  chartHeight,
1574
1794
  xFromIndex,
1575
1795
  yFromPrice,
1796
+ getCandleDirectionByIndex,
1576
1797
  candleSpacing,
1577
1798
  upColor: mergedOptions.upColor,
1578
1799
  downColor: mergedOptions.downColor
@@ -1632,6 +1853,7 @@ function createChart(element, options = {}) {
1632
1853
  chartHeight: paneHeight,
1633
1854
  xFromIndex,
1634
1855
  yFromPrice: null,
1856
+ getCandleDirectionByIndex,
1635
1857
  candleSpacing,
1636
1858
  upColor: mergedOptions.upColor,
1637
1859
  downColor: mergedOptions.downColor
@@ -1661,7 +1883,10 @@ function createChart(element, options = {}) {
1661
1883
  const ratio = tick / yTicks;
1662
1884
  const price = yMin + yRange * ratio;
1663
1885
  const y = yFromPrice(price);
1664
- drawText(formatPrice(price), chartRight + 6, y, "left", "middle", axis.textColor);
1886
+ const prevFont = ctx.font;
1887
+ ctx.font = `${yAxisFontSize}px ${mergedOptions.fontFamily}`;
1888
+ drawText(formatPrice(price), chartRight + 6, y, "left", "middle", yAxis.textColor);
1889
+ ctx.font = prevFont;
1665
1890
  }
1666
1891
  const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1667
1892
  const lastPoint = data[data.length - 1];
@@ -1669,7 +1894,8 @@ function createChart(element, options = {}) {
1669
1894
  const tickerPrice = lastPoint.c;
1670
1895
  const tickerY = yFromPrice(tickerPrice);
1671
1896
  const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
1672
- const tickerColor = ticker.color ?? (lastPoint.c >= lastPoint.o ? mergedOptions.upColor : mergedOptions.downColor);
1897
+ const lastDirection = getCandleDirectionByIndex(data.length - 1);
1898
+ const tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
1673
1899
  const tickerThickness = Math.max(1, ticker.thickness ?? 1);
1674
1900
  const tickerStyle = ticker.style ?? "solid";
1675
1901
  ctx.save();
@@ -1717,7 +1943,10 @@ function createChart(element, options = {}) {
1717
1943
  month: "short",
1718
1944
  day: "numeric"
1719
1945
  });
1720
- drawText(timeLabel, x, fullChartBottom + 8, "center", "top", axis.textColor);
1946
+ const prevFont = ctx.font;
1947
+ ctx.font = `${xAxisFontSize}px ${mergedOptions.fontFamily}`;
1948
+ drawText(timeLabel, x, fullChartBottom + 8, "center", "top", xAxis.textColor);
1949
+ ctx.font = prevFont;
1721
1950
  }
1722
1951
  if (crosshair.visible && crosshairPoint) {
1723
1952
  const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
@@ -2037,7 +2266,7 @@ function createChart(element, options = {}) {
2037
2266
  x,
2038
2267
  y,
2039
2268
  region,
2040
- ...region === "plot" ? { price: Number(priceFromCanvasY(y).toFixed(mergedOptions.priceDecimals)) } : {},
2269
+ ...region === "plot" ? { price: roundToPricePrecision(priceFromCanvasY(y)) } : {},
2041
2270
  ...index === null ? {} : { index },
2042
2271
  ...hoverTime ? { time: hoverTime.toISOString() } : {},
2043
2272
  ...point ? { point } : {}
@@ -2083,7 +2312,7 @@ function createChart(element, options = {}) {
2083
2312
  if (orderRegion) {
2084
2313
  if (orderRegion.draggable) {
2085
2314
  activePointerId = event.pointerId;
2086
- const startPrice = Number(orderRegion.line.price.toFixed(2));
2315
+ const startPrice = roundToPricePrecision(orderRegion.line.price);
2087
2316
  actionDragState = {
2088
2317
  orderId: orderRegion.orderId,
2089
2318
  action: orderRegion.action,
@@ -2142,7 +2371,7 @@ function createChart(element, options = {}) {
2142
2371
  if (activePointerId !== null && event.pointerId !== activePointerId) {
2143
2372
  return;
2144
2373
  }
2145
- const nextPrice = Number(priceFromCanvasY(point.y).toFixed(2));
2374
+ const nextPrice = roundToPricePrecision(priceFromCanvasY(point.y));
2146
2375
  if (nextPrice !== orderDragState.lastPrice) {
2147
2376
  orderDragState.lastPrice = nextPrice;
2148
2377
  orderLines = orderLines.map((line) => {
@@ -2172,7 +2401,7 @@ function createChart(element, options = {}) {
2172
2401
  if (activePointerId !== null && event.pointerId !== activePointerId) {
2173
2402
  return;
2174
2403
  }
2175
- const nextPrice = Number(priceFromCanvasY(point.y).toFixed(2));
2404
+ const nextPrice = roundToPricePrecision(priceFromCanvasY(point.y));
2176
2405
  if (nextPrice !== actionDragState.lastPrice) {
2177
2406
  actionDragState.lastPrice = nextPrice;
2178
2407
  actionDragState.moved = true;
@@ -2289,7 +2518,7 @@ function createChart(element, options = {}) {
2289
2518
  canvas.style.cursor = "default";
2290
2519
  if (event && pointerDownInfo && event.pointerId === pointerDownInfo.pointerId) {
2291
2520
  if (!pointerDownInfo.moved) {
2292
- const clickPrice = pointerDownInfo.region === "plot" ? Number(priceFromCanvasY(pointerDownInfo.y).toFixed(2)) : void 0;
2521
+ const clickPrice = pointerDownInfo.region === "plot" ? roundToPricePrecision(priceFromCanvasY(pointerDownInfo.y)) : void 0;
2293
2522
  chartClickHandler?.({
2294
2523
  x: pointerDownInfo.x,
2295
2524
  y: pointerDownInfo.y,
@@ -2349,7 +2578,7 @@ function createChart(element, options = {}) {
2349
2578
  }
2350
2579
  orderActionHandler?.({
2351
2580
  action: "createLimit",
2352
- price: Number(priceFromCanvasY(point.y).toFixed(2))
2581
+ price: roundToPricePrecision(priceFromCanvasY(point.y))
2353
2582
  });
2354
2583
  return;
2355
2584
  }
@@ -2488,6 +2717,27 @@ function createChart(element, options = {}) {
2488
2717
  indicators = indicators.filter((indicator) => indicator.type !== type);
2489
2718
  draw();
2490
2719
  };
2720
+ const listBuiltInIndicators = () => {
2721
+ return BUILTIN_INDICATORS.map((indicator) => ({
2722
+ id: indicator.id,
2723
+ name: indicator.name,
2724
+ pane: indicator.pane ?? "overlay",
2725
+ ...indicator.paneHeightRatio === void 0 ? {} : { paneHeightRatio: indicator.paneHeightRatio }
2726
+ }));
2727
+ };
2728
+ const getIndicators = () => {
2729
+ return indicators.map((indicator) => ({
2730
+ id: indicator.id,
2731
+ type: indicator.type,
2732
+ visible: indicator.visible,
2733
+ pane: indicator.pane,
2734
+ ...indicator.paneHeightRatio === void 0 ? {} : { paneHeightRatio: indicator.paneHeightRatio },
2735
+ zIndex: indicator.zIndex,
2736
+ excludeFromAutoscale: indicator.excludeFromAutoscale,
2737
+ overlayScaleWeight: indicator.overlayScaleWeight,
2738
+ inputs: { ...indicator.inputs }
2739
+ }));
2740
+ };
2491
2741
  const setIndicators = (nextIndicators) => {
2492
2742
  indicators = nextIndicators.map((indicator) => normalizeIndicatorState(indicator));
2493
2743
  draw();
@@ -2517,6 +2767,9 @@ function createChart(element, options = {}) {
2517
2767
  visible: patch.visible ?? indicator.visible,
2518
2768
  pane: patch.pane ?? indicator.pane ?? plugin?.pane ?? "overlay",
2519
2769
  ...patch.paneHeightRatio !== void 0 || indicator.paneHeightRatio !== void 0 ? { paneHeightRatio: patch.paneHeightRatio ?? indicator.paneHeightRatio } : {},
2770
+ zIndex: patch.zIndex === void 0 ? indicator.zIndex : Math.round(Number(patch.zIndex) || 0),
2771
+ excludeFromAutoscale: patch.excludeFromAutoscale ?? indicator.excludeFromAutoscale,
2772
+ overlayScaleWeight: patch.overlayScaleWeight === void 0 ? indicator.overlayScaleWeight : Math.min(1, Math.max(0, Number(patch.overlayScaleWeight) || 0)),
2520
2773
  type: patch.type ?? indicator.type,
2521
2774
  inputs: {
2522
2775
  ...indicator.inputs,
@@ -2566,6 +2819,8 @@ function createChart(element, options = {}) {
2566
2819
  setDoubleClickAction,
2567
2820
  registerIndicator,
2568
2821
  unregisterIndicator,
2822
+ listBuiltInIndicators,
2823
+ getIndicators,
2569
2824
  addIndicator,
2570
2825
  updateIndicator,
2571
2826
  removeIndicator,