hyperprop-charting-library 0.1.131 → 0.1.133

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,7 +22,9 @@ 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
- createChart: () => createChart
25
+ compileScriptIndicator: () => compileScriptIndicator,
26
+ createChart: () => createChart,
27
+ validateScriptSource: () => validateScriptSource
26
28
  });
27
29
  module.exports = __toCommonJS(index_exports);
28
30
  var POSITION_DEFAULT_COLORS = ["#26a69a", "#ef5350", "#ffffff"];
@@ -629,14 +631,23 @@ var withCachedSeries = (key, data, compute) => {
629
631
  return values;
630
632
  };
631
633
  var builtInComputationCache = /* @__PURE__ */ new Map();
634
+ var COMPUTATION_CACHE_MAX_ENTRIES = 128;
632
635
  var withCachedComputation = (key, data, compute) => {
633
636
  const fingerprint = getSeriesFingerprint(data);
634
637
  const existing = builtInComputationCache.get(key);
635
638
  if (existing && existing.fingerprint === fingerprint) {
639
+ builtInComputationCache.delete(key);
640
+ builtInComputationCache.set(key, existing);
636
641
  return existing.value;
637
642
  }
638
643
  const value = compute();
644
+ builtInComputationCache.delete(key);
639
645
  builtInComputationCache.set(key, { fingerprint, value });
646
+ while (builtInComputationCache.size > COMPUTATION_CACHE_MAX_ENTRIES) {
647
+ const oldest = builtInComputationCache.keys().next().value;
648
+ if (oldest === void 0) break;
649
+ builtInComputationCache.delete(oldest);
650
+ }
640
651
  return value;
641
652
  };
642
653
  var rollingExtremeSeries = (values, length, mode) => {
@@ -1494,6 +1505,495 @@ var drawSeparateMultiSeries = (ctx, renderContext, seriesList, options = {}) =>
1494
1505
  }
1495
1506
  return paneInfo;
1496
1507
  };
1508
+ var scriptRollingExtreme = (values, length, mode) => {
1509
+ const fallback = mode === "max" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
1510
+ const filled = new Array(values.length);
1511
+ for (let i = 0; i < values.length; i += 1) {
1512
+ const value = values[i];
1513
+ filled[i] = value != null && Number.isFinite(value) ? value : fallback;
1514
+ }
1515
+ const raw = rollingExtremeSeries(filled, Math.max(1, Math.floor(length)), mode);
1516
+ return raw.map((value) => value != null && Number.isFinite(value) ? value : null);
1517
+ };
1518
+ var scriptStdevSeries = (values, length) => {
1519
+ const window2 = Math.max(1, Math.floor(length));
1520
+ const result = new Array(values.length).fill(null);
1521
+ let sum = 0;
1522
+ let sumSq = 0;
1523
+ let valid = 0;
1524
+ for (let i = 0; i < values.length; i += 1) {
1525
+ const value = values[i];
1526
+ if (value == null || !Number.isFinite(value)) {
1527
+ sum = 0;
1528
+ sumSq = 0;
1529
+ valid = 0;
1530
+ continue;
1531
+ }
1532
+ sum += value;
1533
+ sumSq += value * value;
1534
+ valid += 1;
1535
+ if (valid > window2) {
1536
+ const old = values[i - window2];
1537
+ sum -= old;
1538
+ sumSq -= old * old;
1539
+ valid -= 1;
1540
+ }
1541
+ if (valid === window2) {
1542
+ const mean = sum / window2;
1543
+ result[i] = Math.sqrt(Math.max(0, sumSq / window2 - mean * mean));
1544
+ }
1545
+ }
1546
+ return result;
1547
+ };
1548
+ var scriptAtrSeries = (bars, length) => {
1549
+ const tr = new Array(bars.length).fill(null);
1550
+ for (let i = 0; i < bars.length; i += 1) {
1551
+ const bar = bars[i];
1552
+ if (i === 0) {
1553
+ tr[i] = bar.h - bar.l;
1554
+ continue;
1555
+ }
1556
+ const prevClose = bars[i - 1].c;
1557
+ tr[i] = Math.max(bar.h - bar.l, Math.abs(bar.h - prevClose), Math.abs(bar.l - prevClose));
1558
+ }
1559
+ return rmaFromValues(tr, Math.max(1, Math.floor(length)));
1560
+ };
1561
+ var SCRIPT_HELPERS = Object.freeze({
1562
+ sma: (values, length) => smaFromValues(values, Math.max(1, Math.floor(length))),
1563
+ ema: (values, length) => emaFromValues(values, Math.max(1, Math.floor(length))),
1564
+ rma: (values, length) => rmaFromValues(values, Math.max(1, Math.floor(length))),
1565
+ highest: (values, length) => scriptRollingExtreme(values, length, "max"),
1566
+ lowest: (values, length) => scriptRollingExtreme(values, length, "min"),
1567
+ change: (values, length = 1) => values.map((value, index) => {
1568
+ const prev = values[index - Math.max(1, Math.floor(length))];
1569
+ return value != null && prev != null ? value - prev : null;
1570
+ }),
1571
+ stdev: scriptStdevSeries,
1572
+ atr: scriptAtrSeries
1573
+ });
1574
+ var SCRIPT_PLOT_PALETTE = ["#2962ff", "#ff6d00", "#26a69a", "#ab47bc", "#ef5350", "#fdd835"];
1575
+ var hashScriptSource = (source) => {
1576
+ let hash = 5381;
1577
+ for (let i = 0; i < source.length; i += 1) {
1578
+ hash = (hash << 5) + hash + source.charCodeAt(i) | 0;
1579
+ }
1580
+ return (hash >>> 0).toString(36);
1581
+ };
1582
+ var SCRIPT_GUARD_BUDGET_MS = 1e3;
1583
+ var ScriptBudgetError = class extends Error {
1584
+ constructor() {
1585
+ super(
1586
+ `Script exceeded the ${SCRIPT_GUARD_BUDGET_MS}ms execution budget (possible infinite loop) \u2014 edit the script to retry`
1587
+ );
1588
+ this.name = "ScriptBudgetError";
1589
+ }
1590
+ };
1591
+ var createScriptGuard = () => {
1592
+ let deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
1593
+ let count = 0;
1594
+ const guard = (() => {
1595
+ count += 1;
1596
+ if ((count & 1023) === 0 && Date.now() > deadline) {
1597
+ throw new ScriptBudgetError();
1598
+ }
1599
+ return true;
1600
+ });
1601
+ guard.reset = () => {
1602
+ count = 0;
1603
+ deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
1604
+ };
1605
+ return guard;
1606
+ };
1607
+ var injectLoopGuards = (source, guardName) => {
1608
+ const n = source.length;
1609
+ const isIdChar = (ch) => ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "$";
1610
+ const scanString = (start) => {
1611
+ const quote = source[start];
1612
+ let j = start + 1;
1613
+ while (j < n) {
1614
+ const cj = source[j];
1615
+ if (cj === "\\") {
1616
+ j += 2;
1617
+ continue;
1618
+ }
1619
+ if (cj === quote) {
1620
+ return j + 1;
1621
+ }
1622
+ if (quote === "`" && cj === "$" && source[j + 1] === "{") {
1623
+ let depth = 1;
1624
+ j += 2;
1625
+ while (j < n && depth > 0) {
1626
+ const ce = source[j];
1627
+ if (ce === "\\") {
1628
+ j += 2;
1629
+ continue;
1630
+ }
1631
+ if (ce === '"' || ce === "'" || ce === "`") {
1632
+ j = scanString(j);
1633
+ continue;
1634
+ }
1635
+ if (ce === "{") depth += 1;
1636
+ else if (ce === "}") depth -= 1;
1637
+ j += 1;
1638
+ }
1639
+ continue;
1640
+ }
1641
+ j += 1;
1642
+ }
1643
+ return n;
1644
+ };
1645
+ const scanRegex = (start) => {
1646
+ let j = start + 1;
1647
+ let inClass = false;
1648
+ while (j < n) {
1649
+ const cj = source[j];
1650
+ if (cj === "\\") {
1651
+ j += 2;
1652
+ continue;
1653
+ }
1654
+ if (cj === "\n") return j;
1655
+ if (cj === "[") inClass = true;
1656
+ else if (cj === "]") inClass = false;
1657
+ else if (cj === "/" && !inClass) return j + 1;
1658
+ j += 1;
1659
+ }
1660
+ return n;
1661
+ };
1662
+ const skipWsAndComments = (k) => {
1663
+ let j = k;
1664
+ while (j < n) {
1665
+ const cj = source[j];
1666
+ if (cj === " " || cj === " " || cj === "\n" || cj === "\r") {
1667
+ j += 1;
1668
+ continue;
1669
+ }
1670
+ if (cj === "/" && source[j + 1] === "/") {
1671
+ const nl = source.indexOf("\n", j);
1672
+ j = nl === -1 ? n : nl + 1;
1673
+ continue;
1674
+ }
1675
+ if (cj === "/" && source[j + 1] === "*") {
1676
+ const end = source.indexOf("*/", j + 2);
1677
+ j = end === -1 ? n : end + 2;
1678
+ continue;
1679
+ }
1680
+ return j;
1681
+ }
1682
+ return n;
1683
+ };
1684
+ const matchParen = (openIdx) => {
1685
+ let depth = 0;
1686
+ let j = openIdx;
1687
+ while (j < n) {
1688
+ const cj = source[j];
1689
+ if (cj === '"' || cj === "'" || cj === "`") {
1690
+ j = scanString(j);
1691
+ continue;
1692
+ }
1693
+ if (cj === "/" && source[j + 1] === "/") {
1694
+ const nl = source.indexOf("\n", j);
1695
+ j = nl === -1 ? n : nl + 1;
1696
+ continue;
1697
+ }
1698
+ if (cj === "/" && source[j + 1] === "*") {
1699
+ const end = source.indexOf("*/", j + 2);
1700
+ j = end === -1 ? n : end + 2;
1701
+ continue;
1702
+ }
1703
+ if (cj === "(") depth += 1;
1704
+ else if (cj === ")") {
1705
+ depth -= 1;
1706
+ if (depth === 0) return j;
1707
+ }
1708
+ j += 1;
1709
+ }
1710
+ return -1;
1711
+ };
1712
+ const splitTopLevelSemicolons = (inner) => {
1713
+ const parts = [];
1714
+ let depth = 0;
1715
+ let j = 0;
1716
+ let last = 0;
1717
+ const m = inner.length;
1718
+ while (j < m) {
1719
+ const cj = inner[j];
1720
+ if (cj === '"' || cj === "'" || cj === "`") {
1721
+ const quote = cj;
1722
+ j += 1;
1723
+ while (j < m) {
1724
+ if (inner[j] === "\\") {
1725
+ j += 2;
1726
+ continue;
1727
+ }
1728
+ if (inner[j] === quote) {
1729
+ j += 1;
1730
+ break;
1731
+ }
1732
+ j += 1;
1733
+ }
1734
+ continue;
1735
+ }
1736
+ if (cj === "(" || cj === "[" || cj === "{") depth += 1;
1737
+ else if (cj === ")" || cj === "]" || cj === "}") depth -= 1;
1738
+ else if (cj === ";" && depth === 0) {
1739
+ parts.push(inner.slice(last, j));
1740
+ last = j + 1;
1741
+ }
1742
+ j += 1;
1743
+ }
1744
+ parts.push(inner.slice(last));
1745
+ return parts;
1746
+ };
1747
+ const REGEX_PRECEDING_WORDS = /* @__PURE__ */ new Set([
1748
+ "return",
1749
+ "typeof",
1750
+ "case",
1751
+ "do",
1752
+ "else",
1753
+ "in",
1754
+ "of",
1755
+ "new",
1756
+ "delete",
1757
+ "void",
1758
+ "instanceof",
1759
+ "yield",
1760
+ "await"
1761
+ ]);
1762
+ let out = "";
1763
+ let i = 0;
1764
+ let lastSig = "";
1765
+ let lastWord = "";
1766
+ while (i < n) {
1767
+ const ch = source[i];
1768
+ if (ch === "/" && source[i + 1] === "/") {
1769
+ const nl = source.indexOf("\n", i);
1770
+ const end = nl === -1 ? n : nl;
1771
+ out += source.slice(i, end);
1772
+ i = end;
1773
+ continue;
1774
+ }
1775
+ if (ch === "/" && source[i + 1] === "*") {
1776
+ const close = source.indexOf("*/", i + 2);
1777
+ const end = close === -1 ? n : close + 2;
1778
+ out += source.slice(i, end);
1779
+ i = end;
1780
+ continue;
1781
+ }
1782
+ if (ch === '"' || ch === "'" || ch === "`") {
1783
+ const end = scanString(i);
1784
+ out += source.slice(i, end);
1785
+ lastSig = ch;
1786
+ lastWord = "";
1787
+ i = end;
1788
+ continue;
1789
+ }
1790
+ if (ch === "/") {
1791
+ const regexLikely = lastSig === "" || "(,=:;!&|?{}[+-*%~^<>".includes(lastSig) || REGEX_PRECEDING_WORDS.has(lastWord);
1792
+ if (regexLikely) {
1793
+ const end = scanRegex(i);
1794
+ out += source.slice(i, end);
1795
+ lastSig = "/";
1796
+ lastWord = "";
1797
+ i = end;
1798
+ continue;
1799
+ }
1800
+ out += ch;
1801
+ lastSig = ch;
1802
+ lastWord = "";
1803
+ i += 1;
1804
+ continue;
1805
+ }
1806
+ if (isIdChar(ch)) {
1807
+ let j = i;
1808
+ while (j < n && isIdChar(source[j])) j += 1;
1809
+ const word = source.slice(i, j);
1810
+ if ((word === "while" || word === "for") && lastSig !== ".") {
1811
+ const k = skipWsAndComments(j);
1812
+ if (source[k] === "(") {
1813
+ const close = matchParen(k);
1814
+ if (close !== -1) {
1815
+ const inner = source.slice(k + 1, close);
1816
+ if (word === "while") {
1817
+ out += `while (${guardName}() && (${inner}))`;
1818
+ lastSig = ")";
1819
+ lastWord = "";
1820
+ i = close + 1;
1821
+ continue;
1822
+ }
1823
+ const parts = splitTopLevelSemicolons(inner);
1824
+ if (parts.length === 3) {
1825
+ const cond = parts[1].trim();
1826
+ const guardedCond = cond ? `${guardName}() && (${cond})` : `${guardName}()`;
1827
+ out += `for (${parts[0]}; ${guardedCond}; ${parts[2]})`;
1828
+ lastSig = ")";
1829
+ lastWord = "";
1830
+ i = close + 1;
1831
+ continue;
1832
+ }
1833
+ out += source.slice(i, close + 1);
1834
+ lastSig = ")";
1835
+ lastWord = "";
1836
+ i = close + 1;
1837
+ continue;
1838
+ }
1839
+ }
1840
+ }
1841
+ out += word;
1842
+ lastSig = word[word.length - 1];
1843
+ lastWord = word;
1844
+ i = j;
1845
+ continue;
1846
+ }
1847
+ out += ch;
1848
+ if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") {
1849
+ lastSig = ch;
1850
+ lastWord = "";
1851
+ }
1852
+ i += 1;
1853
+ }
1854
+ return out;
1855
+ };
1856
+ var compileScriptCompute = (source, guard) => {
1857
+ const makeFactory = (body) => new Function(
1858
+ "__hpGuard",
1859
+ `"use strict";
1860
+ ${body}
1861
+ ;if (typeof compute !== "function") { throw new Error("Script must define function compute(bars, inputs, hp)"); }
1862
+ return compute;`
1863
+ );
1864
+ let factory;
1865
+ try {
1866
+ factory = makeFactory(injectLoopGuards(source, "__hpGuard"));
1867
+ } catch {
1868
+ factory = makeFactory(source);
1869
+ }
1870
+ guard.reset();
1871
+ return factory(guard);
1872
+ };
1873
+ var validateScriptSource = (source) => {
1874
+ try {
1875
+ compileScriptCompute(source, createScriptGuard());
1876
+ return null;
1877
+ } catch (error) {
1878
+ return error instanceof Error ? error.message : String(error);
1879
+ }
1880
+ };
1881
+ var drawScriptError = (ctx, renderContext, name, message) => {
1882
+ ctx.save();
1883
+ ctx.font = "11px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
1884
+ ctx.textAlign = "left";
1885
+ ctx.textBaseline = "top";
1886
+ ctx.fillStyle = "#ef5350";
1887
+ const text = `${name}: ${message}`.slice(0, 160);
1888
+ ctx.fillText(text, renderContext.chartLeft + 10, renderContext.chartTop + 24);
1889
+ ctx.restore();
1890
+ };
1891
+ var compileScriptIndicator = (definition) => {
1892
+ const pane = definition.pane ?? "separate";
1893
+ const defaultInputs = { showValueLine: true };
1894
+ for (const input of definition.inputs ?? []) {
1895
+ defaultInputs[input.key] = input.default;
1896
+ }
1897
+ let compiled = null;
1898
+ let compileError = null;
1899
+ const guard = createScriptGuard();
1900
+ try {
1901
+ compiled = compileScriptCompute(definition.source, guard);
1902
+ } catch (error) {
1903
+ compileError = error instanceof Error ? error.message : String(error);
1904
+ }
1905
+ const sourceKey = hashScriptSource(definition.source);
1906
+ let trippedError = null;
1907
+ const runCompute = (data, inputs) => {
1908
+ if (!compiled) {
1909
+ return { error: compileError ?? "Script failed to compile" };
1910
+ }
1911
+ if (trippedError) {
1912
+ return { error: trippedError };
1913
+ }
1914
+ try {
1915
+ guard.reset();
1916
+ const result = compiled(data, inputs, SCRIPT_HELPERS);
1917
+ if (!result || !Array.isArray(result.plots)) {
1918
+ return { error: "compute() must return { plots: [...] }" };
1919
+ }
1920
+ for (const plot of result.plots) {
1921
+ if (!plot || !Array.isArray(plot.values)) {
1922
+ return { error: "Every plot needs a `values` array" };
1923
+ }
1924
+ }
1925
+ return { result };
1926
+ } catch (error) {
1927
+ const message = error instanceof Error ? error.message : String(error);
1928
+ if (error instanceof ScriptBudgetError) {
1929
+ trippedError = message;
1930
+ }
1931
+ return { error: message };
1932
+ }
1933
+ };
1934
+ const cachedCompute = (data, inputs) => withCachedComputation(
1935
+ `script|${definition.id}|${sourceKey}|${JSON.stringify(inputs)}`,
1936
+ data,
1937
+ () => runCompute(data, inputs)
1938
+ );
1939
+ const plugin = {
1940
+ id: definition.id,
1941
+ name: definition.name,
1942
+ pane,
1943
+ defaultInputs,
1944
+ draw: (ctx, renderContext, inputs) => {
1945
+ const outcome = cachedCompute(renderContext.data, inputs);
1946
+ if (!outcome.result) {
1947
+ drawScriptError(ctx, renderContext, definition.name, outcome.error ?? "Unknown script error");
1948
+ return pane === "separate" ? { title: `${definition.name} (script error)` } : void 0;
1949
+ }
1950
+ const { plots, range, guides, decimals } = outcome.result;
1951
+ if (pane === "separate") {
1952
+ const seriesList = plots.map((plot, index) => ({
1953
+ values: plot.values,
1954
+ color: plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
1955
+ ...plot.width !== void 0 ? { width: plot.width } : {},
1956
+ ...plot.title ? { label: plot.title } : {},
1957
+ ...plot.style === "histogram" ? { histogram: true } : {},
1958
+ ...plot.negativeColor ? { negativeColor: plot.negativeColor } : {}
1959
+ }));
1960
+ return drawSeparateMultiSeries(ctx, renderContext, seriesList, {
1961
+ title: definition.name,
1962
+ ...range?.min !== void 0 ? { minOverride: range.min } : {},
1963
+ ...range?.max !== void 0 ? { maxOverride: range.max } : {},
1964
+ ...guides && guides.length > 0 ? { guideLines: guides } : {},
1965
+ ...decimals !== void 0 ? { decimals } : {},
1966
+ valueLines: inputs.showValueLine !== false
1967
+ });
1968
+ }
1969
+ for (let index = 0; index < plots.length; index += 1) {
1970
+ const plot = plots[index];
1971
+ if (plot.style === "histogram") continue;
1972
+ drawOverlaySeries(
1973
+ ctx,
1974
+ renderContext,
1975
+ plot.values,
1976
+ plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
1977
+ plot.width ?? 2
1978
+ );
1979
+ }
1980
+ return void 0;
1981
+ },
1982
+ ...pane === "overlay" ? {
1983
+ getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
1984
+ const outcome = cachedCompute(data, inputs);
1985
+ if (!outcome.result) return null;
1986
+ const lineSeries = outcome.result.plots.filter((plot) => plot.style !== "histogram").map((plot) => plot.values);
1987
+ if (lineSeries.length === 0) return null;
1988
+ return rangeOfSeries(lineSeries, startIndex, endIndex, skipIndex);
1989
+ }
1990
+ } : {}
1991
+ };
1992
+ if (definition.paneHeightRatio !== void 0) {
1993
+ plugin.paneHeightRatio = definition.paneHeightRatio;
1994
+ }
1995
+ return plugin;
1996
+ };
1497
1997
  var computeMacd = (data, fast, slow, signalLength) => {
1498
1998
  const fastEma = computeEmaSeries(data, fast, "close");
1499
1999
  const slowEma = computeEmaSeries(data, slow, "close");
@@ -2545,7 +3045,7 @@ function createChart(element, options = {}) {
2545
3045
  return;
2546
3046
  }
2547
3047
  const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
2548
- const speed = clamp(tickerOpts.smoothingSpeed ?? 4, 1, 60);
3048
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 16, 1, 60);
2549
3049
  const dt = 1 / 60;
2550
3050
  const lerp = 1 - Math.exp(-speed * dt);
2551
3051
  smoothedTickerPrice += priceDiff * lerp;
@@ -2575,6 +3075,29 @@ function createChart(element, options = {}) {
2575
3075
  smoothingRafId = requestAnimationFrame(tickerSmoothingLoop);
2576
3076
  }
2577
3077
  };
3078
+ let countdownTimerId = null;
3079
+ const countdownRepaintNeeded = () => {
3080
+ if (data.length === 0) {
3081
+ return false;
3082
+ }
3083
+ const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
3084
+ const labels = mergedOptions.labels ?? DEFAULT_OPTIONS.labels;
3085
+ return Boolean(ticker.showCountdownInLabel) || Boolean(labels.visible && labels.showCountdownToBarClose);
3086
+ };
3087
+ const armCountdownHeartbeat = () => {
3088
+ if (countdownTimerId !== null || !countdownRepaintNeeded()) {
3089
+ return;
3090
+ }
3091
+ const delay = 1e3 - Date.now() % 1e3 + 15;
3092
+ countdownTimerId = setTimeout(() => {
3093
+ countdownTimerId = null;
3094
+ if (!countdownRepaintNeeded()) {
3095
+ return;
3096
+ }
3097
+ scheduleDraw({ updateAutoScale: false });
3098
+ armCountdownHeartbeat();
3099
+ }, delay);
3100
+ };
2578
3101
  const canvas = document.createElement("canvas");
2579
3102
  const ctx = canvas.getContext("2d");
2580
3103
  if (!ctx) {
@@ -3500,6 +4023,7 @@ function createChart(element, options = {}) {
3500
4023
  let pendingDrawUpdateAutoScale = false;
3501
4024
  let pendingDrawOverlayOnly = true;
3502
4025
  const scheduleDraw = (options2 = {}) => {
4026
+ armCountdownHeartbeat();
3503
4027
  const overlayOnly = options2.overlayOnly ?? false;
3504
4028
  pendingDrawOverlayOnly = pendingDrawOverlayOnly && overlayOnly;
3505
4029
  pendingDrawUpdateAutoScale = pendingDrawUpdateAutoScale || !overlayOnly && (options2.updateAutoScale ?? true);
@@ -7757,6 +8281,10 @@ function createChart(element, options = {}) {
7757
8281
  cancelAnimationFrame(smoothingRafId);
7758
8282
  smoothingRafId = null;
7759
8283
  }
8284
+ if (countdownTimerId !== null) {
8285
+ clearTimeout(countdownTimerId);
8286
+ countdownTimerId = null;
8287
+ }
7760
8288
  if (drawRafId !== null) {
7761
8289
  cancelAnimationFrame(drawRafId);
7762
8290
  drawRafId = null;
@@ -7778,6 +8306,7 @@ function createChart(element, options = {}) {
7778
8306
  element.innerHTML = "";
7779
8307
  };
7780
8308
  draw();
8309
+ armCountdownHeartbeat();
7781
8310
  return {
7782
8311
  updateOptions,
7783
8312
  setChartType,
@@ -7848,5 +8377,7 @@ function createChart(element, options = {}) {
7848
8377
  0 && (module.exports = {
7849
8378
  FIB_DEFAULT_PALETTE,
7850
8379
  POSITION_DEFAULT_COLORS,
7851
- createChart
8380
+ compileScriptIndicator,
8381
+ createChart,
8382
+ validateScriptSource
7852
8383
  });
@@ -501,6 +501,69 @@ 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-check a script without registering it: returns an error message, or
552
+ * `null` when the script compiles. Runs with the same execution guard as the
553
+ * chart, so a top-level infinite loop returns a budget error instead of
554
+ * hanging the caller (e.g. an editor's save validation).
555
+ */
556
+ declare const validateScriptSource: (source: string) => string | null;
557
+ /**
558
+ * Compile a user-authored script into a regular indicator plugin. The plugin
559
+ * can then be registered with `chart.registerIndicator(...)` and behaves like
560
+ * a built-in: separate panes get the shared grid, legends, value lines,
561
+ * drag-resize and hover controls; overlays autoscale with price.
562
+ *
563
+ * Compile/runtime errors never throw during rendering; they render as an
564
+ * inline error message in the pane instead.
565
+ */
566
+ declare const compileScriptIndicator: (definition: ScriptIndicatorDefinition) => IndicatorPlugin;
504
567
  interface ChartInstance {
505
568
  updateOptions: (options: ChartOptions) => void;
506
569
  /** Switch the main series rendering style (candles, bars, line, ...). */
@@ -597,4 +660,4 @@ interface ViewportState {
597
660
  }
598
661
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
599
662
 
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 };
663
+ 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, validateScriptSource };