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.
- package/dist/hyperprop-charting-library.cjs +534 -3
- package/dist/hyperprop-charting-library.d.ts +64 -1
- package/dist/hyperprop-charting-library.js +531 -2
- package/dist/index.cjs +534 -3
- package/dist/index.d.cts +64 -1
- package/dist/index.d.ts +64 -1
- package/dist/index.js +531 -2
- package/docs/API.md +79 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -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 };
|
package/dist/index.js
CHANGED
|
@@ -603,14 +603,23 @@ var withCachedSeries = (key, data, compute) => {
|
|
|
603
603
|
return values;
|
|
604
604
|
};
|
|
605
605
|
var builtInComputationCache = /* @__PURE__ */ new Map();
|
|
606
|
+
var COMPUTATION_CACHE_MAX_ENTRIES = 128;
|
|
606
607
|
var withCachedComputation = (key, data, compute) => {
|
|
607
608
|
const fingerprint = getSeriesFingerprint(data);
|
|
608
609
|
const existing = builtInComputationCache.get(key);
|
|
609
610
|
if (existing && existing.fingerprint === fingerprint) {
|
|
611
|
+
builtInComputationCache.delete(key);
|
|
612
|
+
builtInComputationCache.set(key, existing);
|
|
610
613
|
return existing.value;
|
|
611
614
|
}
|
|
612
615
|
const value = compute();
|
|
616
|
+
builtInComputationCache.delete(key);
|
|
613
617
|
builtInComputationCache.set(key, { fingerprint, value });
|
|
618
|
+
while (builtInComputationCache.size > COMPUTATION_CACHE_MAX_ENTRIES) {
|
|
619
|
+
const oldest = builtInComputationCache.keys().next().value;
|
|
620
|
+
if (oldest === void 0) break;
|
|
621
|
+
builtInComputationCache.delete(oldest);
|
|
622
|
+
}
|
|
614
623
|
return value;
|
|
615
624
|
};
|
|
616
625
|
var rollingExtremeSeries = (values, length, mode) => {
|
|
@@ -1468,6 +1477,495 @@ var drawSeparateMultiSeries = (ctx, renderContext, seriesList, options = {}) =>
|
|
|
1468
1477
|
}
|
|
1469
1478
|
return paneInfo;
|
|
1470
1479
|
};
|
|
1480
|
+
var scriptRollingExtreme = (values, length, mode) => {
|
|
1481
|
+
const fallback = mode === "max" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
|
|
1482
|
+
const filled = new Array(values.length);
|
|
1483
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
1484
|
+
const value = values[i];
|
|
1485
|
+
filled[i] = value != null && Number.isFinite(value) ? value : fallback;
|
|
1486
|
+
}
|
|
1487
|
+
const raw = rollingExtremeSeries(filled, Math.max(1, Math.floor(length)), mode);
|
|
1488
|
+
return raw.map((value) => value != null && Number.isFinite(value) ? value : null);
|
|
1489
|
+
};
|
|
1490
|
+
var scriptStdevSeries = (values, length) => {
|
|
1491
|
+
const window2 = Math.max(1, Math.floor(length));
|
|
1492
|
+
const result = new Array(values.length).fill(null);
|
|
1493
|
+
let sum = 0;
|
|
1494
|
+
let sumSq = 0;
|
|
1495
|
+
let valid = 0;
|
|
1496
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
1497
|
+
const value = values[i];
|
|
1498
|
+
if (value == null || !Number.isFinite(value)) {
|
|
1499
|
+
sum = 0;
|
|
1500
|
+
sumSq = 0;
|
|
1501
|
+
valid = 0;
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
sum += value;
|
|
1505
|
+
sumSq += value * value;
|
|
1506
|
+
valid += 1;
|
|
1507
|
+
if (valid > window2) {
|
|
1508
|
+
const old = values[i - window2];
|
|
1509
|
+
sum -= old;
|
|
1510
|
+
sumSq -= old * old;
|
|
1511
|
+
valid -= 1;
|
|
1512
|
+
}
|
|
1513
|
+
if (valid === window2) {
|
|
1514
|
+
const mean = sum / window2;
|
|
1515
|
+
result[i] = Math.sqrt(Math.max(0, sumSq / window2 - mean * mean));
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
return result;
|
|
1519
|
+
};
|
|
1520
|
+
var scriptAtrSeries = (bars, length) => {
|
|
1521
|
+
const tr = new Array(bars.length).fill(null);
|
|
1522
|
+
for (let i = 0; i < bars.length; i += 1) {
|
|
1523
|
+
const bar = bars[i];
|
|
1524
|
+
if (i === 0) {
|
|
1525
|
+
tr[i] = bar.h - bar.l;
|
|
1526
|
+
continue;
|
|
1527
|
+
}
|
|
1528
|
+
const prevClose = bars[i - 1].c;
|
|
1529
|
+
tr[i] = Math.max(bar.h - bar.l, Math.abs(bar.h - prevClose), Math.abs(bar.l - prevClose));
|
|
1530
|
+
}
|
|
1531
|
+
return rmaFromValues(tr, Math.max(1, Math.floor(length)));
|
|
1532
|
+
};
|
|
1533
|
+
var SCRIPT_HELPERS = Object.freeze({
|
|
1534
|
+
sma: (values, length) => smaFromValues(values, Math.max(1, Math.floor(length))),
|
|
1535
|
+
ema: (values, length) => emaFromValues(values, Math.max(1, Math.floor(length))),
|
|
1536
|
+
rma: (values, length) => rmaFromValues(values, Math.max(1, Math.floor(length))),
|
|
1537
|
+
highest: (values, length) => scriptRollingExtreme(values, length, "max"),
|
|
1538
|
+
lowest: (values, length) => scriptRollingExtreme(values, length, "min"),
|
|
1539
|
+
change: (values, length = 1) => values.map((value, index) => {
|
|
1540
|
+
const prev = values[index - Math.max(1, Math.floor(length))];
|
|
1541
|
+
return value != null && prev != null ? value - prev : null;
|
|
1542
|
+
}),
|
|
1543
|
+
stdev: scriptStdevSeries,
|
|
1544
|
+
atr: scriptAtrSeries
|
|
1545
|
+
});
|
|
1546
|
+
var SCRIPT_PLOT_PALETTE = ["#2962ff", "#ff6d00", "#26a69a", "#ab47bc", "#ef5350", "#fdd835"];
|
|
1547
|
+
var hashScriptSource = (source) => {
|
|
1548
|
+
let hash = 5381;
|
|
1549
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
1550
|
+
hash = (hash << 5) + hash + source.charCodeAt(i) | 0;
|
|
1551
|
+
}
|
|
1552
|
+
return (hash >>> 0).toString(36);
|
|
1553
|
+
};
|
|
1554
|
+
var SCRIPT_GUARD_BUDGET_MS = 1e3;
|
|
1555
|
+
var ScriptBudgetError = class extends Error {
|
|
1556
|
+
constructor() {
|
|
1557
|
+
super(
|
|
1558
|
+
`Script exceeded the ${SCRIPT_GUARD_BUDGET_MS}ms execution budget (possible infinite loop) \u2014 edit the script to retry`
|
|
1559
|
+
);
|
|
1560
|
+
this.name = "ScriptBudgetError";
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
var createScriptGuard = () => {
|
|
1564
|
+
let deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
|
|
1565
|
+
let count = 0;
|
|
1566
|
+
const guard = (() => {
|
|
1567
|
+
count += 1;
|
|
1568
|
+
if ((count & 1023) === 0 && Date.now() > deadline) {
|
|
1569
|
+
throw new ScriptBudgetError();
|
|
1570
|
+
}
|
|
1571
|
+
return true;
|
|
1572
|
+
});
|
|
1573
|
+
guard.reset = () => {
|
|
1574
|
+
count = 0;
|
|
1575
|
+
deadline = Date.now() + SCRIPT_GUARD_BUDGET_MS;
|
|
1576
|
+
};
|
|
1577
|
+
return guard;
|
|
1578
|
+
};
|
|
1579
|
+
var injectLoopGuards = (source, guardName) => {
|
|
1580
|
+
const n = source.length;
|
|
1581
|
+
const isIdChar = (ch) => ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "$";
|
|
1582
|
+
const scanString = (start) => {
|
|
1583
|
+
const quote = source[start];
|
|
1584
|
+
let j = start + 1;
|
|
1585
|
+
while (j < n) {
|
|
1586
|
+
const cj = source[j];
|
|
1587
|
+
if (cj === "\\") {
|
|
1588
|
+
j += 2;
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
if (cj === quote) {
|
|
1592
|
+
return j + 1;
|
|
1593
|
+
}
|
|
1594
|
+
if (quote === "`" && cj === "$" && source[j + 1] === "{") {
|
|
1595
|
+
let depth = 1;
|
|
1596
|
+
j += 2;
|
|
1597
|
+
while (j < n && depth > 0) {
|
|
1598
|
+
const ce = source[j];
|
|
1599
|
+
if (ce === "\\") {
|
|
1600
|
+
j += 2;
|
|
1601
|
+
continue;
|
|
1602
|
+
}
|
|
1603
|
+
if (ce === '"' || ce === "'" || ce === "`") {
|
|
1604
|
+
j = scanString(j);
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1607
|
+
if (ce === "{") depth += 1;
|
|
1608
|
+
else if (ce === "}") depth -= 1;
|
|
1609
|
+
j += 1;
|
|
1610
|
+
}
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
j += 1;
|
|
1614
|
+
}
|
|
1615
|
+
return n;
|
|
1616
|
+
};
|
|
1617
|
+
const scanRegex = (start) => {
|
|
1618
|
+
let j = start + 1;
|
|
1619
|
+
let inClass = false;
|
|
1620
|
+
while (j < n) {
|
|
1621
|
+
const cj = source[j];
|
|
1622
|
+
if (cj === "\\") {
|
|
1623
|
+
j += 2;
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
if (cj === "\n") return j;
|
|
1627
|
+
if (cj === "[") inClass = true;
|
|
1628
|
+
else if (cj === "]") inClass = false;
|
|
1629
|
+
else if (cj === "/" && !inClass) return j + 1;
|
|
1630
|
+
j += 1;
|
|
1631
|
+
}
|
|
1632
|
+
return n;
|
|
1633
|
+
};
|
|
1634
|
+
const skipWsAndComments = (k) => {
|
|
1635
|
+
let j = k;
|
|
1636
|
+
while (j < n) {
|
|
1637
|
+
const cj = source[j];
|
|
1638
|
+
if (cj === " " || cj === " " || cj === "\n" || cj === "\r") {
|
|
1639
|
+
j += 1;
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
if (cj === "/" && source[j + 1] === "/") {
|
|
1643
|
+
const nl = source.indexOf("\n", j);
|
|
1644
|
+
j = nl === -1 ? n : nl + 1;
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1647
|
+
if (cj === "/" && source[j + 1] === "*") {
|
|
1648
|
+
const end = source.indexOf("*/", j + 2);
|
|
1649
|
+
j = end === -1 ? n : end + 2;
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
return j;
|
|
1653
|
+
}
|
|
1654
|
+
return n;
|
|
1655
|
+
};
|
|
1656
|
+
const matchParen = (openIdx) => {
|
|
1657
|
+
let depth = 0;
|
|
1658
|
+
let j = openIdx;
|
|
1659
|
+
while (j < n) {
|
|
1660
|
+
const cj = source[j];
|
|
1661
|
+
if (cj === '"' || cj === "'" || cj === "`") {
|
|
1662
|
+
j = scanString(j);
|
|
1663
|
+
continue;
|
|
1664
|
+
}
|
|
1665
|
+
if (cj === "/" && source[j + 1] === "/") {
|
|
1666
|
+
const nl = source.indexOf("\n", j);
|
|
1667
|
+
j = nl === -1 ? n : nl + 1;
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1670
|
+
if (cj === "/" && source[j + 1] === "*") {
|
|
1671
|
+
const end = source.indexOf("*/", j + 2);
|
|
1672
|
+
j = end === -1 ? n : end + 2;
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
if (cj === "(") depth += 1;
|
|
1676
|
+
else if (cj === ")") {
|
|
1677
|
+
depth -= 1;
|
|
1678
|
+
if (depth === 0) return j;
|
|
1679
|
+
}
|
|
1680
|
+
j += 1;
|
|
1681
|
+
}
|
|
1682
|
+
return -1;
|
|
1683
|
+
};
|
|
1684
|
+
const splitTopLevelSemicolons = (inner) => {
|
|
1685
|
+
const parts = [];
|
|
1686
|
+
let depth = 0;
|
|
1687
|
+
let j = 0;
|
|
1688
|
+
let last = 0;
|
|
1689
|
+
const m = inner.length;
|
|
1690
|
+
while (j < m) {
|
|
1691
|
+
const cj = inner[j];
|
|
1692
|
+
if (cj === '"' || cj === "'" || cj === "`") {
|
|
1693
|
+
const quote = cj;
|
|
1694
|
+
j += 1;
|
|
1695
|
+
while (j < m) {
|
|
1696
|
+
if (inner[j] === "\\") {
|
|
1697
|
+
j += 2;
|
|
1698
|
+
continue;
|
|
1699
|
+
}
|
|
1700
|
+
if (inner[j] === quote) {
|
|
1701
|
+
j += 1;
|
|
1702
|
+
break;
|
|
1703
|
+
}
|
|
1704
|
+
j += 1;
|
|
1705
|
+
}
|
|
1706
|
+
continue;
|
|
1707
|
+
}
|
|
1708
|
+
if (cj === "(" || cj === "[" || cj === "{") depth += 1;
|
|
1709
|
+
else if (cj === ")" || cj === "]" || cj === "}") depth -= 1;
|
|
1710
|
+
else if (cj === ";" && depth === 0) {
|
|
1711
|
+
parts.push(inner.slice(last, j));
|
|
1712
|
+
last = j + 1;
|
|
1713
|
+
}
|
|
1714
|
+
j += 1;
|
|
1715
|
+
}
|
|
1716
|
+
parts.push(inner.slice(last));
|
|
1717
|
+
return parts;
|
|
1718
|
+
};
|
|
1719
|
+
const REGEX_PRECEDING_WORDS = /* @__PURE__ */ new Set([
|
|
1720
|
+
"return",
|
|
1721
|
+
"typeof",
|
|
1722
|
+
"case",
|
|
1723
|
+
"do",
|
|
1724
|
+
"else",
|
|
1725
|
+
"in",
|
|
1726
|
+
"of",
|
|
1727
|
+
"new",
|
|
1728
|
+
"delete",
|
|
1729
|
+
"void",
|
|
1730
|
+
"instanceof",
|
|
1731
|
+
"yield",
|
|
1732
|
+
"await"
|
|
1733
|
+
]);
|
|
1734
|
+
let out = "";
|
|
1735
|
+
let i = 0;
|
|
1736
|
+
let lastSig = "";
|
|
1737
|
+
let lastWord = "";
|
|
1738
|
+
while (i < n) {
|
|
1739
|
+
const ch = source[i];
|
|
1740
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
1741
|
+
const nl = source.indexOf("\n", i);
|
|
1742
|
+
const end = nl === -1 ? n : nl;
|
|
1743
|
+
out += source.slice(i, end);
|
|
1744
|
+
i = end;
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
1748
|
+
const close = source.indexOf("*/", i + 2);
|
|
1749
|
+
const end = close === -1 ? n : close + 2;
|
|
1750
|
+
out += source.slice(i, end);
|
|
1751
|
+
i = end;
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1754
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
1755
|
+
const end = scanString(i);
|
|
1756
|
+
out += source.slice(i, end);
|
|
1757
|
+
lastSig = ch;
|
|
1758
|
+
lastWord = "";
|
|
1759
|
+
i = end;
|
|
1760
|
+
continue;
|
|
1761
|
+
}
|
|
1762
|
+
if (ch === "/") {
|
|
1763
|
+
const regexLikely = lastSig === "" || "(,=:;!&|?{}[+-*%~^<>".includes(lastSig) || REGEX_PRECEDING_WORDS.has(lastWord);
|
|
1764
|
+
if (regexLikely) {
|
|
1765
|
+
const end = scanRegex(i);
|
|
1766
|
+
out += source.slice(i, end);
|
|
1767
|
+
lastSig = "/";
|
|
1768
|
+
lastWord = "";
|
|
1769
|
+
i = end;
|
|
1770
|
+
continue;
|
|
1771
|
+
}
|
|
1772
|
+
out += ch;
|
|
1773
|
+
lastSig = ch;
|
|
1774
|
+
lastWord = "";
|
|
1775
|
+
i += 1;
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
1778
|
+
if (isIdChar(ch)) {
|
|
1779
|
+
let j = i;
|
|
1780
|
+
while (j < n && isIdChar(source[j])) j += 1;
|
|
1781
|
+
const word = source.slice(i, j);
|
|
1782
|
+
if ((word === "while" || word === "for") && lastSig !== ".") {
|
|
1783
|
+
const k = skipWsAndComments(j);
|
|
1784
|
+
if (source[k] === "(") {
|
|
1785
|
+
const close = matchParen(k);
|
|
1786
|
+
if (close !== -1) {
|
|
1787
|
+
const inner = source.slice(k + 1, close);
|
|
1788
|
+
if (word === "while") {
|
|
1789
|
+
out += `while (${guardName}() && (${inner}))`;
|
|
1790
|
+
lastSig = ")";
|
|
1791
|
+
lastWord = "";
|
|
1792
|
+
i = close + 1;
|
|
1793
|
+
continue;
|
|
1794
|
+
}
|
|
1795
|
+
const parts = splitTopLevelSemicolons(inner);
|
|
1796
|
+
if (parts.length === 3) {
|
|
1797
|
+
const cond = parts[1].trim();
|
|
1798
|
+
const guardedCond = cond ? `${guardName}() && (${cond})` : `${guardName}()`;
|
|
1799
|
+
out += `for (${parts[0]}; ${guardedCond}; ${parts[2]})`;
|
|
1800
|
+
lastSig = ")";
|
|
1801
|
+
lastWord = "";
|
|
1802
|
+
i = close + 1;
|
|
1803
|
+
continue;
|
|
1804
|
+
}
|
|
1805
|
+
out += source.slice(i, close + 1);
|
|
1806
|
+
lastSig = ")";
|
|
1807
|
+
lastWord = "";
|
|
1808
|
+
i = close + 1;
|
|
1809
|
+
continue;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
out += word;
|
|
1814
|
+
lastSig = word[word.length - 1];
|
|
1815
|
+
lastWord = word;
|
|
1816
|
+
i = j;
|
|
1817
|
+
continue;
|
|
1818
|
+
}
|
|
1819
|
+
out += ch;
|
|
1820
|
+
if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") {
|
|
1821
|
+
lastSig = ch;
|
|
1822
|
+
lastWord = "";
|
|
1823
|
+
}
|
|
1824
|
+
i += 1;
|
|
1825
|
+
}
|
|
1826
|
+
return out;
|
|
1827
|
+
};
|
|
1828
|
+
var compileScriptCompute = (source, guard) => {
|
|
1829
|
+
const makeFactory = (body) => new Function(
|
|
1830
|
+
"__hpGuard",
|
|
1831
|
+
`"use strict";
|
|
1832
|
+
${body}
|
|
1833
|
+
;if (typeof compute !== "function") { throw new Error("Script must define function compute(bars, inputs, hp)"); }
|
|
1834
|
+
return compute;`
|
|
1835
|
+
);
|
|
1836
|
+
let factory;
|
|
1837
|
+
try {
|
|
1838
|
+
factory = makeFactory(injectLoopGuards(source, "__hpGuard"));
|
|
1839
|
+
} catch {
|
|
1840
|
+
factory = makeFactory(source);
|
|
1841
|
+
}
|
|
1842
|
+
guard.reset();
|
|
1843
|
+
return factory(guard);
|
|
1844
|
+
};
|
|
1845
|
+
var validateScriptSource = (source) => {
|
|
1846
|
+
try {
|
|
1847
|
+
compileScriptCompute(source, createScriptGuard());
|
|
1848
|
+
return null;
|
|
1849
|
+
} catch (error) {
|
|
1850
|
+
return error instanceof Error ? error.message : String(error);
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
var drawScriptError = (ctx, renderContext, name, message) => {
|
|
1854
|
+
ctx.save();
|
|
1855
|
+
ctx.font = "11px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
1856
|
+
ctx.textAlign = "left";
|
|
1857
|
+
ctx.textBaseline = "top";
|
|
1858
|
+
ctx.fillStyle = "#ef5350";
|
|
1859
|
+
const text = `${name}: ${message}`.slice(0, 160);
|
|
1860
|
+
ctx.fillText(text, renderContext.chartLeft + 10, renderContext.chartTop + 24);
|
|
1861
|
+
ctx.restore();
|
|
1862
|
+
};
|
|
1863
|
+
var compileScriptIndicator = (definition) => {
|
|
1864
|
+
const pane = definition.pane ?? "separate";
|
|
1865
|
+
const defaultInputs = { showValueLine: true };
|
|
1866
|
+
for (const input of definition.inputs ?? []) {
|
|
1867
|
+
defaultInputs[input.key] = input.default;
|
|
1868
|
+
}
|
|
1869
|
+
let compiled = null;
|
|
1870
|
+
let compileError = null;
|
|
1871
|
+
const guard = createScriptGuard();
|
|
1872
|
+
try {
|
|
1873
|
+
compiled = compileScriptCompute(definition.source, guard);
|
|
1874
|
+
} catch (error) {
|
|
1875
|
+
compileError = error instanceof Error ? error.message : String(error);
|
|
1876
|
+
}
|
|
1877
|
+
const sourceKey = hashScriptSource(definition.source);
|
|
1878
|
+
let trippedError = null;
|
|
1879
|
+
const runCompute = (data, inputs) => {
|
|
1880
|
+
if (!compiled) {
|
|
1881
|
+
return { error: compileError ?? "Script failed to compile" };
|
|
1882
|
+
}
|
|
1883
|
+
if (trippedError) {
|
|
1884
|
+
return { error: trippedError };
|
|
1885
|
+
}
|
|
1886
|
+
try {
|
|
1887
|
+
guard.reset();
|
|
1888
|
+
const result = compiled(data, inputs, SCRIPT_HELPERS);
|
|
1889
|
+
if (!result || !Array.isArray(result.plots)) {
|
|
1890
|
+
return { error: "compute() must return { plots: [...] }" };
|
|
1891
|
+
}
|
|
1892
|
+
for (const plot of result.plots) {
|
|
1893
|
+
if (!plot || !Array.isArray(plot.values)) {
|
|
1894
|
+
return { error: "Every plot needs a `values` array" };
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return { result };
|
|
1898
|
+
} catch (error) {
|
|
1899
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1900
|
+
if (error instanceof ScriptBudgetError) {
|
|
1901
|
+
trippedError = message;
|
|
1902
|
+
}
|
|
1903
|
+
return { error: message };
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
const cachedCompute = (data, inputs) => withCachedComputation(
|
|
1907
|
+
`script|${definition.id}|${sourceKey}|${JSON.stringify(inputs)}`,
|
|
1908
|
+
data,
|
|
1909
|
+
() => runCompute(data, inputs)
|
|
1910
|
+
);
|
|
1911
|
+
const plugin = {
|
|
1912
|
+
id: definition.id,
|
|
1913
|
+
name: definition.name,
|
|
1914
|
+
pane,
|
|
1915
|
+
defaultInputs,
|
|
1916
|
+
draw: (ctx, renderContext, inputs) => {
|
|
1917
|
+
const outcome = cachedCompute(renderContext.data, inputs);
|
|
1918
|
+
if (!outcome.result) {
|
|
1919
|
+
drawScriptError(ctx, renderContext, definition.name, outcome.error ?? "Unknown script error");
|
|
1920
|
+
return pane === "separate" ? { title: `${definition.name} (script error)` } : void 0;
|
|
1921
|
+
}
|
|
1922
|
+
const { plots, range, guides, decimals } = outcome.result;
|
|
1923
|
+
if (pane === "separate") {
|
|
1924
|
+
const seriesList = plots.map((plot, index) => ({
|
|
1925
|
+
values: plot.values,
|
|
1926
|
+
color: plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
|
|
1927
|
+
...plot.width !== void 0 ? { width: plot.width } : {},
|
|
1928
|
+
...plot.title ? { label: plot.title } : {},
|
|
1929
|
+
...plot.style === "histogram" ? { histogram: true } : {},
|
|
1930
|
+
...plot.negativeColor ? { negativeColor: plot.negativeColor } : {}
|
|
1931
|
+
}));
|
|
1932
|
+
return drawSeparateMultiSeries(ctx, renderContext, seriesList, {
|
|
1933
|
+
title: definition.name,
|
|
1934
|
+
...range?.min !== void 0 ? { minOverride: range.min } : {},
|
|
1935
|
+
...range?.max !== void 0 ? { maxOverride: range.max } : {},
|
|
1936
|
+
...guides && guides.length > 0 ? { guideLines: guides } : {},
|
|
1937
|
+
...decimals !== void 0 ? { decimals } : {},
|
|
1938
|
+
valueLines: inputs.showValueLine !== false
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
for (let index = 0; index < plots.length; index += 1) {
|
|
1942
|
+
const plot = plots[index];
|
|
1943
|
+
if (plot.style === "histogram") continue;
|
|
1944
|
+
drawOverlaySeries(
|
|
1945
|
+
ctx,
|
|
1946
|
+
renderContext,
|
|
1947
|
+
plot.values,
|
|
1948
|
+
plot.color ?? SCRIPT_PLOT_PALETTE[index % SCRIPT_PLOT_PALETTE.length],
|
|
1949
|
+
plot.width ?? 2
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
return void 0;
|
|
1953
|
+
},
|
|
1954
|
+
...pane === "overlay" ? {
|
|
1955
|
+
getAutoscaleRange: (data, startIndex, endIndex, inputs, skipIndex) => {
|
|
1956
|
+
const outcome = cachedCompute(data, inputs);
|
|
1957
|
+
if (!outcome.result) return null;
|
|
1958
|
+
const lineSeries = outcome.result.plots.filter((plot) => plot.style !== "histogram").map((plot) => plot.values);
|
|
1959
|
+
if (lineSeries.length === 0) return null;
|
|
1960
|
+
return rangeOfSeries(lineSeries, startIndex, endIndex, skipIndex);
|
|
1961
|
+
}
|
|
1962
|
+
} : {}
|
|
1963
|
+
};
|
|
1964
|
+
if (definition.paneHeightRatio !== void 0) {
|
|
1965
|
+
plugin.paneHeightRatio = definition.paneHeightRatio;
|
|
1966
|
+
}
|
|
1967
|
+
return plugin;
|
|
1968
|
+
};
|
|
1471
1969
|
var computeMacd = (data, fast, slow, signalLength) => {
|
|
1472
1970
|
const fastEma = computeEmaSeries(data, fast, "close");
|
|
1473
1971
|
const slowEma = computeEmaSeries(data, slow, "close");
|
|
@@ -2519,7 +3017,7 @@ function createChart(element, options = {}) {
|
|
|
2519
3017
|
return;
|
|
2520
3018
|
}
|
|
2521
3019
|
const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
2522
|
-
const speed = clamp(tickerOpts.smoothingSpeed ??
|
|
3020
|
+
const speed = clamp(tickerOpts.smoothingSpeed ?? 16, 1, 60);
|
|
2523
3021
|
const dt = 1 / 60;
|
|
2524
3022
|
const lerp = 1 - Math.exp(-speed * dt);
|
|
2525
3023
|
smoothedTickerPrice += priceDiff * lerp;
|
|
@@ -2549,6 +3047,29 @@ function createChart(element, options = {}) {
|
|
|
2549
3047
|
smoothingRafId = requestAnimationFrame(tickerSmoothingLoop);
|
|
2550
3048
|
}
|
|
2551
3049
|
};
|
|
3050
|
+
let countdownTimerId = null;
|
|
3051
|
+
const countdownRepaintNeeded = () => {
|
|
3052
|
+
if (data.length === 0) {
|
|
3053
|
+
return false;
|
|
3054
|
+
}
|
|
3055
|
+
const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
3056
|
+
const labels = mergedOptions.labels ?? DEFAULT_OPTIONS.labels;
|
|
3057
|
+
return Boolean(ticker.showCountdownInLabel) || Boolean(labels.visible && labels.showCountdownToBarClose);
|
|
3058
|
+
};
|
|
3059
|
+
const armCountdownHeartbeat = () => {
|
|
3060
|
+
if (countdownTimerId !== null || !countdownRepaintNeeded()) {
|
|
3061
|
+
return;
|
|
3062
|
+
}
|
|
3063
|
+
const delay = 1e3 - Date.now() % 1e3 + 15;
|
|
3064
|
+
countdownTimerId = setTimeout(() => {
|
|
3065
|
+
countdownTimerId = null;
|
|
3066
|
+
if (!countdownRepaintNeeded()) {
|
|
3067
|
+
return;
|
|
3068
|
+
}
|
|
3069
|
+
scheduleDraw({ updateAutoScale: false });
|
|
3070
|
+
armCountdownHeartbeat();
|
|
3071
|
+
}, delay);
|
|
3072
|
+
};
|
|
2552
3073
|
const canvas = document.createElement("canvas");
|
|
2553
3074
|
const ctx = canvas.getContext("2d");
|
|
2554
3075
|
if (!ctx) {
|
|
@@ -3474,6 +3995,7 @@ function createChart(element, options = {}) {
|
|
|
3474
3995
|
let pendingDrawUpdateAutoScale = false;
|
|
3475
3996
|
let pendingDrawOverlayOnly = true;
|
|
3476
3997
|
const scheduleDraw = (options2 = {}) => {
|
|
3998
|
+
armCountdownHeartbeat();
|
|
3477
3999
|
const overlayOnly = options2.overlayOnly ?? false;
|
|
3478
4000
|
pendingDrawOverlayOnly = pendingDrawOverlayOnly && overlayOnly;
|
|
3479
4001
|
pendingDrawUpdateAutoScale = pendingDrawUpdateAutoScale || !overlayOnly && (options2.updateAutoScale ?? true);
|
|
@@ -7731,6 +8253,10 @@ function createChart(element, options = {}) {
|
|
|
7731
8253
|
cancelAnimationFrame(smoothingRafId);
|
|
7732
8254
|
smoothingRafId = null;
|
|
7733
8255
|
}
|
|
8256
|
+
if (countdownTimerId !== null) {
|
|
8257
|
+
clearTimeout(countdownTimerId);
|
|
8258
|
+
countdownTimerId = null;
|
|
8259
|
+
}
|
|
7734
8260
|
if (drawRafId !== null) {
|
|
7735
8261
|
cancelAnimationFrame(drawRafId);
|
|
7736
8262
|
drawRafId = null;
|
|
@@ -7752,6 +8278,7 @@ function createChart(element, options = {}) {
|
|
|
7752
8278
|
element.innerHTML = "";
|
|
7753
8279
|
};
|
|
7754
8280
|
draw();
|
|
8281
|
+
armCountdownHeartbeat();
|
|
7755
8282
|
return {
|
|
7756
8283
|
updateOptions,
|
|
7757
8284
|
setChartType,
|
|
@@ -7821,5 +8348,7 @@ function createChart(element, options = {}) {
|
|
|
7821
8348
|
export {
|
|
7822
8349
|
FIB_DEFAULT_PALETTE,
|
|
7823
8350
|
POSITION_DEFAULT_COLORS,
|
|
7824
|
-
|
|
8351
|
+
compileScriptIndicator,
|
|
8352
|
+
createChart,
|
|
8353
|
+
validateScriptSource
|
|
7825
8354
|
};
|