hyperprop-charting-library 0.1.130 → 0.1.132

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.
@@ -22,6 +22,7 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  FIB_DEFAULT_PALETTE: () => FIB_DEFAULT_PALETTE,
24
24
  POSITION_DEFAULT_COLORS: () => POSITION_DEFAULT_COLORS,
25
+ compileScriptIndicator: () => compileScriptIndicator,
25
26
  createChart: () => createChart
26
27
  });
27
28
  module.exports = __toCommonJS(index_exports);
@@ -1494,6 +1495,195 @@ var drawSeparateMultiSeries = (ctx, renderContext, seriesList, options = {}) =>
1494
1495
  }
1495
1496
  return paneInfo;
1496
1497
  };
1498
+ var scriptRollingExtreme = (values, length, mode) => {
1499
+ const fallback = mode === "max" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
1500
+ const filled = new Array(values.length);
1501
+ for (let i = 0; i < values.length; i += 1) {
1502
+ const value = values[i];
1503
+ filled[i] = value != null && Number.isFinite(value) ? value : fallback;
1504
+ }
1505
+ const raw = rollingExtremeSeries(filled, Math.max(1, Math.floor(length)), mode);
1506
+ return raw.map((value) => value != null && Number.isFinite(value) ? value : null);
1507
+ };
1508
+ var scriptStdevSeries = (values, length) => {
1509
+ const window2 = Math.max(1, Math.floor(length));
1510
+ const result = new Array(values.length).fill(null);
1511
+ let sum = 0;
1512
+ let sumSq = 0;
1513
+ let valid = 0;
1514
+ for (let i = 0; i < values.length; i += 1) {
1515
+ const value = values[i];
1516
+ if (value == null || !Number.isFinite(value)) {
1517
+ sum = 0;
1518
+ sumSq = 0;
1519
+ valid = 0;
1520
+ continue;
1521
+ }
1522
+ sum += value;
1523
+ sumSq += value * value;
1524
+ valid += 1;
1525
+ if (valid > window2) {
1526
+ const old = values[i - window2];
1527
+ sum -= old;
1528
+ sumSq -= old * old;
1529
+ valid -= 1;
1530
+ }
1531
+ if (valid === window2) {
1532
+ const mean = sum / window2;
1533
+ result[i] = Math.sqrt(Math.max(0, sumSq / window2 - mean * mean));
1534
+ }
1535
+ }
1536
+ return result;
1537
+ };
1538
+ var scriptAtrSeries = (bars, length) => {
1539
+ const tr = new Array(bars.length).fill(null);
1540
+ for (let i = 0; i < bars.length; i += 1) {
1541
+ const bar = bars[i];
1542
+ if (i === 0) {
1543
+ tr[i] = bar.h - bar.l;
1544
+ continue;
1545
+ }
1546
+ const prevClose = bars[i - 1].c;
1547
+ tr[i] = Math.max(bar.h - bar.l, Math.abs(bar.h - prevClose), Math.abs(bar.l - prevClose));
1548
+ }
1549
+ return rmaFromValues(tr, Math.max(1, Math.floor(length)));
1550
+ };
1551
+ var SCRIPT_HELPERS = Object.freeze({
1552
+ sma: (values, length) => smaFromValues(values, Math.max(1, Math.floor(length))),
1553
+ ema: (values, length) => emaFromValues(values, Math.max(1, Math.floor(length))),
1554
+ rma: (values, length) => rmaFromValues(values, Math.max(1, Math.floor(length))),
1555
+ highest: (values, length) => scriptRollingExtreme(values, length, "max"),
1556
+ lowest: (values, length) => scriptRollingExtreme(values, length, "min"),
1557
+ change: (values, length = 1) => values.map((value, index) => {
1558
+ const prev = values[index - Math.max(1, Math.floor(length))];
1559
+ return value != null && prev != null ? value - prev : null;
1560
+ }),
1561
+ stdev: scriptStdevSeries,
1562
+ atr: scriptAtrSeries
1563
+ });
1564
+ var SCRIPT_PLOT_PALETTE = ["#2962ff", "#ff6d00", "#26a69a", "#ab47bc", "#ef5350", "#fdd835"];
1565
+ var hashScriptSource = (source) => {
1566
+ let hash = 5381;
1567
+ for (let i = 0; i < source.length; i += 1) {
1568
+ hash = (hash << 5) + hash + source.charCodeAt(i) | 0;
1569
+ }
1570
+ return (hash >>> 0).toString(36);
1571
+ };
1572
+ var compileScriptCompute = (source) => {
1573
+ const factory = new Function(
1574
+ `"use strict";
1575
+ ${source}
1576
+ ;if (typeof compute !== "function") { throw new Error("Script must define function compute(bars, inputs, hp)"); }
1577
+ return compute;`
1578
+ );
1579
+ return factory();
1580
+ };
1581
+ var drawScriptError = (ctx, renderContext, name, message) => {
1582
+ ctx.save();
1583
+ ctx.font = "11px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
1584
+ ctx.textAlign = "left";
1585
+ ctx.textBaseline = "top";
1586
+ ctx.fillStyle = "#ef5350";
1587
+ const text = `${name}: ${message}`.slice(0, 160);
1588
+ ctx.fillText(text, renderContext.chartLeft + 10, renderContext.chartTop + 24);
1589
+ ctx.restore();
1590
+ };
1591
+ var compileScriptIndicator = (definition) => {
1592
+ const pane = definition.pane ?? "separate";
1593
+ const defaultInputs = { showValueLine: true };
1594
+ for (const input of definition.inputs ?? []) {
1595
+ defaultInputs[input.key] = input.default;
1596
+ }
1597
+ let compiled = null;
1598
+ let compileError = null;
1599
+ try {
1600
+ compiled = compileScriptCompute(definition.source);
1601
+ } catch (error) {
1602
+ compileError = error instanceof Error ? error.message : String(error);
1603
+ }
1604
+ const sourceKey = hashScriptSource(definition.source);
1605
+ const runCompute = (data, inputs) => {
1606
+ if (!compiled) {
1607
+ return { error: compileError ?? "Script failed to compile" };
1608
+ }
1609
+ try {
1610
+ const result = compiled(data, inputs, SCRIPT_HELPERS);
1611
+ if (!result || !Array.isArray(result.plots)) {
1612
+ return { error: "compute() must return { plots: [...] }" };
1613
+ }
1614
+ for (const plot of result.plots) {
1615
+ if (!plot || !Array.isArray(plot.values)) {
1616
+ return { error: "Every plot needs a `values` array" };
1617
+ }
1618
+ }
1619
+ return { result };
1620
+ } catch (error) {
1621
+ return { error: error instanceof Error ? error.message : String(error) };
1622
+ }
1623
+ };
1624
+ const cachedCompute = (data, inputs) => withCachedComputation(
1625
+ `script|${definition.id}|${sourceKey}|${JSON.stringify(inputs)}`,
1626
+ data,
1627
+ () => runCompute(data, inputs)
1628
+ );
1629
+ const plugin = {
1630
+ id: definition.id,
1631
+ name: definition.name,
1632
+ pane,
1633
+ defaultInputs,
1634
+ draw: (ctx, renderContext, inputs) => {
1635
+ const outcome = cachedCompute(renderContext.data, inputs);
1636
+ if (!outcome.result) {
1637
+ drawScriptError(ctx, renderContext, definition.name, outcome.error ?? "Unknown script error");
1638
+ return pane === "separate" ? { title: `${definition.name} (script error)` } : void 0;
1639
+ }
1640
+ const { plots, range, guides, decimals } = outcome.result;
1641
+ if (pane === "separate") {
1642
+ const seriesList = plots.map((plot, index) => ({
1643
+ values: plot.values,
1644
+ color: plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
1645
+ ...plot.width !== void 0 ? { width: plot.width } : {},
1646
+ ...plot.title ? { label: plot.title } : {},
1647
+ ...plot.style === "histogram" ? { histogram: true } : {},
1648
+ ...plot.negativeColor ? { negativeColor: plot.negativeColor } : {}
1649
+ }));
1650
+ return drawSeparateMultiSeries(ctx, renderContext, seriesList, {
1651
+ title: definition.name,
1652
+ ...range?.min !== void 0 ? { minOverride: range.min } : {},
1653
+ ...range?.max !== void 0 ? { maxOverride: range.max } : {},
1654
+ ...guides && guides.length > 0 ? { guideLines: guides } : {},
1655
+ ...decimals !== void 0 ? { decimals } : {},
1656
+ valueLines: inputs.showValueLine !== false
1657
+ });
1658
+ }
1659
+ for (let index = 0; index < plots.length; index += 1) {
1660
+ const plot = plots[index];
1661
+ if (plot.style === "histogram") continue;
1662
+ drawOverlaySeries(
1663
+ ctx,
1664
+ renderContext,
1665
+ plot.values,
1666
+ plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
1667
+ plot.width ?? 2
1668
+ );
1669
+ }
1670
+ return void 0;
1671
+ },
1672
+ ...pane === "overlay" ? {
1673
+ getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
1674
+ const outcome = cachedCompute(data, inputs);
1675
+ if (!outcome.result) return null;
1676
+ const lineSeries = outcome.result.plots.filter((plot) => plot.style !== "histogram").map((plot) => plot.values);
1677
+ if (lineSeries.length === 0) return null;
1678
+ return rangeOfSeries(lineSeries, startIndex, endIndex, skipIndex);
1679
+ }
1680
+ } : {}
1681
+ };
1682
+ if (definition.paneHeightRatio !== void 0) {
1683
+ plugin.paneHeightRatio = definition.paneHeightRatio;
1684
+ }
1685
+ return plugin;
1686
+ };
1497
1687
  var computeMacd = (data, fast, slow, signalLength) => {
1498
1688
  const fastEma = computeEmaSeries(data, fast, "close");
1499
1689
  const slowEma = computeEmaSeries(data, slow, "close");
@@ -2545,7 +2735,7 @@ function createChart(element, options = {}) {
2545
2735
  return;
2546
2736
  }
2547
2737
  const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
2548
- const speed = clamp(tickerOpts.smoothingSpeed ?? 4, 1, 60);
2738
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 16, 1, 60);
2549
2739
  const dt = 1 / 60;
2550
2740
  const lerp = 1 - Math.exp(-speed * dt);
2551
2741
  smoothedTickerPrice += priceDiff * lerp;
@@ -2575,6 +2765,29 @@ function createChart(element, options = {}) {
2575
2765
  smoothingRafId = requestAnimationFrame(tickerSmoothingLoop);
2576
2766
  }
2577
2767
  };
2768
+ let countdownTimerId = null;
2769
+ const countdownRepaintNeeded = () => {
2770
+ if (data.length === 0) {
2771
+ return false;
2772
+ }
2773
+ const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
2774
+ const labels = mergedOptions.labels ?? DEFAULT_OPTIONS.labels;
2775
+ return Boolean(ticker.showCountdownInLabel) || Boolean(labels.visible && labels.showCountdownToBarClose);
2776
+ };
2777
+ const armCountdownHeartbeat = () => {
2778
+ if (countdownTimerId !== null || !countdownRepaintNeeded()) {
2779
+ return;
2780
+ }
2781
+ const delay = 1e3 - Date.now() % 1e3 + 15;
2782
+ countdownTimerId = setTimeout(() => {
2783
+ countdownTimerId = null;
2784
+ if (!countdownRepaintNeeded()) {
2785
+ return;
2786
+ }
2787
+ scheduleDraw({ updateAutoScale: false });
2788
+ armCountdownHeartbeat();
2789
+ }, delay);
2790
+ };
2578
2791
  const canvas = document.createElement("canvas");
2579
2792
  const ctx = canvas.getContext("2d");
2580
2793
  if (!ctx) {
@@ -3500,6 +3713,7 @@ function createChart(element, options = {}) {
3500
3713
  let pendingDrawUpdateAutoScale = false;
3501
3714
  let pendingDrawOverlayOnly = true;
3502
3715
  const scheduleDraw = (options2 = {}) => {
3716
+ armCountdownHeartbeat();
3503
3717
  const overlayOnly = options2.overlayOnly ?? false;
3504
3718
  pendingDrawOverlayOnly = pendingDrawOverlayOnly && overlayOnly;
3505
3719
  pendingDrawUpdateAutoScale = pendingDrawUpdateAutoScale || !overlayOnly && (options2.updateAutoScale ?? true);
@@ -5084,18 +5298,17 @@ function createChart(element, options = {}) {
5084
5298
  legendTitle,
5085
5299
  ...legendValues.map((value) => value.text ?? (value.value === void 0 ? "" : formatPaneValue(value.value))).filter(Boolean)
5086
5300
  ].filter(Boolean);
5087
- const legendText = legendParts.join(" ");
5088
- if (legendParts.length > 0) {
5089
- drawText(legendText, chartLeft + 10, paneTop + 8, "left", "top", labels.indicatorTextColor);
5090
- }
5091
5301
  if (hoveredPaneId === indicator.id) {
5302
+ drawText(legendTitle, chartLeft + 10, paneTop + 8, "left", "top", labels.indicatorTextColor);
5092
5303
  drawPaneButtons(
5093
5304
  indicator.id,
5094
5305
  indicator.type,
5095
5306
  true,
5096
- chartLeft + 10 + Math.ceil(measureTextWidth(legendText)) + 10,
5307
+ chartLeft + 10 + Math.ceil(measureTextWidth(legendTitle)) + 10,
5097
5308
  paneTop + 8 + legendFontSize / 2
5098
5309
  );
5310
+ } else if (legendParts.length > 0) {
5311
+ drawText(legendParts.join(" "), chartLeft + 10, paneTop + 8, "left", "top", labels.indicatorTextColor);
5099
5312
  }
5100
5313
  for (const label of paneInfo?.valueLabels ?? []) {
5101
5314
  if (!labels.showIndicatorValueLabels) {
@@ -7758,6 +7971,10 @@ function createChart(element, options = {}) {
7758
7971
  cancelAnimationFrame(smoothingRafId);
7759
7972
  smoothingRafId = null;
7760
7973
  }
7974
+ if (countdownTimerId !== null) {
7975
+ clearTimeout(countdownTimerId);
7976
+ countdownTimerId = null;
7977
+ }
7761
7978
  if (drawRafId !== null) {
7762
7979
  cancelAnimationFrame(drawRafId);
7763
7980
  drawRafId = null;
@@ -7779,6 +7996,7 @@ function createChart(element, options = {}) {
7779
7996
  element.innerHTML = "";
7780
7997
  };
7781
7998
  draw();
7999
+ armCountdownHeartbeat();
7782
8000
  return {
7783
8001
  updateOptions,
7784
8002
  setChartType,
@@ -7849,5 +8067,6 @@ function createChart(element, options = {}) {
7849
8067
  0 && (module.exports = {
7850
8068
  FIB_DEFAULT_PALETTE,
7851
8069
  POSITION_DEFAULT_COLORS,
8070
+ compileScriptIndicator,
7852
8071
  createChart
7853
8072
  });
@@ -501,6 +501,62 @@ interface LabelsOptions {
501
501
  labelHeight?: number;
502
502
  labelPaddingX?: number;
503
503
  }
504
+ interface ScriptIndicatorInputDef {
505
+ key: string;
506
+ label?: string;
507
+ type?: "number" | "color" | "boolean" | "select" | "string";
508
+ default: unknown;
509
+ min?: number;
510
+ max?: number;
511
+ step?: number;
512
+ options?: Array<{
513
+ value: string;
514
+ label: string;
515
+ }>;
516
+ }
517
+ interface ScriptIndicatorDefinition {
518
+ /** Unique indicator id, e.g. "script:my-rsi". */
519
+ id: string;
520
+ name: string;
521
+ /** Defaults to "separate". */
522
+ pane?: IndicatorPane;
523
+ paneHeightRatio?: number;
524
+ inputs?: ScriptIndicatorInputDef[];
525
+ /**
526
+ * JavaScript source that must define `function compute(bars, inputs, hp)`.
527
+ * `bars` is an array of `{ time, o, h, l, c, v }`, `inputs` the resolved
528
+ * input values, and `hp` a bundle of TA helpers (sma, ema, rma, highest,
529
+ * lowest, change, stdev, atr). It must return `{ plots: [...] }`.
530
+ */
531
+ source: string;
532
+ }
533
+ interface ScriptPlotSpec {
534
+ title?: string;
535
+ values: Array<number | null>;
536
+ color?: string;
537
+ width?: number;
538
+ style?: "line" | "histogram";
539
+ negativeColor?: string;
540
+ }
541
+ interface ScriptComputeResult {
542
+ plots: ScriptPlotSpec[];
543
+ range?: {
544
+ min?: number;
545
+ max?: number;
546
+ };
547
+ guides?: number[];
548
+ decimals?: number;
549
+ }
550
+ /**
551
+ * Compile a user-authored script into a regular indicator plugin. The plugin
552
+ * can then be registered with `chart.registerIndicator(...)` and behaves like
553
+ * a built-in: separate panes get the shared grid, legends, value lines,
554
+ * drag-resize and hover controls; overlays autoscale with price.
555
+ *
556
+ * Compile/runtime errors never throw during rendering; they render as an
557
+ * inline error message in the pane instead.
558
+ */
559
+ declare const compileScriptIndicator: (definition: ScriptIndicatorDefinition) => IndicatorPlugin;
504
560
  interface ChartInstance {
505
561
  updateOptions: (options: ChartOptions) => void;
506
562
  /** Switch the main series rendering style (candles, bars, line, ...). */
@@ -597,4 +653,4 @@ interface ViewportState {
597
653
  }
598
654
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
599
655
 
600
- export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type ChartType, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type TickerLineOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, createChart };
656
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type ChartType, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptPlotSpec, type TickerLineOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart };
@@ -1468,6 +1468,195 @@ var drawSeparateMultiSeries = (ctx, renderContext, seriesList, options = {}) =>
1468
1468
  }
1469
1469
  return paneInfo;
1470
1470
  };
1471
+ var scriptRollingExtreme = (values, length, mode) => {
1472
+ const fallback = mode === "max" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
1473
+ const filled = new Array(values.length);
1474
+ for (let i = 0; i < values.length; i += 1) {
1475
+ const value = values[i];
1476
+ filled[i] = value != null && Number.isFinite(value) ? value : fallback;
1477
+ }
1478
+ const raw = rollingExtremeSeries(filled, Math.max(1, Math.floor(length)), mode);
1479
+ return raw.map((value) => value != null && Number.isFinite(value) ? value : null);
1480
+ };
1481
+ var scriptStdevSeries = (values, length) => {
1482
+ const window2 = Math.max(1, Math.floor(length));
1483
+ const result = new Array(values.length).fill(null);
1484
+ let sum = 0;
1485
+ let sumSq = 0;
1486
+ let valid = 0;
1487
+ for (let i = 0; i < values.length; i += 1) {
1488
+ const value = values[i];
1489
+ if (value == null || !Number.isFinite(value)) {
1490
+ sum = 0;
1491
+ sumSq = 0;
1492
+ valid = 0;
1493
+ continue;
1494
+ }
1495
+ sum += value;
1496
+ sumSq += value * value;
1497
+ valid += 1;
1498
+ if (valid > window2) {
1499
+ const old = values[i - window2];
1500
+ sum -= old;
1501
+ sumSq -= old * old;
1502
+ valid -= 1;
1503
+ }
1504
+ if (valid === window2) {
1505
+ const mean = sum / window2;
1506
+ result[i] = Math.sqrt(Math.max(0, sumSq / window2 - mean * mean));
1507
+ }
1508
+ }
1509
+ return result;
1510
+ };
1511
+ var scriptAtrSeries = (bars, length) => {
1512
+ const tr = new Array(bars.length).fill(null);
1513
+ for (let i = 0; i < bars.length; i += 1) {
1514
+ const bar = bars[i];
1515
+ if (i === 0) {
1516
+ tr[i] = bar.h - bar.l;
1517
+ continue;
1518
+ }
1519
+ const prevClose = bars[i - 1].c;
1520
+ tr[i] = Math.max(bar.h - bar.l, Math.abs(bar.h - prevClose), Math.abs(bar.l - prevClose));
1521
+ }
1522
+ return rmaFromValues(tr, Math.max(1, Math.floor(length)));
1523
+ };
1524
+ var SCRIPT_HELPERS = Object.freeze({
1525
+ sma: (values, length) => smaFromValues(values, Math.max(1, Math.floor(length))),
1526
+ ema: (values, length) => emaFromValues(values, Math.max(1, Math.floor(length))),
1527
+ rma: (values, length) => rmaFromValues(values, Math.max(1, Math.floor(length))),
1528
+ highest: (values, length) => scriptRollingExtreme(values, length, "max"),
1529
+ lowest: (values, length) => scriptRollingExtreme(values, length, "min"),
1530
+ change: (values, length = 1) => values.map((value, index) => {
1531
+ const prev = values[index - Math.max(1, Math.floor(length))];
1532
+ return value != null && prev != null ? value - prev : null;
1533
+ }),
1534
+ stdev: scriptStdevSeries,
1535
+ atr: scriptAtrSeries
1536
+ });
1537
+ var SCRIPT_PLOT_PALETTE = ["#2962ff", "#ff6d00", "#26a69a", "#ab47bc", "#ef5350", "#fdd835"];
1538
+ var hashScriptSource = (source) => {
1539
+ let hash = 5381;
1540
+ for (let i = 0; i < source.length; i += 1) {
1541
+ hash = (hash << 5) + hash + source.charCodeAt(i) | 0;
1542
+ }
1543
+ return (hash >>> 0).toString(36);
1544
+ };
1545
+ var compileScriptCompute = (source) => {
1546
+ const factory = new Function(
1547
+ `"use strict";
1548
+ ${source}
1549
+ ;if (typeof compute !== "function") { throw new Error("Script must define function compute(bars, inputs, hp)"); }
1550
+ return compute;`
1551
+ );
1552
+ return factory();
1553
+ };
1554
+ var drawScriptError = (ctx, renderContext, name, message) => {
1555
+ ctx.save();
1556
+ ctx.font = "11px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
1557
+ ctx.textAlign = "left";
1558
+ ctx.textBaseline = "top";
1559
+ ctx.fillStyle = "#ef5350";
1560
+ const text = `${name}: ${message}`.slice(0, 160);
1561
+ ctx.fillText(text, renderContext.chartLeft + 10, renderContext.chartTop + 24);
1562
+ ctx.restore();
1563
+ };
1564
+ var compileScriptIndicator = (definition) => {
1565
+ const pane = definition.pane ?? "separate";
1566
+ const defaultInputs = { showValueLine: true };
1567
+ for (const input of definition.inputs ?? []) {
1568
+ defaultInputs[input.key] = input.default;
1569
+ }
1570
+ let compiled = null;
1571
+ let compileError = null;
1572
+ try {
1573
+ compiled = compileScriptCompute(definition.source);
1574
+ } catch (error) {
1575
+ compileError = error instanceof Error ? error.message : String(error);
1576
+ }
1577
+ const sourceKey = hashScriptSource(definition.source);
1578
+ const runCompute = (data, inputs) => {
1579
+ if (!compiled) {
1580
+ return { error: compileError ?? "Script failed to compile" };
1581
+ }
1582
+ try {
1583
+ const result = compiled(data, inputs, SCRIPT_HELPERS);
1584
+ if (!result || !Array.isArray(result.plots)) {
1585
+ return { error: "compute() must return { plots: [...] }" };
1586
+ }
1587
+ for (const plot of result.plots) {
1588
+ if (!plot || !Array.isArray(plot.values)) {
1589
+ return { error: "Every plot needs a `values` array" };
1590
+ }
1591
+ }
1592
+ return { result };
1593
+ } catch (error) {
1594
+ return { error: error instanceof Error ? error.message : String(error) };
1595
+ }
1596
+ };
1597
+ const cachedCompute = (data, inputs) => withCachedComputation(
1598
+ `script|${definition.id}|${sourceKey}|${JSON.stringify(inputs)}`,
1599
+ data,
1600
+ () => runCompute(data, inputs)
1601
+ );
1602
+ const plugin = {
1603
+ id: definition.id,
1604
+ name: definition.name,
1605
+ pane,
1606
+ defaultInputs,
1607
+ draw: (ctx, renderContext, inputs) => {
1608
+ const outcome = cachedCompute(renderContext.data, inputs);
1609
+ if (!outcome.result) {
1610
+ drawScriptError(ctx, renderContext, definition.name, outcome.error ?? "Unknown script error");
1611
+ return pane === "separate" ? { title: `${definition.name} (script error)` } : void 0;
1612
+ }
1613
+ const { plots, range, guides, decimals } = outcome.result;
1614
+ if (pane === "separate") {
1615
+ const seriesList = plots.map((plot, index) => ({
1616
+ values: plot.values,
1617
+ color: plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
1618
+ ...plot.width !== void 0 ? { width: plot.width } : {},
1619
+ ...plot.title ? { label: plot.title } : {},
1620
+ ...plot.style === "histogram" ? { histogram: true } : {},
1621
+ ...plot.negativeColor ? { negativeColor: plot.negativeColor } : {}
1622
+ }));
1623
+ return drawSeparateMultiSeries(ctx, renderContext, seriesList, {
1624
+ title: definition.name,
1625
+ ...range?.min !== void 0 ? { minOverride: range.min } : {},
1626
+ ...range?.max !== void 0 ? { maxOverride: range.max } : {},
1627
+ ...guides && guides.length > 0 ? { guideLines: guides } : {},
1628
+ ...decimals !== void 0 ? { decimals } : {},
1629
+ valueLines: inputs.showValueLine !== false
1630
+ });
1631
+ }
1632
+ for (let index = 0; index < plots.length; index += 1) {
1633
+ const plot = plots[index];
1634
+ if (plot.style === "histogram") continue;
1635
+ drawOverlaySeries(
1636
+ ctx,
1637
+ renderContext,
1638
+ plot.values,
1639
+ plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
1640
+ plot.width ?? 2
1641
+ );
1642
+ }
1643
+ return void 0;
1644
+ },
1645
+ ...pane === "overlay" ? {
1646
+ getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
1647
+ const outcome = cachedCompute(data, inputs);
1648
+ if (!outcome.result) return null;
1649
+ const lineSeries = outcome.result.plots.filter((plot) => plot.style !== "histogram").map((plot) => plot.values);
1650
+ if (lineSeries.length === 0) return null;
1651
+ return rangeOfSeries(lineSeries, startIndex, endIndex, skipIndex);
1652
+ }
1653
+ } : {}
1654
+ };
1655
+ if (definition.paneHeightRatio !== void 0) {
1656
+ plugin.paneHeightRatio = definition.paneHeightRatio;
1657
+ }
1658
+ return plugin;
1659
+ };
1471
1660
  var computeMacd = (data, fast, slow, signalLength) => {
1472
1661
  const fastEma = computeEmaSeries(data, fast, "close");
1473
1662
  const slowEma = computeEmaSeries(data, slow, "close");
@@ -2519,7 +2708,7 @@ function createChart(element, options = {}) {
2519
2708
  return;
2520
2709
  }
2521
2710
  const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
2522
- const speed = clamp(tickerOpts.smoothingSpeed ?? 4, 1, 60);
2711
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 16, 1, 60);
2523
2712
  const dt = 1 / 60;
2524
2713
  const lerp = 1 - Math.exp(-speed * dt);
2525
2714
  smoothedTickerPrice += priceDiff * lerp;
@@ -2549,6 +2738,29 @@ function createChart(element, options = {}) {
2549
2738
  smoothingRafId = requestAnimationFrame(tickerSmoothingLoop);
2550
2739
  }
2551
2740
  };
2741
+ let countdownTimerId = null;
2742
+ const countdownRepaintNeeded = () => {
2743
+ if (data.length === 0) {
2744
+ return false;
2745
+ }
2746
+ const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
2747
+ const labels = mergedOptions.labels ?? DEFAULT_OPTIONS.labels;
2748
+ return Boolean(ticker.showCountdownInLabel) || Boolean(labels.visible && labels.showCountdownToBarClose);
2749
+ };
2750
+ const armCountdownHeartbeat = () => {
2751
+ if (countdownTimerId !== null || !countdownRepaintNeeded()) {
2752
+ return;
2753
+ }
2754
+ const delay = 1e3 - Date.now() % 1e3 + 15;
2755
+ countdownTimerId = setTimeout(() => {
2756
+ countdownTimerId = null;
2757
+ if (!countdownRepaintNeeded()) {
2758
+ return;
2759
+ }
2760
+ scheduleDraw({ updateAutoScale: false });
2761
+ armCountdownHeartbeat();
2762
+ }, delay);
2763
+ };
2552
2764
  const canvas = document.createElement("canvas");
2553
2765
  const ctx = canvas.getContext("2d");
2554
2766
  if (!ctx) {
@@ -3474,6 +3686,7 @@ function createChart(element, options = {}) {
3474
3686
  let pendingDrawUpdateAutoScale = false;
3475
3687
  let pendingDrawOverlayOnly = true;
3476
3688
  const scheduleDraw = (options2 = {}) => {
3689
+ armCountdownHeartbeat();
3477
3690
  const overlayOnly = options2.overlayOnly ?? false;
3478
3691
  pendingDrawOverlayOnly = pendingDrawOverlayOnly && overlayOnly;
3479
3692
  pendingDrawUpdateAutoScale = pendingDrawUpdateAutoScale || !overlayOnly && (options2.updateAutoScale ?? true);
@@ -5058,18 +5271,17 @@ function createChart(element, options = {}) {
5058
5271
  legendTitle,
5059
5272
  ...legendValues.map((value) => value.text ?? (value.value === void 0 ? "" : formatPaneValue(value.value))).filter(Boolean)
5060
5273
  ].filter(Boolean);
5061
- const legendText = legendParts.join(" ");
5062
- if (legendParts.length > 0) {
5063
- drawText(legendText, chartLeft + 10, paneTop + 8, "left", "top", labels.indicatorTextColor);
5064
- }
5065
5274
  if (hoveredPaneId === indicator.id) {
5275
+ drawText(legendTitle, chartLeft + 10, paneTop + 8, "left", "top", labels.indicatorTextColor);
5066
5276
  drawPaneButtons(
5067
5277
  indicator.id,
5068
5278
  indicator.type,
5069
5279
  true,
5070
- chartLeft + 10 + Math.ceil(measureTextWidth(legendText)) + 10,
5280
+ chartLeft + 10 + Math.ceil(measureTextWidth(legendTitle)) + 10,
5071
5281
  paneTop + 8 + legendFontSize / 2
5072
5282
  );
5283
+ } else if (legendParts.length > 0) {
5284
+ drawText(legendParts.join(" "), chartLeft + 10, paneTop + 8, "left", "top", labels.indicatorTextColor);
5073
5285
  }
5074
5286
  for (const label of paneInfo?.valueLabels ?? []) {
5075
5287
  if (!labels.showIndicatorValueLabels) {
@@ -7732,6 +7944,10 @@ function createChart(element, options = {}) {
7732
7944
  cancelAnimationFrame(smoothingRafId);
7733
7945
  smoothingRafId = null;
7734
7946
  }
7947
+ if (countdownTimerId !== null) {
7948
+ clearTimeout(countdownTimerId);
7949
+ countdownTimerId = null;
7950
+ }
7735
7951
  if (drawRafId !== null) {
7736
7952
  cancelAnimationFrame(drawRafId);
7737
7953
  drawRafId = null;
@@ -7753,6 +7969,7 @@ function createChart(element, options = {}) {
7753
7969
  element.innerHTML = "";
7754
7970
  };
7755
7971
  draw();
7972
+ armCountdownHeartbeat();
7756
7973
  return {
7757
7974
  updateOptions,
7758
7975
  setChartType,
@@ -7822,5 +8039,6 @@ function createChart(element, options = {}) {
7822
8039
  export {
7823
8040
  FIB_DEFAULT_PALETTE,
7824
8041
  POSITION_DEFAULT_COLORS,
8042
+ compileScriptIndicator,
7825
8043
  createChart
7826
8044
  };