openalgo-charts 2.4.0 → 2.4.5

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
@@ -1,5 +1,7 @@
1
+ import { TradingCapabilitySource as TradingCapabilitySource$1 } from 'openalgo-charts';
2
+
1
3
  /** Library version string. Matches package.json (including locally prepared releases). */
2
- declare const VERSION = "2.4.0";
4
+ declare const VERSION = "2.4.5";
3
5
  /** Returns the current library version. */
4
6
  declare function version(): string;
5
7
 
@@ -431,6 +433,28 @@ interface Bar {
431
433
  low: number;
432
434
  close: number;
433
435
  volume?: number;
436
+ /**
437
+ * Open interest: contracts outstanding at the end of this bar. Optional,
438
+ * because only a derivatives feed carries it; on a cash instrument it is
439
+ * absent, and absent is not zero. Zero is a real reading on a contract nobody
440
+ * holds.
441
+ *
442
+ * **It is a level, not a flow, and that is the whole reason it needs saying
443
+ * here.** Volume is a quantity traded *during* the bar, so folding five
444
+ * one-minute bars into a five-minute bar adds five volumes together. Open
445
+ * interest is a position *as at* the bar, so the same fold takes the last
446
+ * one and adding them would produce a number five times too large that still
447
+ * looks entirely plausible on a chart.
448
+ *
449
+ * Every aggregation path in this library therefore treats the two
450
+ * differently: `mergeBars`, the higher-timeframe fold in `securitySeries`,
451
+ * and the tick-to-bar builders all sum volume and carry the latest open
452
+ * interest. A transform that maps one source bar to one output bar passes it
453
+ * through; one that invents bars from price alone (Renko, Point and Figure)
454
+ * does not carry it at all, because a synthetic bar has no instant to be the
455
+ * position as at.
456
+ */
457
+ oi?: number;
434
458
  /**
435
459
  * Per-bar colour override, honoured by every Family-A renderer: candles and
436
460
  * OHLC bars take it on body, border and wick together, histogram and column
@@ -899,8 +923,10 @@ declare class SeriesMarkers implements IPrimitive {
899
923
  * Which price axis a series maps to. 'right' (default) and 'left' each draw an
900
924
  * axis and autoscale independently; '' is a hidden overlay scale (no axis, its
901
925
  * own autoscale) used to pin a volume histogram inside the price pane.
926
+ * `overlay:name` creates an independent hidden scale, shared only by series
927
+ * using that same name on this pane.
902
928
  */
903
- type PriceScaleId = 'right' | 'left' | '';
929
+ type PriceScaleId = 'right' | 'left' | '' | `overlay:${string}`;
904
930
  /**
905
931
  * Value formatting for a price scale (its axis labels and crosshair tag):
906
932
  * `price` (tick-size precision), `volume` (compact 1.2K / 3.4M / 5.6B),
@@ -1338,7 +1364,7 @@ declare class Pane {
1338
1364
  private _rightScale;
1339
1365
  /** Extra scales created on demand: left axis and a hidden overlay (volume). */
1340
1366
  private _leftScale;
1341
- private _overlayScale;
1367
+ private readonly _overlayScales;
1342
1368
  /**
1343
1369
  * Scales whose price-per-bar ratio is pinned, with the geometry the ratio was
1344
1370
  * last held against. A lock stores that geometry rather than a number,
@@ -1548,1344 +1574,1504 @@ declare class Pane {
1548
1574
  }
1549
1575
 
1550
1576
  /**
1551
- * A grid overlay pinned to a corner of the pane rather than to bars.
1552
- *
1553
- * Seasonality heatmaps, performance summaries and signal scoreboards are all
1554
- * the same shape: a small table of coloured cells that stays put while the
1555
- * chart pans underneath. That makes this a screen-space primitive like the
1556
- * watermark and the pane legend, not a series: it has no time anchor, takes no
1557
- * part in autoscale, and survives a zoom untouched.
1577
+ * Horizontal price line primitive (ARCHITECTURE.md §8). The reusable base for
1578
+ * order/SL/TP/alert/indicator-level lines: a line across the plot plus a fixed
1579
+ * right-axis price tag and an optional broker-style segmented pill group on the
1580
+ * line — [badge][qty][label][✕] — with hover / dragging states (the chart
1581
+ * passes `hoverId`/`dragId` on the render context) and a drag ghost at the
1582
+ * pre-drag price via `setDragGhost`. Interaction semantics are unchanged from
1583
+ * the classic tag: the ✕ hit-tests as `${id}::close`, everything else drags.
1558
1584
  */
1559
1585
 
1560
- type TablePosition = 'top-left' | 'top-center' | 'top-right' | 'middle-left' | 'middle-center' | 'middle-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
1561
- interface TableCell {
1562
- text: string;
1563
- /** Cell fill. Transparent when omitted, so the pane shows through. */
1564
- bgColor?: string;
1565
- /** Text colour. Derived from `bgColor` for contrast when omitted. */
1566
- textColor?: string;
1567
- align?: 'left' | 'center' | 'right';
1568
- /** Overrides the table's `fontSize` for this cell, for a heading row. */
1569
- fontSize?: number;
1570
- bold?: boolean;
1571
- }
1572
- interface ChartTableOptions {
1573
- position: TablePosition;
1574
- /** Gap from the pane edge, media px. */
1575
- margin: number;
1576
- /** Column width in media px. A per-column array sizes each one separately. */
1577
- cellWidth: number | readonly number[];
1578
- cellHeight: number;
1579
- /**
1580
- * Type size in media px, or `'auto'` to fit each cell: as large as its row
1581
- * allows, shrunk until its text also fits its column. A stretched grid with
1582
- * one long label would otherwise either clip that cell or be sized down as a
1583
- * whole to suit it.
1584
- */
1585
- fontSize: number | 'auto';
1586
- /** Grid line colour. Omit to draw no grid. */
1587
- borderColor?: string;
1588
- borderWidth: number;
1586
+ interface PriceLineOptions {
1587
+ price: number;
1588
+ color: string;
1589
+ /** Line thickness in media px. Default 1. */
1590
+ lineWidth?: number;
1591
+ /** Legacy two-state dash switch, equivalent to `lineStyle: 'dashed'`. */
1592
+ dashed?: boolean;
1589
1593
  /**
1590
- * Table width as a percentage of the plot, 0 or omitted to size from
1591
- * `cellWidth` instead. Column proportions are preserved, so a per-column
1592
- * `cellWidth` array still controls the relative widths, and the percentage
1593
- * only decides the total.
1594
+ * Dash style, the three-way form of `dashed`. Set, it wins over the boolean;
1595
+ * unset, the boolean still decides, so a line built before this existed draws
1596
+ * exactly as it did.
1594
1597
  */
1595
- widthPercent?: number;
1596
- /** Table height as a percentage of the plot, 0 or omitted to size from `cellHeight`. */
1597
- heightPercent?: number;
1598
+ lineStyle?: CanvasLineStyle;
1599
+ /** Right-axis tag text. Defaults to the formatted price. */
1600
+ label?: string;
1601
+ /** Solid colored badge segment at the start of the pill group (e.g. 'BUY', 'TP', 'SL'). */
1602
+ badge?: string;
1603
+ /** Quantity segment rendered as a neutral box after the badge. */
1604
+ qty?: string | number;
1605
+ /** Info text segment (order type, price, P&L ...) — the classic left tag text. */
1606
+ leftLabel?: string;
1598
1607
  /**
1599
- * Relative row heights, one per row, defaulting to 1. A separator row is the
1600
- * reason this exists: stretched to fill a pane, an equal split makes a rule
1601
- * between two sections as tall as the sections themselves.
1608
+ * Fraction of the plot width the line spans, measured from the right (price)
1609
+ * axis. 1 = full width (default); 0.3 = only the rightmost 30%, like a
1610
+ * partial-width order line. The right-axis tag is always drawn.
1602
1611
  */
1603
- rowWeights?: readonly number[];
1604
- /** Backdrop behind the whole grid, drawn before the cells. */
1605
- background?: string;
1606
- /** Hit-test id, so a host can route clicks the way it does for other primitives. */
1607
- id?: string;
1612
+ extentFromRight?: number;
1613
+ /** Draw a cancel (✕) segment at the end of the pill group; hit-tests as `${id}::close`. */
1614
+ closeButton?: boolean;
1615
+ /** Stable id returned by hit-test (for click/drag routing). */
1616
+ id: string;
1617
+ /** Cursor hint when hovered (e.g. 'ns-resize' for draggable lines). */
1618
+ cursor?: string;
1608
1619
  }
1609
- declare const DEFAULT_CHART_TABLE_OPTIONS: ChartTableOptions;
1610
- /** Top-left corner of the grid for a position keyword, in media px. */
1611
- declare function tableOrigin(position: TablePosition, margin: number, w: number, h: number, plotW: number, plotH: number): {
1612
- x: number;
1613
- y: number;
1614
- };
1615
- declare class ChartTable implements IPrimitive {
1616
- private _rows;
1620
+ declare class PriceLine implements IPrimitive {
1617
1621
  private _opts;
1618
1622
  private _host;
1619
- /** Last drawn rect in media px, for hit-testing without recomputing layout. */
1620
- private _rect;
1621
- constructor(options?: Partial<ChartTableOptions>);
1623
+ private _ghostPrice;
1624
+ /** Pill-group geometry from the last draw (media px) for hit-testing. */
1625
+ private _group;
1626
+ constructor(opts: PriceLineOptions);
1622
1627
  attached(host: PrimitiveHost): void;
1623
1628
  detached(): void;
1629
+ get price(): number;
1630
+ /** Move the line; schedules a repaint via the host. */
1631
+ setPrice(price: number): void;
1632
+ /** Update the info segment text (e.g. live position P&L); repaints. */
1633
+ setLeftLabel(text: string): void;
1634
+ /**
1635
+ * Restyle in place; repaints. `id` is the hit-test handle the chart routes
1636
+ * clicks and drags through, so it is not patchable — swapping it under a
1637
+ * live drag would strand the gesture.
1638
+ *
1639
+ * A last-price line is the case this exists for: it has to follow the tick
1640
+ * direction, and only `setPrice` was updatable, so the colour was stuck at
1641
+ * whatever it was constructed with.
1642
+ */
1643
+ setOptions(patch: Partial<Omit<PriceLineOptions, 'id'>>): void;
1644
+ /**
1645
+ * Show a dimmed reference line at the pre-drag price while the user drags
1646
+ * (pass the original price on drag start, null on drag end to clear).
1647
+ */
1648
+ setDragGhost(price: number | null): void;
1649
+ options(): Readonly<PriceLineOptions>;
1624
1650
  zOrder(): ZOrder;
1625
- options(): Readonly<ChartTableOptions>;
1626
- setOptions(patch: Partial<ChartTableOptions>): void;
1627
- /** Replace the grid. Rows may be ragged; each is drawn to its own length. */
1628
- setRows(rows: readonly (readonly TableCell[])[]): void;
1629
- rows(): readonly (readonly TableCell[])[];
1651
+ autoscaleInfo(): {
1652
+ min: number;
1653
+ max: number;
1654
+ } | null;
1630
1655
  draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
1631
- hitTest(x: number, y: number): PrimitiveHit | null;
1656
+ hitTest(x: number, y: number, rc: PrimitiveRenderContext): PrimitiveHit | null;
1632
1657
  }
1633
1658
 
1634
1659
  /**
1635
- * Indicator registry (ARCHITECTURE.md §6A, §8). The sibling of the chart-type
1636
- * registry: that one answers *"how do I paint an array of bars"*, this one
1637
- * answers *"what do I compute, what does it plot, and what can a user tune"*.
1660
+ * Pane legend (ARCHITECTURE.md §8) — the row at the top-left
1661
+ * of a pane: a color swatch, the source's name, its parameters, the value under
1662
+ * the crosshair, and inline action buttons on the right.
1638
1663
  *
1639
- * A descriptor is data, not code-in-the-core — the chart never switches on an
1640
- * indicator id. Each `plot` names a registered **chart type**, so indicators
1641
- * ride the existing Family-A renderers and add no drawing code at all.
1664
+ * Drawn on the canvas rather than in the DOM, like `BuySellButtons` and
1665
+ * `DomLadder`, so it composites into screenshots and costs no DOM per pane.
1666
+ * Buttons hit-test as `${id}::close`, `${id}::hide`, and `${id}::settings`, so
1667
+ * the host routes them through the same `subscribeClick` path as order pills.
1642
1668
  *
1643
- * The built-in descriptors live in the lazy `openalgo-charts/indicators` tier;
1644
- * only the registry and the runtime ship in the base bundle, so an app that
1645
- * plots its own maths pays nothing for the catalog.
1669
+ * Rows stack: several legends on one pane offset each other vertically, which
1670
+ * the host does by giving each a `row` index.
1671
+ *
1672
+ * This row is also the chart's status line, so `statusLine` carries the
1673
+ * per-field switches a settings dialog expects (logo, title, market status,
1674
+ * chart values, bar change, volume, last day change, background). Every switch
1675
+ * defaults to the behaviour that predates it, so a caller that passes none sees
1676
+ * the row it always saw. Fields the primitive cannot compute (a logo bitmap,
1677
+ * whether the market is open, the change since yesterday's close) arrive
1678
+ * through `status`; with no source they draw nothing at all rather than a
1679
+ * placeholder.
1646
1680
  */
1647
1681
 
1648
- /** Which price a calculation reads from each bar. */
1649
- type IndicatorSource = 'open' | 'high' | 'low' | 'close' | 'hl2' | 'hlc3' | 'ohlc4' | 'volume';
1682
+ type PaneLegendAction = 'hide' | 'settings' | 'up' | 'down' | 'maximize' | 'close';
1650
1683
  /**
1651
- * One tunable input. `type` is what a settings UI renders; the core only reads
1652
- * `key`/`default`.
1653
- *
1654
- * `tooltip` is help text for the row. A label has to stay short enough to fit a
1655
- * dense panel, which leaves nowhere to say what a parameter actually does, and a
1656
- * ported study whose every input carried an explanation arrives here with that
1657
- * explanation dropped. A settings UI renders it as a hover affordance beside the
1658
- * label; the core ignores it.
1684
+ * One reading on a legend row. Multi-plot sources show one per plot, each in
1685
+ * that plot's own color (an MA ribbon's four averages, MACD's three lines) —
1686
+ * a single string in a single color cannot say which number is which.
1659
1687
  */
1660
- type IndicatorInput = {
1661
- key: string;
1662
- type: 'number';
1663
- label: string;
1664
- default: number;
1665
- min?: number;
1666
- max?: number;
1667
- step?: number;
1668
- group?: string;
1669
- tooltip?: string;
1670
- } | {
1671
- key: string;
1672
- type: 'boolean';
1673
- label: string;
1674
- default: boolean;
1675
- group?: string;
1676
- tooltip?: string;
1677
- } | {
1678
- key: string;
1679
- type: 'color';
1680
- label: string;
1681
- default: string;
1682
- group?: string;
1683
- tooltip?: string;
1684
- } | {
1685
- key: string;
1686
- type: 'text';
1687
- label: string;
1688
- default: string;
1689
- group?: string;
1690
- tooltip?: string;
1691
- } | {
1692
- key: string;
1693
- type: 'select';
1694
- label: string;
1695
- default: string;
1696
- options: readonly {
1697
- label: string;
1698
- value: string;
1699
- }[];
1700
- group?: string;
1701
- tooltip?: string;
1702
- } | {
1703
- key: string;
1704
- type: 'source';
1705
- label: string;
1706
- default: IndicatorSource;
1707
- group?: string;
1708
- tooltip?: string;
1688
+ interface LegendValue {
1689
+ /** Dimmed prefix, e.g. `O` / `H` / `Vol`. */
1690
+ label?: string;
1691
+ text: string;
1692
+ /** Defaults to the row's `valueColor`, then `color`, then the theme text. */
1693
+ color?: string;
1694
+ /**
1695
+ * Which status-line switch owns this reading. Untagged readings are the
1696
+ * source's own last value, governed by `statusLine.lastValueLabel`.
1697
+ */
1698
+ field?: LegendField;
1699
+ /** Higher values retain this whole reading on narrow rows. Series readings default to 1; status metadata ranks lower. */
1700
+ priority?: number;
1709
1701
  }
1710
1702
  /**
1711
- * A timeframe code (`'5m'`, `'1d'`), for a study that folds the chart's bars
1712
- * up to a coarser interval. A settings UI renders it as a select over the
1713
- * registered intervals, so the value is always one the engine can bucket by;
1714
- * a free text box would accept `'5min'` and leave the study computing on a
1715
- * code it cannot resolve. An empty default means "the chart's own interval".
1703
+ * Status-line groups a host can feed and switch off independently. The legend
1704
+ * never derives these: it tags what the host hands it, so one switch hides one
1705
+ * group and leaves the rest of the row alone.
1716
1706
  */
1717
- | {
1718
- key: string;
1719
- type: 'interval';
1720
- label: string;
1721
- default: string;
1722
- group?: string;
1723
- tooltip?: string;
1724
- }
1707
+ type LegendField = 'ohlc' | 'change' | 'volume' | 'openInterest';
1725
1708
  /**
1726
- * A wall-clock instant in the chart's zone, written `YYYY-MM-DD HH:MM` (the
1727
- * time part optional), for an anchor a user picks by date: the start of an
1728
- * anchored VWAP, an event to measure from. It is carried as that string, not
1729
- * as UTC seconds, so a layout saved in one zone restores to the same wall
1730
- * clock in another, and `zonedStringToUtcSeconds` turns it into a bar time.
1709
+ * Which name the title shows. `description` and `ticker` come from `status`;
1710
+ * with neither supplied the title falls back to `title`, which always exists.
1731
1711
  */
1732
- | {
1733
- key: string;
1734
- type: 'time';
1735
- label: string;
1736
- default: string;
1737
- group?: string;
1738
- tooltip?: string;
1739
- };
1740
- /** Dash pattern for a level, a drawing, or a plot. */
1741
- type IndicatorLineStyle = 'solid' | 'dashed' | 'dotted';
1742
- /** Line-style options, for a settings UI's Style tab. */
1743
- declare const INDICATOR_LINE_STYLES: readonly {
1744
- label: string;
1745
- value: string;
1746
- }[];
1747
- /** Settings keys the runtime derives for a plot's appearance. */
1748
- declare function plotStyleKeys(plot: IndicatorPlot): {
1749
- color: string;
1750
- width: string;
1751
- lineStyle: string;
1752
- opacity: string;
1753
- type: string;
1754
- };
1712
+ type LegendTitleMode = 'symbol' | 'description' | 'ticker';
1755
1713
  /**
1756
- * Per-plot appearance inputs, generated from the descriptor rather than
1757
- * hand-written on each one — every indicator gets colour, opacity, thickness,
1758
- * and line style for free, and a settings UI can render them as a "Style" tab
1759
- * beside the descriptor's own `inputs`.
1760
- *
1761
- * Defaults come from the plot's declared style (and its legacy `colorKey`), so
1762
- * an indicator that already ships colours keeps them.
1714
+ * The parts of the status line the primitive has no way to know. The host
1715
+ * supplies what it has; anything missing is simply not drawn.
1763
1716
  */
1764
- declare function indicatorStyleInputs(descriptor: IndicatorDescriptor): IndicatorInput[];
1717
+ interface LegendStatusData {
1718
+ /** Already-decoded logo (an `<img>`, an `ImageBitmap`, a canvas). */
1719
+ logo?: CanvasImageSource;
1720
+ /** Long name for `titleMode: 'description'`, e.g. `Apple Inc.`. */
1721
+ description?: string;
1722
+ /** Exchange ticker for `titleMode: 'ticker'`, e.g. `NASDAQ:AAPL`. */
1723
+ ticker?: string;
1724
+ /** Session state, e.g. `{ text: 'Market open', color: '#26a69a' }`. */
1725
+ marketStatus?: LegendValue;
1726
+ /** Change against the previous close, e.g. `{ text: '+1.20 (+0.75%)' }`. */
1727
+ lastDayChange?: LegendValue;
1728
+ }
1765
1729
  /**
1766
- * Chart types a plot can be re-rendered as. A moving average is a line by
1767
- * default, but the same column of numbers reads better as a histogram or an
1768
- * area depending on what you are looking for — and a descriptor cannot know
1769
- * which. Restricted to the types that make sense for a single value column.
1730
+ * A snapshot, or a getter the legend calls each frame, so live fields (market
1731
+ * status, day change) can change without the host patching options at tick
1732
+ * speed. Returning `null` means "nothing to show".
1770
1733
  */
1771
- declare const INDICATOR_PLOT_STYLES: readonly {
1772
- label: string;
1773
- value: string;
1774
- }[];
1775
- /** Canonical option list for a `type: 'source'` input, for settings UIs. */
1776
- declare const INDICATOR_SOURCES: readonly {
1777
- label: string;
1778
- value: IndicatorSource;
1779
- }[];
1780
- type IndicatorSettings = Record<string, unknown>;
1781
- /** One plotted line/band/histogram. `type` is any registered chart type. */
1782
- /** A shaded band between two of an indicator's plots. */
1783
- interface IndicatorFillSpec {
1784
- /** The two plot keys to fill between. */
1785
- between: readonly [string, string];
1786
- /** Colour where the first plot is above the second. */
1787
- colorUp?: string;
1788
- /** Colour where the second is above the first. */
1789
- colorDown?: string;
1790
- /** Settings keys holding those colours, so the band is restyleable. */
1791
- colorUpKey?: string;
1792
- colorDownKey?: string;
1793
- /** 0..1. Defaults to 0.12. */
1794
- opacity?: number;
1795
- /**
1796
- * Draw the band on the price pane even though the indicator owns a pane of
1797
- * its own. The pair with `IndicatorPlot.overlay`: a study can already send
1798
- * one plot to the candles, and a band between two such plots belongs beside
1799
- * them rather than in the study pane the fill would otherwise land in.
1800
- * Ignored for an `'onchart'` descriptor, which is on the price pane already.
1801
- */
1802
- overlay?: boolean;
1803
- }
1734
+ type LegendStatusSource = LegendStatusData | (() => LegendStatusData | null);
1804
1735
  /**
1805
- * What `colorParts` answers: a candle plot's colour split three ways, which is
1806
- * how a study paints a wick in full colour over a translucent body. `body` is
1807
- * the bar's colour (and the only part a line, histogram or column reads); a
1808
- * part left undefined falls back to `colorBy`, then to the plot's own colour.
1736
+ * Per-field switches for the status line. Existing reading fields default to
1737
+ * on; open interest and the background plate default to off. A missing reading
1738
+ * draws nothing whether its switch is on or off.
1809
1739
  */
1810
- type PlotBarColor = {
1811
- body?: string;
1812
- wick?: string;
1813
- border?: string;
1814
- };
1815
- interface IndicatorPlot {
1816
- /** Key into the `calc` result. */
1817
- key: string;
1818
- /** Registered chart type used to draw it ('line', 'histogram', 'area', ...). */
1819
- type: SeriesType;
1820
- /** Legend title. */
1821
- title: string;
1822
- /** Style overrides merged onto the chart type's defaults. */
1823
- style?: SeriesStyle;
1824
- /** Price axis for this plot. Defaults to 'right'. */
1825
- priceScaleId?: PriceScaleId;
1826
- /**
1827
- * Value formatting for the axis and crosshair tag of the scale this plot maps
1828
- * to: `percent` for a ratio study, `volume` for a cumulative one, `custom` for
1829
- * anything else.
1830
- *
1831
- * Like `style.precision`, this is a property of the **price scale**, not of the
1832
- * series, so it belongs to a plot that owns its pane. Setting it on an
1833
- * `'onchart'` plot reformats the instrument's own axis, which is almost never
1834
- * what a study wants.
1835
- *
1836
- * `percent` suffixes the value as it stands and does not scale it, so a study
1837
- * returning a 0..1 fraction should keep returning it and read `0.62%`. Scaling
1838
- * inside `calc` to make the axis read better changes the plotted value, and the
1839
- * legend, the crosshair and every downstream calculation with it.
1840
- */
1841
- priceFormat?: PriceFormat;
1842
- /**
1843
- * Draw this one plot on the price pane even though the indicator owns a pane
1844
- * of its own. An oscillator that also wants a signal
1845
- * band or a stop line sitting on the candles is the case: the study belongs in
1846
- * its own pane, one of its columns belongs on price, and splitting it into two
1847
- * indicators would make the user configure the same inputs twice.
1848
- *
1849
- * Ignored for an `'onchart'` descriptor, which is already on the price pane.
1850
- */
1851
- overlay?: boolean;
1740
+ interface LegendStatusLineOptions {
1741
+ /** Symbol logo, when `status` supplies one. */
1742
+ logo?: boolean;
1743
+ /** The bold name. Also the "name label" switch for an indicator row. */
1744
+ title?: boolean;
1745
+ /** Which name the title shows. Default `symbol`. */
1746
+ titleMode?: LegendTitleMode;
1747
+ /** Session state from `status.marketStatus`. */
1748
+ marketStatus?: boolean;
1749
+ /** The OHLC readout: readings tagged `field: 'ohlc'`. */
1750
+ chartValues?: boolean;
1751
+ /** Change over the hovered bar: readings tagged `field: 'change'`. */
1752
+ barChange?: boolean;
1753
+ /** Readings tagged `field: 'volume'`. */
1754
+ volume?: boolean;
1755
+ /** Readings tagged `field: 'openInterest'`. Off by default. */
1756
+ openInterest?: boolean;
1757
+ /** Change since the previous close, from `status.lastDayChange`. */
1758
+ lastDayChange?: boolean;
1852
1759
  /**
1853
- * Draw the column shifted this many bars to the right (negative: left). The
1854
- * column itself stays one value per bar and `calc` returns exactly what it
1855
- * always did; only where each value is painted moves. Positive is what a
1856
- * displaced cloud or a projected channel wants: the last `offset` values land
1857
- * in the right margin, past the newest candle, where no bar exists to hold
1858
- * them. It shifts the drawn series only. A fill between two plots with the
1859
- * same offset follows; the legend reads the value drawn under the cursor.
1760
+ * The source's own reading (untagged values). This is the scales-and-lines
1761
+ * "last value label" control, which lands here because the legend is what
1762
+ * draws that number on this row.
1860
1763
  */
1861
- offset?: number;
1764
+ lastValueLabel?: boolean;
1862
1765
  /**
1863
- * Settings key holding this plot's color, so a settings change restyles the
1864
- * series without a full rebuild.
1766
+ * Plate behind the row's text, for legibility over candles. Off by default:
1767
+ * the row has never had one, and turning it on is a deliberate choice.
1865
1768
  */
1866
- colorKey?: string;
1769
+ background?: boolean;
1770
+ /** Plate opacity, 0..1. Default 0.8, matching the hover plate. */
1771
+ backgroundOpacity?: number;
1772
+ /** Plate color. Defaults to the theme background. */
1773
+ backgroundColor?: string;
1774
+ }
1775
+ interface PaneLegendOptions {
1776
+ /** Explicit false suppresses OI without discarding its saved switch. Set by an owning chart. */
1777
+ hasOpenInterest?: boolean;
1778
+ /** Stable id; buttons hit-test as `${id}::close` etc. */
1779
+ id: string;
1780
+ /** Bold source name, e.g. `RSI`. */
1781
+ title: string;
1782
+ /** Dimmed parameter summary after the title, e.g. `14 close`. */
1783
+ params?: string;
1784
+ /** Swatch color; omitted draws no swatch. */
1785
+ color?: string;
1867
1786
  /**
1868
- * Four `calc` keys to draw this plot as bar-shaped elements instead of one
1869
- * value per bar: candles, hollow candles, OHLC bars, high-low.
1870
- *
1871
- * A single column cannot express those at all, and the alternative (a second
1872
- * result shape for `calc`) would fork the contract every descriptor and every
1873
- * helper is written against. Naming four columns inside the *same*
1874
- * `IndicatorValues` keeps one shape: a smoothed Heikin-Ashi overlay, a
1875
- * higher-timeframe candle, a synthetic spread instrument each return four
1876
- * ordinary columns and point at them from here.
1877
- *
1878
- * The named columns must all exist and be bar-aligned, or `addIndicator`
1879
- * throws. `key` stays the series identity and the legend reading falls back to
1880
- * the `close` column.
1787
+ * Color for the live value. Defaults to `color`, then the theme's text — so a
1788
+ * row can tint its reading (an up/down change) without being forced to show a
1789
+ * swatch in that same color.
1881
1790
  */
1882
- ohlc?: {
1883
- open: string;
1884
- high: string;
1885
- low: string;
1886
- close: string;
1887
- };
1791
+ valueColor?: string;
1792
+ /** Vertical slot on the pane (0 = topmost). */
1793
+ row?: number;
1888
1794
  /**
1889
- * Per-bar colour, for plots whose meaning changes bar to bar — a MACD
1890
- * histogram is four colours by sign and direction, a conditional study two.
1891
- * Return `undefined` to fall back to the plot's own colour.
1795
+ * Which inline action buttons to draw, left to right. Each hit-tests as
1796
+ * `${id}::<action>`:
1797
+ * - `up` / `down` — move this pane one slot (`::up` / `::down`)
1798
+ * - `hide` — toggle visibility (`::hide`)
1799
+ * - `maximize` — expand this pane to fill the chart (`::maximize`)
1800
+ * - `close` — remove the source, and its pane if it empties (`::close`)
1892
1801
  *
1893
- * Reaches the renderer as `Bar.color`, so every Family-A plot type honours
1894
- * it: histogram, column, candles, OHLC bars, line, step and area.
1895
- */
1896
- colorBy?(ctx: {
1897
- value: number;
1898
- index: number;
1899
- values: IndicatorValues;
1900
- settings: IndicatorSettings;
1901
- }): string | undefined;
1902
- /**
1903
- * Per-bar colour split three ways, for a candle plot whose wick or border
1904
- * should not follow its body: a solid wick over a translucent body, a
1905
- * border in the trend colour. Takes precedence over `colorBy` for the parts
1906
- * it names; a part it leaves undefined falls back to `colorBy`, then to the
1907
- * plot's own colour. A value plot (line, histogram, column) reads `body` only.
1802
+ * When only some actions fit, the end of this list stays visible.
1803
+ * Defaults to `['up', 'down', 'hide', 'maximize', 'close']` for pane sources
1804
+ * and `['hide', 'close']` for overlays (pass explicitly to override).
1908
1805
  */
1909
- colorParts?(ctx: {
1910
- value: number;
1911
- index: number;
1912
- values: IndicatorValues;
1913
- settings: IndicatorSettings;
1914
- }): PlotBarColor | undefined;
1915
- }
1916
- /** A horizontal reference level (RSI 70/30, Stochastic 80/20, a zero line). */
1917
- interface IndicatorLevel {
1918
- price: number;
1919
- color?: string;
1920
- title?: string;
1921
- /** Legacy two-state dash switch. `lineStyle` wins when both are given. */
1922
- dashed?: boolean;
1923
- lineWidth?: number;
1924
- lineStyle?: IndicatorLineStyle;
1806
+ actions?: readonly PaneLegendAction[];
1807
+ /** Rendered as hidden (dimmed, eye hollow). */
1808
+ hidden?: boolean;
1809
+ /** Rendered as maximized (the maximize glyph becomes restore). */
1810
+ maximized?: boolean;
1811
+ /** Text size in media px. Default 11. */
1812
+ font?: number;
1813
+ /** Left inset from the plot edge in media px. Default 8. */
1814
+ left?: number;
1815
+ /** Top inset in media px. Default 6. */
1816
+ top?: number;
1817
+ /** Per-field status-line switches. Patching merges field by field. */
1818
+ statusLine?: LegendStatusLineOptions;
1819
+ /** Host-supplied status-line data (logo, names, market state, day change). */
1820
+ status?: LegendStatusSource;
1925
1821
  }
1926
- /** One end of an indicator drawing: a time on the shared axis, a price on the pane's scale. */
1927
- interface DrawAnchor {
1928
- time: number;
1929
- price: number;
1822
+ declare class PaneLegend implements IPrimitive {
1823
+ private _opts;
1824
+ private _host;
1825
+ private _values;
1826
+ /** Button geometry from the last draw, in media px, for hit-testing. */
1827
+ private _buttons;
1828
+ /** Right edge of the drawn row, in media px. */
1829
+ private _width;
1830
+ private _plotWidth;
1831
+ private _plotHeight;
1832
+ constructor(opts: PaneLegendOptions);
1833
+ attached(host: PrimitiveHost): void;
1834
+ detached(): void;
1835
+ zOrder(): ZOrder;
1836
+ autoscaleInfo(): null;
1837
+ /** A single live reading after the params (typically crosshair-driven). */
1838
+ setValue(text: string, color?: string): void;
1839
+ /** One reading per plot, each in its own color. */
1840
+ setValues(values: readonly LegendValue[]): void;
1841
+ setOptions(patch: Partial<PaneLegendOptions>): void;
1842
+ /** Resolve the host's status data for this frame; `{}` when it has none. */
1843
+ private _status;
1844
+ options(): PaneLegendOptions;
1845
+ draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
1846
+ hitTest(x: number, y: number): PrimitiveHit | null;
1930
1847
  }
1848
+
1931
1849
  /**
1932
- * A free-standing shape an indicator paints in its own pane, anchored to time
1933
- * and price rather than to a bar index.
1850
+ * A grid overlay pinned to a corner of the pane rather than to bars.
1934
1851
  *
1935
- * Plots, levels and markers each answer a different question and none of them
1936
- * answers this one: a pivot-to-pivot trendline, a supply zone, an order block,
1937
- * a measured-move projection are all geometry between two arbitrary points, and
1938
- * a column of one value per bar cannot express any of them. Anchors are times,
1939
- * so a shape stays put when history is paged in and every logical index shifts.
1852
+ * Seasonality heatmaps, performance summaries and signal scoreboards are all
1853
+ * the same shape: a small table of coloured cells that stays put while the
1854
+ * chart pans underneath. That makes this a screen-space primitive like the
1855
+ * watermark and the pane legend, not a series: it has no time anchor, takes no
1856
+ * part in autoscale, and survives a zoom untouched.
1940
1857
  */
1941
- type IndicatorDrawing = {
1942
- kind: 'line';
1943
- from: DrawAnchor;
1944
- to: DrawAnchor;
1945
- color?: string;
1946
- lineWidth?: number;
1947
- lineStyle?: IndicatorLineStyle;
1948
- /** Continue the line past its anchor to the pane edge. */
1949
- extendLeft?: boolean;
1950
- extendRight?: boolean;
1951
- } | {
1952
- kind: 'box';
1953
- from: DrawAnchor;
1954
- to: DrawAnchor;
1955
- /** Border colour. Omit `fillColor` to draw an outline only. */
1956
- color?: string;
1957
- fillColor?: string;
1958
- /** Fill alpha, 0..1. Defaults to 0.12. */
1959
- opacity?: number;
1960
- lineWidth?: number;
1961
- /** Caption drawn on a plate at the centre of the box; `\n` splits lines. */
1962
- text?: string;
1963
- textColor?: string;
1964
- /**
1965
- * Detail shown on a plate while the pointer rests on the box, and gone
1966
- * when it leaves; `\n` splits lines. A zone that carries its size, its
1967
- * age and what formed it cannot print all of that on the box without
1968
- * hiding the candles under it, so the caption names it and this explains
1969
- * it. The box becomes hit-testable, and `id` (or the tooltip text) is
1970
- * what `subscribeClick` reports for it.
1971
- */
1972
- tooltip?: string;
1973
- /** Hit id, for `subscribeClick`. Defaults to the tooltip text. */
1974
- id?: string;
1975
- } | {
1976
- kind: 'label';
1977
- at: DrawAnchor;
1978
- /** `\n` splits lines. */
1858
+
1859
+ type TablePosition = 'top-left' | 'top-center' | 'top-right' | 'middle-left' | 'middle-center' | 'middle-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
1860
+ interface TableCell {
1979
1861
  text: string;
1980
- /** Plate fill. */
1981
- color?: string;
1862
+ /** Cell fill. Transparent when omitted, so the pane shows through. */
1863
+ bgColor?: string;
1864
+ /** Text colour. Derived from `bgColor` for contrast when omitted. */
1982
1865
  textColor?: string;
1983
- /** Which edge of the plate sits on the anchor. Defaults to 'center'. */
1984
1866
  align?: 'left' | 'center' | 'right';
1985
- /** Hover detail, as on a box. */
1986
- tooltip?: string;
1987
- /** Hit id, for `subscribeClick`. Defaults to the tooltip text. */
1988
- id?: string;
1989
- } | {
1990
- kind: 'polyline';
1991
- points: readonly DrawAnchor[];
1992
- color?: string;
1993
- lineWidth?: number;
1994
- /** Close the path back to the first point (a triangle, a wedge). */
1995
- closed?: boolean;
1996
- fillColor?: string;
1997
- /** Fill alpha, 0..1. Defaults to 0.12. */
1998
- opacity?: number;
1999
- };
2000
- /** `calc` output: one array per plot key, aligned 1:1 with the input bars. */
2001
- type IndicatorValues = Record<string, readonly (number | null)[]>;
2002
- /** Per-instance scratch owned by the descriptor (Tier-2 data lands here). */
2003
- type IndicatorStore = Record<string, unknown>;
2004
- /**
2005
- * The fourth, optional argument to `calc` (and the sixth to `calcTail`): what
2006
- * the calculation cannot read off the bars themselves.
2007
- *
2008
- * It is optional so that every descriptor written against `calc(bars, settings,
2009
- * store)` keeps its exact signature and its exact behaviour, which is the whole
2010
- * point: a calculation that ignores the context computes what it always did.
2011
- */
2012
- interface IndicatorCalcContext {
1867
+ /** Overrides the table's `fontSize` for this cell, for a heading row. */
1868
+ fontSize?: number;
1869
+ bold?: boolean;
1870
+ }
1871
+ interface ChartTableOptions {
1872
+ position: TablePosition;
1873
+ /** Gap from the pane edge, media px. */
1874
+ margin: number;
1875
+ /** Column width in media px. A per-column array sizes each one separately. */
1876
+ cellWidth: number | readonly number[];
1877
+ cellHeight: number;
2013
1878
  /**
2014
- * Where the last bar stands, so a study can act once per bar rather than once
2015
- * per tick, or refuse to signal off a bar that is still moving.
1879
+ * Type size in media px, or `'auto'` to fit each cell: as large as its row
1880
+ * allows, shrunk until its text also fits its column. A stretched grid with
1881
+ * one long label would otherwise either clip that cell or be sized down as a
1882
+ * whole to suit it.
2016
1883
  */
2017
- barState: {
2018
- /** The most recent update appended a bar rather than replacing one. */
2019
- isNew: boolean;
2020
- /** The last bar has closed: its interval has elapsed on the chart clock. */
2021
- isConfirmed: boolean;
2022
- /** A live feed is driving updates, rather than a one-off history load. */
2023
- isRealtime: boolean;
2024
- /** Index of the last bar, `bars.length - 1` (-1 when there are none). */
2025
- lastIndex: number;
2026
- };
2027
- /** The instrument, when the host knows one. See `IndicatorAttachContext`. */
2028
- symbol?: string;
2029
- /** The timeframe (`'5m'`, `'1d'`), on the same terms as `symbol`. */
2030
- interval?: string;
2031
- /** The chart's IANA zone, the calendar its axis is labelled in. */
2032
- timezone: string;
2033
- /** Chart wall clock in UTC seconds, the clock the countdown row reads. */
2034
- now(): number;
1884
+ fontSize: number | 'auto';
1885
+ /** Grid line colour. Omit to draw no grid. */
1886
+ borderColor?: string;
1887
+ borderWidth: number;
2035
1888
  /**
2036
- * The instrument's tick size, from the **price pane's** `minMove`.
2037
- *
2038
- * The price pane and not the indicator's own, because `calc` runs on the
2039
- * instrument's bars whichever pane the plot lands in, and a study pane is not
2040
- * quoted in the instrument's tick: an RSI is a dimensionless 0..100 band, so
2041
- * its scale carries no tick at all to read.
2042
- *
2043
- * `undefined` when the host has not told the chart what it is, which is the
2044
- * honest answer rather than a guessed 0.01: an indicator sizing a range in
2045
- * ticks has to tell "one paisa" apart from "nobody said".
1889
+ * Table width as a percentage of the plot, 0 or omitted to size from
1890
+ * `cellWidth` instead. Column proportions are preserved, so a per-column
1891
+ * `cellWidth` array still controls the relative widths, and the percentage
1892
+ * only decides the total.
2046
1893
  */
2047
- tickSize?: number;
2048
- }
2049
- /** What an alert's `when` predicate is handed, for the bar it is judging. */
2050
- interface IndicatorAlertContext {
2051
- bars: readonly Bar[];
2052
- values: IndicatorValues;
2053
- settings: Readonly<IndicatorSettings>;
2054
- /** The bar being evaluated. */
2055
- index: number;
2056
- }
2057
- /**
2058
- * A condition the runtime watches, declared by the descriptor rather than wired
2059
- * up by the host: the indicator is the only thing that knows what a crossover of
2060
- * its own columns means.
2061
- *
2062
- * Evaluated once per bar, for bars that are new since the last evaluation, so
2063
- * adding the indicator to a loaded chart fires nothing for history.
2064
- */
2065
- interface IndicatorAlertSpec {
2066
- /** Stable within the descriptor, e.g. `'cross-up'`. */
2067
- id: string;
2068
- /** Short human label, e.g. `'MACD crossed up'`. */
2069
- title: string;
1894
+ widthPercent?: number;
1895
+ /** Table height as a percentage of the plot, 0 or omitted to size from `cellHeight`. */
1896
+ heightPercent?: number;
2070
1897
  /**
2071
- * Longer text for a notification; defaults to `title`. A function is handed
2072
- * the same context `when` judged, so the message can carry the bar's own
2073
- * numbers: the price it crossed at, the histogram reading, a JSON body for a
2074
- * webhook. It runs only for a bar `when` accepted.
1898
+ * Relative row heights, one per row, defaulting to 1. A separator row is the
1899
+ * reason this exists: stretched to fill a pane, an equal split makes a rule
1900
+ * between two sections as tall as the sections themselves.
2075
1901
  */
2076
- message?: string | ((ctx: IndicatorAlertContext) => string);
2077
- when(ctx: IndicatorAlertContext): boolean;
1902
+ rowWeights?: readonly number[];
1903
+ /** Backdrop behind the whole grid, drawn before the cells. */
1904
+ background?: string;
1905
+ /** Hit-test id, so a host can route clicks the way it does for other primitives. */
1906
+ id?: string;
1907
+ }
1908
+ declare const DEFAULT_CHART_TABLE_OPTIONS: ChartTableOptions;
1909
+ /** Top-left corner of the grid for a position keyword, in media px. */
1910
+ declare function tableOrigin(position: TablePosition, margin: number, w: number, h: number, plotW: number, plotH: number): {
1911
+ x: number;
1912
+ y: number;
1913
+ };
1914
+ declare class ChartTable implements IPrimitive {
1915
+ private _rows;
1916
+ private _opts;
1917
+ private _host;
1918
+ /** Last drawn rect in media px, for hit-testing without recomputing layout. */
1919
+ private _rect;
1920
+ constructor(options?: Partial<ChartTableOptions>);
1921
+ attached(host: PrimitiveHost): void;
1922
+ detached(): void;
1923
+ zOrder(): ZOrder;
1924
+ options(): Readonly<ChartTableOptions>;
1925
+ setOptions(patch: Partial<ChartTableOptions>): void;
1926
+ /** Replace the grid. Rows may be ragged; each is drawn to its own length. */
1927
+ setRows(rows: readonly (readonly TableCell[])[]): void;
1928
+ rows(): readonly (readonly TableCell[])[];
1929
+ draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
1930
+ hitTest(x: number, y: number): PrimitiveHit | null;
2078
1931
  }
1932
+
2079
1933
  /**
2080
- * Thrown by a `calc` (or any hook) to say its inputs cannot produce a study,
2081
- * the way a script language's runtime error does: a period at or below zero, a
2082
- * fast length above the slow one, a benchmark the provider cannot serve.
1934
+ * Vertical gradient across the band, graded between two prices rather than
1935
+ * two pixel rows.
2083
1936
  *
2084
- * Any error out of a recompute is caught by the runtime and published on the
2085
- * instance's data status as `{ state: 'error' }`, so the chart keeps drawing
2086
- * every other indicator and a host can show the reason beside this one. This
2087
- * class exists so a descriptor can throw a **named** condition and a host can
2088
- * tell a bad input, which the user can fix, from a bug, which they cannot.
1937
+ * The stops are anchored in PRICE, not in pixels, because that is what the
1938
+ * shading is claiming: a band graded from its 70 level down to its 30 means
1939
+ * those levels, and a viewport-relative gradient would slide off them the
1940
+ * moment the pane is panned or the scale re-fits.
2089
1941
  */
2090
- declare class IndicatorInputError extends Error {
2091
- constructor(message: string);
1942
+ interface FillGradient {
1943
+ /** Price the top stop sits at. Omitted: the band's own highest value. */
1944
+ topValue?: number;
1945
+ /** Price the bottom stop sits at. Omitted: the band's own lowest value. */
1946
+ bottomValue?: number;
1947
+ topColor: string;
1948
+ bottomColor: string;
2092
1949
  }
2093
- /**
2094
- * Bars of another instrument or interval, supplied by the host on request.
2095
- *
2096
- * The engine is handed one symbol's bars and owns no transport, so a study
2097
- * that compares against a benchmark, or a Tier-2 provider that needs a second
2098
- * series, asks the host through this and the host answers from wherever it
2099
- * keeps history. `from` and `to` are UTC seconds; `signal` is aborted when the
2100
- * instance is removed or its settings change, so a provider can drop the
2101
- * request rather than answer into the void.
2102
- */
2103
- interface IndicatorBarsRequest {
2104
- symbol: string;
2105
- exchange?: string;
2106
- interval: string;
2107
- from: number;
2108
- to: number;
2109
- signal?: AbortSignal;
1950
+ interface IndicatorFillOptions {
1951
+ /** Fill colour where the first series is above the second. */
1952
+ colorUp: string;
1953
+ /** Fill colour where the second is above the first. */
1954
+ colorDown: string;
1955
+ /** 0..1. Defaults to 0.12 — a band must not drown the candles it sits behind. */
1956
+ opacity?: number;
1957
+ /**
1958
+ * Set to grade the band instead of flat-filling it, in place of
1959
+ * `colorUp`/`colorDown`. A point carrying its own `color` still overrides it.
1960
+ * Unset leaves the two-colour fill exactly as it was.
1961
+ */
1962
+ gradient?: FillGradient;
2110
1963
  }
2111
- type IndicatorBarsProvider = (request: IndicatorBarsRequest) => Promise<readonly Bar[]>;
2112
- /** Payload of the `'indicator:alert'` event on the chart's own bus. */
2113
- interface IndicatorAlertPayload {
2114
- /** Descriptor id, e.g. `'macd'`. */
2115
- indicatorId: string;
2116
- /** Instance id, so a host can tell three EMAs apart. */
2117
- instanceId: string;
2118
- alertId: string;
2119
- title: string;
2120
- message: string;
2121
- /** The bar that triggered it: UTC seconds, and its index in `bars`. */
2122
- time: number;
1964
+ /** One bar's pair of values; `null` where either plot has no value yet. */
1965
+ interface FillPoint {
1966
+ /** Logical index on the shared time axis (fractional is fine). */
2123
1967
  index: number;
1968
+ a: number | null;
1969
+ b: number | null;
1970
+ /**
1971
+ * Paints this bar onward in one colour, for a band shaded by something other
1972
+ * than which line leads: trend state, a regime, a third series. The run is
1973
+ * split at the bar where the colour changes, so it is per-bar and not merely
1974
+ * per-crossing. Most specific wins: this, then the gradient, then up/down.
1975
+ */
1976
+ color?: string;
2124
1977
  }
2125
- /** Optional instrument identity supplied by the host. */
2126
- interface ChartDataContext {
2127
- symbol?: string;
2128
- exchange?: string;
2129
- interval?: string;
1978
+ declare class IndicatorFill implements IPrimitive {
1979
+ private _points;
1980
+ private _opts;
1981
+ private _host;
1982
+ private _visible;
1983
+ constructor(options: IndicatorFillOptions);
1984
+ attached(host: PrimitiveHost): void;
1985
+ detached(): void;
1986
+ /** Behind the series, so the lines and candles stay crisp on top of it. */
1987
+ zOrder(): ZOrder;
1988
+ /** The plots it spans already drive the scale; the fill must not widen it. */
1989
+ autoscaleInfo(): null;
1990
+ setPoints(points: readonly FillPoint[]): void;
1991
+ setOptions(patch: Partial<IndicatorFillOptions>): void;
1992
+ setVisible(on: boolean): void;
1993
+ draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
2130
1994
  }
2131
- /** Source identity changed, or the available source-bar range changed. */
2132
- type IndicatorDataChange = 'context' | 'range';
2133
- /** Observable state of an indicator's external data lifecycle. */
2134
- type IndicatorDataStatus = {
2135
- state: 'loading' | 'ready' | 'empty' | 'unsupported';
2136
- } | {
2137
- state: 'error';
2138
- error: unknown;
2139
- };
2140
- /** What an indicator's `attach` lifecycle can reach. */
2141
- interface IndicatorAttachContext {
2142
- /** Optional host identity, independent of the indicator's own settings. */
1995
+
1996
+ /**
1997
+ * Indicator runtime (ARCHITECTURE.md §8). Turns an `IndicatorDescriptor` into
1998
+ * live chart objects: one series per plot, optional reference levels, an
1999
+ * optional fixed pane range — and recomputes them when the source data or the
2000
+ * settings change.
2001
+ *
2002
+ * It adds **no rendering code**. Every plot names a registered chart type, so
2003
+ * indicators draw through the same Family-A renderers as any other series.
2004
+ */
2005
+
2006
+ /** The slice of the chart the runtime needs. Keeps this module testable alone. */
2007
+ interface IndicatorHost {
2008
+ /** Forget a disposed instance, including disposal through its public handle. */
2009
+ indicatorRemoved?(instanceId: string): void;
2010
+ /** Optional instrument identity and source-range notifications. */
2143
2011
  dataContext?(): Readonly<ChartDataContext> | undefined;
2144
- /** Read bars and identity again when the host publishes a change. */
2145
2012
  subscribeDataChanges?(listener: (change: IndicatorDataChange) => void): () => void;
2146
- /** Publish status and an explicit retry action for this instance. */
2147
- setDataStatus?(status: IndicatorDataStatus): void;
2148
- setDataRetry?(retry: (() => void) | null): void;
2149
- /** Instance lifetime. Aborted on removal, preserved across style changes. */
2150
- signal?: AbortSignal;
2151
- /** Current settings (live — read at call time, not captured). */
2152
- settings(): Readonly<IndicatorSettings>;
2153
- /** The chart's current source bars. */
2154
- bars(): readonly Bar[];
2155
- /** Re-run `calc` and repaint — call when external data arrives. */
2156
- requestRecompute(): void;
2157
- /** Scratch this instance owns; the same object `calc` receives. */
2158
- store: IndicatorStore;
2013
+ /** Add the pane-legend row (name + inline up/down/hide/maximize/close). */
2014
+ addIndicatorLegend(opts: {
2015
+ id: string;
2016
+ title: string;
2017
+ params: string;
2018
+ color?: string;
2019
+ row: number;
2020
+ paneIndex: number;
2021
+ }): PaneLegend;
2022
+ removeIndicatorLegend(legend: PaneLegend): void;
2023
+ /** How many legends already sit on this pane, so rows stack. */
2024
+ legendRowsOn(paneIndex: number): number;
2025
+ addIndicatorSeries(type: string, paneIndex: number, style: Record<string, unknown> | undefined, priceScaleId: string | undefined,
2026
+ /** Axis/crosshair formatting for the scale this plot maps to. */
2027
+ priceFormat?: PriceFormat): SeriesApi;
2159
2028
  /**
2160
- * The instrument the chart is showing, when the host knows it.
2029
+ * Add a reference level. One options object rather than seven positional
2030
+ * arguments: the list grew a width and a dash style in 1.7.1, and a call site
2031
+ * of seven bare values is where the next one gets passed in the wrong slot.
2032
+ */
2033
+ addIndicatorLevel(level: {
2034
+ price: number;
2035
+ color: string;
2036
+ /** Kept for hosts predating `lineStyle`; always `lineStyle === 'dashed'`. */
2037
+ dashed: boolean;
2038
+ lineWidth: number;
2039
+ lineStyle: IndicatorLineStyle;
2040
+ label: string;
2041
+ id: string;
2042
+ }, paneIndex: number): PriceLine;
2043
+ removeIndicatorLevel(line: PriceLine): void;
2044
+ /** Attach a band drawn behind the plots (an Ichimoku cloud). */
2045
+ addIndicatorFill(fill: IndicatorFill, paneIndex: number): void;
2046
+ removeIndicatorFill(fill: IndicatorFill): void;
2047
+ /**
2048
+ * Detach a signal-marker layer. There is no matching `add`: the layer comes
2049
+ * from `series.createMarkers()` on a plot's own series, so it already lands in
2050
+ * the right pane. Removing a series does not remove its primitives, hence this.
2051
+ */
2052
+ removeIndicatorMarkers(markers: SeriesMarkers): void;
2053
+ /** Attach a corner-pinned summary grid to a pane, and detach it again. */
2054
+ addIndicatorTable(paneIndex: number): ChartTable;
2055
+ removeIndicatorTable(table: ChartTable): void;
2056
+ /**
2057
+ * Attach an arbitrary primitive to a pane, and detach it again. Carries both
2058
+ * the descriptor's drawing layer and whatever a Tier-2 `attach` lifecycle
2059
+ * wants to paint, so those two do not need a host method each.
2161
2060
  *
2162
- * Hosts can supply this through `IndicatorHost` or Chart's explicit data
2163
- * context. Without a configured identity it stays undefined. External
2164
- * studies read `dataContext` to include the exchange as well.
2061
+ * Optional, like `timezone`, so a host predating it still satisfies this
2062
+ * interface: an indicator that draws simply draws nothing there.
2063
+ */
2064
+ addIndicatorPrimitive?(primitive: IPrimitive, paneIndex: number): void;
2065
+ removeIndicatorPrimitive?(primitive: IPrimitive): void;
2066
+ /**
2067
+ * Recompute whatever the host has marked stale, before a caller reads a value.
2068
+ *
2069
+ * Optional, like `timezone`, so a host predating it still satisfies this
2070
+ * interface: one that recomputes eagerly has nothing to flush.
2071
+ */
2072
+ flushIndicators?(): void;
2073
+ /** Bars of the primary price series — the calculation input. */
2074
+ sourceBars(): readonly Bar[];
2075
+ /** Selected candle after native or linked hover; absent means latest. */
2076
+ legendIndex?(): number | undefined;
2077
+ /** Index of a fresh pane for an indicator that wants its own. */
2078
+ nextPaneIndex(): number;
2079
+ /**
2080
+ * The chart's configured IANA zone. Optional so a host predating the option
2081
+ * still satisfies this interface; absent means the shipped default.
2082
+ *
2083
+ * A descriptor is handed bars and settings and never the chart, so this is
2084
+ * how the calendar an anchor resets on (a VWAP session, a seasonality month)
2085
+ * reaches the calculation. See `IndicatorInstance._descriptorSettings`.
2086
+ */
2087
+ timezone?(): string;
2088
+ /**
2089
+ * The instrument and timeframe on screen, when the host knows them. The
2090
+ * host can supply an explicit `dataContext` instead. Without either hook,
2091
+ * a descriptor sees `undefined` rather than a guessed identity.
2165
2092
  */
2166
2093
  symbol?(): string | undefined;
2167
- /** The chart's timeframe (`'5m'`, `'1d'`), on the same terms as `symbol`. */
2168
2094
  interval?(): string | undefined;
2169
- /** The chart's IANA zone, the one its axis is labelled in. */
2170
- timezone?(): string;
2171
- /** Chart wall clock in UTC seconds, the same clock the countdown row uses. */
2095
+ /** Chart wall clock in UTC seconds. Absent means the system clock. */
2172
2096
  now?(): number;
2173
2097
  /**
2174
- * Ask the host for another instrument's (or interval's) bars. Always present
2175
- * under `chart.addIndicator`; it rejects when the host has registered no
2176
- * provider (`chart.setBarsProvider`), so a study can treat the rejection as
2177
- * "unsupported here" and say so through `setDataStatus`.
2098
+ * Publish an indicator's per-bar colours onto the **primary price series**,
2099
+ * or withdraw them with `null`. `owner` is the instance id: a host holds one
2100
+ * overlay at a time and only lets its current owner withdraw it, so a second
2101
+ * publisher taking over does not get cleared by the first one's teardown.
2102
+ *
2103
+ * Optional, like `timezone`: a host that does not implement it simply gives a
2104
+ * `barColors` descriptor nowhere to publish, and the indicator's own plots are
2105
+ * unaffected.
2106
+ */
2107
+ setBarColors?(colors: readonly (string | null)[] | null, owner: string): void;
2108
+ /** Emit on the chart's event bus (indicator alerts, and `attach`'s own events). */
2109
+ emit?(event: string, payload: unknown): void;
2110
+ /**
2111
+ * Bars of another instrument or interval, from wherever the host keeps its
2112
+ * history. Optional: without it the attach context's `requestBars` rejects,
2113
+ * which a study reads as "not available on this chart".
2178
2114
  */
2179
2115
  requestBars?(request: IndicatorBarsRequest): Promise<readonly Bar[]>;
2180
- /** The pane this instance drew into. Moves when panes are reordered. */
2181
- paneIndex?(): number;
2182
- /** Attach a primitive to this indicator's pane, and detach it again. */
2183
- addPrimitive?(p: IPrimitive): void;
2184
- removePrimitive?(p: IPrimitive): void;
2185
2116
  /**
2186
- * Emit on the chart's own event bus, the one `chart.on(name, cb)` listens to.
2117
+ * Tick size of the named pane's price scale, or undefined when none is set.
2118
+ * Optional so a host predating it still satisfies this interface.
2187
2119
  *
2188
- * The declarative `alerts` slot covers a condition read off the bars; this is
2189
- * the imperative half, for an indicator whose signal arrives from outside the
2190
- * calculation entirely (a subscription its `attach` opened).
2120
+ * Per pane, and the panes genuinely differ: a pane that does not quote the
2121
+ * instrument has no tick to report. Pane 0 is the price pane, so it is the
2122
+ * one to ask for the instrument's own step.
2191
2123
  */
2192
- emit?(event: string, payload: unknown): void;
2193
- }
2194
- /**
2195
- * What `levels` is handed. It carries `bars` and `values` **and** spreads the
2196
- * settings keys onto itself, so the built-ins written against the original
2197
- * `levels(settings)` signature keep working unchanged: they read
2198
- * `ctx.overbought` (or pass `ctx` to a `num(s, key, default)` helper) and find
2199
- * exactly what they found before. A widened parameter is the only way a level
2200
- * can be data-derived (yesterday's high, the session VWAP band), and that is a
2201
- * whole class of level that could not be expressed at all before.
2202
- *
2203
- * The three data members are optional for the same backward-compatibility
2204
- * reason, not because the runtime ever omits them: it always passes all three,
2205
- * but a caller holding only a settings bag must still be able to invoke
2206
- * `levels` directly. A descriptor that needs the data should default them
2207
- * (`ctx.bars ?? []`).
2208
- *
2209
- * `settings`, `bars` and `values` are therefore reserved keys, the way
2210
- * `timezone` already is in the settings a `calc` receives: an input declared
2211
- * under one of those names is shadowed here.
2212
- */
2213
- type IndicatorLevelContext = IndicatorSettings & {
2214
- settings?: Readonly<IndicatorSettings>;
2215
- bars?: readonly Bar[];
2216
- values?: IndicatorValues;
2217
- };
2218
- interface IndicatorDescriptor {
2219
- /** Registry key, e.g. `'macd'`. */
2220
- id: string;
2221
- /** Display name, e.g. `'MACD'`. */
2222
- name: string;
2223
- /** Grouping for a picker UI ('Trend', 'Momentum', 'Volume', 'Volatility'). */
2224
- category?: string;
2225
- /** `'onchart'` overlays the price pane; `'pane'` gets its own pane. */
2226
- placement: 'onchart' | 'pane';
2227
- inputs: readonly IndicatorInput[];
2228
- plots: readonly IndicatorPlot[];
2229
- /**
2230
- * Shaded bands between pairs of plots — the Ichimoku cloud, a Bollinger
2231
- * channel. A pair of lines is not the same picture as a filled region: the
2232
- * fill is what makes "price is above the cloud" readable at a glance, and
2233
- * which side leads is itself the signal, hence the two colours.
2234
- */
2235
- fills?: readonly IndicatorFillSpec[];
2236
- /**
2237
- * Full recompute over every bar. Must return arrays the same length as
2238
- * `bars` (use `null` for warmup gaps — the line renderer breaks across them
2239
- * and autoscale skips them).
2240
- *
2241
- * Tier-1 indicators are pure functions of `(bars, settings)` and ignore
2242
- * `store`. Tier-2 indicators — the ones with their own data — read the
2243
- * external series their `attach` lifecycle put in `store`.
2244
- */
2245
- calc(bars: readonly Bar[], settings: Readonly<IndicatorSettings>, store: IndicatorStore, ctx?: IndicatorCalcContext): IndicatorValues;
2246
- /**
2247
- * Optional per-instance lifecycle, for indicators whose data is not derived
2248
- * from the chart's bars (open interest, CVD, an external feed). Called once
2249
- * when the instance is created; return a teardown function.
2250
- *
2251
- * Fetch into `ctx.store`, then call `ctx.requestRecompute()` — `calc` runs
2252
- * again and reads what you stored.
2253
- */
2254
- attach?(ctx: IndicatorAttachContext): (() => void) | void;
2255
- /**
2256
- * Optional incremental path, called instead of `calc` when only the tail
2257
- * changed (a live tick). Return values for indices `[fromIndex, bars.length)`
2258
- * — the runtime splices them onto the previous result — or `null` to fall
2259
- * back to a full `calc`.
2260
- *
2261
- * Without it every tick costs a full recompute. That is a few hundred
2262
- * microseconds for one indicator over 50k bars, but it is O(n) per tick per
2263
- * indicator, so implement this for anything meant to run in a busy live pane.
2264
- */
2265
- calcTail?(bars: readonly Bar[], settings: Readonly<IndicatorSettings>, fromIndex: number, previous: IndicatorValues, store: IndicatorStore, ctx?: IndicatorCalcContext): IndicatorValues | null;
2266
- /**
2267
- * Optional bar-anchored signal markers — a named "Buy"/"Sell" plate, an arrow
2268
- * at a crossover. Runs after every `calc`, so it reads the values it just
2269
- * produced rather than recomputing anything.
2270
- *
2271
- * A plot cannot express this: a plot is a column of prices drawn as a line or
2272
- * histogram, whereas a signal is a discrete event with a label. Returning `[]`
2273
- * (when a `showLabels`-style input is off, say) clears the layer.
2274
- */
2275
- markers?(ctx: {
2276
- bars: readonly Bar[];
2277
- values: IndicatorValues;
2278
- settings: Readonly<IndicatorSettings>;
2279
- }): readonly SeriesMarker[];
2280
- /**
2281
- * Optional summary grid pinned to a corner of the pane.
2282
- *
2283
- * Some studies are not a value per bar at all: a seasonality heatmap is a
2284
- * matrix of monthly returns, a scoreboard is a handful of statistics. Those
2285
- * have no place in `calc`, whose contract is one column per plot aligned to
2286
- * the bars, so they come back through here instead. Runs after every `calc`.
2287
- *
2288
- * Return `null` (or a zero-row grid) to draw nothing, which is how a
2289
- * `showTable`-style input should switch it off.
2290
- */
2291
- table?(ctx: {
2292
- bars: readonly Bar[];
2293
- values: IndicatorValues;
2294
- settings: Readonly<IndicatorSettings>;
2295
- }): {
2296
- rows: readonly (readonly TableCell[])[];
2297
- options?: Partial<ChartTableOptions>;
2298
- } | null;
2299
- /**
2300
- * Optional free-standing shapes drawn in the indicator's pane: trendlines
2301
- * between pivots, supply and demand boxes, projection labels. Runs after
2302
- * every `calc`, like `markers` and `table`, and the returned list replaces the
2303
- * previous one wholesale, so returning `[]` clears the layer.
2304
- */
2305
- draws?(ctx: {
2306
- bars: readonly Bar[];
2307
- values: IndicatorValues;
2308
- settings: Readonly<IndicatorSettings>;
2309
- }): readonly IndicatorDrawing[];
2310
- /**
2311
- * Optional per-bar shading behind everything else in the indicator's pane: a
2312
- * full-height column per bar, `null` where nothing should be shaded.
2313
- *
2314
- * A regime study answers "which state is the market in right now", and that is
2315
- * a property of the whole bar, not a price. Drawn as a plot it would need a
2316
- * value to sit at and would fight the pane's autoscale; as a column behind the
2317
- * candles it reads at a glance and costs the scale nothing.
2318
- *
2319
- * Runs after every `calc`. Return `[]` to clear the layer.
2320
- */
2321
- background?(ctx: {
2322
- bars: readonly Bar[];
2323
- values: IndicatorValues;
2324
- settings: Readonly<IndicatorSettings>;
2325
- }): readonly (string | null)[];
2124
+ tickSize?(paneIndex: number): number | undefined;
2326
2125
  /**
2327
- * Optional recolouring of the **main price candles**, one entry per bar,
2328
- * `null` to leave that bar with its own colour.
2126
+ * Write a number the way the price axis of that pane writes it.
2329
2127
  *
2330
- * Distinct from a plot's `colorBy`, which paints the indicator's own series: a
2331
- * trend filter, a volatility regime or a higher-timeframe bias is a statement
2332
- * about the price bars themselves, and drawing it as a second series beside
2333
- * them says something weaker.
2128
+ * The legend sits inches from the axis and names the same quantity, so the
2129
+ * two disagreeing is the reading a user has to reconcile themselves. Deriving
2130
+ * the format here from a tick got that wrong twice over: a study pane carries
2131
+ * no tick at all, so a percentage read `0.618` beside an axis saying `0.62`,
2132
+ * and a price pane's tick alone misses the precision floor and the host's own
2133
+ * formatter, so a volume study read seven digits where its axis said `1.20M`.
2334
2134
  *
2335
- * Only one indicator's colours can be on the candles at a time; the most
2336
- * recent publisher wins, and publishers run in `addIndicator` order, so the
2337
- * winner is the same one from frame to frame. Removing it, or hiding it,
2338
- * restores the bars' own colours.
2339
- */
2340
- barColors?(ctx: {
2341
- bars: readonly Bar[];
2342
- values: IndicatorValues;
2343
- settings: Readonly<IndicatorSettings>;
2344
- }): readonly (string | null)[];
2345
- /**
2346
- * Optional conditions the runtime watches on the descriptor's behalf, emitted
2347
- * as `'indicator:alert'` on the chart's event bus with an
2348
- * {@link IndicatorAlertPayload}. See {@link IndicatorAlertSpec}.
2349
- */
2350
- alerts?: readonly IndicatorAlertSpec[];
2351
- /**
2352
- * Optional horizontal reference levels drawn in the indicator's pane.
2353
- * Recomputed after every `calc`, so a level derived from the data (the
2354
- * previous day's high) tracks it. See `IndicatorLevelContext` for why the
2355
- * argument still reads as a settings bag.
2356
- */
2357
- levels?(ctx: IndicatorLevelContext): readonly IndicatorLevel[];
2358
- /**
2359
- * Optional fixed price range for the indicator's own pane (RSI 0..100).
2360
- * Applied only when the indicator creates its pane — two indicators sharing a
2361
- * pane would otherwise fight over it.
2135
+ * Asking the scale removes the second opinion. Optional so a host driving
2136
+ * this module alone still works, falling back to the magnitude ladder.
2362
2137
  */
2363
- range?(settings: Readonly<IndicatorSettings>): {
2138
+ formatPrice?(paneIndex: number, value: number): string | undefined;
2139
+ /** Pin a pane's price scale to a fixed range, or release it with `null`. */
2140
+ setPaneRange(paneIndex: number, range: {
2364
2141
  min: number;
2365
2142
  max: number;
2366
- } | null;
2143
+ } | null): void;
2144
+ }
2145
+ /** Public handle returned by `chart.addIndicator(...)`. */
2146
+ interface IndicatorApi {
2147
+ /** External data state, or null for a study without a managed lifecycle. */
2148
+ dataStatus(): Readonly<IndicatorDataStatus> | null;
2149
+ /** Observe changes; immediately receives the current managed status, if any. */
2150
+ subscribeDataStatus(listener: (status: Readonly<IndicatorDataStatus>) => void): () => void;
2151
+ /** Retry external history when the descriptor supplies a retry action. */
2152
+ retryData(): void;
2153
+ /** Unique instance id (several instances of one indicator can coexist). */
2154
+ readonly id: string;
2155
+ /** The descriptor id, e.g. `'macd'`. */
2156
+ readonly indicatorId: string;
2157
+ /** Display name. */
2158
+ readonly name: string;
2159
+ /** Pane the indicator drew into. */
2160
+ readonly paneIndex: number;
2161
+ /** Current settings (a copy). */
2162
+ settings(): IndicatorSettings;
2163
+ /** Merge a settings patch, recompute, and restyle. */
2164
+ setSettings(patch: Readonly<IndicatorSettings>): void;
2165
+ /** The series backing one plot key, for direct styling. */
2166
+ series(plotKey: string): SeriesApi | undefined;
2167
+ /** Latest computed values (a reference — do not mutate). */
2168
+ values(): IndicatorValues;
2169
+ /** Whether the plots are drawn (the legend's eye toggle). */
2170
+ visible(): boolean;
2171
+ /** Show or hide every plot without removing the instance. */
2172
+ setVisible(on: boolean): void;
2173
+ /** This indicator's legend row, or null if it has none. */
2174
+ legend(): PaneLegend | null;
2175
+ /** Refresh the legend readings for a bar index; omit for the latest bar. */
2176
+ updateLegendValues(index?: number): void;
2177
+ /** Remove every series, level, and legend row this indicator created. */
2178
+ remove(): void;
2367
2179
  }
2368
- /** Register an indicator descriptor. Later registrations of the same id win. */
2369
- declare function registerIndicator(descriptor: IndicatorDescriptor): void;
2370
- declare function getIndicator(id: string): IndicatorDescriptor;
2371
- declare function hasIndicator(id: string): boolean;
2372
- declare function registeredIndicators(): IndicatorDescriptor[];
2373
- /** The descriptor's declared defaults as a settings object. */
2374
- declare function indicatorDefaults(descriptor: IndicatorDescriptor): IndicatorSettings;
2375
- /** Read one bar's value for a price source. */
2376
- declare function sourceValue(bar: Bar, source: IndicatorSource): number;
2377
- /** Read a whole bar array for a price source. */
2378
- declare function sourceValues(bars: readonly Bar[], source: IndicatorSource): number[];
2379
-
2380
- /**
2381
- * Horizontal price line primitive (ARCHITECTURE.md §8). The reusable base for
2382
- * order/SL/TP/alert/indicator-level lines: a line across the plot plus a fixed
2383
- * right-axis price tag and an optional broker-style segmented pill group on the
2384
- * line — [badge][qty][label][✕] — with hover / dragging states (the chart
2385
- * passes `hoverId`/`dragId` on the render context) and a drag ghost at the
2386
- * pre-drag price via `setDragGhost`. Interaction semantics are unchanged from
2387
- * the classic tag: the ✕ hit-tests as `${id}::close`, everything else drags.
2388
- */
2389
2180
 
2390
- interface PriceLineOptions {
2181
+ /** A primary-source mutation, emitted after indicator invalidation. */
2182
+ interface ChartDataUpdate {
2183
+ kind: 'update' | 'reset' | 'prepend';
2184
+ time?: number;
2185
+ }
2186
+ /** Minimum headless chart surface needed to evaluate alerts. */
2187
+ interface AlertChartHost {
2188
+ primaryBars(): readonly Bar[];
2189
+ getDataContext(): Readonly<ChartDataContext> | undefined;
2190
+ on(event: string, callback: (payload: unknown) => void): () => void;
2191
+ emit(event: string, payload: unknown): void;
2192
+ /** Flushes computed study values. Only required by indicator-source alerts. */
2193
+ indicators?(): readonly (Pick<IndicatorApi, 'id' | 'paneIndex' | 'series' | 'values'> & Partial<Pick<IndicatorApi, 'indicatorId'>>)[];
2194
+ addPrimitive?(primitive: IPrimitive, paneIndex?: number): void;
2195
+ removePrimitive?(primitive: IPrimitive): void;
2196
+ alertState?(): AlertsDocument | undefined;
2197
+ setAlertState?(document: AlertsDocument | undefined): void;
2198
+ }
2199
+ type AlertCondition = 'crossing' | 'crossingUp' | 'crossingDown' | 'greaterThan' | 'lessThan' | 'enteringRange' | 'leavingRange' | 'matches';
2200
+ type AlertPolicy = 'onBarClose' | 'onTouch';
2201
+ type AlertRepeat = 'once' | 'everyTime';
2202
+ type AlertState = 'armed' | 'triggered' | 'expired' | 'disabled';
2203
+ /** Prices are in primary-series units. Range conditions require upperPrice. */
2204
+ interface PriceAlertSource {
2205
+ kind: 'price';
2391
2206
  price: number;
2392
- color: string;
2393
- /** Line thickness in media px. Default 1. */
2394
- lineWidth?: number;
2395
- /** Legacy two-state dash switch, equivalent to `lineStyle: 'dashed'`. */
2396
- dashed?: boolean;
2397
- /**
2398
- * Dash style, the three-way form of `dashed`. Set, it wins over the boolean;
2399
- * unset, the boolean still decides, so a line built before this existed draws
2400
- * exactly as it did.
2401
- */
2402
- lineStyle?: CanvasLineStyle;
2403
- /** Right-axis tag text. Defaults to the formatted price. */
2404
- label?: string;
2405
- /** Solid colored badge segment at the start of the pill group (e.g. 'BUY', 'TP', 'SL'). */
2406
- badge?: string;
2407
- /** Quantity segment rendered as a neutral box after the badge. */
2408
- qty?: string | number;
2409
- /** Info text segment (order type, price, P&L ...) — the classic left tag text. */
2410
- leftLabel?: string;
2411
- /**
2412
- * Fraction of the plot width the line spans, measured from the right (price)
2413
- * axis. 1 = full width (default); 0.3 = only the rightmost 30%, like a
2414
- * partial-width order line. The right-axis tag is always drawn.
2415
- */
2416
- extentFromRight?: number;
2417
- /** Draw a cancel (✕) segment at the end of the pill group; hit-tests as `${id}::close`. */
2418
- closeButton?: boolean;
2419
- /** Stable id returned by hit-test (for click/drag routing). */
2207
+ upperPrice?: number;
2208
+ }
2209
+ /** A threshold in plot units, anchored to one specific study instance. */
2210
+ interface IndicatorAlertSource {
2211
+ kind: 'indicator';
2212
+ instanceId: string;
2213
+ plotKey: string;
2214
+ value: number;
2215
+ upperValue?: number;
2216
+ }
2217
+ interface BarConditionAlertSource {
2218
+ kind: 'barCondition';
2420
2219
  id: string;
2421
- /** Cursor hint when hovered (e.g. 'ns-resize' for draggable lines). */
2422
- cursor?: string;
2423
2220
  }
2424
- declare class PriceLine implements IPrimitive {
2425
- private _opts;
2426
- private _host;
2427
- private _ghostPrice;
2428
- /** Pill-group geometry from the last draw (media px) for hit-testing. */
2429
- private _group;
2430
- constructor(opts: PriceLineOptions);
2431
- attached(host: PrimitiveHost): void;
2432
- detached(): void;
2433
- get price(): number;
2434
- /** Move the line; schedules a repaint via the host. */
2435
- setPrice(price: number): void;
2436
- /** Update the info segment text (e.g. live position P&L); repaints. */
2437
- setLeftLabel(text: string): void;
2438
- /**
2439
- * Restyle in place; repaints. `id` is the hit-test handle the chart routes
2440
- * clicks and drags through, so it is not patchable — swapping it under a
2441
- * live drag would strand the gesture.
2442
- *
2443
- * A last-price line is the case this exists for: it has to follow the tick
2444
- * direction, and only `setPrice` was updatable, so the colour was stuck at
2445
- * whatever it was constructed with.
2446
- */
2447
- setOptions(patch: Partial<Omit<PriceLineOptions, 'id'>>): void;
2448
- /**
2449
- * Show a dimmed reference line at the pre-drag price while the user drags
2450
- * (pass the original price on drag start, null on drag end to clear).
2451
- */
2452
- setDragGhost(price: number | null): void;
2453
- options(): Readonly<PriceLineOptions>;
2454
- zOrder(): ZOrder;
2455
- autoscaleInfo(): {
2456
- min: number;
2457
- max: number;
2458
- } | null;
2459
- draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
2460
- hitTest(x: number, y: number, rc: PrimitiveRenderContext): PrimitiveHit | null;
2221
+ interface DrawingAlertSource {
2222
+ kind: 'drawing';
2223
+ drawingId: string;
2224
+ level?: string;
2225
+ /** Required for a drawing on a study pane, so prices are not compared to different units. */
2226
+ input?: {
2227
+ instanceId: string;
2228
+ plotKey: string;
2229
+ };
2230
+ }
2231
+ type AlertSource = PriceAlertSource | IndicatorAlertSource | BarConditionAlertSource | DrawingAlertSource;
2232
+ /** Missing values, an absent anchor or a paused/context-mismatched chart are unavailable. */
2233
+ interface AlertAvailability {
2234
+ available: boolean;
2235
+ reason?: string;
2236
+ paneIndex?: number;
2237
+ }
2238
+ interface AlertDrawingValue {
2239
+ price: number;
2240
+ upperPrice?: number;
2241
+ paneIndex: number;
2242
+ }
2243
+ interface AlertDrawingLevel {
2244
+ id: string;
2245
+ title: string;
2246
+ }
2247
+ interface AlertDrawingInfo extends AlertAvailability {
2248
+ levels: readonly AlertDrawingLevel[];
2249
+ }
2250
+ /** Implemented by the optional drawing tier; the alert engine never imports it. */
2251
+ interface AlertDrawingProvider {
2252
+ get(id: string): unknown;
2253
+ valueAt(id: string, time: number, level?: string): AlertDrawingValue | undefined;
2254
+ alertInfo(id: string): AlertDrawingInfo;
2255
+ }
2256
+ interface BarConditionContext {
2257
+ /** Only the prefix through index is exposed, including for confirmed-bar checks. */
2258
+ bars: readonly Bar[];
2259
+ index: number;
2260
+ }
2261
+ interface BarCondition {
2262
+ id: string;
2263
+ title: string;
2264
+ /** Runs at the alert's chosen policy, never for loaded history. */
2265
+ when(context: BarConditionContext): boolean;
2266
+ }
2267
+ /** An alert belongs to the instrument and interval present when it was armed. */
2268
+ interface AlertScope {
2269
+ symbol?: string;
2270
+ exchange?: string;
2271
+ interval?: string;
2272
+ }
2273
+ interface AlertInput {
2274
+ id?: string;
2275
+ source: AlertSource;
2276
+ condition?: AlertCondition;
2277
+ /** Defaults to onBarClose. An intrabar touch may disappear from final history. */
2278
+ policy?: AlertPolicy;
2279
+ repeat?: AlertRepeat;
2280
+ state?: 'armed' | 'disabled';
2281
+ title?: string;
2282
+ message?: string;
2283
+ cooldownSeconds?: number;
2284
+ /** UTC seconds. At this instant the alert expires before it can trigger. */
2285
+ expiresAt?: number;
2286
+ /** Opaque host routing data. The controller never interprets or delivers it. */
2287
+ payload?: unknown;
2288
+ }
2289
+ type AlertPatch = Partial<Omit<AlertInput, 'id'>>;
2290
+ /** Lifecycle record. Snapshots detach mutable configuration, retaining opaque payloads. */
2291
+ interface Alert extends Omit<AlertInput, 'id' | 'condition' | 'policy' | 'repeat' | 'state' | 'title' | 'cooldownSeconds'> {
2292
+ id: string;
2293
+ condition: AlertCondition;
2294
+ policy: AlertPolicy;
2295
+ repeat: AlertRepeat;
2296
+ state: AlertState;
2297
+ title: string;
2298
+ cooldownSeconds: number;
2299
+ scope: AlertScope;
2300
+ lastTriggeredAt?: number;
2301
+ lastTriggeredTime?: number;
2302
+ /** Newest confirmed bar already judged, including a nonmatch. */
2303
+ lastClosedTime?: number;
2304
+ /** Newest intrabar match consumed, including one suppressed by cooldown. */
2305
+ lastTouchedTime?: number;
2306
+ }
2307
+ /** Portable trader records. JSON persistence rejects unsupported host payloads. */
2308
+ interface AlertsDocument {
2309
+ version: 1;
2310
+ alerts: Alert[];
2311
+ }
2312
+ /** Shared delivery fields for trader and indicator-authored alerts. */
2313
+ interface AlertEventPayload {
2314
+ alertId: string;
2315
+ title: string;
2316
+ message?: string;
2317
+ /** Source bar UTC seconds and source index, not delivery wall-clock time. */
2318
+ time: number;
2319
+ index: number;
2320
+ }
2321
+ interface AlertTriggeredPayload extends AlertEventPayload {
2322
+ price: number;
2323
+ alert: Alert;
2324
+ }
2325
+ interface AlertControllerOptions {
2326
+ /** Delivery and expiry clock in UTC seconds. Defaults to Date.now() / 1000. */
2327
+ now?: () => number;
2328
+ drawings?: AlertDrawingProvider;
2329
+ /** PriceLine visuals are enabled on chart hosts; disable for a model-only consumer. */
2330
+ visuals?: boolean;
2461
2331
  }
2462
2332
 
2463
2333
  /**
2464
- * Pane legend (ARCHITECTURE.md §8) — the row at the top-left
2465
- * of a pane: a color swatch, the source's name, its parameters, the value under
2466
- * the crosshair, and inline action buttons on the right.
2467
- *
2468
- * Drawn on the canvas rather than in the DOM, like `BuySellButtons` and
2469
- * `DomLadder`, so it composites into screenshots and costs no DOM per pane.
2470
- * Buttons hit-test as `${id}::close`, `${id}::hide`, and `${id}::settings`, so
2471
- * the host routes them through the same `subscribeClick` path as order pills.
2334
+ * Indicator registry (ARCHITECTURE.md §6A, §8). The sibling of the chart-type
2335
+ * registry: that one answers *"how do I paint an array of bars"*, this one
2336
+ * answers *"what do I compute, what does it plot, and what can a user tune"*.
2472
2337
  *
2473
- * Rows stack: several legends on one pane offset each other vertically, which
2474
- * the host does by giving each a `row` index.
2338
+ * A descriptor is data, not code-in-the-core — the chart never switches on an
2339
+ * indicator id. Each `plot` names a registered **chart type**, so indicators
2340
+ * ride the existing Family-A renderers and add no drawing code at all.
2475
2341
  *
2476
- * This row is also the chart's status line, so `statusLine` carries the
2477
- * per-field switches a settings dialog expects (logo, title, market status,
2478
- * chart values, bar change, volume, last day change, background). Every switch
2479
- * defaults to the behaviour that predates it, so a caller that passes none sees
2480
- * the row it always saw. Fields the primitive cannot compute (a logo bitmap,
2481
- * whether the market is open, the change since yesterday's close) arrive
2482
- * through `status`; with no source they draw nothing at all rather than a
2483
- * placeholder.
2342
+ * The built-in descriptors live in the lazy `openalgo-charts/indicators` tier;
2343
+ * only the registry and the runtime ship in the base bundle, so an app that
2344
+ * plots its own maths pays nothing for the catalog.
2484
2345
  */
2485
2346
 
2486
- type PaneLegendAction = 'hide' | 'settings' | 'up' | 'down' | 'maximize' | 'close';
2347
+ /** Which price a calculation reads from each bar. */
2348
+ type IndicatorSource = 'open' | 'high' | 'low' | 'close' | 'hl2' | 'hlc3' | 'ohlc4' | 'volume';
2487
2349
  /**
2488
- * One reading on a legend row. Multi-plot sources show one per plot, each in
2489
- * that plot's own color (an MA ribbon's four averages, MACD's three lines) —
2490
- * a single string in a single color cannot say which number is which.
2350
+ * One tunable input. `type` is what a settings UI renders; the core only reads
2351
+ * `key`/`default`.
2352
+ *
2353
+ * `tooltip` is help text for the row. A label has to stay short enough to fit a
2354
+ * dense panel, which leaves nowhere to say what a parameter actually does, and a
2355
+ * ported study whose every input carried an explanation arrives here with that
2356
+ * explanation dropped. A settings UI renders it as a hover affordance beside the
2357
+ * label; the core ignores it.
2491
2358
  */
2492
- interface LegendValue {
2493
- /** Dimmed prefix, e.g. `O` / `H` / `Vol`. */
2494
- label?: string;
2495
- text: string;
2496
- /** Defaults to the row's `valueColor`, then `color`, then the theme text. */
2497
- color?: string;
2498
- /**
2499
- * Which status-line switch owns this reading. Untagged readings are the
2500
- * source's own last value, governed by `statusLine.lastValueLabel`.
2501
- */
2502
- field?: LegendField;
2359
+ type IndicatorInput = {
2360
+ key: string;
2361
+ type: 'number';
2362
+ label: string;
2363
+ default: number;
2364
+ min?: number;
2365
+ max?: number;
2366
+ step?: number;
2367
+ group?: string;
2368
+ tooltip?: string;
2369
+ } | {
2370
+ key: string;
2371
+ type: 'boolean';
2372
+ label: string;
2373
+ default: boolean;
2374
+ group?: string;
2375
+ tooltip?: string;
2376
+ } | {
2377
+ key: string;
2378
+ type: 'color';
2379
+ label: string;
2380
+ default: string;
2381
+ group?: string;
2382
+ tooltip?: string;
2383
+ } | {
2384
+ key: string;
2385
+ type: 'text';
2386
+ label: string;
2387
+ default: string;
2388
+ group?: string;
2389
+ tooltip?: string;
2390
+ } | {
2391
+ key: string;
2392
+ type: 'select';
2393
+ label: string;
2394
+ default: string;
2395
+ options: readonly {
2396
+ label: string;
2397
+ value: string;
2398
+ }[];
2399
+ group?: string;
2400
+ tooltip?: string;
2401
+ } | {
2402
+ key: string;
2403
+ type: 'source';
2404
+ label: string;
2405
+ default: IndicatorSource;
2406
+ group?: string;
2407
+ tooltip?: string;
2503
2408
  }
2504
2409
  /**
2505
- * Status-line groups a host can feed and switch off independently. The legend
2506
- * never derives these: it tags what the host hands it, so one switch hides one
2507
- * group and leaves the rest of the row alone.
2410
+ * A timeframe code (`'5m'`, `'1d'`), for a study that folds the chart's bars
2411
+ * up to a coarser interval. A settings UI renders it as a select over the
2412
+ * registered intervals, so the value is always one the engine can bucket by;
2413
+ * a free text box would accept `'5min'` and leave the study computing on a
2414
+ * code it cannot resolve. An empty default means "the chart's own interval".
2508
2415
  */
2509
- type LegendField = 'ohlc' | 'change' | 'volume';
2416
+ | {
2417
+ key: string;
2418
+ type: 'interval';
2419
+ label: string;
2420
+ default: string;
2421
+ group?: string;
2422
+ tooltip?: string;
2423
+ }
2510
2424
  /**
2511
- * Which name the title shows. `description` and `ticker` come from `status`;
2512
- * with neither supplied the title falls back to `title`, which always exists.
2425
+ * A wall-clock instant in the chart's zone, written `YYYY-MM-DD HH:MM` (the
2426
+ * time part optional), for an anchor a user picks by date: the start of an
2427
+ * anchored VWAP, an event to measure from. It is carried as that string, not
2428
+ * as UTC seconds, so a layout saved in one zone restores to the same wall
2429
+ * clock in another, and `zonedStringToUtcSeconds` turns it into a bar time.
2513
2430
  */
2514
- type LegendTitleMode = 'symbol' | 'description' | 'ticker';
2431
+ | {
2432
+ key: string;
2433
+ type: 'time';
2434
+ label: string;
2435
+ default: string;
2436
+ group?: string;
2437
+ tooltip?: string;
2438
+ };
2439
+ /** Dash pattern for a level, a drawing, or a plot. */
2440
+ type IndicatorLineStyle = 'solid' | 'dashed' | 'dotted';
2441
+ /** Line-style options, for a settings UI's Style tab. */
2442
+ declare const INDICATOR_LINE_STYLES: readonly {
2443
+ label: string;
2444
+ value: string;
2445
+ }[];
2446
+ /** Settings keys the runtime derives for a plot's appearance. */
2447
+ declare function plotStyleKeys(plot: IndicatorPlot): {
2448
+ color: string;
2449
+ width: string;
2450
+ lineStyle: string;
2451
+ opacity: string;
2452
+ type: string;
2453
+ };
2515
2454
  /**
2516
- * The parts of the status line the primitive has no way to know. The host
2517
- * supplies what it has; anything missing is simply not drawn.
2455
+ * Per-plot appearance inputs, generated from the descriptor rather than
2456
+ * hand-written on each one — every indicator gets colour, opacity, thickness,
2457
+ * and line style for free, and a settings UI can render them as a "Style" tab
2458
+ * beside the descriptor's own `inputs`.
2459
+ *
2460
+ * Defaults come from the plot's declared style (and its legacy `colorKey`), so
2461
+ * an indicator that already ships colours keeps them.
2518
2462
  */
2519
- interface LegendStatusData {
2520
- /** Already-decoded logo (an `<img>`, an `ImageBitmap`, a canvas). */
2521
- logo?: CanvasImageSource;
2522
- /** Long name for `titleMode: 'description'`, e.g. `Apple Inc.`. */
2523
- description?: string;
2524
- /** Exchange ticker for `titleMode: 'ticker'`, e.g. `NASDAQ:AAPL`. */
2525
- ticker?: string;
2526
- /** Session state, e.g. `{ text: 'Market open', color: '#26a69a' }`. */
2527
- marketStatus?: LegendValue;
2528
- /** Change against the previous close, e.g. `{ text: '+1.20 (+0.75%)' }`. */
2529
- lastDayChange?: LegendValue;
2530
- }
2463
+ declare function indicatorStyleInputs(descriptor: IndicatorDescriptor): IndicatorInput[];
2531
2464
  /**
2532
- * A snapshot, or a getter the legend calls each frame, so live fields (market
2533
- * status, day change) can change without the host patching options at tick
2534
- * speed. Returning `null` means "nothing to show".
2465
+ * Chart types a plot can be re-rendered as. A moving average is a line by
2466
+ * default, but the same column of numbers reads better as a histogram or an
2467
+ * area depending on what you are looking for — and a descriptor cannot know
2468
+ * which. Restricted to the types that make sense for a single value column.
2535
2469
  */
2536
- type LegendStatusSource = LegendStatusData | (() => LegendStatusData | null);
2470
+ declare const INDICATOR_PLOT_STYLES: readonly {
2471
+ label: string;
2472
+ value: string;
2473
+ }[];
2474
+ /** Canonical option list for a `type: 'source'` input, for settings UIs. */
2475
+ declare const INDICATOR_SOURCES: readonly {
2476
+ label: string;
2477
+ value: IndicatorSource;
2478
+ }[];
2479
+ type IndicatorSettings = Record<string, unknown>;
2480
+ /** One plotted line/band/histogram. `type` is any registered chart type. */
2481
+ /** A shaded band between two of an indicator's plots. */
2482
+ interface IndicatorFillSpec {
2483
+ /** The two plot keys to fill between. */
2484
+ between: readonly [string, string];
2485
+ /** Colour where the first plot is above the second. */
2486
+ colorUp?: string;
2487
+ /** Colour where the second is above the first. */
2488
+ colorDown?: string;
2489
+ /** Settings keys holding those colours, so the band is restyleable. */
2490
+ colorUpKey?: string;
2491
+ colorDownKey?: string;
2492
+ /** 0..1. Defaults to 0.12. */
2493
+ opacity?: number;
2494
+ /**
2495
+ * Draw the band on the price pane even though the indicator owns a pane of
2496
+ * its own. The pair with `IndicatorPlot.overlay`: a study can already send
2497
+ * one plot to the candles, and a band between two such plots belongs beside
2498
+ * them rather than in the study pane the fill would otherwise land in.
2499
+ * Ignored for an `'onchart'` descriptor, which is on the price pane already.
2500
+ */
2501
+ overlay?: boolean;
2502
+ }
2537
2503
  /**
2538
- * Per-field switches for the status line. Every one defaults to on, so the
2539
- * absent option object reproduces the row exactly as it drew before these
2540
- * existed. A field whose data is missing draws nothing whether it is on or off.
2504
+ * What `colorParts` answers: a candle plot's colour split three ways, which is
2505
+ * how a study paints a wick in full colour over a translucent body. `body` is
2506
+ * the bar's colour (and the only part a line, histogram or column reads); a
2507
+ * part left undefined falls back to `colorBy`, then to the plot's own colour.
2541
2508
  */
2542
- interface LegendStatusLineOptions {
2543
- /** Symbol logo, when `status` supplies one. */
2544
- logo?: boolean;
2545
- /** The bold name. Also the "name label" switch for an indicator row. */
2546
- title?: boolean;
2547
- /** Which name the title shows. Default `symbol`. */
2548
- titleMode?: LegendTitleMode;
2549
- /** Session state from `status.marketStatus`. */
2550
- marketStatus?: boolean;
2551
- /** The OHLC readout: readings tagged `field: 'ohlc'`. */
2552
- chartValues?: boolean;
2553
- /** Change over the hovered bar: readings tagged `field: 'change'`. */
2554
- barChange?: boolean;
2555
- /** Readings tagged `field: 'volume'`. */
2556
- volume?: boolean;
2557
- /** Change since the previous close, from `status.lastDayChange`. */
2558
- lastDayChange?: boolean;
2509
+ type PlotBarColor = {
2510
+ body?: string;
2511
+ wick?: string;
2512
+ border?: string;
2513
+ };
2514
+ interface IndicatorPlot {
2515
+ /** Key into the `calc` result. */
2516
+ key: string;
2517
+ /** Registered chart type used to draw it ('line', 'histogram', 'area', ...). */
2518
+ type: SeriesType;
2519
+ /** Legend title. */
2520
+ title: string;
2521
+ /** Style overrides merged onto the chart type's defaults. */
2522
+ style?: SeriesStyle;
2523
+ /** Price axis for this plot. Defaults to 'right'. */
2524
+ priceScaleId?: PriceScaleId;
2559
2525
  /**
2560
- * The source's own reading (untagged values). This is the scales-and-lines
2561
- * "last value label" control, which lands here because the legend is what
2562
- * draws that number on this row.
2526
+ * Value formatting for the axis and crosshair tag of the scale this plot maps
2527
+ * to: `percent` for a ratio study, `volume` for a cumulative one, `custom` for
2528
+ * anything else.
2529
+ *
2530
+ * Like `style.precision`, this is a property of the **price scale**, not of the
2531
+ * series, so it belongs to a plot that owns its pane. Setting it on an
2532
+ * `'onchart'` plot reformats the instrument's own axis, which is almost never
2533
+ * what a study wants.
2534
+ *
2535
+ * `percent` suffixes the value as it stands and does not scale it, so a study
2536
+ * returning a 0..1 fraction should keep returning it and read `0.62%`. Scaling
2537
+ * inside `calc` to make the axis read better changes the plotted value, and the
2538
+ * legend, the crosshair and every downstream calculation with it.
2563
2539
  */
2564
- lastValueLabel?: boolean;
2540
+ priceFormat?: PriceFormat;
2565
2541
  /**
2566
- * Plate behind the row's text, for legibility over candles. Off by default:
2567
- * the row has never had one, and turning it on is a deliberate choice.
2542
+ * Draw this one plot on the price pane even though the indicator owns a pane
2543
+ * of its own. An oscillator that also wants a signal
2544
+ * band or a stop line sitting on the candles is the case: the study belongs in
2545
+ * its own pane, one of its columns belongs on price, and splitting it into two
2546
+ * indicators would make the user configure the same inputs twice.
2547
+ *
2548
+ * Ignored for an `'onchart'` descriptor, which is already on the price pane.
2568
2549
  */
2569
- background?: boolean;
2570
- /** Plate opacity, 0..1. Default 0.8, matching the hover plate. */
2571
- backgroundOpacity?: number;
2572
- /** Plate color. Defaults to the theme background. */
2573
- backgroundColor?: string;
2574
- }
2575
- interface PaneLegendOptions {
2576
- /** Stable id; buttons hit-test as `${id}::close` etc. */
2577
- id: string;
2578
- /** Bold source name, e.g. `RSI`. */
2579
- title: string;
2580
- /** Dimmed parameter summary after the title, e.g. `14 close`. */
2581
- params?: string;
2582
- /** Swatch color; omitted draws no swatch. */
2583
- color?: string;
2550
+ overlay?: boolean;
2584
2551
  /**
2585
- * Color for the live value. Defaults to `color`, then the theme's text — so a
2586
- * row can tint its reading (an up/down change) without being forced to show a
2587
- * swatch in that same color.
2552
+ * Draw the column shifted this many bars to the right (negative: left). The
2553
+ * column itself stays one value per bar and `calc` returns exactly what it
2554
+ * always did; only where each value is painted moves. Positive is what a
2555
+ * displaced cloud or a projected channel wants: the last `offset` values land
2556
+ * in the right margin, past the newest candle, where no bar exists to hold
2557
+ * them. It shifts the drawn series only. A fill between two plots with the
2558
+ * same offset follows; the legend reads the value drawn under the cursor.
2588
2559
  */
2589
- valueColor?: string;
2590
- /** Vertical slot on the pane (0 = topmost). */
2591
- row?: number;
2560
+ offset?: number;
2592
2561
  /**
2593
- * Which inline action buttons to draw, left to right. Each hit-tests as
2594
- * `${id}::<action>`:
2595
- * - `up` / `down` — move this pane one slot (`::up` / `::down`)
2596
- * - `hide` — toggle visibility (`::hide`)
2597
- * - `maximize` — expand this pane to fill the chart (`::maximize`)
2598
- * - `close` — remove the source, and its pane if it empties (`::close`)
2562
+ * Settings key holding this plot's color, so a settings change restyles the
2563
+ * series without a full rebuild.
2564
+ */
2565
+ colorKey?: string;
2566
+ /**
2567
+ * Four `calc` keys to draw this plot as bar-shaped elements instead of one
2568
+ * value per bar: candles, hollow candles, OHLC bars, high-low.
2599
2569
  *
2600
- * Defaults to `['up', 'down', 'hide', 'maximize', 'close']` for pane sources
2601
- * and `['hide', 'close']` for overlays (pass explicitly to override).
2570
+ * A single column cannot express those at all, and the alternative (a second
2571
+ * result shape for `calc`) would fork the contract every descriptor and every
2572
+ * helper is written against. Naming four columns inside the *same*
2573
+ * `IndicatorValues` keeps one shape: a smoothed Heikin-Ashi overlay, a
2574
+ * higher-timeframe candle, a synthetic spread instrument each return four
2575
+ * ordinary columns and point at them from here.
2576
+ *
2577
+ * The named columns must all exist and be bar-aligned, or `addIndicator`
2578
+ * throws. `key` stays the series identity and the legend reading falls back to
2579
+ * the `close` column.
2602
2580
  */
2603
- actions?: readonly PaneLegendAction[];
2604
- /** Rendered as hidden (dimmed, eye hollow). */
2605
- hidden?: boolean;
2606
- /** Rendered as maximized (the maximize glyph becomes restore). */
2607
- maximized?: boolean;
2608
- /** Text size in media px. Default 11. */
2609
- font?: number;
2610
- /** Left inset from the plot edge in media px. Default 8. */
2611
- left?: number;
2612
- /** Top inset in media px. Default 6. */
2613
- top?: number;
2614
- /** Per-field status-line switches. Patching merges field by field. */
2615
- statusLine?: LegendStatusLineOptions;
2616
- /** Host-supplied status-line data (logo, names, market state, day change). */
2617
- status?: LegendStatusSource;
2581
+ ohlc?: {
2582
+ open: string;
2583
+ high: string;
2584
+ low: string;
2585
+ close: string;
2586
+ };
2587
+ /**
2588
+ * Per-bar colour, for plots whose meaning changes bar to bar — a MACD
2589
+ * histogram is four colours by sign and direction, a conditional study two.
2590
+ * Return `undefined` to fall back to the plot's own colour.
2591
+ *
2592
+ * Reaches the renderer as `Bar.color`, so every Family-A plot type honours
2593
+ * it: histogram, column, candles, OHLC bars, line, step and area.
2594
+ */
2595
+ colorBy?(ctx: {
2596
+ value: number;
2597
+ index: number;
2598
+ values: IndicatorValues;
2599
+ settings: IndicatorSettings;
2600
+ }): string | undefined;
2601
+ /**
2602
+ * Per-bar colour split three ways, for a candle plot whose wick or border
2603
+ * should not follow its body: a solid wick over a translucent body, a
2604
+ * border in the trend colour. Takes precedence over `colorBy` for the parts
2605
+ * it names; a part it leaves undefined falls back to `colorBy`, then to the
2606
+ * plot's own colour. A value plot (line, histogram, column) reads `body` only.
2607
+ */
2608
+ colorParts?(ctx: {
2609
+ value: number;
2610
+ index: number;
2611
+ values: IndicatorValues;
2612
+ settings: IndicatorSettings;
2613
+ }): PlotBarColor | undefined;
2618
2614
  }
2619
- declare class PaneLegend implements IPrimitive {
2620
- private _opts;
2621
- private _host;
2622
- private _values;
2623
- /** Button geometry from the last draw, in media px, for hit-testing. */
2624
- private _buttons;
2625
- /** Right edge of the drawn row, in media px. */
2626
- private _width;
2627
- constructor(opts: PaneLegendOptions);
2628
- attached(host: PrimitiveHost): void;
2629
- detached(): void;
2630
- zOrder(): ZOrder;
2631
- autoscaleInfo(): null;
2632
- /** A single live reading after the params (typically crosshair-driven). */
2633
- setValue(text: string, color?: string): void;
2634
- /** One reading per plot, each in its own color. */
2635
- setValues(values: readonly LegendValue[]): void;
2636
- setOptions(patch: Partial<PaneLegendOptions>): void;
2637
- /** Resolve the host's status data for this frame; `{}` when it has none. */
2638
- private _status;
2639
- options(): PaneLegendOptions;
2640
- draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
2641
- hitTest(x: number, y: number): PrimitiveHit | null;
2615
+ /** A horizontal reference level (RSI 70/30, Stochastic 80/20, a zero line). */
2616
+ interface IndicatorLevel {
2617
+ price: number;
2618
+ color?: string;
2619
+ title?: string;
2620
+ /** Legacy two-state dash switch. `lineStyle` wins when both are given. */
2621
+ dashed?: boolean;
2622
+ lineWidth?: number;
2623
+ lineStyle?: IndicatorLineStyle;
2624
+ }
2625
+ /** One end of an indicator drawing: a time on the shared axis, a price on the pane's scale. */
2626
+ interface DrawAnchor {
2627
+ time: number;
2628
+ price: number;
2642
2629
  }
2643
-
2644
2630
  /**
2645
- * Vertical gradient across the band, graded between two prices rather than
2646
- * two pixel rows.
2631
+ * A free-standing shape an indicator paints in its own pane, anchored to time
2632
+ * and price rather than to a bar index.
2633
+ *
2634
+ * Plots, levels and markers each answer a different question and none of them
2635
+ * answers this one: a pivot-to-pivot trendline, a supply zone, an order block,
2636
+ * a measured-move projection are all geometry between two arbitrary points, and
2637
+ * a column of one value per bar cannot express any of them. Anchors are times,
2638
+ * so a shape stays put when history is paged in and every logical index shifts.
2639
+ */
2640
+ type IndicatorDrawing = {
2641
+ kind: 'line';
2642
+ from: DrawAnchor;
2643
+ to: DrawAnchor;
2644
+ color?: string;
2645
+ lineWidth?: number;
2646
+ lineStyle?: IndicatorLineStyle;
2647
+ /** Continue the line past its anchor to the pane edge. */
2648
+ extendLeft?: boolean;
2649
+ extendRight?: boolean;
2650
+ } | {
2651
+ kind: 'box';
2652
+ from: DrawAnchor;
2653
+ to: DrawAnchor;
2654
+ /** Border colour. Omit `fillColor` to draw an outline only. */
2655
+ color?: string;
2656
+ fillColor?: string;
2657
+ /** Fill alpha, 0..1. Defaults to 0.12. */
2658
+ opacity?: number;
2659
+ lineWidth?: number;
2660
+ /** Caption drawn on a plate at the centre of the box; `\n` splits lines. */
2661
+ text?: string;
2662
+ textColor?: string;
2663
+ /**
2664
+ * Detail shown on a plate while the pointer rests on the box, and gone
2665
+ * when it leaves; `\n` splits lines. A zone that carries its size, its
2666
+ * age and what formed it cannot print all of that on the box without
2667
+ * hiding the candles under it, so the caption names it and this explains
2668
+ * it. The box becomes hit-testable, and `id` (or the tooltip text) is
2669
+ * what `subscribeClick` reports for it.
2670
+ */
2671
+ tooltip?: string;
2672
+ /** Hit id, for `subscribeClick`. Defaults to the tooltip text. */
2673
+ id?: string;
2674
+ } | {
2675
+ kind: 'label';
2676
+ at: DrawAnchor;
2677
+ /** `\n` splits lines. */
2678
+ text: string;
2679
+ /** Plate fill. */
2680
+ color?: string;
2681
+ textColor?: string;
2682
+ /** Which edge of the plate sits on the anchor. Defaults to 'center'. */
2683
+ align?: 'left' | 'center' | 'right';
2684
+ /** Hover detail, as on a box. */
2685
+ tooltip?: string;
2686
+ /** Hit id, for `subscribeClick`. Defaults to the tooltip text. */
2687
+ id?: string;
2688
+ } | {
2689
+ kind: 'polyline';
2690
+ points: readonly DrawAnchor[];
2691
+ color?: string;
2692
+ lineWidth?: number;
2693
+ /** Close the path back to the first point (a triangle, a wedge). */
2694
+ closed?: boolean;
2695
+ fillColor?: string;
2696
+ /** Fill alpha, 0..1. Defaults to 0.12. */
2697
+ opacity?: number;
2698
+ };
2699
+ /** `calc` output: one array per plot key, aligned 1:1 with the input bars. */
2700
+ type IndicatorValues = Record<string, readonly (number | null)[]>;
2701
+ /** Per-instance scratch owned by the descriptor (Tier-2 data lands here). */
2702
+ type IndicatorStore = Record<string, unknown>;
2703
+ /**
2704
+ * The fourth, optional argument to `calc` (and the sixth to `calcTail`): what
2705
+ * the calculation cannot read off the bars themselves.
2647
2706
  *
2648
- * The stops are anchored in PRICE, not in pixels, because that is what the
2649
- * shading is claiming: a band graded from its 70 level down to its 30 means
2650
- * those levels, and a viewport-relative gradient would slide off them the
2651
- * moment the pane is panned or the scale re-fits.
2707
+ * It is optional so that every descriptor written against `calc(bars, settings,
2708
+ * store)` keeps its exact signature and its exact behaviour, which is the whole
2709
+ * point: a calculation that ignores the context computes what it always did.
2652
2710
  */
2653
- interface FillGradient {
2654
- /** Price the top stop sits at. Omitted: the band's own highest value. */
2655
- topValue?: number;
2656
- /** Price the bottom stop sits at. Omitted: the band's own lowest value. */
2657
- bottomValue?: number;
2658
- topColor: string;
2659
- bottomColor: string;
2660
- }
2661
- interface IndicatorFillOptions {
2662
- /** Fill colour where the first series is above the second. */
2663
- colorUp: string;
2664
- /** Fill colour where the second is above the first. */
2665
- colorDown: string;
2666
- /** 0..1. Defaults to 0.12 — a band must not drown the candles it sits behind. */
2667
- opacity?: number;
2711
+ interface IndicatorCalcContext {
2668
2712
  /**
2669
- * Set to grade the band instead of flat-filling it, in place of
2670
- * `colorUp`/`colorDown`. A point carrying its own `color` still overrides it.
2671
- * Unset leaves the two-colour fill exactly as it was.
2713
+ * Where the last bar stands, so a study can act once per bar rather than once
2714
+ * per tick, or refuse to signal off a bar that is still moving.
2672
2715
  */
2673
- gradient?: FillGradient;
2716
+ barState: {
2717
+ /** The most recent update appended a bar rather than replacing one. */
2718
+ isNew: boolean;
2719
+ /** The last bar has closed: its interval has elapsed on the chart clock. */
2720
+ isConfirmed: boolean;
2721
+ /** A live feed is driving updates, rather than a one-off history load. */
2722
+ isRealtime: boolean;
2723
+ /** Index of the last bar, `bars.length - 1` (-1 when there are none). */
2724
+ lastIndex: number;
2725
+ };
2726
+ /** The instrument, when the host knows one. See `IndicatorAttachContext`. */
2727
+ symbol?: string;
2728
+ /** The timeframe (`'5m'`, `'1d'`), on the same terms as `symbol`. */
2729
+ interval?: string;
2730
+ /** The chart's IANA zone, the calendar its axis is labelled in. */
2731
+ timezone: string;
2732
+ /** Chart wall clock in UTC seconds, the clock the countdown row reads. */
2733
+ now(): number;
2734
+ /**
2735
+ * The instrument's tick size, from the **price pane's** `minMove`.
2736
+ *
2737
+ * The price pane and not the indicator's own, because `calc` runs on the
2738
+ * instrument's bars whichever pane the plot lands in, and a study pane is not
2739
+ * quoted in the instrument's tick: an RSI is a dimensionless 0..100 band, so
2740
+ * its scale carries no tick at all to read.
2741
+ *
2742
+ * `undefined` when the host has not told the chart what it is, which is the
2743
+ * honest answer rather than a guessed 0.01: an indicator sizing a range in
2744
+ * ticks has to tell "one paisa" apart from "nobody said".
2745
+ */
2746
+ tickSize?: number;
2674
2747
  }
2675
- /** One bar's pair of values; `null` where either plot has no value yet. */
2676
- interface FillPoint {
2677
- /** Logical index on the shared time axis (fractional is fine). */
2748
+ /** What an alert's `when` predicate is handed, for the bar it is judging. */
2749
+ interface IndicatorAlertContext {
2750
+ bars: readonly Bar[];
2751
+ values: IndicatorValues;
2752
+ settings: Readonly<IndicatorSettings>;
2753
+ /** The bar being evaluated. */
2678
2754
  index: number;
2679
- a: number | null;
2680
- b: number | null;
2755
+ }
2756
+ /**
2757
+ * A condition the runtime watches, declared by the descriptor rather than wired
2758
+ * up by the host: the indicator is the only thing that knows what a crossover of
2759
+ * its own columns means.
2760
+ *
2761
+ * Evaluated once per bar, for bars that are new since the last evaluation, so
2762
+ * adding the indicator to a loaded chart fires nothing for history.
2763
+ */
2764
+ interface IndicatorAlertSpec {
2765
+ /** Stable within the descriptor, e.g. `'cross-up'`. */
2766
+ id: string;
2767
+ /** Short human label, e.g. `'MACD crossed up'`. */
2768
+ title: string;
2681
2769
  /**
2682
- * Paints this bar onward in one colour, for a band shaded by something other
2683
- * than which line leads: trend state, a regime, a third series. The run is
2684
- * split at the bar where the colour changes, so it is per-bar and not merely
2685
- * per-crossing. Most specific wins: this, then the gradient, then up/down.
2770
+ * Longer text for a notification; defaults to `title`. A function is handed
2771
+ * the same context `when` judged, so the message can carry the bar's own
2772
+ * numbers: the price it crossed at, the histogram reading, a JSON body for a
2773
+ * webhook. It runs only for a bar `when` accepted.
2686
2774
  */
2687
- color?: string;
2775
+ message?: string | ((ctx: IndicatorAlertContext) => string);
2776
+ when(ctx: IndicatorAlertContext): boolean;
2688
2777
  }
2689
- declare class IndicatorFill implements IPrimitive {
2690
- private _points;
2691
- private _opts;
2692
- private _host;
2693
- private _visible;
2694
- constructor(options: IndicatorFillOptions);
2695
- attached(host: PrimitiveHost): void;
2696
- detached(): void;
2697
- /** Behind the series, so the lines and candles stay crisp on top of it. */
2698
- zOrder(): ZOrder;
2699
- /** The plots it spans already drive the scale; the fill must not widen it. */
2700
- autoscaleInfo(): null;
2701
- setPoints(points: readonly FillPoint[]): void;
2702
- setOptions(patch: Partial<IndicatorFillOptions>): void;
2703
- setVisible(on: boolean): void;
2704
- draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
2778
+ /**
2779
+ * Thrown by a `calc` (or any hook) to say its inputs cannot produce a study,
2780
+ * the way a script language's runtime error does: a period at or below zero, a
2781
+ * fast length above the slow one, a benchmark the provider cannot serve.
2782
+ *
2783
+ * Any error out of a recompute is caught by the runtime and published on the
2784
+ * instance's data status as `{ state: 'error' }`, so the chart keeps drawing
2785
+ * every other indicator and a host can show the reason beside this one. This
2786
+ * class exists so a descriptor can throw a **named** condition and a host can
2787
+ * tell a bad input, which the user can fix, from a bug, which they cannot.
2788
+ */
2789
+ declare class IndicatorInputError extends Error {
2790
+ constructor(message: string);
2705
2791
  }
2706
-
2707
2792
  /**
2708
- * Indicator runtime (ARCHITECTURE.md §8). Turns an `IndicatorDescriptor` into
2709
- * live chart objects: one series per plot, optional reference levels, an
2710
- * optional fixed pane range — and recomputes them when the source data or the
2711
- * settings change.
2793
+ * Bars of another instrument or interval, supplied by the host on request.
2712
2794
  *
2713
- * It adds **no rendering code**. Every plot names a registered chart type, so
2714
- * indicators draw through the same Family-A renderers as any other series.
2795
+ * The engine is handed one symbol's bars and owns no transport, so a study
2796
+ * that compares against a benchmark, or a Tier-2 provider that needs a second
2797
+ * series, asks the host through this and the host answers from wherever it
2798
+ * keeps history. `from` and `to` are UTC seconds; `signal` is aborted when the
2799
+ * instance is removed or its settings change, so a provider can drop the
2800
+ * request rather than answer into the void.
2715
2801
  */
2716
-
2717
- /** The slice of the chart the runtime needs. Keeps this module testable alone. */
2718
- interface IndicatorHost {
2719
- /** Forget a disposed instance, including disposal through its public handle. */
2720
- indicatorRemoved?(instanceId: string): void;
2721
- /** Optional instrument identity and source-range notifications. */
2802
+ interface IndicatorBarsRequest {
2803
+ symbol: string;
2804
+ exchange?: string;
2805
+ interval: string;
2806
+ from: number;
2807
+ to: number;
2808
+ signal?: AbortSignal;
2809
+ }
2810
+ type IndicatorBarsProvider = (request: IndicatorBarsRequest) => Promise<readonly Bar[]>;
2811
+ /** Payload of the `'indicator:alert'` event on the chart's own bus. */
2812
+ interface IndicatorAlertPayload extends AlertEventPayload {
2813
+ /** Descriptor id, e.g. `'macd'`. */
2814
+ indicatorId: string;
2815
+ /** Instance id, so a host can tell three EMAs apart. */
2816
+ instanceId: string;
2817
+ message: string;
2818
+ }
2819
+ /** Optional instrument identity and capability supplied by the host. */
2820
+ interface ChartDataContext {
2821
+ symbol?: string;
2822
+ exchange?: string;
2823
+ interval?: string;
2824
+ /** Instrument capability, independent of readings: false unsupported, absent unknown. */
2825
+ hasOpenInterest?: boolean;
2826
+ }
2827
+ /** Source identity changed, or the available source-bar range changed. */
2828
+ type IndicatorDataChange = 'context' | 'range';
2829
+ /** Observable state of an indicator's external data lifecycle. */
2830
+ type IndicatorDataStatus = {
2831
+ state: 'loading' | 'ready' | 'empty' | 'unsupported';
2832
+ } | {
2833
+ state: 'error';
2834
+ error: unknown;
2835
+ };
2836
+ /** What an indicator's `attach` lifecycle can reach. */
2837
+ interface IndicatorAttachContext {
2838
+ /** Optional host identity, independent of the indicator's own settings. */
2722
2839
  dataContext?(): Readonly<ChartDataContext> | undefined;
2840
+ /** Read bars and identity again when the host publishes a change. */
2723
2841
  subscribeDataChanges?(listener: (change: IndicatorDataChange) => void): () => void;
2724
- /** Add the pane-legend row (name + inline up/down/hide/maximize/close). */
2725
- addIndicatorLegend(opts: {
2726
- id: string;
2727
- title: string;
2728
- params: string;
2729
- color?: string;
2730
- row: number;
2731
- paneIndex: number;
2732
- }): PaneLegend;
2733
- removeIndicatorLegend(legend: PaneLegend): void;
2734
- /** How many legends already sit on this pane, so rows stack. */
2735
- legendRowsOn(paneIndex: number): number;
2736
- addIndicatorSeries(type: string, paneIndex: number, style: Record<string, unknown> | undefined, priceScaleId: string | undefined,
2737
- /** Axis/crosshair formatting for the scale this plot maps to. */
2738
- priceFormat?: PriceFormat): SeriesApi;
2842
+ /** Publish status and an explicit retry action for this instance. */
2843
+ setDataStatus?(status: IndicatorDataStatus): void;
2844
+ setDataRetry?(retry: (() => void) | null): void;
2845
+ /** Instance lifetime. Aborted on removal, preserved across style changes. */
2846
+ signal?: AbortSignal;
2847
+ /** Current settings (live — read at call time, not captured). */
2848
+ settings(): Readonly<IndicatorSettings>;
2849
+ /** The chart's current source bars. */
2850
+ bars(): readonly Bar[];
2851
+ /** Re-run `calc` and repaint — call when external data arrives. */
2852
+ requestRecompute(): void;
2853
+ /** Scratch this instance owns; the same object `calc` receives. */
2854
+ store: IndicatorStore;
2739
2855
  /**
2740
- * Add a reference level. One options object rather than seven positional
2741
- * arguments: the list grew a width and a dash style in 1.7.1, and a call site
2742
- * of seven bare values is where the next one gets passed in the wrong slot.
2856
+ * The instrument the chart is showing, when the host knows it.
2857
+ *
2858
+ * Hosts can supply this through `IndicatorHost` or Chart's explicit data
2859
+ * context. Without a configured identity it stays undefined. External
2860
+ * studies read `dataContext` to include the exchange as well.
2743
2861
  */
2744
- addIndicatorLevel(level: {
2745
- price: number;
2746
- color: string;
2747
- /** Kept for hosts predating `lineStyle`; always `lineStyle === 'dashed'`. */
2748
- dashed: boolean;
2749
- lineWidth: number;
2750
- lineStyle: IndicatorLineStyle;
2751
- label: string;
2752
- id: string;
2753
- }, paneIndex: number): PriceLine;
2754
- removeIndicatorLevel(line: PriceLine): void;
2755
- /** Attach a band drawn behind the plots (an Ichimoku cloud). */
2756
- addIndicatorFill(fill: IndicatorFill, paneIndex: number): void;
2757
- removeIndicatorFill(fill: IndicatorFill): void;
2862
+ symbol?(): string | undefined;
2863
+ /** The chart's timeframe (`'5m'`, `'1d'`), on the same terms as `symbol`. */
2864
+ interval?(): string | undefined;
2865
+ /** The chart's IANA zone, the one its axis is labelled in. */
2866
+ timezone?(): string;
2867
+ /** Chart wall clock in UTC seconds, the same clock the countdown row uses. */
2868
+ now?(): number;
2869
+ /**
2870
+ * Ask the host for another instrument's (or interval's) bars. Always present
2871
+ * under `chart.addIndicator`; it rejects when the host has registered no
2872
+ * provider (`chart.setBarsProvider`), so a study can treat the rejection as
2873
+ * "unsupported here" and say so through `setDataStatus`.
2874
+ */
2875
+ requestBars?(request: IndicatorBarsRequest): Promise<readonly Bar[]>;
2876
+ /** The pane this instance drew into. Moves when panes are reordered. */
2877
+ paneIndex?(): number;
2878
+ /** Attach a primitive to this indicator's pane, and detach it again. */
2879
+ addPrimitive?(p: IPrimitive): void;
2880
+ removePrimitive?(p: IPrimitive): void;
2881
+ /**
2882
+ * Emit on the chart's own event bus, the one `chart.on(name, cb)` listens to.
2883
+ *
2884
+ * The declarative `alerts` slot covers a condition read off the bars; this is
2885
+ * the imperative half, for an indicator whose signal arrives from outside the
2886
+ * calculation entirely (a subscription its `attach` opened).
2887
+ */
2888
+ emit?(event: string, payload: unknown): void;
2889
+ }
2890
+ /**
2891
+ * What `levels` is handed. It carries `bars` and `values` **and** spreads the
2892
+ * settings keys onto itself, so the built-ins written against the original
2893
+ * `levels(settings)` signature keep working unchanged: they read
2894
+ * `ctx.overbought` (or pass `ctx` to a `num(s, key, default)` helper) and find
2895
+ * exactly what they found before. A widened parameter is the only way a level
2896
+ * can be data-derived (yesterday's high, the session VWAP band), and that is a
2897
+ * whole class of level that could not be expressed at all before.
2898
+ *
2899
+ * The three data members are optional for the same backward-compatibility
2900
+ * reason, not because the runtime ever omits them: it always passes all three,
2901
+ * but a caller holding only a settings bag must still be able to invoke
2902
+ * `levels` directly. A descriptor that needs the data should default them
2903
+ * (`ctx.bars ?? []`).
2904
+ *
2905
+ * `settings`, `bars` and `values` are therefore reserved keys, the way
2906
+ * `timezone` already is in the settings a `calc` receives: an input declared
2907
+ * under one of those names is shadowed here.
2908
+ */
2909
+ type IndicatorLevelContext = IndicatorSettings & {
2910
+ settings?: Readonly<IndicatorSettings>;
2911
+ bars?: readonly Bar[];
2912
+ values?: IndicatorValues;
2913
+ };
2914
+ interface IndicatorDescriptor {
2915
+ /** Registry key, e.g. `'macd'`. */
2916
+ id: string;
2917
+ /** Display name, e.g. `'MACD'`. */
2918
+ name: string;
2919
+ /** Grouping for a picker UI ('Trend', 'Momentum', 'Volume', 'Volatility'). */
2920
+ category?: string;
2921
+ /** `'onchart'` overlays the price pane; `'pane'` gets its own pane. */
2922
+ placement: 'onchart' | 'pane';
2923
+ inputs: readonly IndicatorInput[];
2924
+ plots: readonly IndicatorPlot[];
2758
2925
  /**
2759
- * Detach a signal-marker layer. There is no matching `add`: the layer comes
2760
- * from `series.createMarkers()` on a plot's own series, so it already lands in
2761
- * the right pane. Removing a series does not remove its primitives, hence this.
2926
+ * Shaded bands between pairs of plots — the Ichimoku cloud, a Bollinger
2927
+ * channel. A pair of lines is not the same picture as a filled region: the
2928
+ * fill is what makes "price is above the cloud" readable at a glance, and
2929
+ * which side leads is itself the signal, hence the two colours.
2762
2930
  */
2763
- removeIndicatorMarkers(markers: SeriesMarkers): void;
2764
- /** Attach a corner-pinned summary grid to a pane, and detach it again. */
2765
- addIndicatorTable(paneIndex: number): ChartTable;
2766
- removeIndicatorTable(table: ChartTable): void;
2931
+ fills?: readonly IndicatorFillSpec[];
2767
2932
  /**
2768
- * Attach an arbitrary primitive to a pane, and detach it again. Carries both
2769
- * the descriptor's drawing layer and whatever a Tier-2 `attach` lifecycle
2770
- * wants to paint, so those two do not need a host method each.
2933
+ * Full recompute over every bar. Must return arrays the same length as
2934
+ * `bars` (use `null` for warmup gaps — the line renderer breaks across them
2935
+ * and autoscale skips them).
2771
2936
  *
2772
- * Optional, like `timezone`, so a host predating it still satisfies this
2773
- * interface: an indicator that draws simply draws nothing there.
2937
+ * Tier-1 indicators are pure functions of `(bars, settings)` and ignore
2938
+ * `store`. Tier-2 indicators — the ones with their own data — read the
2939
+ * external series their `attach` lifecycle put in `store`.
2774
2940
  */
2775
- addIndicatorPrimitive?(primitive: IPrimitive, paneIndex: number): void;
2776
- removeIndicatorPrimitive?(primitive: IPrimitive): void;
2941
+ calc(bars: readonly Bar[], settings: Readonly<IndicatorSettings>, store: IndicatorStore, ctx?: IndicatorCalcContext): IndicatorValues;
2777
2942
  /**
2778
- * Recompute whatever the host has marked stale, before a caller reads a value.
2943
+ * Optional per-instance lifecycle, for indicators whose data is not derived
2944
+ * from the chart's bars (CVD, PCR, an external feed). Called once
2945
+ * when the instance is created; return a teardown function.
2779
2946
  *
2780
- * Optional, like `timezone`, so a host predating it still satisfies this
2781
- * interface: one that recomputes eagerly has nothing to flush.
2947
+ * Fetch into `ctx.store`, then call `ctx.requestRecompute()` — `calc` runs
2948
+ * again and reads what you stored.
2782
2949
  */
2783
- flushIndicators?(): void;
2784
- /** Bars of the primary price series — the calculation input. */
2785
- sourceBars(): readonly Bar[];
2786
- /** Index of a fresh pane for an indicator that wants its own. */
2787
- nextPaneIndex(): number;
2950
+ attach?(ctx: IndicatorAttachContext): (() => void) | void;
2788
2951
  /**
2789
- * The chart's configured IANA zone. Optional so a host predating the option
2790
- * still satisfies this interface; absent means the shipped default.
2952
+ * Optional incremental path, called instead of `calc` when only the tail
2953
+ * changed (a live tick). Return values for indices `[fromIndex, bars.length)`
2954
+ * — the runtime splices them onto the previous result — or `null` to fall
2955
+ * back to a full `calc`.
2791
2956
  *
2792
- * A descriptor is handed bars and settings and never the chart, so this is
2793
- * how the calendar an anchor resets on (a VWAP session, a seasonality month)
2794
- * reaches the calculation. See `IndicatorInstance._descriptorSettings`.
2957
+ * Without it every tick costs a full recompute. That is a few hundred
2958
+ * microseconds for one indicator over 50k bars, but it is O(n) per tick per
2959
+ * indicator, so implement this for anything meant to run in a busy live pane.
2795
2960
  */
2796
- timezone?(): string;
2961
+ calcTail?(bars: readonly Bar[], settings: Readonly<IndicatorSettings>, fromIndex: number, previous: IndicatorValues, store: IndicatorStore, ctx?: IndicatorCalcContext): IndicatorValues | null;
2797
2962
  /**
2798
- * The instrument and timeframe on screen, when the host knows them. The
2799
- * host can supply an explicit `dataContext` instead. Without either hook,
2800
- * a descriptor sees `undefined` rather than a guessed identity.
2963
+ * Optional bar-anchored signal markers — a named "Buy"/"Sell" plate, an arrow
2964
+ * at a crossover. Runs after every `calc`, so it reads the values it just
2965
+ * produced rather than recomputing anything.
2966
+ *
2967
+ * A plot cannot express this: a plot is a column of prices drawn as a line or
2968
+ * histogram, whereas a signal is a discrete event with a label. Returning `[]`
2969
+ * (when a `showLabels`-style input is off, say) clears the layer.
2801
2970
  */
2802
- symbol?(): string | undefined;
2803
- interval?(): string | undefined;
2804
- /** Chart wall clock in UTC seconds. Absent means the system clock. */
2805
- now?(): number;
2971
+ markers?(ctx: {
2972
+ bars: readonly Bar[];
2973
+ values: IndicatorValues;
2974
+ settings: Readonly<IndicatorSettings>;
2975
+ }): readonly SeriesMarker[];
2806
2976
  /**
2807
- * Publish an indicator's per-bar colours onto the **primary price series**,
2808
- * or withdraw them with `null`. `owner` is the instance id: a host holds one
2809
- * overlay at a time and only lets its current owner withdraw it, so a second
2810
- * publisher taking over does not get cleared by the first one's teardown.
2977
+ * Optional summary grid pinned to a corner of the pane.
2811
2978
  *
2812
- * Optional, like `timezone`: a host that does not implement it simply gives a
2813
- * `barColors` descriptor nowhere to publish, and the indicator's own plots are
2814
- * unaffected.
2979
+ * Some studies are not a value per bar at all: a seasonality heatmap is a
2980
+ * matrix of monthly returns, a scoreboard is a handful of statistics. Those
2981
+ * have no place in `calc`, whose contract is one column per plot aligned to
2982
+ * the bars, so they come back through here instead. Runs after every `calc`.
2983
+ *
2984
+ * Return `null` (or a zero-row grid) to draw nothing, which is how a
2985
+ * `showTable`-style input should switch it off.
2815
2986
  */
2816
- setBarColors?(colors: readonly (string | null)[] | null, owner: string): void;
2817
- /** Emit on the chart's event bus (indicator alerts, and `attach`'s own events). */
2818
- emit?(event: string, payload: unknown): void;
2987
+ table?(ctx: {
2988
+ bars: readonly Bar[];
2989
+ values: IndicatorValues;
2990
+ settings: Readonly<IndicatorSettings>;
2991
+ }): {
2992
+ rows: readonly (readonly TableCell[])[];
2993
+ options?: Partial<ChartTableOptions>;
2994
+ } | null;
2819
2995
  /**
2820
- * Bars of another instrument or interval, from wherever the host keeps its
2821
- * history. Optional: without it the attach context's `requestBars` rejects,
2822
- * which a study reads as "not available on this chart".
2996
+ * Optional free-standing shapes drawn in the indicator's pane: trendlines
2997
+ * between pivots, supply and demand boxes, projection labels. Runs after
2998
+ * every `calc`, like `markers` and `table`, and the returned list replaces the
2999
+ * previous one wholesale, so returning `[]` clears the layer.
2823
3000
  */
2824
- requestBars?(request: IndicatorBarsRequest): Promise<readonly Bar[]>;
3001
+ draws?(ctx: {
3002
+ bars: readonly Bar[];
3003
+ values: IndicatorValues;
3004
+ settings: Readonly<IndicatorSettings>;
3005
+ }): readonly IndicatorDrawing[];
2825
3006
  /**
2826
- * Tick size of the named pane's price scale, or undefined when none is set.
2827
- * Optional so a host predating it still satisfies this interface.
3007
+ * Optional per-bar shading behind everything else in the indicator's pane: a
3008
+ * full-height column per bar, `null` where nothing should be shaded.
2828
3009
  *
2829
- * Per pane, and the panes genuinely differ: a pane that does not quote the
2830
- * instrument has no tick to report. Pane 0 is the price pane, so it is the
2831
- * one to ask for the instrument's own step.
3010
+ * A regime study answers "which state is the market in right now", and that is
3011
+ * a property of the whole bar, not a price. Drawn as a plot it would need a
3012
+ * value to sit at and would fight the pane's autoscale; as a column behind the
3013
+ * candles it reads at a glance and costs the scale nothing.
3014
+ *
3015
+ * Runs after every `calc`. Return `[]` to clear the layer.
2832
3016
  */
2833
- tickSize?(paneIndex: number): number | undefined;
3017
+ background?(ctx: {
3018
+ bars: readonly Bar[];
3019
+ values: IndicatorValues;
3020
+ settings: Readonly<IndicatorSettings>;
3021
+ }): readonly (string | null)[];
2834
3022
  /**
2835
- * Write a number the way the price axis of that pane writes it.
3023
+ * Optional recolouring of the **main price candles**, one entry per bar,
3024
+ * `null` to leave that bar with its own colour.
2836
3025
  *
2837
- * The legend sits inches from the axis and names the same quantity, so the
2838
- * two disagreeing is the reading a user has to reconcile themselves. Deriving
2839
- * the format here from a tick got that wrong twice over: a study pane carries
2840
- * no tick at all, so a percentage read `0.618` beside an axis saying `0.62`,
2841
- * and a price pane's tick alone misses the precision floor and the host's own
2842
- * formatter, so a volume study read seven digits where its axis said `1.20M`.
3026
+ * Distinct from a plot's `colorBy`, which paints the indicator's own series: a
3027
+ * trend filter, a volatility regime or a higher-timeframe bias is a statement
3028
+ * about the price bars themselves, and drawing it as a second series beside
3029
+ * them says something weaker.
2843
3030
  *
2844
- * Asking the scale removes the second opinion. Optional so a host driving
2845
- * this module alone still works, falling back to the magnitude ladder.
3031
+ * Only one indicator's colours can be on the candles at a time; the most
3032
+ * recent publisher wins, and publishers run in `addIndicator` order, so the
3033
+ * winner is the same one from frame to frame. Removing it, or hiding it,
3034
+ * restores the bars' own colours.
2846
3035
  */
2847
- formatPrice?(paneIndex: number, value: number): string | undefined;
2848
- /** Pin a pane's price scale to a fixed range, or release it with `null`. */
2849
- setPaneRange(paneIndex: number, range: {
3036
+ barColors?(ctx: {
3037
+ bars: readonly Bar[];
3038
+ values: IndicatorValues;
3039
+ settings: Readonly<IndicatorSettings>;
3040
+ }): readonly (string | null)[];
3041
+ /**
3042
+ * Optional conditions the runtime watches on the descriptor's behalf, emitted
3043
+ * as `'indicator:alert'` on the chart's event bus with an
3044
+ * {@link IndicatorAlertPayload}. See {@link IndicatorAlertSpec}.
3045
+ */
3046
+ alerts?: readonly IndicatorAlertSpec[];
3047
+ /**
3048
+ * Optional horizontal reference levels drawn in the indicator's pane.
3049
+ * Recomputed after every `calc`, so a level derived from the data (the
3050
+ * previous day's high) tracks it. See `IndicatorLevelContext` for why the
3051
+ * argument still reads as a settings bag.
3052
+ */
3053
+ levels?(ctx: IndicatorLevelContext): readonly IndicatorLevel[];
3054
+ /**
3055
+ * Optional fixed price range for the indicator's own pane (RSI 0..100).
3056
+ * Applied only when the indicator creates its pane — two indicators sharing a
3057
+ * pane would otherwise fight over it.
3058
+ */
3059
+ range?(settings: Readonly<IndicatorSettings>): {
2850
3060
  min: number;
2851
3061
  max: number;
2852
- } | null): void;
2853
- }
2854
- /** Public handle returned by `chart.addIndicator(...)`. */
2855
- interface IndicatorApi {
2856
- /** External data state, or null for a study without a managed lifecycle. */
2857
- dataStatus(): Readonly<IndicatorDataStatus> | null;
2858
- /** Observe changes; immediately receives the current managed status, if any. */
2859
- subscribeDataStatus(listener: (status: Readonly<IndicatorDataStatus>) => void): () => void;
2860
- /** Retry external history when the descriptor supplies a retry action. */
2861
- retryData(): void;
2862
- /** Unique instance id (several instances of one indicator can coexist). */
2863
- readonly id: string;
2864
- /** The descriptor id, e.g. `'macd'`. */
2865
- readonly indicatorId: string;
2866
- /** Display name. */
2867
- readonly name: string;
2868
- /** Pane the indicator drew into. */
2869
- readonly paneIndex: number;
2870
- /** Current settings (a copy). */
2871
- settings(): IndicatorSettings;
2872
- /** Merge a settings patch, recompute, and restyle. */
2873
- setSettings(patch: Readonly<IndicatorSettings>): void;
2874
- /** The series backing one plot key, for direct styling. */
2875
- series(plotKey: string): SeriesApi | undefined;
2876
- /** Latest computed values (a reference — do not mutate). */
2877
- values(): IndicatorValues;
2878
- /** Whether the plots are drawn (the legend's eye toggle). */
2879
- visible(): boolean;
2880
- /** Show or hide every plot without removing the instance. */
2881
- setVisible(on: boolean): void;
2882
- /** This indicator's legend row, or null if it has none. */
2883
- legend(): PaneLegend | null;
2884
- /** Refresh the legend readings for a bar index; omit for the latest bar. */
2885
- updateLegendValues(index?: number): void;
2886
- /** Remove every series, level, and legend row this indicator created. */
2887
- remove(): void;
3062
+ } | null;
2888
3063
  }
3064
+ /** Register an indicator descriptor. Later registrations of the same id win. */
3065
+ declare function registerIndicator(descriptor: IndicatorDescriptor): void;
3066
+ declare function getIndicator(id: string): IndicatorDescriptor;
3067
+ declare function hasIndicator(id: string): boolean;
3068
+ declare function registeredIndicators(): IndicatorDescriptor[];
3069
+ /** The descriptor's declared defaults as a settings object. */
3070
+ declare function indicatorDefaults(descriptor: IndicatorDescriptor): IndicatorSettings;
3071
+ /** Read one bar's value for a price source. */
3072
+ declare function sourceValue(bar: Bar, source: IndicatorSource): number;
3073
+ /** Read a whole bar array for a price source. */
3074
+ declare function sourceValues(bars: readonly Bar[], source: IndicatorSource): number[];
2889
3075
 
2890
3076
  /**
2891
3077
  * Serialisable chart state — the keystone the persistence-shaped features hang
@@ -2929,6 +3115,8 @@ interface SeriesState {
2929
3115
  }
2930
3116
  interface IndicatorState {
2931
3117
  indicatorId: string;
3118
+ /** Stable workspace identity. Omitted by legacy states and reusable templates. */
3119
+ instanceId?: string;
2932
3120
  settings: IndicatorSettings;
2933
3121
  paneIndex: number;
2934
3122
  /** Omitted by older layouts, which restore the indicator as visible. */
@@ -2947,6 +3135,7 @@ interface ChartState {
2947
3135
  horzLines: boolean;
2948
3136
  };
2949
3137
  crosshairMode?: 'normal' | 'magnet';
3138
+ crosshairSnapToBar?: boolean;
2950
3139
  panes?: PaneState[];
2951
3140
  /** Informational: `restoreState` does not recreate these (it has no data). */
2952
3141
  series?: SeriesState[];
@@ -2957,6 +3146,7 @@ interface ChartState {
2957
3146
  * tier is loaded.
2958
3147
  */
2959
3148
  drawings?: unknown;
3149
+ alerts?: AlertsDocument;
2960
3150
  }
2961
3151
  /** What `restoreState` actually applied, so a caller can finish the job. */
2962
3152
  interface RestoreReport {
@@ -3848,6 +4038,8 @@ interface ChartOptions {
3848
4038
  * bar under the cursor (price pane only).
3849
4039
  */
3850
4040
  crosshairMode?: CrosshairMode;
4041
+ /** Snap the vertical crosshair to the nearest primary bar's center. Default false; independent of the price magnet. */
4042
+ crosshairSnapToBar?: boolean;
3851
4043
  /**
3852
4044
  * Optional chrome on the axis strips: the corner clock and the bar-close
3853
4045
  * countdown. Both are off unless asked for, so a chart that omits this block
@@ -4013,6 +4205,8 @@ declare function compactVolume(v: number): string;
4013
4205
  * floating tooltip. See `subscribeCrosshairMove`.
4014
4206
  */
4015
4207
  interface CrosshairMoveEvent {
4208
+ /** Linked readouts have no physical pointer position and are not pointer gestures. */
4209
+ source?: 'linked';
4016
4210
  /** UTC seconds of the hovered bar, or null when off the data / pointer left. */
4017
4211
  time: number | null;
4018
4212
  /** Logical index under the cursor, or null. */
@@ -4150,6 +4344,8 @@ interface ContextMenuTarget {
4150
4344
  id: string | null;
4151
4345
  /** Indicator instance id, when `kind` is 'indicator'. */
4152
4346
  instanceId?: string;
4347
+ /** Exact plot key when a study's plotted series was hit rather than its legend. */
4348
+ plotKey?: string;
4153
4349
  /** Series type, when `kind` is 'series'. */
4154
4350
  seriesType?: SeriesType;
4155
4351
  /** Which axis strip was hit, when `kind` is 'price-scale'. */
@@ -4235,6 +4431,7 @@ declare class Chart {
4235
4431
  private _height;
4236
4432
  private _hasFitContent;
4237
4433
  private _crosshairMode;
4434
+ private _crosshairSnapToBar;
4238
4435
  private _shortcuts;
4239
4436
  private _trading;
4240
4437
  private _pointerInside;
@@ -4317,6 +4514,7 @@ declare class Chart {
4317
4514
  private readonly _firstDataId;
4318
4515
  /** Handle + record of the primary price series (see `primarySeries`). */
4319
4516
  private _primary;
4517
+ private readonly _seriesRecords;
4320
4518
  private readonly _indicators;
4321
4519
  private _dataContext;
4322
4520
  private _barsProvider;
@@ -4336,6 +4534,7 @@ declare class Chart {
4336
4534
  private _barColorAnchor;
4337
4535
  /** Opaque drawing-tier payload, round-tripped through get/restoreState. */
4338
4536
  private _drawingState;
4537
+ private _alertState;
4339
4538
  /** Pane currently maximized, and the weights to restore when it un-maximizes. */
4340
4539
  private _maximizedPane;
4341
4540
  /** Legend rows per pane, so new ones stack below existing ones. */
@@ -4346,6 +4545,7 @@ declare class Chart {
4346
4545
  private _loadingHistory;
4347
4546
  private _clickCb;
4348
4547
  private _crosshairCb;
4548
+ private _readoutTime;
4349
4549
  private _pointerMoved;
4350
4550
  /** While true, pointer gestures place anchors instead of panning. */
4351
4551
  private _placementMode;
@@ -4406,6 +4606,8 @@ declare class Chart {
4406
4606
  /** Call after a history-paging load resolves to re-enable the trigger. */
4407
4607
  historyLoadComplete(): void;
4408
4608
  get dataLayer(): DataLayer;
4609
+ /** Readonly source bars, without allocating a history copy on each live update. */
4610
+ primaryBars(): readonly Bar[];
4409
4611
  get timeScale(): TimeScale;
4410
4612
  /** Restore a saved logical range (e.g. preserve the user's zoom across a data reload). */
4411
4613
  setVisibleLogicalRange(range: LogicalRange): void;
@@ -4536,6 +4738,8 @@ declare class Chart {
4536
4738
  private _forgetIndicator;
4537
4739
  /** Optional instrument identity supplied by the host, never inferred from bars. */
4538
4740
  getDataContext(): Readonly<ChartDataContext> | undefined;
4741
+ /** Instrument capability from the host. A missing bar reading does not change it. */
4742
+ get hasOpenInterest(): boolean | undefined;
4539
4743
  /** Clear the previous source bars before changing context, then load the new source. */
4540
4744
  setDataContext(context: ChartDataContext | undefined): void;
4541
4745
  /**
@@ -4607,9 +4811,17 @@ declare class Chart {
4607
4811
  /**
4608
4812
  * Subscribe to crosshair movement for an OHLC legend / tooltip. The callback
4609
4813
  * fires with the hovered bar of the primary price series on every move, and
4610
- * with all-null fields when the pointer leaves the plot.
4814
+ * with all-null fields when the pointer leaves the plot. A linked crosshair
4815
+ * also updates the readout, with source 'linked' and no pointer coordinates.
4611
4816
  */
4612
4817
  subscribeCrosshairMove(cb: (e: CrosshairMoveEvent) => void): void;
4818
+ /**
4819
+ * Update the readout under a link group's separately drawn crosshair. The
4820
+ * physical pointer takes precedence. This never emits a pointer move event,
4821
+ * so hosts do not interpret it as drawing input or echo it to another group.
4822
+ */
4823
+ setLinkedCrosshairIndex(index: number | null): void;
4824
+ private _readoutIndex;
4613
4825
  /**
4614
4826
  * Subscribe to drags of draggable primitives (order / SL / TP lines, drawing
4615
4827
  * handles). Fires per move and on release.
@@ -4809,6 +5021,8 @@ declare class Chart {
4809
5021
  axisChromeOptions(): AxisChromeOptions;
4810
5022
  /** Crosshair behaviour ('normal' or 'magnet'). Set it via `applyOptions`. */
4811
5023
  crosshairMode(): CrosshairMode;
5024
+ /** Whether the vertical crosshair snaps to an existing primary bar's center. */
5025
+ crosshairSnapToBar(): boolean;
4812
5026
  /** The active palette. Swap it with `setTheme`. */
4813
5027
  theme(): ChartTheme;
4814
5028
  /**
@@ -4942,6 +5156,7 @@ declare class Chart {
4942
5156
  timeFormatter?: ((utcSeconds: number, tickMark?: TickMarkType) => string) | undefined;
4943
5157
  timezone?: string;
4944
5158
  crosshairMode?: CrosshairMode;
5159
+ crosshairSnapToBar?: boolean;
4945
5160
  }): void;
4946
5161
  panes(): readonly Pane[];
4947
5162
  /**
@@ -4971,12 +5186,17 @@ declare class Chart {
4971
5186
  * (or `setVisibleLogicalRange`) once the series are populated.
4972
5187
  */
4973
5188
  restoreState(state: unknown): RestoreReport;
5189
+ private _restoreState;
4974
5190
  /**
4975
5191
  * The opaque `drawings` slot in the chart state. The base engine only
4976
5192
  * round-trips it; the drawing tier reads and writes it.
4977
5193
  */
4978
5194
  drawingState(): unknown;
4979
5195
  setDrawingState(value: unknown): void;
5196
+ /** Detached JSON state, also available when no alert controller is attached. */
5197
+ alertState(): AlertsDocument | undefined;
5198
+ /** Runtime snapshot. JSON safety of opaque payloads is checked when state is read. */
5199
+ setAlertState(document: AlertsDocument | undefined): void;
4980
5200
  invalidate(build: (mask: InvalidateMask) => void): void;
4981
5201
  /**
4982
5202
  * Measure the container once more, a frame after construction.
@@ -5449,6 +5669,330 @@ declare class SvgContext {
5449
5669
  private _unsupported;
5450
5670
  }
5451
5671
 
5672
+ /**
5673
+ * Aligning a second instrument onto the primary series' bars.
5674
+ *
5675
+ * The x-axis is a gapless logical index over the times the shared DataLayer
5676
+ * holds (ARCHITECTURE.md §4.1, §5.3), so two instruments do not share a bar
5677
+ * index and cannot be laid side by side by position. They are matched by
5678
+ * timestamp, and the two directions of mismatch get opposite answers:
5679
+ *
5680
+ * - **A comparison bar with no primary bar is dropped.** The DataLayer merges
5681
+ * *every* series' times into one index space, so a time only the comparison
5682
+ * has would mint a new logical index: a column the primary instrument has no
5683
+ * candle for, inserted mid-chart, shifting every bar after it. That happens
5684
+ * for real (a different exchange's holiday calendar, a 24/7 instrument next
5685
+ * to an NSE one, a feed that emits a stray print), and warping the primary's
5686
+ * own axis to accommodate a comparison is never the right trade. The print is
5687
+ * counted in `dropped` so a host can say so rather than silently losing it.
5688
+ *
5689
+ * - **A primary bar with no comparison bar becomes whitespace**, which is a NaN
5690
+ * bar the line renderer breaks across, so the comparison shows a *gap*. The
5691
+ * alternative, carrying the last known value forward, draws a flat segment
5692
+ * through a session the instrument never traded and, worse, in percentage
5693
+ * mode it anchors the move on the far side of the gap to a print that does
5694
+ * not exist. Omitting the bar entirely is worse still: the renderer would
5695
+ * join the two sides with one straight line across the holiday.
5696
+ *
5697
+ * Matching is on the exact timestamp. Bar-open times are bucketed by the candle
5698
+ * builder (§10.2) and stored as UTC seconds (§4.0), so two instruments on the
5699
+ * same interval agree to the second; anything that does not agree is a
5700
+ * different interval, which no tolerance window could rescue.
5701
+ */
5702
+
5703
+ /** What one alignment pass did, for a host that wants to report coverage. */
5704
+ interface ComparisonAlignment {
5705
+ /** Items handed to the comparison series: one per primary bar. */
5706
+ bars: number;
5707
+ /** Primary bars the comparison also traded (a value is drawn). */
5708
+ matched: number;
5709
+ /** Primary bars with no comparison print (drawn as a gap). */
5710
+ gaps: number;
5711
+ /** Comparison prints discarded for having no primary bar at that time. */
5712
+ dropped: number;
5713
+ }
5714
+ /**
5715
+ * Project `comparison` onto `primary`'s bars: one item per primary bar, in the
5716
+ * primary's order, so the result occupies exactly the logical indices the chart
5717
+ * already has and adds none of its own.
5718
+ *
5719
+ * Neither input is mutated and neither has to be sorted: matching goes through
5720
+ * a time map, which also collapses a repeated timestamp to its last item, the
5721
+ * same rule the DataLayer applies when it merges (`sortedUniqueByTime`).
5722
+ */
5723
+ declare function alignToPrimary(primary: readonly Bar[], comparison: readonly SeriesDataItem[]): {
5724
+ items: SeriesDataItem[];
5725
+ alignment: ComparisonAlignment;
5726
+ };
5727
+
5728
+ /**
5729
+ * Multi-symbol comparison: put a second instrument on the primary one's pane
5730
+ * and read them together (NIFTY against BANKNIFTY, a stock against its index).
5731
+ *
5732
+ * Headless, in the spirit of `DrawingController` (src/draw/controller.ts) and
5733
+ * `ReplayController`: it owns the series, the alignment and the scales, and
5734
+ * ships no DOM, so the host draws its own symbol chips and legend rows from
5735
+ * `list()`.
5736
+ *
5737
+ * Three decisions carry the design:
5738
+ *
5739
+ * 1. **Each comparison owns a scale.** A free legacy overlay or left scale
5740
+ * preserves the first source's placement; additional sources use named
5741
+ * hidden scales so their different price units cannot affect one another.
5742
+ *
5743
+ * 2. **Comparability comes from the scale, not from the data.** The bars handed
5744
+ * over are stored as the instrument's own prices, so the legend, the
5745
+ * crosshair and any live update still speak in real prices. What makes the
5746
+ * lines readable together is the pane mode: `percentage` and
5747
+ * `indexed-to-100` give every scale its own baseline (its first visible
5748
+ * close, or an explicit common timestamp), and `_mirror` gives it the same
5749
+ * band of percent the primary's axis is showing. Without that mirror each
5750
+ * scale would autoscale to its own data and a 1% mover would look exactly
5751
+ * like a 10% mover, both filling the pane.
5752
+ *
5753
+ * 3. **Alignment is by timestamp** and lives in `./align`, which documents what
5754
+ * happens in each direction of mismatch.
5755
+ *
5756
+ * Each comparison owns a scale and baseline. Further instruments use keyed
5757
+ * hidden scales so their absolute prices cannot change another source's units.
5758
+ */
5759
+
5760
+ /**
5761
+ * How the pane quotes prices while a comparison is on it. The two rebasing
5762
+ * modes are the reason the lines are comparable at all; `none` leaves the
5763
+ * pane's own mode alone, for a host that wants the raw overlay.
5764
+ */
5765
+ type ComparisonMode = 'percentage' | 'indexed-to-100' | 'none';
5766
+ /** Independent first-visible closes, or the first visible timestamp shared by all visible sources. */
5767
+ type ComparisonBaseline = 'first-visible' | 'common';
5768
+ interface ComparisonOptions {
5769
+ /** Instrument label, e.g. 'BANKNIFTY'. Carried on the handle for the host's UI. */
5770
+ symbol: string;
5771
+ /** The instrument's own bars. Aligned to the primary series, see `./align`. */
5772
+ bars: readonly SeriesDataItem[];
5773
+ /** Line colour shorthand; `style.color` wins if both are given. */
5774
+ color?: string;
5775
+ /** Style overrides merged onto the chart type's defaults. */
5776
+ style?: SeriesStyle;
5777
+ /** Renderer for the comparison. Default 'line'. */
5778
+ type?: SeriesType;
5779
+ /** Pane to draw on. Default 0, the price pane. */
5780
+ paneIndex?: number;
5781
+ }
5782
+ interface ComparisonControllerOptions {
5783
+ /** Pane mode applied while any comparison is on it. Default 'percentage'. */
5784
+ mode?: ComparisonMode;
5785
+ /** Baseline policy. Default 'first-visible' preserves existing integrations. */
5786
+ baseline?: ComparisonBaseline;
5787
+ }
5788
+ /** What `addComparison` hands back: one instrument on the chart. */
5789
+ interface ComparisonHandle {
5790
+ readonly symbol: string;
5791
+ /**
5792
+ * The series this comparison draws through, for style patches and markers.
5793
+ * Data set on it directly skips alignment (use `setBars`), and removing it
5794
+ * directly leaves the pane rebased with nothing on it (use `remove`).
5795
+ */
5796
+ readonly series: SeriesApi;
5797
+ readonly paneIndex: number;
5798
+ /** The hidden scale it maps to. Never the pane's own price axis. */
5799
+ priceScale(): PriceScale;
5800
+ /** How the last alignment against the primary's bars went. */
5801
+ alignment(): ComparisonAlignment;
5802
+ /** Eligible aligned bar in its own price units; null for gaps, suppressed baselines or forming replay candles. */
5803
+ barAt(time: number): Readonly<Bar> | null;
5804
+ /** Replace the instrument's bars (a longer history, a refreshed fetch). */
5805
+ setBars(bars: readonly SeriesDataItem[]): void;
5806
+ /** Take this instrument off the chart. Safe to call twice. */
5807
+ remove(): void;
5808
+ /** Every comparison on this chart, in the order they were added. */
5809
+ list(): readonly ComparisonHandle[];
5810
+ }
5811
+ /**
5812
+ * The slice of a pane the controller reads. Declared structurally, like
5813
+ * `ReplayViewport`, so nothing here depends on `Pane` beyond the two members
5814
+ * that decide where a comparison can go.
5815
+ */
5816
+ interface ComparisonPane {
5817
+ readonly priceScale: PriceScale;
5818
+ series(): readonly {
5819
+ readonly scaleId: string;
5820
+ readonly style?: SeriesStyle;
5821
+ }[];
5822
+ }
5823
+ /**
5824
+ * The slice of the chart this controller needs. `Chart` satisfies it; declaring
5825
+ * it structurally keeps the controller testable against a stub.
5826
+ */
5827
+ interface ComparisonChartHost {
5828
+ addSeries(type: SeriesType, options: AddSeriesOptions): SeriesApi;
5829
+ panes(): readonly ComparisonPane[];
5830
+ addPrimitive(primitive: IPrimitive, paneIndex?: number): void;
5831
+ removePrimitive(primitive: IPrimitive): void;
5832
+ primarySeries(): SeriesApi | null;
5833
+ /** Avoids allocating a primary history copy when the host provides it. */
5834
+ primaryBars?(): readonly Bar[];
5835
+ getVisibleLogicalRange?(): {
5836
+ from: number;
5837
+ to: number;
5838
+ };
5839
+ on?(event: string, callback: (payload: unknown) => void): () => void;
5840
+ /** The shared logical axis locates visible primary bars even when another series adds times. */
5841
+ readonly dataLayer: {
5842
+ readonly length: number;
5843
+ indexToTime?(index: number): number | undefined;
5844
+ };
5845
+ }
5846
+ declare class ComparisonController {
5847
+ private readonly _chart;
5848
+ private _mode;
5849
+ private _baseline;
5850
+ private readonly _panes;
5851
+ /** Insertion-ordered, and the handle is the key so `remove` is a lookup. */
5852
+ private readonly _items;
5853
+ /** Axis length plus primary identity and boundaries avoid scanning history each frame. */
5854
+ private _alignedAt;
5855
+ /** Guards `realign` against re-entry through its own `setData` repaint. */
5856
+ private _realigning;
5857
+ private _nextScale;
5858
+ private _syncing;
5859
+ private _alignedPrimary;
5860
+ private _primaryLength;
5861
+ private _primaryFirst;
5862
+ private _primaryLast;
5863
+ private readonly _off;
5864
+ private _destroyed;
5865
+ constructor(chart: ComparisonChartHost, options?: ComparisonControllerOptions);
5866
+ /** Put an instrument on the chart alongside the primary series. */
5867
+ add(options: ComparisonOptions): ComparisonHandle;
5868
+ /** Take one instrument off. Returns false if it was already gone. */
5869
+ remove(handle: ComparisonHandle): boolean;
5870
+ /** Every comparison on the chart, in the order they were added. */
5871
+ list(): readonly ComparisonHandle[];
5872
+ /** Take them all off, putting every pane back the way it was found. */
5873
+ clear(): void;
5874
+ /**
5875
+ * Change the mode the panes are held in while comparisons are on them.
5876
+ * Panes that already have one switch immediately.
5877
+ */
5878
+ setMode(mode: ComparisonMode): void;
5879
+ get mode(): ComparisonMode;
5880
+ /** Select a shared timestamp without changing the instruments' stored price units. */
5881
+ setBaseline(baseline: ComparisonBaseline): void;
5882
+ get baseline(): ComparisonBaseline;
5883
+ /** Shared baseline timestamp, or null for no overlap, no visible sources, or independent baselines. */
5884
+ baselineTime(paneIndex?: number): number | null;
5885
+ /**
5886
+ * Re-project onto the primary's current bars. Chart handles data replacement
5887
+ * and replay automatically. A structural host without data events or primary
5888
+ * history identity can call this after replacing its timestamps.
5889
+ */
5890
+ realign(): void;
5891
+ /**
5892
+ * Bring the overlay scales in line with the primary axis, re-aligning first
5893
+ * if the shared time axis has moved under us (a live bar, history paged in,
5894
+ * a replay step). Runs once per base paint through the per-pane hook.
5895
+ *
5896
+ * Re-aligning here writes series data mid-frame, which is safe because the
5897
+ * hook is a `bottom` primitive: it runs after the pane has autoscaled and
5898
+ * before any series is drawn, so the frame that notices the change is also
5899
+ * the frame that draws it. The repaint that write asks for lands on the next
5900
+ * frame, or re-enters this one on a host that runs frames synchronously,
5901
+ * which is what `realign`'s guard is for.
5902
+ */
5903
+ sync(): void;
5904
+ /** Remove every comparison and forget the chart. */
5905
+ destroy(): void;
5906
+ private _dispose;
5907
+ private _needsAlignment;
5908
+ private _rememberAlignment;
5909
+ /**
5910
+ * Preserve the first comparison's legacy placement when the scale is free.
5911
+ * Additional comparisons need their own baselines, and an occupied volume
5912
+ * or left scale must keep its source and units.
5913
+ */
5914
+ private _scaleIdFor;
5915
+ private _openPane;
5916
+ private _closePane;
5917
+ private _applyMode;
5918
+ private _restoreScale;
5919
+ private _restoreMode;
5920
+ /**
5921
+ * Give the overlay the same band of percent the primary axis is showing, so
5922
+ * equal moves land on equal pixels and the divergence between two
5923
+ * instruments is the thing you see.
5924
+ *
5925
+ * Each scale has its own price baseline, and rebasing maps price to the
5926
+ * ratio `price / baseline`. So the
5927
+ * two scales agree exactly when their ranges hold the same ratios, which is
5928
+ * one multiplication: the primary's range times `baseline_overlay /
5929
+ * baseline_primary`. It holds for `indexed-to-100` and `percentage` alike,
5930
+ * since they are one ladder a hundred points apart.
5931
+ *
5932
+ * A null baseline means this scale is not rebasing (linear, logarithmic, or
5933
+ * a pane with nothing visible on it yet). Then there is no shared ladder to
5934
+ * join and the overlay keeps its own autoscale, which is what mode 'none'
5935
+ * asks for: two instruments each filling the pane, comparable in shape only.
5936
+ *
5937
+ * The overlay stays under autoscale throughout, even though the range it
5938
+ * measures is overwritten here every frame. That measurement is one pass over
5939
+ * bars the renderer is about to walk anyway, and paying for it buys the
5940
+ * failure mode we want: a chart that stops calling `sync` falls back to an
5941
+ * independently scaled overlay instead of freezing on a stale range.
5942
+ */
5943
+ private _mirror;
5944
+ private _primaryBars;
5945
+ private _record;
5946
+ private _visible;
5947
+ private _visiblePrimary;
5948
+ private _commonAnchor;
5949
+ private _setSuppressed;
5950
+ private _write;
5951
+ private _align;
5952
+ /**
5953
+ * Ask for a full repaint after a mode change. The pane hands a rebasing scale
5954
+ * its baseline during the autoscale pass, so a `Light` repaint (all a
5955
+ * primitive's `requestUpdate` raises) would paint the new mode before it has
5956
+ * anything to quote against. `applyOptions` is the series handle's own route
5957
+ * to a Full invalidation, and an empty patch changes no style. Several of
5958
+ * them coalesce into one frame, so asking every comparison costs nothing.
5959
+ */
5960
+ private _repaint;
5961
+ }
5962
+ /**
5963
+ * The controller for a chart, created on first use. Use it to change the mode
5964
+ * for the whole chart, to list what is on it, or to clear it.
5965
+ */
5966
+ declare function comparisonController(chart: ComparisonChartHost, options?: ComparisonControllerOptions): ComparisonController;
5967
+ /**
5968
+ * Put an instrument on the chart next to the primary one:
5969
+ *
5970
+ * ```ts
5971
+ * const bn = addComparison(chart, { symbol: 'BANKNIFTY', bars, color: '#f0b90b' });
5972
+ * ```
5973
+ *
5974
+ * The pane switches to percentage while any comparison is on it and goes back
5975
+ * to the mode it had when the last one leaves.
5976
+ */
5977
+ declare function addComparison(chart: ComparisonChartHost, options: ComparisonOptions): ComparisonHandle;
5978
+
5979
+ /** Options for a numeric snapshot of the chart's currently loaded data. */
5980
+ interface ChartDataCsvOptions {
5981
+ /** Include every configured study's declared plots, including hidden studies. Default true. */
5982
+ indicators?: boolean;
5983
+ /** Override the chart's registered comparisons, for example with an explicitly managed controller's list. */
5984
+ comparisons?: readonly Pick<ComparisonHandle, 'symbol' | 'barAt'>[];
5985
+ }
5986
+ /**
5987
+ * CSV with UTC seconds, unrounded OHLC/volume/OI, study values and aligned comparison closes.
5988
+ * Reads only installed primary rows, so replay exposes only its revealed prefix.
5989
+ * Missing or nonfinite values are blank. Study values precede visual plot offsets;
5990
+ * comparison values retain their own price units, regardless of axis rebasing.
5991
+ * Headers have fixed prefixes so custom names cannot become spreadsheet formulas.
5992
+ * Does not fetch data, include trading state or initiate a browser download.
5993
+ */
5994
+ declare function exportChartDataCsv(chart: Chart, options?: ChartDataCsvOptions): string;
5995
+
5452
5996
  type ChartObjectKind = 'source' | 'indicator' | 'drawing' | 'profile';
5453
5997
  interface ChartObjectCapabilities {
5454
5998
  readonly select: boolean;
@@ -5786,542 +6330,394 @@ declare class Canvas2dBackend implements IRenderBackend {
5786
6330
  * Nothing to do: the pane's `CanvasLayer` owns the backing store and sized it
5787
6331
  * already. Re-sizing it here would clear a bitmap that was just painted.
5788
6332
  */
5789
- resize(_widthPx: number, _heightPx: number, _dpr: number): void;
5790
- /** The same clear `CanvasLayer.clearBitmap` does, so the op stream is unchanged. */
5791
- beginFrame(clear: boolean): void;
5792
- drawSeries(entry: RendererEntry, items: readonly DrawItem[], priceToY: (price: number) => number, barSpacing: number, dpr: number, style: SeriesStyle, rc: SeriesRenderContext): void;
5793
- /** Every call above drew straight to the canvas; there is nothing to flush. */
5794
- endFrame(): void;
5795
- overlay2d(): CanvasRenderingContext2D | null;
5796
- destroy(): void;
5797
- }
5798
-
5799
- /**
5800
- * Histogram / column renderer (ARCHITECTURE.md §6). Used for the volume pane.
5801
- * Bars are drawn from a base value (0) up to each bar's close.
5802
- */
5803
-
5804
- interface HistogramStyle {
5805
- color: string;
5806
- /** Optional separate colors keyed by an up/down flag set on the bar's volume sign. */
5807
- base: number;
5808
- }
5809
- declare const DEFAULT_HISTOGRAM_STYLE: HistogramStyle;
5810
-
5811
- /**
5812
- * Market replay: walk a historical session forward bar by bar so a trader can
5813
- * practise on it, and so every indicator redraws exactly as it stood at that
5814
- * past moment.
5815
- *
5816
- * Headless, in the spirit of `DrawingController` (src/draw/controller.ts): it
5817
- * owns the playhead and the transitions and ships no DOM, so the host renders
5818
- * its own transport bar, scrub slider and clock from `state()` and the events.
5819
- *
5820
- * The mechanic is deliberately boring. Replay feeds the chart a **prefix** of
5821
- * the full bar array through the ordinary `series.setData` path, and that is
5822
- * what makes indicators free: `Chart._setData` calls `_recomputeIndicators` for
5823
- * the primary series, and `IndicatorInstance.recompute` re-reads the whole
5824
- * history from `sourceBars()`. Shorten that history and every indicator, level,
5825
- * fill, marker and legend reconstructs itself as it was at that bar, with no
5826
- * replay-aware code anywhere in the indicator tier.
5827
- */
5828
-
5829
- /** Schedules a repeating callback and returns its canceller. Inject in tests. */
5830
- type ReplayScheduler = (cb: () => void, intervalMs: number) => () => void;
5831
- /**
5832
- * The slice of the time scale replay saves and restores. Declared structurally
5833
- * rather than as `TimeScale` so nothing here depends on the scale's shape
5834
- * beyond the two numbers that *are* the viewport.
5835
- */
5836
- interface ReplayViewport {
5837
- readonly barSpacing: number;
5838
- readonly rightOffset: number;
5839
- setBarSpacing(value: number): void;
5840
- setRightOffset(value: number): void;
5841
- }
5842
- /**
5843
- * The slice of the chart this controller needs. `Chart` satisfies it; declaring
5844
- * it structurally keeps the controller testable against a stub and free of a
5845
- * circular import back into core.
5846
- */
5847
- interface ReplayChartHost {
5848
- emit(event: string, payload: unknown): void;
5849
- readonly timeScale: ReplayViewport;
5850
- /**
5851
- * Optional. When the chart can name its own primary price series, `series`
5852
- * may be omitted and replay drives that one.
5853
- */
5854
- primarySeries?(): SeriesApi | null;
5855
- }
5856
- /** Everything a transport bar and a clock need, in one object. */
5857
- interface ReplayState {
5858
- /** 0-based index of the newest bar currently on the chart. */
5859
- index: number;
5860
- /** Bars in the replay set (the scrub bar's maximum is `total - 1`). */
5861
- total: number;
5862
- playing: boolean;
5863
- /** Multiplier over `barMs`: 2 plays two bars per `barMs`. */
5864
- speed: number;
5865
- /**
5866
- * The bar at `index`, the replay clock's "now". Null when there is no data.
5867
- *
5868
- * Under intra-bar replay this is the **partial** bar as it stands this step,
5869
- * not the completed one, which is what makes it the right thing to drive a
5870
- * host's own forming volume bar or an OHLC readout from.
5871
- */
5872
- bar: Bar | null;
5873
- /**
5874
- * Which step of the forming bar this is, 0-based, and how many it takes. Both
5875
- * are 0 and 1 without `subBars`, so a transport that shows them needs no
5876
- * special case for the plain mode.
5877
- */
5878
- subIndex: number;
5879
- subSteps: number;
5880
- }
5881
- interface ReplayOptions {
5882
- /**
5883
- * The series replay drives. The first one owns the timeline; any others (a
5884
- * volume histogram, a comparison line) are truncated to the same instant by
5885
- * time, because the shared DataLayer merges *every* series onto one axis and
5886
- * a series left at full length would drag future timestamps back onto it.
5887
- *
5888
- * Optional only if the host chart implements `primarySeries()`.
5889
- */
5890
- series?: SeriesApi | readonly SeriesApi[];
5891
- /**
5892
- * The full session. Defaults to the primary series' current data. Never
5893
- * mutated, and never handed to the chart as-is: each frame gets its own slice.
5894
- */
5895
- bars?: readonly Bar[];
5896
- /**
5897
- * Finer-grained bars the displayed ones are built from: the 1-minute session
5898
- * under a 5-minute chart. Given these, the playhead advances a **sub-bar** at
5899
- * a time and the newest bar forms in front of the user the way a live one
5900
- * does, instead of appearing complete. A 5-minute bar over 1-minute data
5901
- * takes five steps.
5902
- *
5903
- * They must cover the same session as `bars` and be sorted by time; sub-bars
5904
- * outside any displayed bucket are ignored. The last step of a bucket emits
5905
- * the displayed bar **verbatim** rather than the aggregate, so a bucket always
5906
- * closes on exactly the number the chart would have shown without this option,
5907
- * whatever the two feeds disagree about in between.
5908
- *
5909
- * Only the first driven series forms partially. Followers are cut to completed
5910
- * buckets, because the controller cannot know how to half-aggregate an
5911
- * arbitrary one: a volume histogram is summed, not OHLC-merged. The partial
5912
- * bar reaches the host as `ReplayState.bar` on every frame, so a host that
5913
- * wants a growing volume bar writes it from there.
5914
- */
5915
- subBars?: readonly Bar[];
5916
- /** Bar to open at (0-based). Default 0. */
5917
- startIndex?: number;
5918
- /** Wall-clock milliseconds per bar at speed 1. Default 1000. */
5919
- barMs?: number;
5920
- /** Initial speed multiplier. Default 1. */
5921
- speed?: number;
5922
- /** Called on every playhead move, after the chart has been updated. */
5923
- onFrame?: (state: ReplayState) => void;
5924
- /** Playback clock. Default `performance.now`. */
5925
- now?: () => number;
5926
- /** Playback timer. Default `setInterval`. */
5927
- scheduler?: ReplayScheduler;
5928
- }
5929
- declare class ReplayController {
5930
- private readonly _chart;
5931
- private readonly _series;
5932
- private readonly _bars;
5933
- /** What each driven series held before replay took over, for `stop()`. */
5934
- private readonly _restore;
5935
- private readonly _view;
5936
- private readonly _onFrame;
5937
- private readonly _now;
5938
- private readonly _schedule;
5939
- private readonly _barMs;
5940
- private readonly _startIndex;
5941
- private readonly _subBars;
5942
- /**
5943
- * For each displayed bar, where its sub-bars start in `_subBars` and how many
5944
- * there are. Built once: the alternative is a binary search per step, on a
5945
- * path that runs on every played frame.
5946
- *
5947
- * A bucket with no sub-bars takes one step and shows the displayed bar, so a
5948
- * gap in the finer feed costs that bar its formation, not its existence.
5949
- */
5950
- private readonly _subStart;
5951
- private readonly _subCount;
5952
- private readonly _intra;
5953
- private _speed;
5954
- private _index;
5955
- /** Step within the forming bar, 0-based. Always 0 without `subBars`. */
5956
- private _sub;
5957
- private _playing;
5958
- /** True once replay owns the chart's data; false before start and after stop. */
5959
- private _active;
5960
- private _cancel;
5961
- private _interval;
5962
- /** Clock reading the last advance was charged to; keeps playback drift-free. */
5963
- private _lastAdvance;
5964
- /**
5965
- * Constructing the controller **enters replay**: it snapshots the chart's data
5966
- * and viewport and immediately shows `startIndex`. That is the gesture a
5967
- * replay button makes, and it means the snapshot is taken before anything has
5968
- * moved, so `stop()` can put the user back exactly where they were.
5969
- */
5970
- constructor(chart: ReplayChartHost, options?: ReplayOptions);
5971
- /**
5972
- * Map every sub-bar onto the displayed bar whose bucket it falls in.
5973
- *
5974
- * The displayed bars' own times are the bucket starts, so one forward pass
5975
- * over both sorted arrays does it. A sub-bar before the first displayed bar
5976
- * belongs to no bucket and is dropped rather than folded into bar 0, which
5977
- * would open that bar at a price the chart never showed.
5978
- */
5979
- private _buildBuckets;
5980
- /** How many steps the bar at `index` takes. At least one, always. */
5981
- private _steps;
5982
- /**
5983
- * Jump the playhead to a bar. Out-of-range values clamp to the session.
5984
- *
5985
- * A seek lands on a **complete** bar even under intra-bar replay: scrubbing to
5986
- * a half-formed candle is not a position anyone asks for, and it would make
5987
- * the same slider position mean different things on the way past.
5988
- */
5989
- seek(index: number): void;
5990
- /**
5991
- * Move `n` steps forward. Stops dead at the end of the session.
5992
- *
5993
- * A step is one displayed bar normally, and one sub-bar under intra-bar
5994
- * replay, so the transport button means "advance the chart by the smallest
5995
- * amount it can show" in both modes.
5996
- */
5997
- step(n?: number): void;
5998
- /** Move `n` steps back. Stops dead at the first bar. */
5999
- stepBack(n?: number): void;
6000
- /**
6001
- * Move the playhead `delta` steps, carrying across bar boundaries.
6002
- *
6003
- * Written as a walk rather than arithmetic on a flat step count because
6004
- * buckets hold different numbers of sub-bars: a session with a gap, or a
6005
- * partial first bucket, has no constant steps-per-bar to divide by.
6006
- */
6007
- private _advance;
6008
- /**
6009
- * Start (or re-speed) playback. Calling it on the last bar plays nothing and
6010
- * reports `replay:end` instead of arming a timer that could never advance.
6011
- */
6012
- play(options?: {
6013
- speed?: number;
6014
- }): void;
6015
- /** Halt playback, leaving the playhead (and the chart) where it is. */
6016
- pause(): void;
6017
- /**
6018
- * Leave replay: every driven series gets its pre-replay data back and the
6019
- * viewport is restored to the pixel, so a user who exits does not lose their
6020
- * place. Safe to call twice; a later `seek`/`step`/`play` re-enters replay
6021
- * from `startIndex`.
6022
- */
6023
- stop(): void;
6024
- state(): ReplayState;
6025
- /**
6026
- * Put the chart at `index`. Every transition goes through here (seek, step
6027
- * and each played frame alike), so arriving at a bar leaves the chart in the
6028
- * same state however the user got there.
6029
- */
6030
- private _apply;
6031
- /**
6032
- * One timer tick. The number of bars owed comes from the clock, not from the
6033
- * tick count, so a coarse or throttled timer still plays at the requested
6034
- * speed, and a host may drive this from rAF instead with no change here.
6035
- */
6036
- private readonly _tick;
6037
- /** True on the last step of the last bar, which is where playback stops. */
6038
- private _atEnd;
6039
- /** The session is over: stop the timer and tell the host once. */
6040
- private _end;
6041
- private _stopTimer;
6333
+ resize(_widthPx: number, _heightPx: number, _dpr: number): void;
6334
+ /** The same clear `CanvasLayer.clearBitmap` does, so the op stream is unchanged. */
6335
+ beginFrame(clear: boolean): void;
6336
+ drawSeries(entry: RendererEntry, items: readonly DrawItem[], priceToY: (price: number) => number, barSpacing: number, dpr: number, style: SeriesStyle, rc: SeriesRenderContext): void;
6337
+ /** Every call above drew straight to the canvas; there is nothing to flush. */
6338
+ endFrame(): void;
6339
+ overlay2d(): CanvasRenderingContext2D | null;
6340
+ destroy(): void;
6042
6341
  }
6043
6342
 
6044
6343
  /**
6045
- * Aligning a second instrument onto the primary series' bars.
6046
- *
6047
- * The x-axis is a gapless logical index over the times the shared DataLayer
6048
- * holds (ARCHITECTURE.md §4.1, §5.3), so two instruments do not share a bar
6049
- * index and cannot be laid side by side by position. They are matched by
6050
- * timestamp, and the two directions of mismatch get opposite answers:
6051
- *
6052
- * - **A comparison bar with no primary bar is dropped.** The DataLayer merges
6053
- * *every* series' times into one index space, so a time only the comparison
6054
- * has would mint a new logical index: a column the primary instrument has no
6055
- * candle for, inserted mid-chart, shifting every bar after it. That happens
6056
- * for real (a different exchange's holiday calendar, a 24/7 instrument next
6057
- * to an NSE one, a feed that emits a stray print), and warping the primary's
6058
- * own axis to accommodate a comparison is never the right trade. The print is
6059
- * counted in `dropped` so a host can say so rather than silently losing it.
6060
- *
6061
- * - **A primary bar with no comparison bar becomes whitespace**, which is a NaN
6062
- * bar the line renderer breaks across, so the comparison shows a *gap*. The
6063
- * alternative, carrying the last known value forward, draws a flat segment
6064
- * through a session the instrument never traded and, worse, in percentage
6065
- * mode it anchors the move on the far side of the gap to a print that does
6066
- * not exist. Omitting the bar entirely is worse still: the renderer would
6067
- * join the two sides with one straight line across the holiday.
6068
- *
6069
- * Matching is on the exact timestamp. Bar-open times are bucketed by the candle
6070
- * builder (§10.2) and stored as UTC seconds (§4.0), so two instruments on the
6071
- * same interval agree to the second; anything that does not agree is a
6072
- * different interval, which no tolerance window could rescue.
6344
+ * Histogram / column renderer (ARCHITECTURE.md §6). Used for the volume pane.
6345
+ * Bars are drawn from a base value (0) up to each bar's close.
6073
6346
  */
6074
6347
 
6075
- /** What one alignment pass did, for a host that wants to report coverage. */
6076
- interface ComparisonAlignment {
6077
- /** Items handed to the comparison series: one per primary bar. */
6078
- bars: number;
6079
- /** Primary bars the comparison also traded (a value is drawn). */
6080
- matched: number;
6081
- /** Primary bars with no comparison print (drawn as a gap). */
6082
- gaps: number;
6083
- /** Comparison prints discarded for having no primary bar at that time. */
6084
- dropped: number;
6348
+ interface HistogramStyle {
6349
+ color: string;
6350
+ /** Optional separate colors keyed by an up/down flag set on the bar's volume sign. */
6351
+ base: number;
6352
+ }
6353
+ declare const DEFAULT_HISTOGRAM_STYLE: HistogramStyle;
6354
+
6355
+ /** UTC seconds when this recorded candle becomes complete and available. */
6356
+ type ReplayBarEndTime = (bar: Bar, index: number) => number;
6357
+ /** Explicit availability avoids treating a coarse candle's open as its close. */
6358
+ interface ReplayTiming {
6359
+ barEndTime: ReplayBarEndTime;
6360
+ /** Required with finer bars; their final prices also need a known availability time. */
6361
+ subBarEndTime?: ReplayBarEndTime;
6085
6362
  }
6086
- /**
6087
- * Project `comparison` onto `primary`'s bars: one item per primary bar, in the
6088
- * primary's order, so the result occupies exactly the logical indices the chart
6089
- * already has and adds none of its own.
6090
- *
6091
- * Neither input is mutated and neither has to be sorted: matching goes through
6092
- * a time map, which also collapses a repeated timestamp to its last item, the
6093
- * same rule the DataLayer applies when it merges (`sortedUniqueByTime`).
6094
- */
6095
- declare function alignToPrimary(primary: readonly Bar[], comparison: readonly SeriesDataItem[]): {
6096
- items: SeriesDataItem[];
6097
- alignment: ComparisonAlignment;
6098
- };
6099
6363
 
6100
6364
  /**
6101
- * Multi-symbol comparison: put a second instrument on the primary one's pane
6102
- * and read them together (NIFTY against BANKNIFTY, a stock against its index).
6103
- *
6104
- * Headless, in the spirit of `DrawingController` (src/draw/controller.ts) and
6105
- * `ReplayController`: it owns the series, the alignment and the scales, and
6106
- * ships no DOM, so the host draws its own symbol chips and legend rows from
6107
- * `list()`.
6108
- *
6109
- * Three decisions carry the design:
6110
- *
6111
- * 1. **The comparison never touches the primary's axis.** It goes on the pane's
6112
- * hidden overlay scale (`priceScaleId: ''`, see `Pane._scaleFor`), which
6113
- * autoscales on its own and draws no ticks, so a 46,000 instrument next to a
6114
- * 22,000 one cannot compress the primary's candles or relabel its ladder.
6115
- *
6116
- * 2. **Comparability comes from the scale, not from the data.** The bars handed
6117
- * over are stored as the instrument's own prices, so the legend, the
6118
- * crosshair and any live update still speak in real prices. What makes the
6119
- * lines readable together is the pane mode: `percentage` and
6120
- * `indexed-to-100` give every scale its own baseline (the first *visible*
6121
- * bar, so panning re-bases), and `_mirror` then gives the overlay the same
6122
- * band of percent the primary's axis is showing. Without that mirror each
6123
- * scale would autoscale to its own data and a 1% mover would look exactly
6124
- * like a 10% mover, both filling the pane.
6365
+ * Market replay: walk a historical session forward bar by bar so a trader can
6366
+ * practise on it, and so every indicator redraws exactly as it stood at that
6367
+ * past moment.
6125
6368
  *
6126
- * 3. **Alignment is by timestamp** and lives in `./align`, which documents what
6127
- * happens in each direction of mismatch.
6369
+ * Headless, in the spirit of `DrawingController` (src/draw/controller.ts): it
6370
+ * owns the playhead and the transitions and ships no DOM, so the host renders
6371
+ * its own transport bar, scrub slider and clock from `state()` and the events.
6128
6372
  *
6129
- * Known limit: a pane has exactly *one* hidden overlay scale, so every
6130
- * comparison on a pane shares one baseline. That is exactly right for one
6131
- * comparison, which is the common case, and it is why a second comparison in
6132
- * the same pane is quoted against the first instrument's price. Keyed overlay
6133
- * scales in `Pane` are the fix; until then, put further instruments on their
6134
- * own pane with `paneIndex`.
6373
+ * The mechanic is deliberately boring. Replay feeds the chart a **prefix** of
6374
+ * the full bar array through the ordinary `series.setData` path, and that is
6375
+ * what makes indicators free: `Chart._setData` calls `_recomputeIndicators` for
6376
+ * the primary series, and `IndicatorInstance.recompute` re-reads the whole
6377
+ * history from `sourceBars()`. Shorten that history and every indicator, level,
6378
+ * fill, marker and legend reconstructs itself as it was at that bar, with no
6379
+ * replay-aware code anywhere in the indicator tier.
6135
6380
  */
6136
6381
 
6382
+ /** Schedules a repeating callback and returns its canceller. Inject in tests. */
6383
+ type ReplayScheduler = (cb: () => void, intervalMs: number) => () => void;
6137
6384
  /**
6138
- * How the pane quotes prices while a comparison is on it. The two rebasing
6139
- * modes are the reason the lines are comparable at all; `none` leaves the
6140
- * pane's own mode alone, for a host that wants the raw overlay.
6385
+ * The slice of the time scale replay saves and restores. Declared structurally
6386
+ * rather than as `TimeScale` so nothing here depends on the scale's shape
6387
+ * beyond the two numbers that *are* the viewport.
6141
6388
  */
6142
- type ComparisonMode = 'percentage' | 'indexed-to-100' | 'none';
6143
- interface ComparisonOptions {
6144
- /** Instrument label, e.g. 'BANKNIFTY'. Carried on the handle for the host's UI. */
6145
- symbol: string;
6146
- /** The instrument's own bars. Aligned to the primary series, see `./align`. */
6147
- bars: readonly SeriesDataItem[];
6148
- /** Line colour shorthand; `style.color` wins if both are given. */
6149
- color?: string;
6150
- /** Style overrides merged onto the chart type's defaults. */
6151
- style?: SeriesStyle;
6152
- /** Renderer for the comparison. Default 'line'. */
6153
- type?: SeriesType;
6154
- /** Pane to draw on. Default 0, the price pane. */
6155
- paneIndex?: number;
6156
- }
6157
- interface ComparisonControllerOptions {
6158
- /** Pane mode applied while any comparison is on it. Default 'percentage'. */
6159
- mode?: ComparisonMode;
6389
+ interface ReplayViewport {
6390
+ readonly barSpacing: number;
6391
+ readonly rightOffset: number;
6392
+ setBarSpacing(value: number): void;
6393
+ setRightOffset(value: number): void;
6160
6394
  }
6161
- /** What `addComparison` hands back: one instrument on the chart. */
6162
- interface ComparisonHandle {
6163
- readonly symbol: string;
6395
+ /**
6396
+ * The slice of the chart this controller needs. `Chart` satisfies it; declaring
6397
+ * it structurally keeps the controller testable against a stub and free of a
6398
+ * circular import back into core.
6399
+ */
6400
+ interface ReplayChartHost {
6401
+ emit(event: string, payload: unknown): void;
6402
+ readonly timeScale: ReplayViewport;
6164
6403
  /**
6165
- * The series this comparison draws through, for style patches and markers.
6166
- * Data set on it directly skips alignment (use `setBars`), and removing it
6167
- * directly leaves the pane rebased with nothing on it (use `remove`).
6404
+ * Optional. When the chart can name its own primary price series, `series`
6405
+ * may be omitted and replay drives that one.
6168
6406
  */
6169
- readonly series: SeriesApi;
6170
- readonly paneIndex: number;
6171
- /** The hidden scale it maps to. Never the pane's own price axis. */
6172
- priceScale(): PriceScale;
6173
- /** How the last alignment against the primary's bars went. */
6174
- alignment(): ComparisonAlignment;
6175
- /** Replace the instrument's bars (a longer history, a refreshed fetch). */
6176
- setBars(bars: readonly SeriesDataItem[]): void;
6177
- /** Take this instrument off the chart. Safe to call twice. */
6178
- remove(): void;
6179
- /** Every comparison on this chart, in the order they were added. */
6180
- list(): readonly ComparisonHandle[];
6407
+ primarySeries?(): SeriesApi | null;
6181
6408
  }
6182
- /**
6183
- * The slice of a pane the controller reads. Declared structurally, like
6184
- * `ReplayViewport`, so nothing here depends on `Pane` beyond the two members
6185
- * that decide where a comparison can go.
6186
- */
6187
- interface ComparisonPane {
6188
- readonly priceScale: PriceScale;
6189
- series(): readonly {
6190
- readonly scaleId: string;
6191
- }[];
6409
+ /** Everything a transport bar and a clock need, in one object. */
6410
+ interface ReplayState {
6411
+ /** 0-based newest bar; -1 before the first observation in timed replay. */
6412
+ index: number;
6413
+ /** Bars in the replay set (the scrub bar's maximum is `total - 1`). */
6414
+ total: number;
6415
+ playing: boolean;
6416
+ /** Multiplier over `barMs`: 2 plays two bars per `barMs`. */
6417
+ speed: number;
6418
+ /**
6419
+ * The bar at `index`, the replay clock's "now". Null when there is no data.
6420
+ *
6421
+ * Under intra-bar replay this is the **partial** bar as it stands this step,
6422
+ * not the completed one, which is what makes it the right thing to drive a
6423
+ * host's own forming volume bar or an OHLC readout from.
6424
+ */
6425
+ bar: Bar | null;
6426
+ /**
6427
+ * Which step of the forming bar this is, 0-based, and how many it takes. Both
6428
+ * are 0 and 1 without `subBars`, so a transport that shows them needs no
6429
+ * special case for the plain mode.
6430
+ */
6431
+ subIndex: number;
6432
+ subSteps: number;
6192
6433
  }
6193
- /**
6194
- * The slice of the chart this controller needs. `Chart` satisfies it; declaring
6195
- * it structurally keeps the controller testable against a stub.
6196
- */
6197
- interface ComparisonChartHost {
6198
- addSeries(type: SeriesType, options: AddSeriesOptions): SeriesApi;
6199
- panes(): readonly ComparisonPane[];
6200
- addPrimitive(primitive: IPrimitive, paneIndex?: number): void;
6201
- removePrimitive(primitive: IPrimitive): void;
6202
- primarySeries(): SeriesApi | null;
6203
- /** Only `length` is read: it changes exactly when the shared time axis does. */
6204
- readonly dataLayer: {
6205
- readonly length: number;
6206
- };
6434
+ interface ReplayOptions {
6435
+ /** Explicit candle availability for time-aligned replay. Omitted preserves index-based replay. */
6436
+ timing?: ReplayTiming;
6437
+ /** Initial UTC availability time. Requires timing; otherwise startIndex selects a completed candle. */
6438
+ startTime?: number;
6439
+ /** False prepares and validates the snapshot without changing the chart. Default true. */
6440
+ autoStart?: boolean;
6441
+ /**
6442
+ * The series replay drives. The first one owns the timeline; any others (a
6443
+ * volume histogram, a comparison line) are truncated to the same instant by
6444
+ * time, because the shared DataLayer merges *every* series onto one axis and
6445
+ * a series left at full length would drag future timestamps back onto it.
6446
+ *
6447
+ * Optional only if the host chart implements `primarySeries()`.
6448
+ */
6449
+ series?: SeriesApi | readonly SeriesApi[];
6450
+ /**
6451
+ * The full session. Defaults to the primary series' current data. Never
6452
+ * mutated, and never handed to the chart as-is: each frame gets its own slice.
6453
+ */
6454
+ bars?: readonly Bar[];
6455
+ /**
6456
+ * Finer-grained bars the displayed ones are built from: the 1-minute session
6457
+ * under a 5-minute chart. Given these, the playhead advances a **sub-bar** at
6458
+ * a time and the newest bar forms in front of the user the way a live one
6459
+ * does, instead of appearing complete. A 5-minute bar over 1-minute data
6460
+ * takes five steps.
6461
+ *
6462
+ * They must cover the same session as `bars` and be sorted by time; sub-bars
6463
+ * outside any displayed bucket are ignored. The last step of a bucket emits
6464
+ * the displayed bar **verbatim** rather than the aggregate, so a bucket always
6465
+ * closes on exactly the number the chart would have shown without this option,
6466
+ * whatever the two feeds disagree about in between.
6467
+ *
6468
+ * Only the first driven series forms partially. Followers are cut to completed
6469
+ * buckets, because the controller cannot know how to half-aggregate an
6470
+ * arbitrary one: a volume histogram is summed, not OHLC-merged. The partial
6471
+ * bar reaches the host as `ReplayState.bar` on every frame, so a host that
6472
+ * wants a growing volume bar writes it from there.
6473
+ */
6474
+ subBars?: readonly Bar[];
6475
+ /** Bar to open at (0-based). Default 0. */
6476
+ startIndex?: number;
6477
+ /** Wall-clock milliseconds per bar at speed 1. Default 1000. */
6478
+ barMs?: number;
6479
+ /** Initial speed multiplier. Default 1. */
6480
+ speed?: number;
6481
+ /** Called on every playhead move, after the chart has been updated. */
6482
+ onFrame?: (state: ReplayState) => void;
6483
+ /** Playback clock. Default `performance.now`. */
6484
+ now?: () => number;
6485
+ /** Playback timer. Default `setInterval`. */
6486
+ scheduler?: ReplayScheduler;
6207
6487
  }
6208
- declare class ComparisonController {
6488
+ declare class ReplayController {
6209
6489
  private readonly _chart;
6210
- private _mode;
6211
- private readonly _panes;
6212
- /** Insertion-ordered, and the handle is the key so `remove` is a lookup. */
6213
- private readonly _items;
6490
+ private readonly _series;
6491
+ private readonly _bars;
6492
+ /** What each driven series held before replay took over, for `stop()`. */
6493
+ private readonly _restore;
6494
+ private readonly _view;
6495
+ private readonly _onFrame;
6496
+ private readonly _now;
6497
+ private readonly _schedule;
6498
+ private readonly _barMs;
6499
+ private readonly _startIndex;
6500
+ private readonly _timeline;
6501
+ private readonly _startTime;
6502
+ private _time;
6503
+ private _pointIndex;
6504
+ private readonly _subBars;
6214
6505
  /**
6215
- * `dataLayer.length` as of the last alignment. Alignment depends only on the
6216
- * primary's set of *times*, and that set is what the length counts, so this
6217
- * is an O(1) staleness check for a per-frame hook (see `sync`).
6506
+ * For each displayed bar, where its sub-bars start in `_subBars` and how many
6507
+ * there are. Built once: the alternative is a binary search per step, on a
6508
+ * path that runs on every played frame.
6509
+ *
6510
+ * A bucket with no sub-bars takes one step and shows the displayed bar, so a
6511
+ * gap in the finer feed costs that bar its formation, not its existence.
6218
6512
  */
6219
- private _alignedAt;
6220
- /** Guards `realign` against re-entry through its own `setData` repaint. */
6221
- private _realigning;
6222
- constructor(chart: ComparisonChartHost, options?: ComparisonControllerOptions);
6223
- /** Put an instrument on the chart alongside the primary series. */
6224
- add(options: ComparisonOptions): ComparisonHandle;
6225
- /** Take one instrument off. Returns false if it was already gone. */
6226
- remove(handle: ComparisonHandle): boolean;
6227
- /** Every comparison on the chart, in the order they were added. */
6228
- list(): readonly ComparisonHandle[];
6229
- /** Take them all off, putting every pane back the way it was found. */
6230
- clear(): void;
6513
+ private readonly _subStart;
6514
+ private readonly _subCount;
6515
+ private readonly _intra;
6516
+ private _speed;
6517
+ private _index;
6518
+ /** Step within the forming bar, 0-based. Always 0 without `subBars`. */
6519
+ private _sub;
6520
+ private _playing;
6521
+ /** True once replay owns the chart's data; false before start and after stop. */
6522
+ private _active;
6523
+ private _cancel;
6524
+ private _interval;
6525
+ /** Clock reading the last advance was charged to; keeps playback drift-free. */
6526
+ private _lastAdvance;
6231
6527
  /**
6232
- * Change the mode the panes are held in while comparisons are on them.
6233
- * Panes that already have one switch immediately.
6528
+ * Constructing the controller **enters replay**: it snapshots the chart's data
6529
+ * and viewport and immediately shows `startIndex`. That is the gesture a
6530
+ * replay button makes, and it means the snapshot is taken before anything has
6531
+ * moved, so `stop()` can put the user back exactly where they were.
6234
6532
  */
6235
- setMode(mode: ComparisonMode): void;
6236
- get mode(): ComparisonMode;
6533
+ constructor(chart: ReplayChartHost, options?: ReplayOptions);
6237
6534
  /**
6238
- * Re-project every instrument onto the primary's current bars. Called for
6239
- * free when the shared time axis changes (see `sync`); a host only needs it
6240
- * after replacing the primary's data with a *different* set of the same
6241
- * length, which the length check cannot see.
6535
+ * Map every sub-bar onto the displayed bar whose bucket it falls in.
6536
+ *
6537
+ * The displayed bars' own times are the bucket starts, so one forward pass
6538
+ * over both sorted arrays does it. A sub-bar before the first displayed bar
6539
+ * belongs to no bucket and is dropped rather than folded into bar 0, which
6540
+ * would open that bar at a price the chart never showed.
6242
6541
  */
6243
- realign(): void;
6542
+ private _buildBuckets;
6543
+ /** How many steps the bar at `index` takes. At least one, always. */
6544
+ private _steps;
6244
6545
  /**
6245
- * Bring the overlay scales in line with the primary axis, re-aligning first
6246
- * if the shared time axis has moved under us (a live bar, history paged in,
6247
- * a replay step). Runs once per base paint through the per-pane hook.
6546
+ * Jump the playhead to a bar. Out-of-range values clamp to the session.
6248
6547
  *
6249
- * Re-aligning here writes series data mid-frame, which is safe because the
6250
- * hook is a `bottom` primitive: it runs after the pane has autoscaled and
6251
- * before any series is drawn, so the frame that notices the change is also
6252
- * the frame that draws it. The repaint that write asks for lands on the next
6253
- * frame, or re-enters this one on a host that runs frames synchronously,
6254
- * which is what `realign`'s guard is for.
6548
+ * A seek lands on a **complete** bar even under intra-bar replay: scrubbing to
6549
+ * a half-formed candle is not a position anyone asks for, and it would make
6550
+ * the same slider position mean different things on the way past.
6255
6551
  */
6256
- sync(): void;
6257
- /** Remove every comparison and forget the chart. */
6258
- destroy(): void;
6552
+ seek(index: number): void;
6553
+ /** Project only observations available by these UTC seconds. Requires timing. */
6554
+ seekTime(time: number): void;
6555
+ /** Availability clock in timed mode; the displayed bar's timestamp in legacy mode. */
6556
+ time(): number | null;
6557
+ /** Observation timestamps for a shared clock. Requires timing; returns a copy. */
6558
+ timePoints(): readonly number[];
6259
6559
  /**
6260
- * Where a comparison can sit on a pane. The hidden overlay is the right
6261
- * answer, but there is only one of it per pane and the volume histogram in
6262
- * the price pane is usually already on it (that is what `priceScaleId: ''`
6263
- * is best known for). Sharing it would autoscale price and volume together
6264
- * and flatten both, so when it is taken the comparison goes to the left axis
6265
- * instead: a visible second ladder is a far smaller surprise than an
6266
- * invisible line at the bottom of the pane.
6560
+ * Move `n` steps forward. Stops dead at the end of the session.
6561
+ *
6562
+ * A step is one displayed bar normally, and one sub-bar under intra-bar
6563
+ * replay, so the transport button means "advance the chart by the smallest
6564
+ * amount it can show" in both modes.
6267
6565
  */
6268
- private _scaleIdFor;
6269
- private _openPane;
6270
- private _closePane;
6271
- private _applyMode;
6272
- private _restoreMode;
6566
+ step(n?: number): void;
6567
+ /** Move `n` steps back. Stops dead at the first bar. */
6568
+ stepBack(n?: number): void;
6273
6569
  /**
6274
- * Give the overlay the same band of percent the primary axis is showing, so
6275
- * equal moves land on equal pixels and the divergence between two
6276
- * instruments is the thing you see.
6277
- *
6278
- * Both scales rebase against a baseline of their own (the first visible bar
6279
- * on each), and a rebase maps price to the ratio `price / baseline`. So the
6280
- * two scales agree exactly when their ranges hold the same ratios, which is
6281
- * one multiplication: the primary's range times `baseline_overlay /
6282
- * baseline_primary`. It holds for `indexed-to-100` and `percentage` alike,
6283
- * since they are one ladder a hundred points apart.
6284
- *
6285
- * A null baseline means this scale is not rebasing (linear, logarithmic, or
6286
- * a pane with nothing visible on it yet). Then there is no shared ladder to
6287
- * join and the overlay keeps its own autoscale, which is what mode 'none'
6288
- * asks for: two instruments each filling the pane, comparable in shape only.
6570
+ * Move the playhead `delta` steps, carrying across bar boundaries.
6289
6571
  *
6290
- * The overlay stays under autoscale throughout, even though the range it
6291
- * measures is overwritten here every frame. That measurement is one pass over
6292
- * bars the renderer is about to walk anyway, and paying for it buys the
6293
- * failure mode we want: a chart that stops calling `sync` falls back to an
6294
- * independently scaled overlay instead of freezing on a stale range.
6572
+ * Written as a walk rather than arithmetic on a flat step count because
6573
+ * buckets hold different numbers of sub-bars: a session with a gap, or a
6574
+ * partial first bucket, has no constant steps-per-bar to divide by.
6295
6575
  */
6296
- private _mirror;
6297
- private _primaryBars;
6298
- private _align;
6576
+ private _advance;
6299
6577
  /**
6300
- * Ask for a full repaint after a mode change. The pane hands a rebasing scale
6301
- * its baseline during the autoscale pass, so a `Light` repaint (all a
6302
- * primitive's `requestUpdate` raises) would paint the new mode before it has
6303
- * anything to quote against. `applyOptions` is the series handle's own route
6304
- * to a Full invalidation, and an empty patch changes no style. Several of
6305
- * them coalesce into one frame, so asking every comparison costs nothing.
6578
+ * Start (or re-speed) playback. Calling it on the last bar plays nothing and
6579
+ * reports `replay:end` instead of arming a timer that could never advance.
6306
6580
  */
6307
- private _repaint;
6581
+ play(options?: {
6582
+ speed?: number;
6583
+ }): void;
6584
+ /** Halt playback, leaving the playhead (and the chart) where it is. */
6585
+ pause(): void;
6586
+ /**
6587
+ * Leave replay: every driven series gets its pre-replay data back and the
6588
+ * viewport is restored to the pixel, so a user who exits does not lose their
6589
+ * place. Safe to call twice; a later `seek`/`step`/`play` re-enters replay
6590
+ * from `startIndex`.
6591
+ */
6592
+ stop(): void;
6593
+ state(): ReplayState;
6594
+ /**
6595
+ * Put the chart at `index`. Every transition goes through here (seek, step
6596
+ * and each played frame alike), so arriving at a bar leaves the chart in the
6597
+ * same state however the user got there.
6598
+ */
6599
+ private _apply;
6600
+ private _write;
6601
+ /**
6602
+ * One timer tick. The number of bars owed comes from the clock, not from the
6603
+ * tick count, so a coarse or throttled timer still plays at the requested
6604
+ * speed, and a host may drive this from rAF instead with no change here.
6605
+ */
6606
+ private readonly _tick;
6607
+ /** True on the last step of the last bar, which is where playback stops. */
6608
+ private _atEnd;
6609
+ /** The session is over: stop the timer and tell the host once. */
6610
+ private _end;
6611
+ private _stopTimer;
6612
+ }
6613
+
6614
+ /** True while replay owns this chart, including when its playback clock is paused. */
6615
+ declare function isReplaying(chart: object): boolean;
6616
+
6617
+ type ReplayScope = 'focused' | 'all';
6618
+ /** Chart implements this; headless hosts may supply the lifecycle hooks too. */
6619
+ interface ReplayGroupChartHost extends ReplayChartHost {
6620
+ readonly isDestroyed?: boolean;
6621
+ on?(event: 'destroy', callback: () => void): () => void;
6622
+ }
6623
+ interface ReplayGroupMember {
6624
+ id: string;
6625
+ chart: ReplayGroupChartHost;
6626
+ /** Transport and clock options belong to the group. */
6627
+ options: Pick<ReplayOptions, 'series' | 'bars' | 'subBars' | 'onFrame'> & {
6628
+ timing: ReplayTiming;
6629
+ };
6630
+ }
6631
+ interface ReplayGroupState {
6632
+ active: boolean;
6633
+ destroyed: boolean;
6634
+ scope: ReplayScope;
6635
+ focusedId: string;
6636
+ /** UTC availability time, or null when the active histories have no observations. */
6637
+ time: number | null;
6638
+ /** Index in the union of active observation times; -1 before the first. */
6639
+ index: number;
6640
+ total: number;
6641
+ playing: boolean;
6642
+ speed: number;
6643
+ members: readonly {
6644
+ id: string;
6645
+ active: boolean;
6646
+ state: ReplayState;
6647
+ }[];
6648
+ }
6649
+ interface ReplayGroupOptions {
6650
+ /** Default focused. Changing toolbar focus alone does not redirect replay. */
6651
+ scope?: ReplayScope;
6652
+ /** Default the first member. */
6653
+ focusedId?: string;
6654
+ startTime?: number;
6655
+ /** Wall-clock milliseconds per observation at speed 1. Default 1000. */
6656
+ barMs?: number;
6657
+ speed?: number;
6658
+ now?: () => number;
6659
+ scheduler?: ReplayScheduler;
6660
+ /** Runs after all active charts reach a frame or transport transition. */
6661
+ onChange?: (state: ReplayGroupState) => void;
6662
+ }
6663
+ /** One opt-in clock driving validated ReplayControllers by availability time. */
6664
+ declare class ReplayGroup {
6665
+ private _members;
6666
+ private _activeIds;
6667
+ private _times;
6668
+ private _scope;
6669
+ private _focusedId;
6670
+ private _time;
6671
+ private _startTime;
6672
+ private _active;
6673
+ private _destroyed;
6674
+ private _playing;
6675
+ private _busy;
6676
+ private _closing;
6677
+ private _revision;
6678
+ private _speed;
6679
+ private readonly _barMs;
6680
+ private readonly _now;
6681
+ private readonly _scheduler;
6682
+ private readonly _onChange?;
6683
+ private _cancel;
6684
+ private _clockRevision;
6685
+ private _lastAdvance;
6686
+ private _pending;
6687
+ constructor(members: readonly ReplayGroupMember[], options?: ReplayGroupOptions);
6688
+ state(): ReplayGroupState;
6689
+ seekTime(time: number): void;
6690
+ /** Seek an observation index, clamped to the active timeline. */
6691
+ seek(index: number): void;
6692
+ step(n?: number): void;
6693
+ stepBack(n?: number): void;
6694
+ private _move;
6695
+ setScope(scope: ReplayScope, focusedId?: string): void;
6696
+ play(options?: {
6697
+ speed?: number;
6698
+ }): void;
6699
+ pause(): void;
6700
+ /** Restore data/viewports, retaining the captured session for later re-entry. */
6701
+ stop(): void;
6702
+ /** Restore surviving charts and release snapshots, listeners and ownership. */
6703
+ destroy(): void;
6704
+ private _validateScope;
6705
+ private _prepare;
6706
+ private _prepareEntering;
6707
+ private _install;
6708
+ private _wanted;
6709
+ private _rebuildTimes;
6710
+ private _check;
6711
+ private _apply;
6712
+ private _atEnd;
6713
+ private _tick;
6714
+ private _clearClock;
6715
+ private _emit;
6716
+ private _notify;
6717
+ private _execute;
6718
+ private _chartGone;
6719
+ private _cleanup;
6308
6720
  }
6309
- /**
6310
- * The controller for a chart, created on first use. Use it to change the mode
6311
- * for the whole chart, to list what is on it, or to clear it.
6312
- */
6313
- declare function comparisonController(chart: ComparisonChartHost, options?: ComparisonControllerOptions): ComparisonController;
6314
- /**
6315
- * Put an instrument on the chart next to the primary one:
6316
- *
6317
- * ```ts
6318
- * const bn = addComparison(chart, { symbol: 'BANKNIFTY', bars, color: '#f0b90b' });
6319
- * ```
6320
- *
6321
- * The pane switches to percentage while any comparison is on it and goes back
6322
- * to the mode it had when the last one leaves.
6323
- */
6324
- declare function addComparison(chart: ComparisonChartHost, options: ComparisonOptions): ComparisonHandle;
6325
6721
 
6326
6722
  /**
6327
6723
  * Time alignment for chart linking: the one thing that makes a grid of charts
@@ -6409,7 +6805,7 @@ declare function followerRange(leader: LinkDataLayer, follower: LinkDataLayer, r
6409
6805
  * ships no DOM, so the host draws its own link badge / colour chips and decides
6410
6806
  * which charts belong to which group.
6411
6807
  *
6412
- * Three channels sync, each switchable on its own because a user routinely
6808
+ * Four channels sync, each switchable on its own because a user routinely
6413
6809
  * wants one without the others (mirror the cursor across four timeframes but
6414
6810
  * keep each zoom; or slave every chart's symbol but let each keep its own
6415
6811
  * window):
@@ -6417,6 +6813,7 @@ declare function followerRange(leader: LinkDataLayer, follower: LinkDataLayer, r
6417
6813
  * - **crosshair**: hovering one chart marks the same instant on the others.
6418
6814
  * - **viewport**: panning or zooming one moves the others to the same window.
6419
6815
  * - **symbol**: changing the instrument on one changes it on the others.
6816
+ * - **interval**: changing a timeframe asks each following host to adopt it.
6420
6817
  *
6421
6818
  * Four decisions carry the design.
6422
6819
  *
@@ -6476,6 +6873,8 @@ interface LinkChart {
6476
6873
  panes(): readonly unknown[];
6477
6874
  addPrimitive(primitive: IPrimitive, paneIndex?: number): void;
6478
6875
  removePrimitive(primitive: IPrimitive): void;
6876
+ /** Optional readout support alongside the group's vertical marker. */
6877
+ setLinkedCrosshairIndex?(index: number | null): void;
6479
6878
  }
6480
6879
  interface LinkOptions {
6481
6880
  /** Mirror the hovered instant onto every other member. Default true. */
@@ -6484,6 +6883,8 @@ interface LinkOptions {
6484
6883
  viewport?: boolean;
6485
6884
  /** Mirror the instrument, via each member's `onSymbol`. Default false. */
6486
6885
  symbol?: boolean;
6886
+ /** Mirror the timeframe, via each member's `onInterval`. Default false. */
6887
+ interval?: boolean;
6487
6888
  /** What a follower does with an instant it has no bar for. Default 'nearest'. */
6488
6889
  whenMissing?: LinkMissingPolicy;
6489
6890
  }
@@ -6496,6 +6897,10 @@ interface LinkMemberOptions {
6496
6897
  * it still broadcasts its own changes, it just never follows anyone else's.
6497
6898
  */
6498
6899
  onSymbol?: (symbol: string, chart: LinkChart) => void;
6900
+ /** The interval this chart is showing, if the host tracks one. */
6901
+ interval?: string;
6902
+ /** Apply the interval synchronously; return false to refuse an unsupported token. */
6903
+ onInterval?: (interval: string, chart: LinkChart) => boolean | void;
6499
6904
  }
6500
6905
  /** Every option resolved, as `options()` reports them. */
6501
6906
  type ResolvedLinkOptions = Required<LinkOptions>;
@@ -6505,6 +6910,7 @@ declare class LinkGroup {
6505
6910
  /** True while the group is applying a change to followers. See decision 3. */
6506
6911
  private _broadcasting;
6507
6912
  private _symbol;
6913
+ private _interval;
6508
6914
  private _destroyed;
6509
6915
  constructor(options?: LinkOptions);
6510
6916
  options(): ResolvedLinkOptions;
@@ -6524,6 +6930,8 @@ declare class LinkGroup {
6524
6930
  has(chart: LinkChart): boolean;
6525
6931
  /** The instrument the group has agreed on, or null if nobody declared one. */
6526
6932
  symbol(): string | null;
6933
+ /** Latest interval selected by a member, even while interval linking is off. */
6934
+ interval(): string | null;
6527
6935
  /**
6528
6936
  * The member's own logical index its linked crosshair is marking, or null
6529
6937
  * when it is showing none (it is the chart being hovered, the leader's
@@ -6551,6 +6959,8 @@ declare class LinkGroup {
6551
6959
  * symbol sync is on, loads it into every other member that can follow.
6552
6960
  */
6553
6961
  setSymbol(chart: LinkChart, symbol: string): void;
6962
+ /** Report a host-owned interval selection; followers opt in through `onInterval`. */
6963
+ setInterval(chart: LinkChart, interval: string): void;
6554
6964
  /** Unlink everything: no listeners, no linked crosshairs, no references. */
6555
6965
  destroy(): void;
6556
6966
  private _onCrosshair;
@@ -6559,6 +6969,9 @@ declare class LinkGroup {
6559
6969
  private _applySymbol;
6560
6970
  /** Push the group's agreed symbol onto everyone who is not already on it. */
6561
6971
  private _convergeSymbol;
6972
+ private _applyInterval;
6973
+ private _followInterval;
6974
+ private _convergeInterval;
6562
6975
  /**
6563
6976
  * Run `apply` on every member except the originator, exactly once, with the
6564
6977
  * echo guard held. `onLeader` (optional) runs on the originator itself.
@@ -6647,6 +7060,14 @@ interface Tick {
6647
7060
  ltq?: number;
6648
7061
  /** Cumulative day volume (Quote mode). */
6649
7062
  cumDayVolume?: number;
7063
+ /**
7064
+ * Open interest as at this tick, where the feed carries it. Unlike the two
7065
+ * quantities above it is not accumulated into the bar: it replaces, because
7066
+ * it is a level and not a flow (see `Bar.oi`). A feed that does not report it
7067
+ * leaves the built bar without one, which is the honest result rather than a
7068
+ * zero that reads as "nobody is holding this".
7069
+ */
7070
+ oi?: number;
6650
7071
  }
6651
7072
  interface CandleUpdate {
6652
7073
  bar: Bar;
@@ -7270,6 +7691,86 @@ interface TradeFeed {
7270
7691
  subscribePositions(cb: (positions: unknown[]) => void): UnsubscribeFn;
7271
7692
  }
7272
7693
 
7694
+ interface InstrumentCalendar {
7695
+ /** HHMM-HHMM[:days], with opening weekdays 1 (Sunday) through 7. */
7696
+ readonly sessions: readonly string[];
7697
+ /** Local opening dates replace weekly sessions. An empty list closes that date. */
7698
+ readonly exceptions?: Readonly<Record<string, readonly string[]>>;
7699
+ }
7700
+ /** Host-supplied market rules, separate from observations and saved user preferences. */
7701
+ interface InstrumentMetadata {
7702
+ readonly symbol: string;
7703
+ readonly exchange: string;
7704
+ readonly timezone: string;
7705
+ readonly priceTick: number;
7706
+ readonly pricePrecision: number;
7707
+ /** In the order adapter's units. This is a grid, never a lot conversion factor. */
7708
+ readonly quantityStep: number;
7709
+ readonly intervals: readonly string[];
7710
+ readonly calendar: InstrumentCalendar;
7711
+ readonly hasOpenInterest?: boolean;
7712
+ }
7713
+ interface InstrumentSession {
7714
+ /** Local opening date, including for an overnight window. */
7715
+ readonly date: string;
7716
+ /** UTC seconds, inclusive. */
7717
+ readonly open: number;
7718
+ /** UTC seconds, exclusive. */
7719
+ readonly close: number;
7720
+ }
7721
+ /** Validated, detached rules. Construction does not change global intervals or chart defaults. */
7722
+ declare class Instrument {
7723
+ readonly metadata: InstrumentMetadata;
7724
+ private readonly _sessions;
7725
+ private readonly _exceptions;
7726
+ constructor(input: unknown);
7727
+ /** Provider tokens are exact: a feed may distinguish monthly M from minute m. */
7728
+ supportsInterval(code: string): boolean;
7729
+ /** Formats display only. The source bars retain their unrounded values. */
7730
+ formatPrice(value: number): string;
7731
+ /** Resolve an active window. Closed dates and breaks return null, never inferred hours. */
7732
+ sessionAt(utcSeconds: number): InstrumentSession | null;
7733
+ /** The host clears old bars and owns source loading; only metadata is applied here. */
7734
+ applyTo(chart: Chart, interval: string): void;
7735
+ }
7736
+
7737
+ type TradingOperation = 'place' | 'modify' | 'cancel';
7738
+ /** Optional restrictions on existing write paths; omissions preserve legacy support. */
7739
+ interface TradingCapabilities {
7740
+ readonly place?: boolean | 'unknown';
7741
+ readonly modify?: boolean | 'unknown';
7742
+ readonly cancel?: boolean | 'unknown';
7743
+ /** Accepted types for new orders. An empty list disables placement. */
7744
+ readonly orderTypes?: readonly OrderType$1[];
7745
+ /** Accepted modes for new orders; does not change the broker's actual mode. */
7746
+ readonly modes?: readonly ('live' | 'analyzer')[];
7747
+ }
7748
+ interface TradingCapabilityRequest {
7749
+ readonly operation: TradingOperation;
7750
+ readonly symbol?: string;
7751
+ readonly exchange?: string;
7752
+ readonly orderId?: string;
7753
+ readonly type?: OrderType$1;
7754
+ readonly mode?: 'live' | 'analyzer';
7755
+ }
7756
+ /** A configured provider returning undefined declares that support is unavailable. */
7757
+ type TradingCapabilitySource = TradingCapabilities | ((request: Readonly<TradingCapabilityRequest>) => TradingCapabilities | undefined);
7758
+ type TradingCapabilityResult = {
7759
+ supported: true;
7760
+ } | {
7761
+ supported: false;
7762
+ reason: string;
7763
+ };
7764
+ /** Shared by host controls and the write boundary. Never grants broker authority. */
7765
+ declare function checkTradingCapability(source: TradingCapabilitySource | undefined, request: TradingCapabilityRequest): TradingCapabilityResult;
7766
+ /** Raised only before delivery, so an unsupported write does not become ambiguous. */
7767
+ declare class TradingCapabilityError extends Error {
7768
+ readonly preflight: true;
7769
+ readonly operation: TradingOperation;
7770
+ constructor(operation: TradingOperation, reason: string);
7771
+ }
7772
+ declare function assertTradingCapability(source: TradingCapabilitySource | undefined, request: TradingCapabilityRequest): void;
7773
+
7273
7774
  /** Limits apply to each pool, whose identity belongs to one data feed. */
7274
7775
  interface HistoryRequestPoolOptions {
7275
7776
  maxConcurrent?: number;
@@ -7443,6 +7944,12 @@ interface OpenAlgoConfig {
7443
7944
  apiKey: string;
7444
7945
  /** Injectable fetch (defaults to global fetch); lets the adapter be tested offline. */
7445
7946
  fetchImpl?: typeof fetch;
7947
+ /**
7948
+ * Instrument capability from host metadata. Explicit false omits the API's
7949
+ * placeholder OI column before it can become a misleading cash reading.
7950
+ * Missing/unknown capability preserves finite observations, including zero.
7951
+ */
7952
+ hasOpenInterest?(request: Readonly<BarsRequest>): boolean | undefined;
7446
7953
  }
7447
7954
  interface HistoryRow {
7448
7955
  timestamp?: number | string;
@@ -7452,6 +7959,8 @@ interface HistoryRow {
7452
7959
  low: number;
7453
7960
  close: number;
7454
7961
  volume?: number;
7962
+ /** Present on a derivatives response; the platform always sends the column. */
7963
+ oi?: number;
7455
7964
  }
7456
7965
  interface HistoryResponse {
7457
7966
  status?: string;
@@ -7461,7 +7970,7 @@ interface HistoryResponse {
7461
7970
  /** Pure: coerce a row timestamp (epoch s / epoch ms / IST string) to UTC seconds. */
7462
7971
  declare function rowTimeToUtcSeconds(value: number | string): number;
7463
7972
  /** Pure: map an OpenAlgo history response into sorted internal bars. */
7464
- declare function mapHistoryResponse(json: HistoryResponse): Bar[];
7973
+ declare function mapHistoryResponse(json: HistoryResponse, hasOpenInterest?: boolean): Bar[];
7465
7974
  declare class OpenAlgoDataFeed implements DataFeed {
7466
7975
  private readonly _config;
7467
7976
  private readonly _fetch;
@@ -7925,6 +8434,8 @@ interface ModifyPatch {
7925
8434
  * implement `OrderFeed` for the engine's write path.
7926
8435
  */
7927
8436
  interface OrderFeed {
8437
+ /** Optional support declaration; a configured provider can report unavailable metadata. */
8438
+ readonly capabilities?: TradingCapabilitySource$1;
7928
8439
  place(req: PlaceRequest & {
7929
8440
  mode: TradeMode;
7930
8441
  }): Promise<{
@@ -7960,6 +8471,8 @@ type ModeCheck = 'off' | 'auto' | 'always';
7960
8471
  interface OpenAlgoTradeConfig {
7961
8472
  baseUrl: string;
7962
8473
  apiKey: string;
8474
+ /** Optional host/broker restrictions; omitted preserves the existing OpenAlgo path. */
8475
+ capabilities?: TradingCapabilitySource;
7963
8476
  /**
7964
8477
  * Instrument constraints for the advisory pre-trade check on `place`, looked
7965
8478
  * up per order. Return undefined when the instrument is unknown; the
@@ -8085,7 +8598,8 @@ declare class OpenAlgoTradeFeed implements OrderFeed {
8085
8598
  * or a retried promise does not become two orders.
8086
8599
  */
8087
8600
  private _claimToken;
8088
- place(req: PlaceRequest & {
8601
+ get capabilities(): TradingCapabilitySource | undefined;
8602
+ place(request: PlaceRequest & {
8089
8603
  mode: TradeMode;
8090
8604
  }): Promise<{
8091
8605
  orderId: string;
@@ -8106,7 +8620,7 @@ declare class OpenAlgoTradeFeed implements OrderFeed {
8106
8620
  * that an ambiguous order did not reach the exchange.
8107
8621
  */
8108
8622
  releaseToken(token: string): void;
8109
- modify(orderId: string, patch: {
8623
+ modify(orderId: string, changes: {
8110
8624
  price?: number;
8111
8625
  triggerPrice?: number;
8112
8626
  qty?: number;
@@ -8533,6 +9047,11 @@ interface AggTick {
8533
9047
  time: number;
8534
9048
  price: number;
8535
9049
  qty: number;
9050
+ /**
9051
+ * Open interest as at this tick, where the feed carries it. Carried onto the
9052
+ * bar as the latest reading rather than accumulated: see `Bar.oi`.
9053
+ */
9054
+ oi?: number;
8536
9055
  }
8537
9056
  interface BarUpdate {
8538
9057
  bar: Bar;
@@ -8957,4 +9476,67 @@ declare function lerp(a: number, b: number, t: number): number;
8957
9476
  */
8958
9477
  declare function roundToTick(value: number, step: number): number;
8959
9478
 
8960
- export { ALT_PRESET, type AddSeriesOptions, type AggTick, type AxisChromeOptions, type AxisStyle, BAR_CACHE_VERSION, BUILTIN_COMMANDS, type Bar, BarCache, type BarCacheOptions, type BarCacheStats, type BarCacheStore, type BarSubscriptionOptions, type BarUpdate, type BarsPage, type BarsPageRequest, type BarsRequest, type BrandingChangedEvent, type Bucketing, BuySellButtons, type BuySellButtonsOptions, CHART_STATE_VERSION, type CachedBars, type CachedBarsRequest, type CalendarBucketing, type CalendarUnit, CandleBuilder, type CandleBuilderOptions, type CandleGeometry, type CandleStyle, type CandleTier, type CandleUpdate, Canvas2dBackend, type CanvasLineStyle, type CanvasOptions, Chart, type ChartClickEvent, type ChartDataContext, type ChartDragEndEvent, type ChartDragEvent, type ChartEvent, type ChartEventOptions, type ChartNavigationOptions, type ChartObjectCapabilities, type ChartObjectDefinition, type ChartObjectDrawing, type ChartObjectDrawingSource, type ChartObjectKind, type ChartObjectProvider, type ChartObjectSnapshot, ChartObjects, type ChartObjectsOptions, type ChartOptions, type ChartSettingsColorPairInput, type ChartSettingsInput, type ChartSettingsState, type ChartSettingsTab, type ChartSettingsTabId, type ChartSettingsValue, type ChartSettingsValues, type ChartState, ChartTable, type ChartTableOptions, type ChartTheme, type ChartWatermarkOptions, type ComparisonAlignment, type ComparisonChartHost, ComparisonController, type ComparisonControllerOptions, type ComparisonHandle, type ComparisonMode, type ComparisonOptions, type ComparisonPane, type ContextMenuEvent, type ContextMenuTarget, type ContextMenuTargetKind, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairStyle, type CustomShortcut, DEFAULT_CANDLE_BUILDER_OPTIONS, DEFAULT_CANDLE_STYLE, DEFAULT_CHART_TABLE_OPTIONS, DEFAULT_HISTOGRAM_STYLE, DEFAULT_KEYMAP, DEFAULT_PRICE_SCALE_OPTIONS, DEFAULT_THEME, DEFAULT_TIMEZONE, DEFAULT_TIME_NAVIGATOR_OPTIONS, DEFAULT_TIME_SCALE_OPTIONS, DEFAULT_TRADING_COLORS, DEFAULT_ZOOM_GLIDE_OPTIONS, type DataFeed, DataLayer, DataLoadingController, type DataLoadingOptions, type DataLoadingSnapshot, type DataLoadingStatus, type DataUpdateReason, type DecodedOrder, type DepthLevel, type DoubleClickAction, type DoubleClickEvent, type DrawAnchor, type DrawItem, EventMarkers, type ExportSvgOptions, FakeDataFeed, type FeedScheduler, type FillGradient, type FillPoint, type GridAxisStyle, type GridOptions, type GridStyle, type HistogramStyle, type HistoryLoadingStatus, HistoryRequestPool, type HistoryRequestPoolOptions, INDICATOR_LINE_STYLES, INDICATOR_PLOT_STYLES, INDICATOR_SOURCES, type IPrimitive, type IRenderBackend, IST_OFFSET_SECONDS, type IndexedBar, type IndicatorAlertContext, type IndicatorAlertPayload, type IndicatorAlertSpec, type IndicatorApi, type IndicatorAttachContext, IndicatorBackground, type IndicatorBarsProvider, type IndicatorBarsRequest, type IndicatorCalcContext, type IndicatorDataChange, type IndicatorDataStatus, type IndicatorDescriptor, type IndicatorDrawing, IndicatorDrawings, IndicatorFill, type IndicatorFillOptions, type IndicatorFillSpec, type IndicatorHost, type IndicatorInput, IndicatorInputError, type IndicatorLevel, type IndicatorLevelContext, type IndicatorLineStyle, type IndicatorPlot, type IndicatorSettings, type IndicatorSource, type IndicatorState, type IndicatorStore, type IndicatorValues, type IntervalBucketing, type IntervalDescriptor, type IntervalParts, InvalidationLevel, type IstParts, type KeymapEntry, LINK_CROSSHAIR_ALPHA, type LateTickPolicy, type LegendField, type LegendStatusData, type LegendStatusLineOptions, type LegendStatusSource, type LegendTitleMode, type LegendValue, type LinePoint, type LinkChart, LinkCrosshair, type LinkDataLayer, LinkGroup, type LinkMemberOptions, type LinkMissingPolicy, type LinkOptions, type LiveBarMeta, type LogicalRange, LogoWatermark, type LogoWatermarkOptions, type LtpEvent, type MarkerPosition, type MarkerShape, type MarkerSize, type MarketDepth, type MarketPhase, type MarketPhaseFn, type MaybePromise, type ModeCheck, type OpenAlgoConfig, OpenAlgoDataFeed, type OpenAlgoLiveConfig, OpenAlgoLiveDataFeed, type OpenAlgoTradeConfig, OpenAlgoTradeFeed, type OpenAlgoWsConfig, OpenAlgoWsFeed, type OrderBookSnapshot, type OrderDecodeCode, type OrderDecodeIssue, type OrderDecodeResult, type OrderSide$1 as OrderSide, type OrderType$1 as OrderType, type OrderUpdateEvent, type OriginalTime, PRICE_LEVEL_KINDS, PRICE_SCALE_MODES, Pane, type PaneInvalidation, PaneLegend, type PaneLegendAction, type PaneLegendOptions, type PaneState, type PickHost, type PickKind, type PlaceOrder, type PlotBarColor, type PlotMarginOptions, type PointerInfo, type PointerKind, type PointerModifiers, type PointerSample, type PositionSide, type PriceAxisState, type PriceFormat, type PriceLevelInput, type PriceLevelKind, type PriceLevelQuote, type PriceLevelStyle, type PriceLevelValues, PriceLevels, type PriceLevelsOptions, PriceLine, type PriceLineOptions, type PriceRange, PriceScale, type PriceScaleId, type PriceScaleMode, type PriceScaleOptions, type PriceScaleState, type PrimitiveAnchor, type PrimitiveHit, type PrimitiveHost, type PrimitivePlacement, type PrimitiveRenderContext, type QuarantinedRow, type RawOrder, type RenderBackendFactory, type RenderBackendKind, type RenderDevice, type RendererChoice, type RendererEntry, type RendererFallbackEvent, type RendererFallbackReason, type ReplayChartHost, ReplayController, type ReplayOptions, type ReplayScheduler, ReplayShade, type ReplayShadeOptions, type ReplayState, type ReplayViewport, type ResolvedLinkOptions, type RestoreReport, SCALE_FONT_MAX, SCALE_FONT_MIN, type ScaleCanvasOptions, type SeriesApi, type SeriesDataItem, type SeriesId, type SeriesMarker, SeriesMarkers, type SeriesRenderContext, type SeriesState, type SeriesStyle, type SeriesType, type SessionSpec, type ShortcutListItem, ShortcutManager, type ShortcutManagerOptions, type ShortcutPreset, type ShortcutScope, type ShortcutTriggerEvent, type Size, type SocketFactory, type SocketLike, type SupertrendPoint, SvgContext, type SvgContextOptions, SvgLinearGradient, type TableCell, type TablePosition, TextWatermark, type TextWatermarkOptions, type Tick, TickBarAggregator, type TickBarOptions, type TickCountBucketing, type TickMarkType, type TickTimeframe, TimeNavigator, type TimeNavigatorAction, type TimeNavigatorOptions, TimeScale, type TimeScaleOp, type TimeScaleOptions, type TradeFeed, type TradeMarkerVariant, TradeMarkersPrimitive, type TradingColors, TradingController, type TradingHost, type TradingLineStyle, type TradingLineVariant, type TradingOrder, type TradingOrderSide, type TradingOrderType, type TradingPosition, type TradingSettings, type TradingSyncPayload, type TradingTrade, type UTCSeconds, UnknownIntervalError, type UnsubscribeFn, VERSION, type VolumeBucketing, type VolumeMode, type WatermarkPosition, type Whitespace, type WsClientWarning, type WsControlMessage, type WsMode, type WsState, type ZOrder, type ZonedParts, type ZonedPeriod, type ZoomAnchor, ZoomGlide, type ZoomGlideOptions, addComparison, alignToPrimary, applyChartSettings, atr, autoscaleRange, backendDegradation, backoffDelayMs, barCacheKey, barCloseSec, beginPick, bestHit, bitmapSize, bucketStartOf, calendarPeriodFlags, candleGeometry, candleTier, chartSettingsSchema, clamp, classifyAuthAck, compactVolume, comparisonController, computePriceLevels, conflateBars, conflateItems, conflationGroupSize, createChart, createLinkGroup, createRenderBackend, darkTheme, dashPattern, decodeOrder, drawLabel, drawShape, effectiveMarkerPx, ema, emaSeries, epochMsToUtcSeconds, eventToCombo, followerIndex, followerRange, formatCombo, formatIstCrosshairLabel, formatIstDate, formatIstTime, formatIstTimeSeconds, formatSubscribe, formatUnsubscribe, formatZonedCrosshairLabel, formatZonedDate, formatZonedTime, formatZonedTimeSeconds, fromGradient, generateBars, getChartType, getIndicator, hasIndicator, inSessionAt, indicatorDefaults, indicatorStyleInputs, intervalParts, intervalToSeconds, isDailyInterval, isIntradayInterval, isKnownInterval, isNewIstDay, isNewZonedDay, isNewZonedMonth, isNewZonedPeriod, isNewZonedQuarter, isNewZonedWeek, isNewZonedYear, isRebasing, isReservedCombo, isSecondsInterval, isTickInterval, isTimeBucketed, isValidCombo, isValidTimezone, isWhitespace, istStringToUtcSeconds, lastPriceLevelFromSeriesStyle, lerp, lightTheme, mapHistoryResponse, mapOrder, mapOrderStatus, mapPosition, markerSizePx, mergeBars, nextBucketStart, niceTicks, normalizeCombo, optimalBarWidth, parseCombo, parseMessage, parseSessionSpec, parseTopic, plotStyleKeys, precisionForStep, readChartSettings, readSequence, registerChartType, registerIndicator, registerInterval, registerRenderBackend, registeredChartTypes, registeredIndicators, registeredIntervals, registeredRenderBackends, resolveCrosshairStyle, resolveGridStyle, resolveInterval, resolvePlotMargins, resolveRenderBackend, resolveScaleStyle, roundToTick, rowTimeToUtcSeconds, rsi, rsiSeries, seriesStyleForLastPriceLevel, sessionFlags, sessionStartFlags, sessionStartIndices, sharedHistoryRequests, snapToDevicePixel, sourceValue, sourceValues, startOfZonedDay, startOfZonedMonth, startOfZonedWeek, supertrend, supertrendSeries, tableOrigin, toBar, trueRange, tryResolveInterval, unregisterInterval, unregisterRenderBackend, utcSecondsToIstDateString, utcSecondsToIstParts, utcSecondsToZonedDateString, utcSecondsToZonedParts, version, verticalGradient, watermarkRect, withAlpha, withBarCache, zoneOffsetSeconds, zonedDayIndex, zonedStringToUtcSeconds, zonedWallClockToUtcSeconds, zonedWeekIndex };
9479
+ /** Headless, chart-owned trader alerts. Hosts subscribe to alert:triggered for delivery. */
9480
+ declare class AlertController {
9481
+ private readonly _chart;
9482
+ private readonly _records;
9483
+ private readonly _off;
9484
+ private readonly _now;
9485
+ private readonly _drawings;
9486
+ private readonly _visuals;
9487
+ private _timer;
9488
+ private _timerAt;
9489
+ private _paused;
9490
+ private _restoring;
9491
+ private _replay;
9492
+ private _destroyed;
9493
+ private _revision;
9494
+ constructor(_chart: AlertChartHost, options?: AlertControllerOptions);
9495
+ add(input: AlertInput): Alert;
9496
+ update(id: string, patch: AlertPatch): Alert | undefined;
9497
+ remove(id: string): boolean;
9498
+ private _remove;
9499
+ list(): Alert[];
9500
+ toJSON(): AlertsDocument;
9501
+ /** Validate the complete replacement before touching current alerts or their visuals. */
9502
+ fromJSON(input: unknown): void;
9503
+ private _saveState;
9504
+ /** Availability is independent of lifecycle state; disabled records can still have valid anchors. */
9505
+ availability(id: string): AlertAvailability;
9506
+ enable(id: string): Alert | undefined;
9507
+ disable(id: string): Alert | undefined;
9508
+ /** Explicit host pause is independent of replay and never delivers a backlog. */
9509
+ setPaused(paused: boolean): void;
9510
+ destroy(): void;
9511
+ private _assertAlive;
9512
+ private _seed;
9513
+ private _seedAll;
9514
+ private _onObjects;
9515
+ private _syncVisual;
9516
+ private _onData;
9517
+ private _plot;
9518
+ private _reading;
9519
+ private _closedMatch;
9520
+ private _touchMatch;
9521
+ private _barMatch;
9522
+ private _drawingValue;
9523
+ private _drawingTouch;
9524
+ private _trigger;
9525
+ private _expireDue;
9526
+ private _clearTimer;
9527
+ private _scheduleExpiry;
9528
+ }
9529
+
9530
+ /** Shared editor fields in source units. Drawing bands own their bounds. */
9531
+ declare function alertSettingsSchema(source: AlertSource, condition?: AlertCondition): readonly IndicatorInput[];
9532
+
9533
+ /** Validate and detach a version-1 document, or migrate a legacy bare list. */
9534
+ declare function parseAlertsDocument(input: unknown): AlertsDocument;
9535
+
9536
+ /** Register runtime code by stable id; persisted alerts store only that id. */
9537
+ declare function registerBarCondition(condition: BarCondition): void;
9538
+ declare function unregisterBarCondition(id: string): boolean;
9539
+ declare function getBarCondition(id: string): Readonly<BarCondition> | undefined;
9540
+ declare function registeredBarConditions(): readonly Readonly<BarCondition>[];
9541
+
9542
+ export { ALT_PRESET, type AddSeriesOptions, type AggTick, type Alert, type AlertAvailability, type AlertChartHost, type AlertCondition, AlertController, type AlertControllerOptions, type AlertDrawingInfo, type AlertDrawingLevel, type AlertDrawingProvider, type AlertDrawingValue, type AlertEventPayload, type AlertInput, type AlertPatch, type AlertPolicy, type AlertRepeat, type AlertScope, type AlertSource, type AlertState, type AlertTriggeredPayload, type AlertsDocument, type AxisChromeOptions, type AxisStyle, BAR_CACHE_VERSION, BUILTIN_COMMANDS, type Bar, BarCache, type BarCacheOptions, type BarCacheStats, type BarCacheStore, type BarCondition, type BarConditionAlertSource, type BarConditionContext, type BarSubscriptionOptions, type BarUpdate, type BarsPage, type BarsPageRequest, type BarsRequest, type BrandingChangedEvent, type Bucketing, BuySellButtons, type BuySellButtonsOptions, CHART_STATE_VERSION, type CachedBars, type CachedBarsRequest, type CalendarBucketing, type CalendarUnit, CandleBuilder, type CandleBuilderOptions, type CandleGeometry, type CandleStyle, type CandleTier, type CandleUpdate, Canvas2dBackend, type CanvasLineStyle, type CanvasOptions, Chart, type ChartClickEvent, type ChartDataContext, type ChartDataCsvOptions, type ChartDataUpdate, type ChartDragEndEvent, type ChartDragEvent, type ChartEvent, type ChartEventOptions, type ChartNavigationOptions, type ChartObjectCapabilities, type ChartObjectDefinition, type ChartObjectDrawing, type ChartObjectDrawingSource, type ChartObjectKind, type ChartObjectProvider, type ChartObjectSnapshot, ChartObjects, type ChartObjectsOptions, type ChartOptions, type ChartSettingsColorPairInput, type ChartSettingsInput, type ChartSettingsState, type ChartSettingsTab, type ChartSettingsTabId, type ChartSettingsValue, type ChartSettingsValues, type ChartState, ChartTable, type ChartTableOptions, type ChartTheme, type ChartWatermarkOptions, type ComparisonAlignment, type ComparisonBaseline, type ComparisonChartHost, ComparisonController, type ComparisonControllerOptions, type ComparisonHandle, type ComparisonMode, type ComparisonOptions, type ComparisonPane, type ContextMenuEvent, type ContextMenuTarget, type ContextMenuTargetKind, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairStyle, type CustomShortcut, DEFAULT_CANDLE_BUILDER_OPTIONS, DEFAULT_CANDLE_STYLE, DEFAULT_CHART_TABLE_OPTIONS, DEFAULT_HISTOGRAM_STYLE, DEFAULT_KEYMAP, DEFAULT_PRICE_SCALE_OPTIONS, DEFAULT_THEME, DEFAULT_TIMEZONE, DEFAULT_TIME_NAVIGATOR_OPTIONS, DEFAULT_TIME_SCALE_OPTIONS, DEFAULT_TRADING_COLORS, DEFAULT_ZOOM_GLIDE_OPTIONS, type DataFeed, DataLayer, DataLoadingController, type DataLoadingOptions, type DataLoadingSnapshot, type DataLoadingStatus, type DataUpdateReason, type DecodedOrder, type DepthLevel, type DoubleClickAction, type DoubleClickEvent, type DrawAnchor, type DrawItem, type DrawingAlertSource, EventMarkers, type ExportSvgOptions, FakeDataFeed, type FeedScheduler, type FillGradient, type FillPoint, type GridAxisStyle, type GridOptions, type GridStyle, type HistogramStyle, type HistoryLoadingStatus, HistoryRequestPool, type HistoryRequestPoolOptions, INDICATOR_LINE_STYLES, INDICATOR_PLOT_STYLES, INDICATOR_SOURCES, type IPrimitive, type IRenderBackend, IST_OFFSET_SECONDS, type IndexedBar, type IndicatorAlertContext, type IndicatorAlertPayload, type IndicatorAlertSource, type IndicatorAlertSpec, type IndicatorApi, type IndicatorAttachContext, IndicatorBackground, type IndicatorBarsProvider, type IndicatorBarsRequest, type IndicatorCalcContext, type IndicatorDataChange, type IndicatorDataStatus, type IndicatorDescriptor, type IndicatorDrawing, IndicatorDrawings, IndicatorFill, type IndicatorFillOptions, type IndicatorFillSpec, type IndicatorHost, type IndicatorInput, IndicatorInputError, type IndicatorLevel, type IndicatorLevelContext, type IndicatorLineStyle, type IndicatorPlot, type IndicatorSettings, type IndicatorSource, type IndicatorState, type IndicatorStore, type IndicatorValues, Instrument, type InstrumentCalendar, type InstrumentMetadata, type InstrumentSession, type IntervalBucketing, type IntervalDescriptor, type IntervalParts, InvalidationLevel, type IstParts, type KeymapEntry, LINK_CROSSHAIR_ALPHA, type LateTickPolicy, type LegendField, type LegendStatusData, type LegendStatusLineOptions, type LegendStatusSource, type LegendTitleMode, type LegendValue, type LinePoint, type LinkChart, LinkCrosshair, type LinkDataLayer, LinkGroup, type LinkMemberOptions, type LinkMissingPolicy, type LinkOptions, type LiveBarMeta, type LogicalRange, LogoWatermark, type LogoWatermarkOptions, type LtpEvent, type MarkerPosition, type MarkerShape, type MarkerSize, type MarketDepth, type MarketPhase, type MarketPhaseFn, type MaybePromise, type ModeCheck, type OpenAlgoConfig, OpenAlgoDataFeed, type OpenAlgoLiveConfig, OpenAlgoLiveDataFeed, type OpenAlgoTradeConfig, OpenAlgoTradeFeed, type OpenAlgoWsConfig, OpenAlgoWsFeed, type OrderBookSnapshot, type OrderDecodeCode, type OrderDecodeIssue, type OrderDecodeResult, type OrderSide$1 as OrderSide, type OrderType$1 as OrderType, type OrderUpdateEvent, type OriginalTime, PRICE_LEVEL_KINDS, PRICE_SCALE_MODES, Pane, type PaneInvalidation, PaneLegend, type PaneLegendAction, type PaneLegendOptions, type PaneState, type PickHost, type PickKind, type PlaceOrder, type PlotBarColor, type PlotMarginOptions, type PointerInfo, type PointerKind, type PointerModifiers, type PointerSample, type PositionSide, type PriceAlertSource, type PriceAxisState, type PriceFormat, type PriceLevelInput, type PriceLevelKind, type PriceLevelQuote, type PriceLevelStyle, type PriceLevelValues, PriceLevels, type PriceLevelsOptions, PriceLine, type PriceLineOptions, type PriceRange, PriceScale, type PriceScaleId, type PriceScaleMode, type PriceScaleOptions, type PriceScaleState, type PrimitiveAnchor, type PrimitiveHit, type PrimitiveHost, type PrimitivePlacement, type PrimitiveRenderContext, type QuarantinedRow, type RawOrder, type RenderBackendFactory, type RenderBackendKind, type RenderDevice, type RendererChoice, type RendererEntry, type RendererFallbackEvent, type RendererFallbackReason, type ReplayBarEndTime, type ReplayChartHost, ReplayController, ReplayGroup, type ReplayGroupChartHost, type ReplayGroupMember, type ReplayGroupOptions, type ReplayGroupState, type ReplayOptions, type ReplayScheduler, type ReplayScope, ReplayShade, type ReplayShadeOptions, type ReplayState, type ReplayTiming, type ReplayViewport, type ResolvedLinkOptions, type RestoreReport, SCALE_FONT_MAX, SCALE_FONT_MIN, type ScaleCanvasOptions, type SeriesApi, type SeriesDataItem, type SeriesId, type SeriesMarker, SeriesMarkers, type SeriesRenderContext, type SeriesState, type SeriesStyle, type SeriesType, type SessionSpec, type ShortcutListItem, ShortcutManager, type ShortcutManagerOptions, type ShortcutPreset, type ShortcutScope, type ShortcutTriggerEvent, type Size, type SocketFactory, type SocketLike, type SupertrendPoint, SvgContext, type SvgContextOptions, SvgLinearGradient, type TableCell, type TablePosition, TextWatermark, type TextWatermarkOptions, type Tick, TickBarAggregator, type TickBarOptions, type TickCountBucketing, type TickMarkType, type TickTimeframe, TimeNavigator, type TimeNavigatorAction, type TimeNavigatorOptions, TimeScale, type TimeScaleOp, type TimeScaleOptions, type TradeFeed, type TradeMarkerVariant, TradeMarkersPrimitive, type TradingCapabilities, TradingCapabilityError, type TradingCapabilityRequest, type TradingCapabilityResult, type TradingCapabilitySource, type TradingColors, TradingController, type TradingHost, type TradingLineStyle, type TradingLineVariant, type TradingOperation, type TradingOrder, type TradingOrderSide, type TradingOrderType, type TradingPosition, type TradingSettings, type TradingSyncPayload, type TradingTrade, type UTCSeconds, UnknownIntervalError, type UnsubscribeFn, VERSION, type VolumeBucketing, type VolumeMode, type WatermarkPosition, type Whitespace, type WsClientWarning, type WsControlMessage, type WsMode, type WsState, type ZOrder, type ZonedParts, type ZonedPeriod, type ZoomAnchor, ZoomGlide, type ZoomGlideOptions, addComparison, alertSettingsSchema, alignToPrimary, applyChartSettings, assertTradingCapability, atr, autoscaleRange, backendDegradation, backoffDelayMs, barCacheKey, barCloseSec, beginPick, bestHit, bitmapSize, bucketStartOf, calendarPeriodFlags, candleGeometry, candleTier, chartSettingsSchema, checkTradingCapability, clamp, classifyAuthAck, compactVolume, comparisonController, computePriceLevels, conflateBars, conflateItems, conflationGroupSize, createChart, createLinkGroup, createRenderBackend, darkTheme, dashPattern, decodeOrder, drawLabel, drawShape, effectiveMarkerPx, ema, emaSeries, epochMsToUtcSeconds, eventToCombo, exportChartDataCsv, followerIndex, followerRange, formatCombo, formatIstCrosshairLabel, formatIstDate, formatIstTime, formatIstTimeSeconds, formatSubscribe, formatUnsubscribe, formatZonedCrosshairLabel, formatZonedDate, formatZonedTime, formatZonedTimeSeconds, fromGradient, generateBars, getBarCondition, getChartType, getIndicator, hasIndicator, inSessionAt, indicatorDefaults, indicatorStyleInputs, intervalParts, intervalToSeconds, isDailyInterval, isIntradayInterval, isKnownInterval, isNewIstDay, isNewZonedDay, isNewZonedMonth, isNewZonedPeriod, isNewZonedQuarter, isNewZonedWeek, isNewZonedYear, isRebasing, isReplaying, isReservedCombo, isSecondsInterval, isTickInterval, isTimeBucketed, isValidCombo, isValidTimezone, isWhitespace, istStringToUtcSeconds, lastPriceLevelFromSeriesStyle, lerp, lightTheme, mapHistoryResponse, mapOrder, mapOrderStatus, mapPosition, markerSizePx, mergeBars, nextBucketStart, niceTicks, normalizeCombo, optimalBarWidth, parseAlertsDocument, parseCombo, parseMessage, parseSessionSpec, parseTopic, plotStyleKeys, precisionForStep, readChartSettings, readSequence, registerBarCondition, registerChartType, registerIndicator, registerInterval, registerRenderBackend, registeredBarConditions, registeredChartTypes, registeredIndicators, registeredIntervals, registeredRenderBackends, resolveCrosshairStyle, resolveGridStyle, resolveInterval, resolvePlotMargins, resolveRenderBackend, resolveScaleStyle, roundToTick, rowTimeToUtcSeconds, rsi, rsiSeries, seriesStyleForLastPriceLevel, sessionFlags, sessionStartFlags, sessionStartIndices, sharedHistoryRequests, snapToDevicePixel, sourceValue, sourceValues, startOfZonedDay, startOfZonedMonth, startOfZonedWeek, supertrend, supertrendSeries, tableOrigin, toBar, trueRange, tryResolveInterval, unregisterBarCondition, unregisterInterval, unregisterRenderBackend, utcSecondsToIstDateString, utcSecondsToIstParts, utcSecondsToZonedDateString, utcSecondsToZonedParts, version, verticalGradient, watermarkRect, withAlpha, withBarCache, zoneOffsetSeconds, zonedDayIndex, zonedStringToUtcSeconds, zonedWallClockToUtcSeconds, zonedWeekIndex };