openalgo-charts 2.4.0 → 2.4.6

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.6";
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
@@ -835,6 +859,11 @@ interface IPrimitive {
835
859
  /** Pick the best hit across primitives: nearest distance, then z-order priority. */
836
860
  declare function bestHit(hits: readonly (PrimitiveHit | null)[]): PrimitiveHit | null;
837
861
 
862
+ /**
863
+ * Series markers (ARCHITECTURE.md §8.1): buy/sell signals and shapes anchored
864
+ * to bars. Visible-range culled, per-bar stacked, four discrete sizes.
865
+ */
866
+
838
867
  /**
839
868
  * `labelUp` / `labelDown` are text plates with a tail, for named signals ("Buy",
840
869
  * "Sell") rather than bare glyphs. The tail points *at* the anchor price and the
@@ -876,10 +905,23 @@ declare function drawShape(ctx: CanvasRenderingContext2D, shape: MarkerShape, cx
876
905
  declare function drawLabel(ctx: CanvasRenderingContext2D, up: boolean, cx: number, anchorY: number, text: string, color: string, fontPx: number): void;
877
906
  declare class SeriesMarkers implements IPrimitive {
878
907
  private readonly _seriesId;
908
+ private readonly _fallbackBars;
909
+ private readonly _priceScale;
879
910
  private _markers;
880
911
  private _host;
881
912
  private _lastPositions;
882
- constructor(seriesId: SeriesId);
913
+ /**
914
+ * @param seriesId The series whose pane and price scale the marks live on.
915
+ * @param fallbackBars Bars to position against where that series has none.
916
+ *
917
+ * The second argument exists because a marker's series decides *where* it is
918
+ * drawn while the bar under it decides *how high*, and those are not always
919
+ * the same row of data. An indicator that draws one line in an uptrend and
920
+ * another in a downtrend has a gap in each, and a mark that lands in a gap
921
+ * had no bar to measure from and was dropped without a word. The caller
922
+ * passes the instrument's own bars, which have no gaps.
923
+ */
924
+ constructor(seriesId: SeriesId, fallbackBars?: () => readonly Bar[], priceScale?: () => PriceScale);
883
925
  attached(host: PrimitiveHost): void;
884
926
  detached(): void;
885
927
  zOrder(): ZOrder;
@@ -899,8 +941,10 @@ declare class SeriesMarkers implements IPrimitive {
899
941
  * Which price axis a series maps to. 'right' (default) and 'left' each draw an
900
942
  * axis and autoscale independently; '' is a hidden overlay scale (no axis, its
901
943
  * own autoscale) used to pin a volume histogram inside the price pane.
944
+ * `overlay:name` creates an independent hidden scale, shared only by series
945
+ * using that same name on this pane.
902
946
  */
903
- type PriceScaleId = 'right' | 'left' | '';
947
+ type PriceScaleId = 'right' | 'left' | '' | `overlay:${string}`;
904
948
  /**
905
949
  * Value formatting for a price scale (its axis labels and crosshair tag):
906
950
  * `price` (tick-size precision), `volume` (compact 1.2K / 3.4M / 5.6B),
@@ -949,8 +993,14 @@ interface SeriesApi {
949
993
  remove(): void;
950
994
  /** The price scale this series maps to (call `.setOptions({ marginTop, marginBottom })` on it). */
951
995
  priceScale(): PriceScale;
952
- /** Create a markers layer (buy/sell signals, shapes) bound to this series. */
953
- createMarkers(): SeriesMarkers;
996
+ /**
997
+ * Create a markers layer (buy/sell signals, shapes) bound to this series.
998
+ *
999
+ * `fallbackBars` positions a mark whose time this series has no point for,
1000
+ * which happens whenever the series is drawn with gaps. Without it such a
1001
+ * mark is dropped silently.
1002
+ */
1003
+ createMarkers(fallbackBars?: () => readonly Bar[]): SeriesMarkers;
954
1004
  }
955
1005
 
956
1006
  /**
@@ -1338,7 +1388,7 @@ declare class Pane {
1338
1388
  private _rightScale;
1339
1389
  /** Extra scales created on demand: left axis and a hidden overlay (volume). */
1340
1390
  private _leftScale;
1341
- private _overlayScale;
1391
+ private readonly _overlayScales;
1342
1392
  /**
1343
1393
  * Scales whose price-per-bar ratio is pinned, with the geometry the ratio was
1344
1394
  * last held against. A lock stores that geometry rather than a number,
@@ -1548,1344 +1598,1550 @@ declare class Pane {
1548
1598
  }
1549
1599
 
1550
1600
  /**
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.
1601
+ * Horizontal price line primitive (ARCHITECTURE.md §8). The reusable base for
1602
+ * order/SL/TP/alert/indicator-level lines: a line across the plot plus a fixed
1603
+ * right-axis price tag and an optional broker-style segmented pill group on the
1604
+ * line — [badge][qty][label][✕] — with hover / dragging states (the chart
1605
+ * passes `hoverId`/`dragId` on the render context) and a drag ghost at the
1606
+ * pre-drag price via `setDragGhost`. Interaction semantics are unchanged from
1607
+ * the classic tag: the ✕ hit-tests as `${id}::close`, everything else drags.
1558
1608
  */
1559
1609
 
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;
1610
+ interface PriceLineOptions {
1611
+ price: number;
1612
+ color: string;
1613
+ /** Line thickness in media px. Default 1. */
1614
+ lineWidth?: number;
1615
+ /** Legacy two-state dash switch, equivalent to `lineStyle: 'dashed'`. */
1616
+ dashed?: boolean;
1589
1617
  /**
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.
1618
+ * Dash style, the three-way form of `dashed`. Set, it wins over the boolean;
1619
+ * unset, the boolean still decides, so a line built before this existed draws
1620
+ * exactly as it did.
1594
1621
  */
1595
- widthPercent?: number;
1596
- /** Table height as a percentage of the plot, 0 or omitted to size from `cellHeight`. */
1597
- heightPercent?: number;
1622
+ lineStyle?: CanvasLineStyle;
1623
+ /** Right-axis tag text. Defaults to the formatted price. */
1624
+ label?: string;
1625
+ /** Solid colored badge segment at the start of the pill group (e.g. 'BUY', 'TP', 'SL'). */
1626
+ badge?: string;
1627
+ /** Quantity segment rendered as a neutral box after the badge. */
1628
+ qty?: string | number;
1629
+ /** Info text segment (order type, price, P&L ...) — the classic left tag text. */
1630
+ leftLabel?: string;
1598
1631
  /**
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.
1632
+ * Fraction of the plot width the line spans, measured from the right (price)
1633
+ * axis. 1 = full width (default); 0.3 = only the rightmost 30%, like a
1634
+ * partial-width order line. The right-axis tag is always drawn.
1602
1635
  */
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;
1636
+ extentFromRight?: number;
1637
+ /** Draw a cancel (✕) segment at the end of the pill group; hit-tests as `${id}::close`. */
1638
+ closeButton?: boolean;
1639
+ /** Stable id returned by hit-test (for click/drag routing). */
1640
+ id: string;
1641
+ /** Cursor hint when hovered (e.g. 'ns-resize' for draggable lines). */
1642
+ cursor?: string;
1608
1643
  }
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;
1644
+ declare class PriceLine implements IPrimitive {
1617
1645
  private _opts;
1618
1646
  private _host;
1619
- /** Last drawn rect in media px, for hit-testing without recomputing layout. */
1620
- private _rect;
1621
- constructor(options?: Partial<ChartTableOptions>);
1647
+ private _ghostPrice;
1648
+ /** Pill-group geometry from the last draw (media px) for hit-testing. */
1649
+ private _group;
1650
+ constructor(opts: PriceLineOptions);
1622
1651
  attached(host: PrimitiveHost): void;
1623
1652
  detached(): void;
1653
+ get price(): number;
1654
+ /** Move the line; schedules a repaint via the host. */
1655
+ setPrice(price: number): void;
1656
+ /** Update the info segment text (e.g. live position P&L); repaints. */
1657
+ setLeftLabel(text: string): void;
1658
+ /**
1659
+ * Restyle in place; repaints. `id` is the hit-test handle the chart routes
1660
+ * clicks and drags through, so it is not patchable — swapping it under a
1661
+ * live drag would strand the gesture.
1662
+ *
1663
+ * A last-price line is the case this exists for: it has to follow the tick
1664
+ * direction, and only `setPrice` was updatable, so the colour was stuck at
1665
+ * whatever it was constructed with.
1666
+ */
1667
+ setOptions(patch: Partial<Omit<PriceLineOptions, 'id'>>): void;
1668
+ /**
1669
+ * Show a dimmed reference line at the pre-drag price while the user drags
1670
+ * (pass the original price on drag start, null on drag end to clear).
1671
+ */
1672
+ setDragGhost(price: number | null): void;
1673
+ options(): Readonly<PriceLineOptions>;
1624
1674
  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[])[];
1675
+ autoscaleInfo(): {
1676
+ min: number;
1677
+ max: number;
1678
+ } | null;
1630
1679
  draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
1631
- hitTest(x: number, y: number): PrimitiveHit | null;
1680
+ hitTest(x: number, y: number, rc: PrimitiveRenderContext): PrimitiveHit | null;
1632
1681
  }
1633
1682
 
1634
1683
  /**
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"*.
1684
+ * Pane legend (ARCHITECTURE.md §8) — the row at the top-left
1685
+ * of a pane: a color swatch, the source's name, its parameters, the value under
1686
+ * the crosshair, and inline action buttons on the right.
1638
1687
  *
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.
1688
+ * Drawn on the canvas rather than in the DOM, like `BuySellButtons` and
1689
+ * `DomLadder`, so it composites into screenshots and costs no DOM per pane.
1690
+ * Buttons hit-test as `${id}::close`, `${id}::hide`, and `${id}::settings`, so
1691
+ * the host routes them through the same `subscribeClick` path as order pills.
1642
1692
  *
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.
1693
+ * Rows stack: several legends on one pane offset each other vertically, which
1694
+ * the host does by giving each a `row` index.
1695
+ *
1696
+ * This row is also the chart's status line, so `statusLine` carries the
1697
+ * per-field switches a settings dialog expects (logo, title, market status,
1698
+ * chart values, bar change, volume, last day change, background). Every switch
1699
+ * defaults to the behaviour that predates it, so a caller that passes none sees
1700
+ * the row it always saw. Fields the primitive cannot compute (a logo bitmap,
1701
+ * whether the market is open, the change since yesterday's close) arrive
1702
+ * through `status`; with no source they draw nothing at all rather than a
1703
+ * placeholder.
1646
1704
  */
1647
1705
 
1648
- /** Which price a calculation reads from each bar. */
1649
- type IndicatorSource = 'open' | 'high' | 'low' | 'close' | 'hl2' | 'hlc3' | 'ohlc4' | 'volume';
1706
+ type PaneLegendAction = 'hide' | 'settings' | 'source' | 'up' | 'down' | 'maximize' | 'close';
1650
1707
  /**
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.
1708
+ * One reading on a legend row. Multi-plot sources show one per plot, each in
1709
+ * that plot's own color (an MA ribbon's four averages, MACD's three lines) —
1710
+ * a single string in a single color cannot say which number is which.
1659
1711
  */
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;
1712
+ interface LegendValue {
1713
+ /** Dimmed prefix, e.g. `O` / `H` / `Vol`. */
1714
+ label?: string;
1715
+ text: string;
1716
+ /** Defaults to the row's `valueColor`, then `color`, then the theme text. */
1717
+ color?: string;
1718
+ /**
1719
+ * Which status-line switch owns this reading. Untagged readings are the
1720
+ * source's own last value, governed by `statusLine.lastValueLabel`.
1721
+ */
1722
+ field?: LegendField;
1723
+ /** Higher values retain this whole reading on narrow rows. Series readings default to 1; status metadata ranks lower. */
1724
+ priority?: number;
1709
1725
  }
1710
1726
  /**
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".
1716
- */
1717
- | {
1718
- key: string;
1719
- type: 'interval';
1720
- label: string;
1721
- default: string;
1722
- group?: string;
1723
- tooltip?: string;
1724
- }
1725
- /**
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.
1727
+ * Status-line groups a host can feed and switch off independently. The legend
1728
+ * never derives these: it tags what the host hands it, so one switch hides one
1729
+ * group and leaves the rest of the row alone.
1731
1730
  */
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
- };
1731
+ type LegendField = 'ohlc' | 'change' | 'volume' | 'openInterest';
1755
1732
  /**
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.
1733
+ * Which name the title shows. `description` and `ticker` come from `status`;
1734
+ * with neither supplied the title falls back to `title`, which always exists.
1763
1735
  */
1764
- declare function indicatorStyleInputs(descriptor: IndicatorDescriptor): IndicatorInput[];
1736
+ type LegendTitleMode = 'symbol' | 'description' | 'ticker';
1765
1737
  /**
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.
1738
+ * The parts of the status line the primitive has no way to know. The host
1739
+ * supplies what it has; anything missing is simply not drawn.
1770
1740
  */
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;
1741
+ interface LegendStatusData {
1742
+ /** Already-decoded logo (an `<img>`, an `ImageBitmap`, a canvas). */
1743
+ logo?: CanvasImageSource;
1744
+ /** Long name for `titleMode: 'description'`, e.g. `Apple Inc.`. */
1745
+ description?: string;
1746
+ /** Exchange ticker for `titleMode: 'ticker'`, e.g. `NASDAQ:AAPL`. */
1747
+ ticker?: string;
1748
+ /** Session state, e.g. `{ text: 'Market open', color: '#26a69a' }`. */
1749
+ marketStatus?: LegendValue;
1750
+ /** Change against the previous close, e.g. `{ text: '+1.20 (+0.75%)' }`. */
1751
+ lastDayChange?: LegendValue;
1803
1752
  }
1804
1753
  /**
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.
1754
+ * A snapshot, or a getter the legend calls each frame, so live fields (market
1755
+ * status, day change) can change without the host patching options at tick
1756
+ * speed. Returning `null` means "nothing to show".
1809
1757
  */
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;
1758
+ type LegendStatusSource = LegendStatusData | (() => LegendStatusData | null);
1759
+ /**
1760
+ * Per-field switches for the status line. Existing reading fields default to
1761
+ * on; open interest and the background plate default to off. A missing reading
1762
+ * draws nothing whether its switch is on or off.
1763
+ */
1764
+ interface LegendStatusLineOptions {
1765
+ /** Symbol logo, when `status` supplies one. */
1766
+ logo?: boolean;
1767
+ /** The bold name. Also the "name label" switch for an indicator row. */
1768
+ title?: boolean;
1769
+ /** Which name the title shows. Default `symbol`. */
1770
+ titleMode?: LegendTitleMode;
1771
+ /** Session state from `status.marketStatus`. */
1772
+ marketStatus?: boolean;
1773
+ /** The OHLC readout: readings tagged `field: 'ohlc'`. */
1774
+ chartValues?: boolean;
1775
+ /** Change over the hovered bar: readings tagged `field: 'change'`. */
1776
+ barChange?: boolean;
1777
+ /** Readings tagged `field: 'volume'`. */
1778
+ volume?: boolean;
1779
+ /** Readings tagged `field: 'openInterest'`. Off by default. */
1780
+ openInterest?: boolean;
1781
+ /** Change since the previous close, from `status.lastDayChange`. */
1782
+ lastDayChange?: boolean;
1842
1783
  /**
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.
1784
+ * The source's own reading (untagged values). This is the scales-and-lines
1785
+ * "last value label" control, which lands here because the legend is what
1786
+ * draws that number on this row.
1850
1787
  */
1851
- overlay?: boolean;
1788
+ lastValueLabel?: boolean;
1852
1789
  /**
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.
1790
+ * Plate behind the row's text, for legibility over candles. Off by default:
1791
+ * the row has never had one, and turning it on is a deliberate choice.
1860
1792
  */
1861
- offset?: number;
1793
+ background?: boolean;
1794
+ /** Plate opacity, 0..1. Default 0.8, matching the hover plate. */
1795
+ backgroundOpacity?: number;
1796
+ /** Plate color. Defaults to the theme background. */
1797
+ backgroundColor?: string;
1798
+ }
1799
+ interface PaneLegendOptions {
1800
+ /** Explicit false suppresses OI without discarding its saved switch. Set by an owning chart. */
1801
+ hasOpenInterest?: boolean;
1802
+ /** Stable id; buttons hit-test as `${id}::close` etc. */
1803
+ id: string;
1804
+ /** Bold source name, e.g. `RSI`. */
1805
+ title: string;
1806
+ /** Dimmed parameter summary after the title, e.g. `14 close`. */
1807
+ params?: string;
1808
+ /** Swatch color; omitted draws no swatch. */
1809
+ color?: string;
1862
1810
  /**
1863
- * Settings key holding this plot's color, so a settings change restyles the
1864
- * series without a full rebuild.
1811
+ * Color for the live value. Defaults to `color`, then the theme's text — so a
1812
+ * row can tint its reading (an up/down change) without being forced to show a
1813
+ * swatch in that same color.
1865
1814
  */
1866
- colorKey?: string;
1815
+ valueColor?: string;
1816
+ /** Vertical slot on the pane (0 = topmost). */
1817
+ row?: number;
1867
1818
  /**
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.
1819
+ * Which inline action buttons to draw, left to right. Each hit-tests as
1820
+ * `${id}::<action>`:
1821
+ * - `up` / `down` — move this pane one slot (`::up` / `::down`)
1822
+ * - `hide` — toggle visibility (`::hide`)
1823
+ * - `source`: show the code this source was written from (`::source`)
1824
+ * - `maximize` — expand this pane to fill the chart (`::maximize`)
1825
+ * - `close` — remove the source, and its pane if it empties (`::close`)
1877
1826
  *
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.
1827
+ * When only some actions fit, the end of this list stays visible.
1828
+ * Defaults to `['up', 'down', 'hide', 'maximize', 'close']` for pane sources
1829
+ * and `['hide', 'close']` for overlays (pass explicitly to override).
1881
1830
  */
1882
- ohlc?: {
1883
- open: string;
1884
- high: string;
1885
- low: string;
1886
- close: string;
1887
- };
1831
+ actions?: readonly PaneLegendAction[];
1832
+ /** Rendered as hidden (dimmed, eye hollow). */
1833
+ hidden?: boolean;
1834
+ /** Rendered as maximized (the maximize glyph becomes restore). */
1835
+ maximized?: boolean;
1836
+ /** Text size in media px. Default 11. */
1837
+ font?: number;
1888
1838
  /**
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.
1839
+ * Square side of one action button in media px. Default 16.
1892
1840
  *
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.
1841
+ * The row grows to hold it, so raising this moves every legend row below it
1842
+ * down by the same amount and nothing overlaps. It is a chart-wide setting
1843
+ * for that reason: two legends on one pane with different button sizes would
1844
+ * stack against different row heights and collide.
1908
1845
  */
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;
1846
+ iconSize?: number;
1847
+ /** Left inset from the plot edge in media px. Default 8. */
1848
+ left?: number;
1849
+ /** Top inset in media px. Default 6. */
1850
+ top?: number;
1851
+ /** Per-field status-line switches. Patching merges field by field. */
1852
+ statusLine?: LegendStatusLineOptions;
1853
+ /** Host-supplied status-line data (logo, names, market state, day change). */
1854
+ status?: LegendStatusSource;
1925
1855
  }
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;
1856
+ declare class PaneLegend implements IPrimitive {
1857
+ private _opts;
1858
+ private _host;
1859
+ private _values;
1860
+ /** Button geometry from the last draw, in media px, for hit-testing. */
1861
+ private _buttons;
1862
+ /** Right edge of the drawn row, in media px. */
1863
+ private _width;
1864
+ private _plotWidth;
1865
+ private _plotHeight;
1866
+ constructor(opts: PaneLegendOptions);
1867
+ attached(host: PrimitiveHost): void;
1868
+ detached(): void;
1869
+ zOrder(): ZOrder;
1870
+ autoscaleInfo(): null;
1871
+ /** A single live reading after the params (typically crosshair-driven). */
1872
+ setValue(text: string, color?: string): void;
1873
+ /** One reading per plot, each in its own color. */
1874
+ setValues(values: readonly LegendValue[]): void;
1875
+ setOptions(patch: Partial<PaneLegendOptions>): void;
1876
+ /** Resolve the host's status data for this frame; `{}` when it has none. */
1877
+ private _status;
1878
+ options(): PaneLegendOptions;
1879
+ draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
1880
+ hitTest(x: number, y: number): PrimitiveHit | null;
1930
1881
  }
1882
+
1931
1883
  /**
1932
- * A free-standing shape an indicator paints in its own pane, anchored to time
1933
- * and price rather than to a bar index.
1884
+ * A grid overlay pinned to a corner of the pane rather than to bars.
1934
1885
  *
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.
1886
+ * Seasonality heatmaps, performance summaries and signal scoreboards are all
1887
+ * the same shape: a small table of coloured cells that stays put while the
1888
+ * chart pans underneath. That makes this a screen-space primitive like the
1889
+ * watermark and the pane legend, not a series: it has no time anchor, takes no
1890
+ * part in autoscale, and survives a zoom untouched.
1940
1891
  */
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. */
1892
+
1893
+ type TablePosition = 'top-left' | 'top-center' | 'top-right' | 'middle-left' | 'middle-center' | 'middle-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
1894
+ interface TableCell {
1979
1895
  text: string;
1980
- /** Plate fill. */
1981
- color?: string;
1896
+ /** Cell fill. Transparent when omitted, so the pane shows through. */
1897
+ bgColor?: string;
1898
+ /** Text colour. Derived from `bgColor` for contrast when omitted. */
1982
1899
  textColor?: string;
1983
- /** Which edge of the plate sits on the anchor. Defaults to 'center'. */
1984
1900
  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 {
1901
+ /** Overrides the table's `fontSize` for this cell, for a heading row. */
1902
+ fontSize?: number;
1903
+ bold?: boolean;
1904
+ }
1905
+ interface ChartTableOptions {
1906
+ position: TablePosition;
1907
+ /** Gap from the pane edge, media px. */
1908
+ margin: number;
1909
+ /** Column width in media px. A per-column array sizes each one separately. */
1910
+ cellWidth: number | readonly number[];
1911
+ cellHeight: number;
2013
1912
  /**
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.
1913
+ * Type size in media px, or `'auto'` to fit each cell: as large as its row
1914
+ * allows, shrunk until its text also fits its column. A stretched grid with
1915
+ * one long label would otherwise either clip that cell or be sized down as a
1916
+ * whole to suit it.
2016
1917
  */
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;
1918
+ fontSize: number | 'auto';
1919
+ /** Grid line colour. Omit to draw no grid. */
1920
+ borderColor?: string;
1921
+ borderWidth: number;
2035
1922
  /**
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".
1923
+ * Table width as a percentage of the plot, 0 or omitted to size from
1924
+ * `cellWidth` instead. Column proportions are preserved, so a per-column
1925
+ * `cellWidth` array still controls the relative widths, and the percentage
1926
+ * only decides the total.
2046
1927
  */
2047
- tickSize?: number;
1928
+ widthPercent?: number;
1929
+ /** Table height as a percentage of the plot, 0 or omitted to size from `cellHeight`. */
1930
+ heightPercent?: number;
1931
+ /**
1932
+ * Relative row heights, one per row, defaulting to 1. A separator row is the
1933
+ * reason this exists: stretched to fill a pane, an equal split makes a rule
1934
+ * between two sections as tall as the sections themselves.
1935
+ */
1936
+ rowWeights?: readonly number[];
1937
+ /** Backdrop behind the whole grid, drawn before the cells. */
1938
+ background?: string;
1939
+ /** Hit-test id, so a host can route clicks the way it does for other primitives. */
1940
+ id?: string;
2048
1941
  }
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;
1942
+ declare const DEFAULT_CHART_TABLE_OPTIONS: ChartTableOptions;
1943
+ /** Top-left corner of the grid for a position keyword, in media px. */
1944
+ declare function tableOrigin(position: TablePosition, margin: number, w: number, h: number, plotW: number, plotH: number): {
1945
+ x: number;
1946
+ y: number;
1947
+ };
1948
+ declare class ChartTable implements IPrimitive {
1949
+ private _rows;
1950
+ private _opts;
1951
+ private _host;
1952
+ /** Last drawn rect in media px, for hit-testing without recomputing layout. */
1953
+ private _rect;
1954
+ constructor(options?: Partial<ChartTableOptions>);
1955
+ attached(host: PrimitiveHost): void;
1956
+ detached(): void;
1957
+ zOrder(): ZOrder;
1958
+ options(): Readonly<ChartTableOptions>;
1959
+ setOptions(patch: Partial<ChartTableOptions>): void;
1960
+ /** Replace the grid. Rows may be ragged; each is drawn to its own length. */
1961
+ setRows(rows: readonly (readonly TableCell[])[]): void;
1962
+ rows(): readonly (readonly TableCell[])[];
1963
+ draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
1964
+ hitTest(x: number, y: number): PrimitiveHit | null;
2056
1965
  }
1966
+
2057
1967
  /**
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.
1968
+ * Vertical gradient across the band, graded between two prices rather than
1969
+ * two pixel rows.
2061
1970
  *
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.
1971
+ * The stops are anchored in PRICE, not in pixels, because that is what the
1972
+ * shading is claiming: a band graded from its 70 level down to its 30 means
1973
+ * those levels, and a viewport-relative gradient would slide off them the
1974
+ * moment the pane is panned or the scale re-fits.
2064
1975
  */
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;
2070
- /**
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.
1976
+ interface FillGradient {
1977
+ /** Price the top stop sits at. Omitted: the band's own highest value. */
1978
+ topValue?: number;
1979
+ /** Price the bottom stop sits at. Omitted: the band's own lowest value. */
1980
+ bottomValue?: number;
1981
+ topColor: string;
1982
+ bottomColor: string;
1983
+ }
1984
+ interface IndicatorFillOptions {
1985
+ /** Fill colour where the first series is above the second. */
1986
+ colorUp: string;
1987
+ /** Fill colour where the second is above the first. */
1988
+ colorDown: string;
1989
+ /** 0..1. Defaults to 0.12 — a band must not drown the candles it sits behind. */
1990
+ opacity?: number;
1991
+ /**
1992
+ * Set to grade the band instead of flat-filling it, in place of
1993
+ * `colorUp`/`colorDown`. A point carrying its own `color` still overrides it.
1994
+ * Unset leaves the two-colour fill exactly as it was.
2075
1995
  */
2076
- message?: string | ((ctx: IndicatorAlertContext) => string);
2077
- when(ctx: IndicatorAlertContext): boolean;
1996
+ gradient?: FillGradient;
2078
1997
  }
2079
- /**
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.
2083
- *
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.
2089
- */
2090
- declare class IndicatorInputError extends Error {
2091
- constructor(message: string);
1998
+ /** One bar's pair of values; `null` where either plot has no value yet. */
1999
+ interface FillPoint {
2000
+ /** Logical index on the shared time axis (fractional is fine). */
2001
+ index: number;
2002
+ a: number | null;
2003
+ b: number | null;
2004
+ /**
2005
+ * Paints this bar onward in one colour, for a band shaded by something other
2006
+ * than which line leads: trend state, a regime, a third series. The run is
2007
+ * split at the bar where the colour changes, so it is per-bar and not merely
2008
+ * per-crossing. Most specific wins: this, then the gradient, then up/down.
2009
+ */
2010
+ color?: string;
2011
+ }
2012
+ declare class IndicatorFill implements IPrimitive {
2013
+ private _points;
2014
+ private _opts;
2015
+ private _host;
2016
+ private _visible;
2017
+ constructor(options: IndicatorFillOptions);
2018
+ attached(host: PrimitiveHost): void;
2019
+ detached(): void;
2020
+ /** Behind the series, so the lines and candles stay crisp on top of it. */
2021
+ zOrder(): ZOrder;
2022
+ /** The plots it spans already drive the scale; the fill must not widen it. */
2023
+ autoscaleInfo(): null;
2024
+ setPoints(points: readonly FillPoint[]): void;
2025
+ setOptions(patch: Partial<IndicatorFillOptions>): void;
2026
+ setVisible(on: boolean): void;
2027
+ draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
2092
2028
  }
2029
+
2093
2030
  /**
2094
- * Bars of another instrument or interval, supplied by the host on request.
2031
+ * Indicator runtime (ARCHITECTURE.md §8). Turns an `IndicatorDescriptor` into
2032
+ * live chart objects: one series per plot, optional reference levels, an
2033
+ * optional fixed pane range — and recomputes them when the source data or the
2034
+ * settings change.
2095
2035
  *
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.
2036
+ * It adds **no rendering code**. Every plot names a registered chart type, so
2037
+ * indicators draw through the same Family-A renderers as any other series.
2102
2038
  */
2103
- interface IndicatorBarsRequest {
2104
- symbol: string;
2105
- exchange?: string;
2106
- interval: string;
2107
- from: number;
2108
- to: number;
2109
- signal?: AbortSignal;
2110
- }
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;
2123
- index: number;
2124
- }
2125
- /** Optional instrument identity supplied by the host. */
2126
- interface ChartDataContext {
2127
- symbol?: string;
2128
- exchange?: string;
2129
- interval?: string;
2130
- }
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. */
2039
+
2040
+ /** The slice of the chart the runtime needs. Keeps this module testable alone. */
2041
+ interface IndicatorHost {
2042
+ /** Forget a disposed instance, including disposal through its public handle. */
2043
+ indicatorRemoved?(instanceId: string): void;
2044
+ /** Optional instrument identity and source-range notifications. */
2143
2045
  dataContext?(): Readonly<ChartDataContext> | undefined;
2144
- /** Read bars and identity again when the host publishes a change. */
2145
2046
  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;
2047
+ /** Add the pane-legend row (name + inline up/down/hide/maximize/close). */
2048
+ addIndicatorLegend(opts: {
2049
+ id: string;
2050
+ title: string;
2051
+ params: string;
2052
+ color?: string;
2053
+ row: number;
2054
+ paneIndex: number;
2055
+ /** The descriptor's `hasSource`, so the row can offer a source button. */
2056
+ hasSource?: boolean;
2057
+ }): PaneLegend;
2058
+ removeIndicatorLegend(legend: PaneLegend): void;
2059
+ /** How many legends already sit on this pane, so rows stack. */
2060
+ legendRowsOn(paneIndex: number): number;
2061
+ /** The instrument's own series, for a descriptor anchoring marks to price. */
2062
+ primarySeries?(): SeriesApi | null;
2063
+ addIndicatorSeries(type: string, paneIndex: number, style: Record<string, unknown> | undefined, priceScaleId: string | undefined,
2064
+ /** Axis/crosshair formatting for the scale this plot maps to. */
2065
+ priceFormat?: PriceFormat): SeriesApi;
2159
2066
  /**
2160
- * The instrument the chart is showing, when the host knows it.
2067
+ * Add a reference level. One options object rather than seven positional
2068
+ * arguments: the list grew a width and a dash style in 1.7.1, and a call site
2069
+ * of seven bare values is where the next one gets passed in the wrong slot.
2070
+ */
2071
+ addIndicatorLevel(level: {
2072
+ price: number;
2073
+ color: string;
2074
+ /** Kept for hosts predating `lineStyle`; always `lineStyle === 'dashed'`. */
2075
+ dashed: boolean;
2076
+ lineWidth: number;
2077
+ lineStyle: IndicatorLineStyle;
2078
+ label: string;
2079
+ id: string;
2080
+ }, paneIndex: number): PriceLine;
2081
+ removeIndicatorLevel(line: PriceLine): void;
2082
+ /** Attach a band drawn behind the plots (an Ichimoku cloud). */
2083
+ addIndicatorFill(fill: IndicatorFill, paneIndex: number): void;
2084
+ removeIndicatorFill(fill: IndicatorFill): void;
2085
+ /**
2086
+ * Detach a signal-marker layer. There is no matching `add`: the layer comes
2087
+ * from `series.createMarkers()` on a plot's own series, so it already lands in
2088
+ * the right pane. Removing a series does not remove its primitives, hence this.
2089
+ */
2090
+ removeIndicatorMarkers(markers: SeriesMarkers): void;
2091
+ /** Attach a corner-pinned summary grid to a pane, and detach it again. */
2092
+ addIndicatorTable(paneIndex: number): ChartTable;
2093
+ removeIndicatorTable(table: ChartTable): void;
2094
+ /**
2095
+ * Attach an arbitrary primitive to a pane, and detach it again. Carries both
2096
+ * the descriptor's drawing layer and whatever a Tier-2 `attach` lifecycle
2097
+ * wants to paint, so those two do not need a host method each.
2161
2098
  *
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.
2099
+ * Optional, like `timezone`, so a host predating it still satisfies this
2100
+ * interface: an indicator that draws simply draws nothing there.
2101
+ */
2102
+ addIndicatorPrimitive?(primitive: IPrimitive, paneIndex: number): void;
2103
+ removeIndicatorPrimitive?(primitive: IPrimitive): void;
2104
+ /**
2105
+ * Recompute whatever the host has marked stale, before a caller reads a value.
2106
+ *
2107
+ * Optional, like `timezone`, so a host predating it still satisfies this
2108
+ * interface: one that recomputes eagerly has nothing to flush.
2109
+ */
2110
+ flushIndicators?(): void;
2111
+ /** Bars of the primary price series — the calculation input. */
2112
+ sourceBars(): readonly Bar[];
2113
+ /** Selected candle after native or linked hover; absent means latest. */
2114
+ legendIndex?(): number | undefined;
2115
+ /** Index of a fresh pane for an indicator that wants its own. */
2116
+ nextPaneIndex(): number;
2117
+ /**
2118
+ * The chart's configured IANA zone. Optional so a host predating the option
2119
+ * still satisfies this interface; absent means the shipped default.
2120
+ *
2121
+ * A descriptor is handed bars and settings and never the chart, so this is
2122
+ * how the calendar an anchor resets on (a VWAP session, a seasonality month)
2123
+ * reaches the calculation. See `IndicatorInstance._descriptorSettings`.
2124
+ */
2125
+ timezone?(): string;
2126
+ /**
2127
+ * The instrument and timeframe on screen, when the host knows them. The
2128
+ * host can supply an explicit `dataContext` instead. Without either hook,
2129
+ * a descriptor sees `undefined` rather than a guessed identity.
2165
2130
  */
2166
2131
  symbol?(): string | undefined;
2167
- /** The chart's timeframe (`'5m'`, `'1d'`), on the same terms as `symbol`. */
2168
2132
  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. */
2133
+ /** Chart wall clock in UTC seconds. Absent means the system clock. */
2172
2134
  now?(): number;
2173
2135
  /**
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`.
2136
+ * Publish an indicator's per-bar colours onto the **primary price series**,
2137
+ * or withdraw them with `null`. `owner` is the instance id: a host holds one
2138
+ * overlay at a time and only lets its current owner withdraw it, so a second
2139
+ * publisher taking over does not get cleared by the first one's teardown.
2140
+ *
2141
+ * Optional, like `timezone`: a host that does not implement it simply gives a
2142
+ * `barColors` descriptor nowhere to publish, and the indicator's own plots are
2143
+ * unaffected.
2144
+ */
2145
+ setBarColors?(colors: readonly (string | null)[] | null, owner: string): void;
2146
+ /** Emit on the chart's event bus (indicator alerts, and `attach`'s own events). */
2147
+ emit?(event: string, payload: unknown): void;
2148
+ /**
2149
+ * Bars of another instrument or interval, from wherever the host keeps its
2150
+ * history. Optional: without it the attach context's `requestBars` rejects,
2151
+ * which a study reads as "not available on this chart".
2178
2152
  */
2179
2153
  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
2154
  /**
2186
- * Emit on the chart's own event bus, the one `chart.on(name, cb)` listens to.
2155
+ * Tick size of the named pane's price scale, or undefined when none is set.
2156
+ * Optional so a host predating it still satisfies this interface.
2187
2157
  *
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).
2191
- */
2192
- emit?(event: string, payload: unknown): void;
2158
+ * Per pane, and the panes genuinely differ: a pane that does not quote the
2159
+ * instrument has no tick to report. Pane 0 is the price pane, so it is the
2160
+ * one to ask for the instrument's own step.
2161
+ */
2162
+ tickSize?(paneIndex: number): number | undefined;
2163
+ /**
2164
+ * Write a number the way the price axis of that pane writes it.
2165
+ *
2166
+ * The legend sits inches from the axis and names the same quantity, so the
2167
+ * two disagreeing is the reading a user has to reconcile themselves. Deriving
2168
+ * the format here from a tick got that wrong twice over: a study pane carries
2169
+ * no tick at all, so a percentage read `0.618` beside an axis saying `0.62`,
2170
+ * and a price pane's tick alone misses the precision floor and the host's own
2171
+ * formatter, so a volume study read seven digits where its axis said `1.20M`.
2172
+ *
2173
+ * Asking the scale removes the second opinion. Optional so a host driving
2174
+ * this module alone still works, falling back to the magnitude ladder.
2175
+ */
2176
+ formatPrice?(paneIndex: number, value: number): string | undefined;
2177
+ /** Pin a pane's price scale to a fixed range, or release it with `null`. */
2178
+ setPaneRange(paneIndex: number, range: {
2179
+ min: number;
2180
+ max: number;
2181
+ } | null): void;
2193
2182
  }
2183
+ /** Public handle returned by `chart.addIndicator(...)`. */
2184
+ interface IndicatorApi {
2185
+ /** External data state, or null for a study without a managed lifecycle. */
2186
+ dataStatus(): Readonly<IndicatorDataStatus> | null;
2187
+ /** Observe changes; immediately receives the current managed status, if any. */
2188
+ subscribeDataStatus(listener: (status: Readonly<IndicatorDataStatus>) => void): () => void;
2189
+ /** Retry external history when the descriptor supplies a retry action. */
2190
+ retryData(): void;
2191
+ /** Unique instance id (several instances of one indicator can coexist). */
2192
+ readonly id: string;
2193
+ /** The descriptor id, e.g. `'macd'`. */
2194
+ readonly indicatorId: string;
2195
+ /** Display name. */
2196
+ readonly name: string;
2197
+ /** Pane the indicator drew into. */
2198
+ readonly paneIndex: number;
2199
+ /** Current settings (a copy). */
2200
+ settings(): IndicatorSettings;
2201
+ /** Merge a settings patch, recompute, and restyle. */
2202
+ setSettings(patch: Readonly<IndicatorSettings>): void;
2203
+ /** The series backing one plot key, for direct styling. */
2204
+ series(plotKey: string): SeriesApi | undefined;
2205
+ /** Latest computed values (a reference — do not mutate). */
2206
+ values(): IndicatorValues;
2207
+ /** Whether the plots are drawn (the legend's eye toggle). */
2208
+ visible(): boolean;
2209
+ /** Show or hide every plot without removing the instance. */
2210
+ setVisible(on: boolean): void;
2211
+ /** This indicator's legend row, or null if it has none. */
2212
+ legend(): PaneLegend | null;
2213
+ /** Refresh the legend readings for a bar index; omit for the latest bar. */
2214
+ updateLegendValues(index?: number): void;
2215
+ /** Remove every series, level, and legend row this indicator created. */
2216
+ remove(): void;
2217
+ }
2218
+
2219
+ /** A primary-source mutation, emitted after indicator invalidation. */
2220
+ interface ChartDataUpdate {
2221
+ kind: 'update' | 'reset' | 'prepend';
2222
+ time?: number;
2223
+ }
2224
+ /** Minimum headless chart surface needed to evaluate alerts. */
2225
+ interface AlertChartHost {
2226
+ primaryBars(): readonly Bar[];
2227
+ getDataContext(): Readonly<ChartDataContext> | undefined;
2228
+ on(event: string, callback: (payload: unknown) => void): () => void;
2229
+ emit(event: string, payload: unknown): void;
2230
+ /** Flushes computed study values. Only required by indicator-source alerts. */
2231
+ indicators?(): readonly (Pick<IndicatorApi, 'id' | 'paneIndex' | 'series' | 'values'> & Partial<Pick<IndicatorApi, 'indicatorId'>>)[];
2232
+ addPrimitive?(primitive: IPrimitive, paneIndex?: number): void;
2233
+ removePrimitive?(primitive: IPrimitive): void;
2234
+ alertState?(): AlertsDocument | undefined;
2235
+ setAlertState?(document: AlertsDocument | undefined): void;
2236
+ }
2237
+ type AlertCondition = 'crossing' | 'crossingUp' | 'crossingDown' | 'greaterThan' | 'lessThan' | 'enteringRange' | 'leavingRange' | 'matches';
2238
+ type AlertPolicy = 'onBarClose' | 'onTouch';
2239
+ type AlertRepeat = 'once' | 'everyTime';
2240
+ type AlertState = 'armed' | 'triggered' | 'expired' | 'disabled';
2241
+ /** Prices are in primary-series units. Range conditions require upperPrice. */
2242
+ interface PriceAlertSource {
2243
+ kind: 'price';
2244
+ price: number;
2245
+ upperPrice?: number;
2246
+ }
2247
+ /** A threshold in plot units, anchored to one specific study instance. */
2248
+ interface IndicatorAlertSource {
2249
+ kind: 'indicator';
2250
+ instanceId: string;
2251
+ plotKey: string;
2252
+ value: number;
2253
+ upperValue?: number;
2254
+ }
2255
+ interface BarConditionAlertSource {
2256
+ kind: 'barCondition';
2257
+ id: string;
2258
+ }
2259
+ interface DrawingAlertSource {
2260
+ kind: 'drawing';
2261
+ drawingId: string;
2262
+ level?: string;
2263
+ /** Required for a drawing on a study pane, so prices are not compared to different units. */
2264
+ input?: {
2265
+ instanceId: string;
2266
+ plotKey: string;
2267
+ };
2268
+ }
2269
+ type AlertSource = PriceAlertSource | IndicatorAlertSource | BarConditionAlertSource | DrawingAlertSource;
2270
+ /** Missing values, an absent anchor or a paused/context-mismatched chart are unavailable. */
2271
+ interface AlertAvailability {
2272
+ available: boolean;
2273
+ reason?: string;
2274
+ paneIndex?: number;
2275
+ }
2276
+ interface AlertDrawingValue {
2277
+ price: number;
2278
+ upperPrice?: number;
2279
+ paneIndex: number;
2280
+ }
2281
+ interface AlertDrawingLevel {
2282
+ id: string;
2283
+ title: string;
2284
+ }
2285
+ interface AlertDrawingInfo extends AlertAvailability {
2286
+ levels: readonly AlertDrawingLevel[];
2287
+ }
2288
+ /** Implemented by the optional drawing tier; the alert engine never imports it. */
2289
+ interface AlertDrawingProvider {
2290
+ get(id: string): unknown;
2291
+ valueAt(id: string, time: number, level?: string): AlertDrawingValue | undefined;
2292
+ alertInfo(id: string): AlertDrawingInfo;
2293
+ }
2294
+ interface BarConditionContext {
2295
+ /** Only the prefix through index is exposed, including for confirmed-bar checks. */
2296
+ bars: readonly Bar[];
2297
+ index: number;
2298
+ }
2299
+ interface BarCondition {
2300
+ id: string;
2301
+ title: string;
2302
+ /** Runs at the alert's chosen policy, never for loaded history. */
2303
+ when(context: BarConditionContext): boolean;
2304
+ }
2305
+ /** An alert belongs to the instrument and interval present when it was armed. */
2306
+ interface AlertScope {
2307
+ symbol?: string;
2308
+ exchange?: string;
2309
+ interval?: string;
2310
+ }
2311
+ interface AlertInput {
2312
+ id?: string;
2313
+ source: AlertSource;
2314
+ condition?: AlertCondition;
2315
+ /** Defaults to onBarClose. An intrabar touch may disappear from final history. */
2316
+ policy?: AlertPolicy;
2317
+ repeat?: AlertRepeat;
2318
+ state?: 'armed' | 'disabled';
2319
+ title?: string;
2320
+ message?: string;
2321
+ cooldownSeconds?: number;
2322
+ /** UTC seconds. At this instant the alert expires before it can trigger. */
2323
+ expiresAt?: number;
2324
+ /** Opaque host routing data. The controller never interprets or delivers it. */
2325
+ payload?: unknown;
2326
+ }
2327
+ type AlertPatch = Partial<Omit<AlertInput, 'id'>>;
2328
+ /** Lifecycle record. Snapshots detach mutable configuration, retaining opaque payloads. */
2329
+ interface Alert extends Omit<AlertInput, 'id' | 'condition' | 'policy' | 'repeat' | 'state' | 'title' | 'cooldownSeconds'> {
2330
+ id: string;
2331
+ condition: AlertCondition;
2332
+ policy: AlertPolicy;
2333
+ repeat: AlertRepeat;
2334
+ state: AlertState;
2335
+ title: string;
2336
+ cooldownSeconds: number;
2337
+ scope: AlertScope;
2338
+ lastTriggeredAt?: number;
2339
+ lastTriggeredTime?: number;
2340
+ /** Newest confirmed bar already judged, including a nonmatch. */
2341
+ lastClosedTime?: number;
2342
+ /** Newest intrabar match consumed, including one suppressed by cooldown. */
2343
+ lastTouchedTime?: number;
2344
+ }
2345
+ /** Portable trader records. JSON persistence rejects unsupported host payloads. */
2346
+ interface AlertsDocument {
2347
+ version: 1;
2348
+ alerts: Alert[];
2349
+ }
2350
+ /** Shared delivery fields for trader and indicator-authored alerts. */
2351
+ interface AlertEventPayload {
2352
+ alertId: string;
2353
+ title: string;
2354
+ message?: string;
2355
+ /** Source bar UTC seconds and source index, not delivery wall-clock time. */
2356
+ time: number;
2357
+ index: number;
2358
+ }
2359
+ interface AlertTriggeredPayload extends AlertEventPayload {
2360
+ price: number;
2361
+ alert: Alert;
2362
+ }
2363
+ interface AlertControllerOptions {
2364
+ /** Delivery and expiry clock in UTC seconds. Defaults to Date.now() / 1000. */
2365
+ now?: () => number;
2366
+ drawings?: AlertDrawingProvider;
2367
+ /** PriceLine visuals are enabled on chart hosts; disable for a model-only consumer. */
2368
+ visuals?: boolean;
2369
+ }
2370
+
2194
2371
  /**
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.
2372
+ * Indicator registry (ARCHITECTURE.md §6A, §8). The sibling of the chart-type
2373
+ * registry: that one answers *"how do I paint an array of bars"*, this one
2374
+ * answers *"what do I compute, what does it plot, and what can a user tune"*.
2202
2375
  *
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 ?? []`).
2376
+ * A descriptor is data, not code-in-the-core — the chart never switches on an
2377
+ * indicator id. Each `plot` names a registered **chart type**, so indicators
2378
+ * ride the existing Family-A renderers and add no drawing code at all.
2208
2379
  *
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.
2380
+ * The built-in descriptors live in the lazy `openalgo-charts/indicators` tier;
2381
+ * only the registry and the runtime ship in the base bundle, so an app that
2382
+ * plots its own maths pays nothing for the catalog.
2212
2383
  */
2213
- type IndicatorLevelContext = IndicatorSettings & {
2214
- settings?: Readonly<IndicatorSettings>;
2215
- bars?: readonly Bar[];
2216
- values?: IndicatorValues;
2384
+
2385
+ /** Which price a calculation reads from each bar. */
2386
+ type IndicatorSource = 'open' | 'high' | 'low' | 'close' | 'hl2' | 'hlc3' | 'ohlc4' | 'volume';
2387
+ /**
2388
+ * One tunable input. `type` is what a settings UI renders; the core only reads
2389
+ * `key`/`default`.
2390
+ *
2391
+ * `tooltip` is help text for the row. A label has to stay short enough to fit a
2392
+ * dense panel, which leaves nowhere to say what a parameter actually does, and a
2393
+ * ported study whose every input carried an explanation arrives here with that
2394
+ * explanation dropped. A settings UI renders it as a hover affordance beside the
2395
+ * label; the core ignores it.
2396
+ */
2397
+ type IndicatorInput = {
2398
+ key: string;
2399
+ type: 'number';
2400
+ label: string;
2401
+ default: number;
2402
+ min?: number;
2403
+ max?: number;
2404
+ step?: number;
2405
+ group?: string;
2406
+ tooltip?: string;
2407
+ } | {
2408
+ key: string;
2409
+ type: 'boolean';
2410
+ label: string;
2411
+ default: boolean;
2412
+ group?: string;
2413
+ tooltip?: string;
2414
+ } | {
2415
+ key: string;
2416
+ type: 'color';
2417
+ label: string;
2418
+ default: string;
2419
+ group?: string;
2420
+ tooltip?: string;
2421
+ } | {
2422
+ key: string;
2423
+ type: 'text';
2424
+ label: string;
2425
+ default: string;
2426
+ group?: string;
2427
+ tooltip?: string;
2428
+ } | {
2429
+ key: string;
2430
+ type: 'select';
2431
+ label: string;
2432
+ default: string;
2433
+ options: readonly {
2434
+ label: string;
2435
+ value: string;
2436
+ }[];
2437
+ group?: string;
2438
+ tooltip?: string;
2439
+ } | {
2440
+ key: string;
2441
+ type: 'source';
2442
+ label: string;
2443
+ default: IndicatorSource;
2444
+ group?: string;
2445
+ tooltip?: string;
2446
+ }
2447
+ /**
2448
+ * A timeframe code (`'5m'`, `'1d'`), for a study that folds the chart's bars
2449
+ * up to a coarser interval. A settings UI renders it as a select over the
2450
+ * registered intervals, so the value is always one the engine can bucket by;
2451
+ * a free text box would accept `'5min'` and leave the study computing on a
2452
+ * code it cannot resolve. An empty default means "the chart's own interval".
2453
+ */
2454
+ | {
2455
+ key: string;
2456
+ type: 'interval';
2457
+ label: string;
2458
+ default: string;
2459
+ group?: string;
2460
+ tooltip?: string;
2461
+ }
2462
+ /**
2463
+ * A wall-clock instant in the chart's zone, written `YYYY-MM-DD HH:MM` (the
2464
+ * time part optional), for an anchor a user picks by date: the start of an
2465
+ * anchored VWAP, an event to measure from. It is carried as that string, not
2466
+ * as UTC seconds, so a layout saved in one zone restores to the same wall
2467
+ * clock in another, and `zonedStringToUtcSeconds` turns it into a bar time.
2468
+ */
2469
+ | {
2470
+ key: string;
2471
+ type: 'time';
2472
+ label: string;
2473
+ default: string;
2474
+ group?: string;
2475
+ tooltip?: string;
2217
2476
  };
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;
2477
+ /** Dash pattern for a level, a drawing, or a plot. */
2478
+ type IndicatorLineStyle = 'solid' | 'dashed' | 'dotted';
2479
+ /** Line-style options, for a settings UI's Style tab. */
2480
+ declare const INDICATOR_LINE_STYLES: readonly {
2481
+ label: string;
2482
+ value: string;
2483
+ }[];
2484
+ /** Settings keys the runtime derives for a plot's appearance. */
2485
+ declare function plotStyleKeys(plot: IndicatorPlot): {
2486
+ color: string;
2487
+ width: string;
2488
+ lineStyle: string;
2489
+ opacity: string;
2490
+ type: string;
2491
+ };
2492
+ /**
2493
+ * Per-plot appearance inputs, generated from the descriptor rather than
2494
+ * hand-written on each one — every indicator gets colour, opacity, thickness,
2495
+ * and line style for free, and a settings UI can render them as a "Style" tab
2496
+ * beside the descriptor's own `inputs`.
2497
+ *
2498
+ * Defaults come from the plot's declared style (and its legacy `colorKey`), so
2499
+ * an indicator that already ships colours keeps them.
2500
+ */
2501
+ declare function indicatorStyleInputs(descriptor: IndicatorDescriptor): IndicatorInput[];
2502
+ /**
2503
+ * Chart types a plot can be re-rendered as. A moving average is a line by
2504
+ * default, but the same column of numbers reads better as a histogram or an
2505
+ * area depending on what you are looking for — and a descriptor cannot know
2506
+ * which. Restricted to the types that make sense for a single value column.
2507
+ */
2508
+ declare const INDICATOR_PLOT_STYLES: readonly {
2509
+ label: string;
2510
+ value: string;
2511
+ }[];
2512
+ /** Canonical option list for a `type: 'source'` input, for settings UIs. */
2513
+ declare const INDICATOR_SOURCES: readonly {
2514
+ label: string;
2515
+ value: IndicatorSource;
2516
+ }[];
2517
+ type IndicatorSettings = Record<string, unknown>;
2518
+ /** One plotted line/band/histogram. `type` is any registered chart type. */
2519
+ /** A shaded band between two of an indicator's plots. */
2520
+ interface IndicatorFillSpec {
2521
+ /** The two plot keys to fill between. */
2522
+ between: readonly [string, string];
2523
+ /** Colour where the first plot is above the second. */
2524
+ colorUp?: string;
2525
+ /** Colour where the second is above the first. */
2526
+ colorDown?: string;
2527
+ /** Settings keys holding those colours, so the band is restyleable. */
2528
+ colorUpKey?: string;
2529
+ colorDownKey?: string;
2530
+ /** 0..1. Defaults to 0.12. */
2531
+ opacity?: number;
2246
2532
  /**
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.
2533
+ * Draw the band on the price pane even though the indicator owns a pane of
2534
+ * its own. The pair with `IndicatorPlot.overlay`: a study can already send
2535
+ * one plot to the candles, and a band between two such plots belongs beside
2536
+ * them rather than in the study pane the fill would otherwise land in.
2537
+ * Ignored for an `'onchart'` descriptor, which is on the price pane already.
2253
2538
  */
2254
- attach?(ctx: IndicatorAttachContext): (() => void) | void;
2539
+ overlay?: boolean;
2540
+ }
2541
+ /**
2542
+ * What `colorParts` answers: a candle plot's colour split three ways, which is
2543
+ * how a study paints a wick in full colour over a translucent body. `body` is
2544
+ * the bar's colour (and the only part a line, histogram or column reads); a
2545
+ * part left undefined falls back to `colorBy`, then to the plot's own colour.
2546
+ */
2547
+ type PlotBarColor = {
2548
+ body?: string;
2549
+ wick?: string;
2550
+ border?: string;
2551
+ };
2552
+ interface IndicatorPlot {
2553
+ /** Key into the `calc` result. */
2554
+ key: string;
2555
+ /** Registered chart type used to draw it ('line', 'histogram', 'area', ...). */
2556
+ type: SeriesType;
2557
+ /** Legend title. */
2558
+ title: string;
2559
+ /** Style overrides merged onto the chart type's defaults. */
2560
+ style?: SeriesStyle;
2561
+ /** Price axis for this plot. Defaults to 'right'. */
2562
+ priceScaleId?: PriceScaleId;
2255
2563
  /**
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`.
2564
+ * Value formatting for the axis and crosshair tag of the scale this plot maps
2565
+ * to: `percent` for a ratio study, `volume` for a cumulative one, `custom` for
2566
+ * anything else.
2260
2567
  *
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.
2568
+ * Like `style.precision`, this is a property of the **price scale**, not of the
2569
+ * series, so it belongs to a plot that owns its pane. Setting it on an
2570
+ * `'onchart'` plot reformats the instrument's own axis, which is almost never
2571
+ * what a study wants.
2572
+ *
2573
+ * `percent` suffixes the value as it stands and does not scale it, so a study
2574
+ * returning a 0..1 fraction should keep returning it and read `0.62%`. Scaling
2575
+ * inside `calc` to make the axis read better changes the plotted value, and the
2576
+ * legend, the crosshair and every downstream calculation with it.
2264
2577
  */
2265
- calcTail?(bars: readonly Bar[], settings: Readonly<IndicatorSettings>, fromIndex: number, previous: IndicatorValues, store: IndicatorStore, ctx?: IndicatorCalcContext): IndicatorValues | null;
2578
+ priceFormat?: PriceFormat;
2266
2579
  /**
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.
2580
+ * Draw this one plot on the price pane even though the indicator owns a pane
2581
+ * of its own. An oscillator that also wants a signal
2582
+ * band or a stop line sitting on the candles is the case: the study belongs in
2583
+ * its own pane, one of its columns belongs on price, and splitting it into two
2584
+ * indicators would make the user configure the same inputs twice.
2270
2585
  *
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.
2586
+ * Ignored for an `'onchart'` descriptor, which is already on the price pane.
2274
2587
  */
2275
- markers?(ctx: {
2276
- bars: readonly Bar[];
2277
- values: IndicatorValues;
2278
- settings: Readonly<IndicatorSettings>;
2279
- }): readonly SeriesMarker[];
2588
+ overlay?: boolean;
2280
2589
  /**
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.
2590
+ * Draw the column shifted this many bars to the right (negative: left). The
2591
+ * column itself stays one value per bar and `calc` returns exactly what it
2592
+ * always did; only where each value is painted moves. Positive is what a
2593
+ * displaced cloud or a projected channel wants: the last `offset` values land
2594
+ * in the right margin, past the newest candle, where no bar exists to hold
2595
+ * them. It shifts the drawn series only. A fill between two plots with the
2596
+ * same offset follows; the legend reads the value drawn under the cursor.
2290
2597
  */
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;
2598
+ offset?: number;
2299
2599
  /**
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.
2600
+ * Settings key holding this plot's color, so a settings change restyles the
2601
+ * series without a full rebuild.
2304
2602
  */
2305
- draws?(ctx: {
2306
- bars: readonly Bar[];
2307
- values: IndicatorValues;
2308
- settings: Readonly<IndicatorSettings>;
2309
- }): readonly IndicatorDrawing[];
2603
+ colorKey?: string;
2310
2604
  /**
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.
2605
+ * Four `calc` keys to draw this plot as bar-shaped elements instead of one
2606
+ * value per bar: candles, hollow candles, OHLC bars, high-low.
2313
2607
  *
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.
2608
+ * A single column cannot express those at all, and the alternative (a second
2609
+ * result shape for `calc`) would fork the contract every descriptor and every
2610
+ * helper is written against. Naming four columns inside the *same*
2611
+ * `IndicatorValues` keeps one shape: a smoothed Heikin-Ashi overlay, a
2612
+ * higher-timeframe candle, a synthetic spread instrument each return four
2613
+ * ordinary columns and point at them from here.
2318
2614
  *
2319
- * Runs after every `calc`. Return `[]` to clear the layer.
2615
+ * The named columns must all exist and be bar-aligned, or `addIndicator`
2616
+ * throws. `key` stays the series identity and the legend reading falls back to
2617
+ * the `close` column.
2320
2618
  */
2321
- background?(ctx: {
2322
- bars: readonly Bar[];
2323
- values: IndicatorValues;
2324
- settings: Readonly<IndicatorSettings>;
2325
- }): readonly (string | null)[];
2619
+ ohlc?: {
2620
+ open: string;
2621
+ high: string;
2622
+ low: string;
2623
+ close: string;
2624
+ };
2326
2625
  /**
2327
- * Optional recolouring of the **main price candles**, one entry per bar,
2328
- * `null` to leave that bar with its own colour.
2329
- *
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.
2626
+ * Per-bar colour, for plots whose meaning changes bar to bar — a MACD
2627
+ * histogram is four colours by sign and direction, a conditional study two.
2628
+ * Return `undefined` to fall back to the plot's own colour.
2334
2629
  *
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.
2630
+ * Reaches the renderer as `Bar.color`, so every Family-A plot type honours
2631
+ * it: histogram, column, candles, OHLC bars, line, step and area.
2339
2632
  */
2340
- barColors?(ctx: {
2341
- bars: readonly Bar[];
2633
+ colorBy?(ctx: {
2634
+ value: number;
2635
+ index: number;
2342
2636
  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.
2362
- */
2363
- range?(settings: Readonly<IndicatorSettings>): {
2364
- min: number;
2365
- max: number;
2366
- } | null;
2367
- }
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
-
2390
- interface PriceLineOptions {
2391
- 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;
2637
+ settings: IndicatorSettings;
2638
+ }): string | undefined;
2411
2639
  /**
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.
2640
+ * Per-bar colour split three ways, for a candle plot whose wick or border
2641
+ * should not follow its body: a solid wick over a translucent body, a
2642
+ * border in the trend colour. Takes precedence over `colorBy` for the parts
2643
+ * it names; a part it leaves undefined falls back to `colorBy`, then to the
2644
+ * plot's own colour. A value plot (line, histogram, column) reads `body` only.
2415
2645
  */
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). */
2420
- id: string;
2421
- /** Cursor hint when hovered (e.g. 'ns-resize' for draggable lines). */
2422
- cursor?: string;
2646
+ colorParts?(ctx: {
2647
+ value: number;
2648
+ index: number;
2649
+ values: IndicatorValues;
2650
+ settings: IndicatorSettings;
2651
+ }): PlotBarColor | undefined;
2423
2652
  }
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;
2653
+ /** A horizontal reference level (RSI 70/30, Stochastic 80/20, a zero line). */
2654
+ interface IndicatorLevel {
2655
+ price: number;
2656
+ color?: string;
2657
+ title?: string;
2658
+ /** Legacy two-state dash switch. `lineStyle` wins when both are given. */
2659
+ dashed?: boolean;
2660
+ lineWidth?: number;
2661
+ lineStyle?: IndicatorLineStyle;
2662
+ }
2663
+ /** One end of an indicator drawing: a time on the shared axis, a price on the pane's scale. */
2664
+ interface DrawAnchor {
2665
+ time: number;
2666
+ price: number;
2461
2667
  }
2462
-
2463
2668
  /**
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.
2472
- *
2473
- * Rows stack: several legends on one pane offset each other vertically, which
2474
- * the host does by giving each a `row` index.
2669
+ * A free-standing shape an indicator paints in its own pane, anchored to time
2670
+ * and price rather than to a bar index.
2475
2671
  *
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.
2484
- */
2485
-
2486
- type PaneLegendAction = 'hide' | 'settings' | 'up' | 'down' | 'maximize' | 'close';
2487
- /**
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.
2672
+ * Plots, levels and markers each answer a different question and none of them
2673
+ * answers this one: a pivot-to-pivot trendline, a supply zone, an order block,
2674
+ * a measured-move projection are all geometry between two arbitrary points, and
2675
+ * a column of one value per bar cannot express any of them. Anchors are times,
2676
+ * so a shape stays put when history is paged in and every logical index shifts.
2491
2677
  */
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. */
2678
+ type IndicatorDrawing = {
2679
+ kind: 'line';
2680
+ from: DrawAnchor;
2681
+ to: DrawAnchor;
2682
+ color?: string;
2683
+ lineWidth?: number;
2684
+ lineStyle?: IndicatorLineStyle;
2685
+ /** Continue the line past its anchor to the pane edge. */
2686
+ extendLeft?: boolean;
2687
+ extendRight?: boolean;
2688
+ } | {
2689
+ kind: 'box';
2690
+ from: DrawAnchor;
2691
+ to: DrawAnchor;
2692
+ /** Border colour. Omit `fillColor` to draw an outline only. */
2497
2693
  color?: string;
2694
+ fillColor?: string;
2695
+ /** Fill alpha, 0..1. Defaults to 0.12. */
2696
+ opacity?: number;
2697
+ lineWidth?: number;
2698
+ /** Caption drawn on a plate at the centre of the box; `\n` splits lines. */
2699
+ text?: string;
2700
+ textColor?: string;
2498
2701
  /**
2499
- * Which status-line switch owns this reading. Untagged readings are the
2500
- * source's own last value, governed by `statusLine.lastValueLabel`.
2702
+ * Detail shown on a plate while the pointer rests on the box, and gone
2703
+ * when it leaves; `\n` splits lines. A zone that carries its size, its
2704
+ * age and what formed it cannot print all of that on the box without
2705
+ * hiding the candles under it, so the caption names it and this explains
2706
+ * it. The box becomes hit-testable, and `id` (or the tooltip text) is
2707
+ * what `subscribeClick` reports for it.
2501
2708
  */
2502
- field?: LegendField;
2503
- }
2504
- /**
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.
2508
- */
2509
- type LegendField = 'ohlc' | 'change' | 'volume';
2510
- /**
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.
2513
- */
2514
- type LegendTitleMode = 'symbol' | 'description' | 'ticker';
2515
- /**
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.
2518
- */
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
- }
2531
- /**
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".
2535
- */
2536
- type LegendStatusSource = LegendStatusData | (() => LegendStatusData | null);
2709
+ tooltip?: string;
2710
+ /** Hit id, for `subscribeClick`. Defaults to the tooltip text. */
2711
+ id?: string;
2712
+ } | {
2713
+ kind: 'label';
2714
+ at: DrawAnchor;
2715
+ /** `\n` splits lines. */
2716
+ text: string;
2717
+ /** Plate fill. */
2718
+ color?: string;
2719
+ textColor?: string;
2720
+ /** Which edge of the plate sits on the anchor. Defaults to 'center'. */
2721
+ align?: 'left' | 'center' | 'right';
2722
+ /** Hover detail, as on a box. */
2723
+ tooltip?: string;
2724
+ /** Hit id, for `subscribeClick`. Defaults to the tooltip text. */
2725
+ id?: string;
2726
+ } | {
2727
+ kind: 'polyline';
2728
+ points: readonly DrawAnchor[];
2729
+ color?: string;
2730
+ lineWidth?: number;
2731
+ /** Close the path back to the first point (a triangle, a wedge). */
2732
+ closed?: boolean;
2733
+ fillColor?: string;
2734
+ /** Fill alpha, 0..1. Defaults to 0.12. */
2735
+ opacity?: number;
2736
+ };
2737
+ /** `calc` output: one array per plot key, aligned 1:1 with the input bars. */
2738
+ type IndicatorValues = Record<string, readonly (number | null)[]>;
2739
+ /** Per-instance scratch owned by the descriptor (Tier-2 data lands here). */
2740
+ type IndicatorStore = Record<string, unknown>;
2537
2741
  /**
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.
2742
+ * The fourth, optional argument to `calc` (and the sixth to `calcTail`): what
2743
+ * the calculation cannot read off the bars themselves.
2744
+ *
2745
+ * It is optional so that every descriptor written against `calc(bars, settings,
2746
+ * store)` keeps its exact signature and its exact behaviour, which is the whole
2747
+ * point: a calculation that ignores the context computes what it always did.
2541
2748
  */
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;
2749
+ interface IndicatorCalcContext {
2559
2750
  /**
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.
2751
+ * Where the last bar stands, so a study can act once per bar rather than once
2752
+ * per tick, or refuse to signal off a bar that is still moving.
2563
2753
  */
2564
- lastValueLabel?: boolean;
2754
+ barState: {
2755
+ /** The most recent update appended a bar rather than replacing one. */
2756
+ isNew: boolean;
2757
+ /** The last bar has closed: its interval has elapsed on the chart clock. */
2758
+ isConfirmed: boolean;
2759
+ /** A live feed is driving updates, rather than a one-off history load. */
2760
+ isRealtime: boolean;
2761
+ /** Index of the last bar, `bars.length - 1` (-1 when there are none). */
2762
+ lastIndex: number;
2763
+ };
2764
+ /** The instrument, when the host knows one. See `IndicatorAttachContext`. */
2765
+ symbol?: string;
2766
+ /** The timeframe (`'5m'`, `'1d'`), on the same terms as `symbol`. */
2767
+ interval?: string;
2768
+ /** The chart's IANA zone, the calendar its axis is labelled in. */
2769
+ timezone: string;
2770
+ /** Chart wall clock in UTC seconds, the clock the countdown row reads. */
2771
+ now(): number;
2565
2772
  /**
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.
2773
+ * The instrument's tick size, from the **price pane's** `minMove`.
2774
+ *
2775
+ * The price pane and not the indicator's own, because `calc` runs on the
2776
+ * instrument's bars whichever pane the plot lands in, and a study pane is not
2777
+ * quoted in the instrument's tick: an RSI is a dimensionless 0..100 band, so
2778
+ * its scale carries no tick at all to read.
2779
+ *
2780
+ * `undefined` when the host has not told the chart what it is, which is the
2781
+ * honest answer rather than a guessed 0.01: an indicator sizing a range in
2782
+ * ticks has to tell "one paisa" apart from "nobody said".
2568
2783
  */
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;
2784
+ tickSize?: number;
2574
2785
  }
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;
2584
- /**
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.
2588
- */
2589
- valueColor?: string;
2590
- /** Vertical slot on the pane (0 = topmost). */
2591
- row?: number;
2592
- /**
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`)
2599
- *
2600
- * Defaults to `['up', 'down', 'hide', 'maximize', 'close']` for pane sources
2601
- * and `['hide', 'close']` for overlays (pass explicitly to override).
2786
+ /** What an alert's `when` predicate is handed, for the bar it is judging. */
2787
+ interface IndicatorAlertContext {
2788
+ bars: readonly Bar[];
2789
+ values: IndicatorValues;
2790
+ settings: Readonly<IndicatorSettings>;
2791
+ /** The bar being evaluated. */
2792
+ index: number;
2793
+ }
2794
+ /**
2795
+ * A condition the runtime watches, declared by the descriptor rather than wired
2796
+ * up by the host: the indicator is the only thing that knows what a crossover of
2797
+ * its own columns means.
2798
+ *
2799
+ * Evaluated once per bar, for bars that are new since the last evaluation, so
2800
+ * adding the indicator to a loaded chart fires nothing for history.
2801
+ */
2802
+ interface IndicatorAlertSpec {
2803
+ /** Stable within the descriptor, e.g. `'cross-up'`. */
2804
+ id: string;
2805
+ /** Short human label, e.g. `'MACD crossed up'`. */
2806
+ title: string;
2807
+ /**
2808
+ * Longer text for a notification; defaults to `title`. A function is handed
2809
+ * the same context `when` judged, so the message can carry the bar's own
2810
+ * numbers: the price it crossed at, the histogram reading, a JSON body for a
2811
+ * webhook. It runs only for a bar `when` accepted.
2602
2812
  */
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;
2813
+ message?: string | ((ctx: IndicatorAlertContext) => string);
2814
+ when(ctx: IndicatorAlertContext): boolean;
2618
2815
  }
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;
2816
+ /**
2817
+ * Thrown by a `calc` (or any hook) to say its inputs cannot produce a study,
2818
+ * the way a script language's runtime error does: a period at or below zero, a
2819
+ * fast length above the slow one, a benchmark the provider cannot serve.
2820
+ *
2821
+ * Any error out of a recompute is caught by the runtime and published on the
2822
+ * instance's data status as `{ state: 'error' }`, so the chart keeps drawing
2823
+ * every other indicator and a host can show the reason beside this one. This
2824
+ * class exists so a descriptor can throw a **named** condition and a host can
2825
+ * tell a bad input, which the user can fix, from a bug, which they cannot.
2826
+ */
2827
+ declare class IndicatorInputError extends Error {
2828
+ constructor(message: string);
2642
2829
  }
2643
-
2644
2830
  /**
2645
- * Vertical gradient across the band, graded between two prices rather than
2646
- * two pixel rows.
2831
+ * Bars of another instrument or interval, supplied by the host on request.
2647
2832
  *
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.
2833
+ * The engine is handed one symbol's bars and owns no transport, so a study
2834
+ * that compares against a benchmark, or a Tier-2 provider that needs a second
2835
+ * series, asks the host through this and the host answers from wherever it
2836
+ * keeps history. `from` and `to` are UTC seconds; `signal` is aborted when the
2837
+ * instance is removed or its settings change, so a provider can drop the
2838
+ * request rather than answer into the void.
2652
2839
  */
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;
2840
+ interface IndicatorBarsRequest {
2841
+ symbol: string;
2842
+ exchange?: string;
2843
+ interval: string;
2844
+ from: number;
2845
+ to: number;
2846
+ signal?: AbortSignal;
2660
2847
  }
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;
2848
+ type IndicatorBarsProvider = (request: IndicatorBarsRequest) => Promise<readonly Bar[]>;
2849
+ /** Payload of the `'indicator:alert'` event on the chart's own bus. */
2850
+ interface IndicatorAlertPayload extends AlertEventPayload {
2851
+ /** Descriptor id, e.g. `'macd'`. */
2852
+ indicatorId: string;
2853
+ /** Instance id, so a host can tell three EMAs apart. */
2854
+ instanceId: string;
2855
+ message: string;
2856
+ }
2857
+ /** Optional instrument identity and capability supplied by the host. */
2858
+ interface ChartDataContext {
2859
+ symbol?: string;
2860
+ exchange?: string;
2861
+ interval?: string;
2862
+ /** Instrument capability, independent of readings: false unsupported, absent unknown. */
2863
+ hasOpenInterest?: boolean;
2864
+ }
2865
+ /** Source identity changed, or the available source-bar range changed. */
2866
+ type IndicatorDataChange = 'context' | 'range';
2867
+ /** Observable state of an indicator's external data lifecycle. */
2868
+ type IndicatorDataStatus = {
2869
+ state: 'loading' | 'ready' | 'empty' | 'unsupported';
2870
+ } | {
2871
+ state: 'error';
2872
+ error: unknown;
2873
+ };
2874
+ /** What an indicator's `attach` lifecycle can reach. */
2875
+ interface IndicatorAttachContext {
2876
+ /** Optional host identity, independent of the indicator's own settings. */
2877
+ dataContext?(): Readonly<ChartDataContext> | undefined;
2878
+ /** Read bars and identity again when the host publishes a change. */
2879
+ subscribeDataChanges?(listener: (change: IndicatorDataChange) => void): () => void;
2880
+ /** Publish status and an explicit retry action for this instance. */
2881
+ setDataStatus?(status: IndicatorDataStatus): void;
2882
+ setDataRetry?(retry: (() => void) | null): void;
2883
+ /** Instance lifetime. Aborted on removal, preserved across style changes. */
2884
+ signal?: AbortSignal;
2885
+ /** Current settings (live — read at call time, not captured). */
2886
+ settings(): Readonly<IndicatorSettings>;
2887
+ /** The chart's current source bars. */
2888
+ bars(): readonly Bar[];
2889
+ /** Re-run `calc` and repaint — call when external data arrives. */
2890
+ requestRecompute(): void;
2891
+ /** Scratch this instance owns; the same object `calc` receives. */
2892
+ store: IndicatorStore;
2668
2893
  /**
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.
2894
+ * The instrument the chart is showing, when the host knows it.
2895
+ *
2896
+ * Hosts can supply this through `IndicatorHost` or Chart's explicit data
2897
+ * context. Without a configured identity it stays undefined. External
2898
+ * studies read `dataContext` to include the exchange as well.
2672
2899
  */
2673
- gradient?: FillGradient;
2674
- }
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). */
2678
- index: number;
2679
- a: number | null;
2680
- b: number | null;
2900
+ symbol?(): string | undefined;
2901
+ /** The chart's timeframe (`'5m'`, `'1d'`), on the same terms as `symbol`. */
2902
+ interval?(): string | undefined;
2903
+ /** The chart's IANA zone, the one its axis is labelled in. */
2904
+ timezone?(): string;
2905
+ /** Chart wall clock in UTC seconds, the same clock the countdown row uses. */
2906
+ now?(): number;
2681
2907
  /**
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.
2908
+ * Ask the host for another instrument's (or interval's) bars. Always present
2909
+ * under `chart.addIndicator`; it rejects when the host has registered no
2910
+ * provider (`chart.setBarsProvider`), so a study can treat the rejection as
2911
+ * "unsupported here" and say so through `setDataStatus`.
2686
2912
  */
2687
- color?: string;
2688
- }
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;
2913
+ requestBars?(request: IndicatorBarsRequest): Promise<readonly Bar[]>;
2914
+ /** The pane this instance drew into. Moves when panes are reordered. */
2915
+ paneIndex?(): number;
2916
+ /** Attach a primitive to this indicator's pane, and detach it again. */
2917
+ addPrimitive?(p: IPrimitive): void;
2918
+ removePrimitive?(p: IPrimitive): void;
2919
+ /**
2920
+ * Emit on the chart's own event bus, the one `chart.on(name, cb)` listens to.
2921
+ *
2922
+ * The declarative `alerts` slot covers a condition read off the bars; this is
2923
+ * the imperative half, for an indicator whose signal arrives from outside the
2924
+ * calculation entirely (a subscription its `attach` opened).
2925
+ */
2926
+ emit?(event: string, payload: unknown): void;
2705
2927
  }
2706
-
2707
2928
  /**
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.
2929
+ * What `levels` is handed. It carries `bars` and `values` **and** spreads the
2930
+ * settings keys onto itself, so the built-ins written against the original
2931
+ * `levels(settings)` signature keep working unchanged: they read
2932
+ * `ctx.overbought` (or pass `ctx` to a `num(s, key, default)` helper) and find
2933
+ * exactly what they found before. A widened parameter is the only way a level
2934
+ * can be data-derived (yesterday's high, the session VWAP band), and that is a
2935
+ * whole class of level that could not be expressed at all before.
2712
2936
  *
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.
2937
+ * The three data members are optional for the same backward-compatibility
2938
+ * reason, not because the runtime ever omits them: it always passes all three,
2939
+ * but a caller holding only a settings bag must still be able to invoke
2940
+ * `levels` directly. A descriptor that needs the data should default them
2941
+ * (`ctx.bars ?? []`).
2942
+ *
2943
+ * `settings`, `bars` and `values` are therefore reserved keys, the way
2944
+ * `timezone` already is in the settings a `calc` receives: an input declared
2945
+ * under one of those names is shadowed here.
2715
2946
  */
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. */
2722
- dataContext?(): Readonly<ChartDataContext> | undefined;
2723
- 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;
2947
+ type IndicatorLevelContext = IndicatorSettings & {
2948
+ settings?: Readonly<IndicatorSettings>;
2949
+ bars?: readonly Bar[];
2950
+ values?: IndicatorValues;
2951
+ };
2952
+ interface IndicatorDescriptor {
2953
+ /** Registry key, e.g. `'macd'`. */
2954
+ id: string;
2955
+ /** Display name, e.g. `'MACD'`. */
2956
+ name: string;
2957
+ /** Grouping for a picker UI ('Trend', 'Momentum', 'Volume', 'Volatility'). */
2958
+ category?: string;
2959
+ /** `'onchart'` overlays the price pane; `'pane'` gets its own pane. */
2960
+ placement: 'onchart' | 'pane';
2739
2961
  /**
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.
2962
+ * This indicator was written from code the host can show the user.
2963
+ *
2964
+ * Its legend row then carries a source button beside the gear, and pressing
2965
+ * it emits `indicatorSource` with the same payload `indicatorSettings`
2966
+ * carries. The engine does not hold the code and does not want to: a
2967
+ * descriptor may be compiled from a script, generated, or written by hand in
2968
+ * the host's own bundle, and only the host knows which of those it can put in
2969
+ * front of somebody. So this says a button is worth offering, and the host
2970
+ * decides what the button opens.
2971
+ *
2972
+ * Absent or false draws no button, which is every built-in study.
2743
2973
  */
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;
2974
+ hasSource?: boolean;
2975
+ inputs: readonly IndicatorInput[];
2976
+ plots: readonly IndicatorPlot[];
2758
2977
  /**
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.
2978
+ * Shaded bands between pairs of plots — the Ichimoku cloud, a Bollinger
2979
+ * channel. A pair of lines is not the same picture as a filled region: the
2980
+ * fill is what makes "price is above the cloud" readable at a glance, and
2981
+ * which side leads is itself the signal, hence the two colours.
2762
2982
  */
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;
2983
+ fills?: readonly IndicatorFillSpec[];
2767
2984
  /**
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.
2985
+ * Full recompute over every bar. Must return arrays the same length as
2986
+ * `bars` (use `null` for warmup gaps — the line renderer breaks across them
2987
+ * and autoscale skips them).
2771
2988
  *
2772
- * Optional, like `timezone`, so a host predating it still satisfies this
2773
- * interface: an indicator that draws simply draws nothing there.
2989
+ * Tier-1 indicators are pure functions of `(bars, settings)` and ignore
2990
+ * `store`. Tier-2 indicators — the ones with their own data — read the
2991
+ * external series their `attach` lifecycle put in `store`.
2774
2992
  */
2775
- addIndicatorPrimitive?(primitive: IPrimitive, paneIndex: number): void;
2776
- removeIndicatorPrimitive?(primitive: IPrimitive): void;
2993
+ calc(bars: readonly Bar[], settings: Readonly<IndicatorSettings>, store: IndicatorStore, ctx?: IndicatorCalcContext): IndicatorValues;
2777
2994
  /**
2778
- * Recompute whatever the host has marked stale, before a caller reads a value.
2995
+ * Optional per-instance lifecycle, for indicators whose data is not derived
2996
+ * from the chart's bars (CVD, PCR, an external feed). Called once
2997
+ * when the instance is created; return a teardown function.
2779
2998
  *
2780
- * Optional, like `timezone`, so a host predating it still satisfies this
2781
- * interface: one that recomputes eagerly has nothing to flush.
2999
+ * Fetch into `ctx.store`, then call `ctx.requestRecompute()` — `calc` runs
3000
+ * again and reads what you stored.
2782
3001
  */
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;
3002
+ attach?(ctx: IndicatorAttachContext): (() => void) | void;
2788
3003
  /**
2789
- * The chart's configured IANA zone. Optional so a host predating the option
2790
- * still satisfies this interface; absent means the shipped default.
3004
+ * Optional incremental path, called instead of `calc` when only the tail
3005
+ * changed (a live tick). Return values for indices `[fromIndex, bars.length)`
3006
+ * — the runtime splices them onto the previous result — or `null` to fall
3007
+ * back to a full `calc`.
2791
3008
  *
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`.
3009
+ * Without it every tick costs a full recompute. That is a few hundred
3010
+ * microseconds for one indicator over 50k bars, but it is O(n) per tick per
3011
+ * indicator, so implement this for anything meant to run in a busy live pane.
2795
3012
  */
2796
- timezone?(): string;
3013
+ calcTail?(bars: readonly Bar[], settings: Readonly<IndicatorSettings>, fromIndex: number, previous: IndicatorValues, store: IndicatorStore, ctx?: IndicatorCalcContext): IndicatorValues | null;
2797
3014
  /**
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.
3015
+ * Optional bar-anchored signal markers — a named "Buy"/"Sell" plate, an arrow
3016
+ * at a crossover. Runs after every `calc`, so it reads the values it just
3017
+ * produced rather than recomputing anything.
3018
+ *
3019
+ * A plot cannot express this: a plot is a column of prices drawn as a line or
3020
+ * histogram, whereas a signal is a discrete event with a label. Returning `[]`
3021
+ * (when a `showLabels`-style input is off, say) clears the layer.
2801
3022
  */
2802
- symbol?(): string | undefined;
2803
- interval?(): string | undefined;
2804
- /** Chart wall clock in UTC seconds. Absent means the system clock. */
2805
- now?(): number;
3023
+ markers?(ctx: {
3024
+ bars: readonly Bar[];
3025
+ values: IndicatorValues;
3026
+ settings: Readonly<IndicatorSettings>;
3027
+ }): readonly SeriesMarker[];
2806
3028
  /**
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.
3029
+ * What `aboveBar` and `belowBar` are measured against.
2811
3030
  *
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.
3031
+ * `'plot'`, the default, is this study's own first plot, which is right for a
3032
+ * mark that belongs to the line: an arrow on a moving average sits against
3033
+ * the average.
3034
+ *
3035
+ * `'price'` is the instrument's candles, so above is above the high and below
3036
+ * is below the low. That is what a buy or sell signal on an overlay study
3037
+ * means, and anchoring one to the study's own column instead puts it wherever
3038
+ * that column happens to sit: a study that anchors its marks to a mid-body
3039
+ * line draws every "below" mark through the middle of the candle.
3040
+ *
3041
+ * Ignored by a study in its own pane, which has no candles to measure
3042
+ * against, and ignored when the chart has no primary series yet. Both fall
3043
+ * back to the first plot rather than dropping the marker.
2815
3044
  */
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;
3045
+ markerAnchor?: 'plot' | 'price';
2819
3046
  /**
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".
3047
+ * Optional summary grid pinned to a corner of the pane.
3048
+ *
3049
+ * Some studies are not a value per bar at all: a seasonality heatmap is a
3050
+ * matrix of monthly returns, a scoreboard is a handful of statistics. Those
3051
+ * have no place in `calc`, whose contract is one column per plot aligned to
3052
+ * the bars, so they come back through here instead. Runs after every `calc`.
3053
+ *
3054
+ * Return `null` (or a zero-row grid) to draw nothing, which is how a
3055
+ * `showTable`-style input should switch it off.
2823
3056
  */
2824
- requestBars?(request: IndicatorBarsRequest): Promise<readonly Bar[]>;
3057
+ table?(ctx: {
3058
+ bars: readonly Bar[];
3059
+ values: IndicatorValues;
3060
+ settings: Readonly<IndicatorSettings>;
3061
+ }): {
3062
+ rows: readonly (readonly TableCell[])[];
3063
+ options?: Partial<ChartTableOptions>;
3064
+ } | null;
2825
3065
  /**
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.
3066
+ * Optional free-standing shapes drawn in the indicator's pane: trendlines
3067
+ * between pivots, supply and demand boxes, projection labels. Runs after
3068
+ * every `calc`, like `markers` and `table`, and the returned list replaces the
3069
+ * previous one wholesale, so returning `[]` clears the layer.
3070
+ */
3071
+ draws?(ctx: {
3072
+ bars: readonly Bar[];
3073
+ values: IndicatorValues;
3074
+ settings: Readonly<IndicatorSettings>;
3075
+ }): readonly IndicatorDrawing[];
3076
+ /**
3077
+ * Optional per-bar shading behind everything else in the indicator's pane: a
3078
+ * full-height column per bar, `null` where nothing should be shaded.
2828
3079
  *
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.
3080
+ * A regime study answers "which state is the market in right now", and that is
3081
+ * a property of the whole bar, not a price. Drawn as a plot it would need a
3082
+ * value to sit at and would fight the pane's autoscale; as a column behind the
3083
+ * candles it reads at a glance and costs the scale nothing.
3084
+ *
3085
+ * Runs after every `calc`. Return `[]` to clear the layer.
2832
3086
  */
2833
- tickSize?(paneIndex: number): number | undefined;
3087
+ background?(ctx: {
3088
+ bars: readonly Bar[];
3089
+ values: IndicatorValues;
3090
+ settings: Readonly<IndicatorSettings>;
3091
+ }): readonly (string | null)[];
2834
3092
  /**
2835
- * Write a number the way the price axis of that pane writes it.
3093
+ * Optional recolouring of the **main price candles**, one entry per bar,
3094
+ * `null` to leave that bar with its own colour.
2836
3095
  *
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`.
3096
+ * Distinct from a plot's `colorBy`, which paints the indicator's own series: a
3097
+ * trend filter, a volatility regime or a higher-timeframe bias is a statement
3098
+ * about the price bars themselves, and drawing it as a second series beside
3099
+ * them says something weaker.
2843
3100
  *
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.
3101
+ * Only one indicator's colours can be on the candles at a time; the most
3102
+ * recent publisher wins, and publishers run in `addIndicator` order, so the
3103
+ * winner is the same one from frame to frame. Removing it, or hiding it,
3104
+ * restores the bars' own colours.
2846
3105
  */
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: {
3106
+ barColors?(ctx: {
3107
+ bars: readonly Bar[];
3108
+ values: IndicatorValues;
3109
+ settings: Readonly<IndicatorSettings>;
3110
+ }): readonly (string | null)[];
3111
+ /**
3112
+ * Optional conditions the runtime watches on the descriptor's behalf, emitted
3113
+ * as `'indicator:alert'` on the chart's event bus with an
3114
+ * {@link IndicatorAlertPayload}. See {@link IndicatorAlertSpec}.
3115
+ */
3116
+ alerts?: readonly IndicatorAlertSpec[];
3117
+ /**
3118
+ * Optional horizontal reference levels drawn in the indicator's pane.
3119
+ * Recomputed after every `calc`, so a level derived from the data (the
3120
+ * previous day's high) tracks it. See `IndicatorLevelContext` for why the
3121
+ * argument still reads as a settings bag.
3122
+ */
3123
+ levels?(ctx: IndicatorLevelContext): readonly IndicatorLevel[];
3124
+ /**
3125
+ * Optional fixed price range for the indicator's own pane (RSI 0..100).
3126
+ * Applied only when the indicator creates its pane — two indicators sharing a
3127
+ * pane would otherwise fight over it.
3128
+ */
3129
+ range?(settings: Readonly<IndicatorSettings>): {
2850
3130
  min: number;
2851
3131
  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;
3132
+ } | null;
2888
3133
  }
3134
+ /** Register an indicator descriptor. Later registrations of the same id win. */
3135
+ declare function registerIndicator(descriptor: IndicatorDescriptor): void;
3136
+ declare function getIndicator(id: string): IndicatorDescriptor;
3137
+ declare function hasIndicator(id: string): boolean;
3138
+ declare function registeredIndicators(): IndicatorDescriptor[];
3139
+ /** The descriptor's declared defaults as a settings object. */
3140
+ declare function indicatorDefaults(descriptor: IndicatorDescriptor): IndicatorSettings;
3141
+ /** Read one bar's value for a price source. */
3142
+ declare function sourceValue(bar: Bar, source: IndicatorSource): number;
3143
+ /** Read a whole bar array for a price source. */
3144
+ declare function sourceValues(bars: readonly Bar[], source: IndicatorSource): number[];
2889
3145
 
2890
3146
  /**
2891
3147
  * Serialisable chart state — the keystone the persistence-shaped features hang
@@ -2929,6 +3185,8 @@ interface SeriesState {
2929
3185
  }
2930
3186
  interface IndicatorState {
2931
3187
  indicatorId: string;
3188
+ /** Stable workspace identity. Omitted by legacy states and reusable templates. */
3189
+ instanceId?: string;
2932
3190
  settings: IndicatorSettings;
2933
3191
  paneIndex: number;
2934
3192
  /** Omitted by older layouts, which restore the indicator as visible. */
@@ -2947,6 +3205,7 @@ interface ChartState {
2947
3205
  horzLines: boolean;
2948
3206
  };
2949
3207
  crosshairMode?: 'normal' | 'magnet';
3208
+ crosshairSnapToBar?: boolean;
2950
3209
  panes?: PaneState[];
2951
3210
  /** Informational: `restoreState` does not recreate these (it has no data). */
2952
3211
  series?: SeriesState[];
@@ -2957,6 +3216,7 @@ interface ChartState {
2957
3216
  * tier is loaded.
2958
3217
  */
2959
3218
  drawings?: unknown;
3219
+ alerts?: AlertsDocument;
2960
3220
  }
2961
3221
  /** What `restoreState` actually applied, so a caller can finish the job. */
2962
3222
  interface RestoreReport {
@@ -3848,6 +4108,8 @@ interface ChartOptions {
3848
4108
  * bar under the cursor (price pane only).
3849
4109
  */
3850
4110
  crosshairMode?: CrosshairMode;
4111
+ /** Snap the vertical crosshair to the nearest primary bar's center. Default false; independent of the price magnet. */
4112
+ crosshairSnapToBar?: boolean;
3851
4113
  /**
3852
4114
  * Optional chrome on the axis strips: the corner clock and the bar-close
3853
4115
  * countdown. Both are off unless asked for, so a chart that omits this block
@@ -3920,6 +4182,11 @@ interface ChartOptions {
3920
4182
  canvas?: CanvasOptions;
3921
4183
  /** Per-field status-line switches applied to every pane legend on the chart. */
3922
4184
  statusLine?: LegendStatusLineOptions;
4185
+ /**
4186
+ * Square side of a legend action button in media px. Default 16, held to
4187
+ * 12..28. Applied to every pane legend, because the rows stack against it.
4188
+ */
4189
+ legendIconSize?: number;
3923
4190
  /** Accessible label for the chart container (screen readers). */
3924
4191
  ariaLabel?: string;
3925
4192
  /**
@@ -4013,6 +4280,8 @@ declare function compactVolume(v: number): string;
4013
4280
  * floating tooltip. See `subscribeCrosshairMove`.
4014
4281
  */
4015
4282
  interface CrosshairMoveEvent {
4283
+ /** Linked readouts have no physical pointer position and are not pointer gestures. */
4284
+ source?: 'linked';
4016
4285
  /** UTC seconds of the hovered bar, or null when off the data / pointer left. */
4017
4286
  time: number | null;
4018
4287
  /** Logical index under the cursor, or null. */
@@ -4150,6 +4419,8 @@ interface ContextMenuTarget {
4150
4419
  id: string | null;
4151
4420
  /** Indicator instance id, when `kind` is 'indicator'. */
4152
4421
  instanceId?: string;
4422
+ /** Exact plot key when a study's plotted series was hit rather than its legend. */
4423
+ plotKey?: string;
4153
4424
  /** Series type, when `kind` is 'series'. */
4154
4425
  seriesType?: SeriesType;
4155
4426
  /** Which axis strip was hit, when `kind` is 'price-scale'. */
@@ -4235,6 +4506,7 @@ declare class Chart {
4235
4506
  private _height;
4236
4507
  private _hasFitContent;
4237
4508
  private _crosshairMode;
4509
+ private _crosshairSnapToBar;
4238
4510
  private _shortcuts;
4239
4511
  private _trading;
4240
4512
  private _pointerInside;
@@ -4261,6 +4533,8 @@ declare class Chart {
4261
4533
  private readonly _canvas;
4262
4534
  /** Status-line switches pushed onto every pane legend, host-added ones included. */
4263
4535
  private readonly _statusLine;
4536
+ /** Legend action-button side in media px; undefined leaves the primitive's default. */
4537
+ private _legendIconSize;
4264
4538
  /** Axis-strip chrome switches. Empty is the shipped chart: neither drawn. */
4265
4539
  private readonly _axisChrome;
4266
4540
  /**
@@ -4317,6 +4591,7 @@ declare class Chart {
4317
4591
  private readonly _firstDataId;
4318
4592
  /** Handle + record of the primary price series (see `primarySeries`). */
4319
4593
  private _primary;
4594
+ private readonly _seriesRecords;
4320
4595
  private readonly _indicators;
4321
4596
  private _dataContext;
4322
4597
  private _barsProvider;
@@ -4336,6 +4611,7 @@ declare class Chart {
4336
4611
  private _barColorAnchor;
4337
4612
  /** Opaque drawing-tier payload, round-tripped through get/restoreState. */
4338
4613
  private _drawingState;
4614
+ private _alertState;
4339
4615
  /** Pane currently maximized, and the weights to restore when it un-maximizes. */
4340
4616
  private _maximizedPane;
4341
4617
  /** Legend rows per pane, so new ones stack below existing ones. */
@@ -4346,6 +4622,7 @@ declare class Chart {
4346
4622
  private _loadingHistory;
4347
4623
  private _clickCb;
4348
4624
  private _crosshairCb;
4625
+ private _readoutTime;
4349
4626
  private _pointerMoved;
4350
4627
  /** While true, pointer gestures place anchors instead of panning. */
4351
4628
  private _placementMode;
@@ -4406,6 +4683,8 @@ declare class Chart {
4406
4683
  /** Call after a history-paging load resolves to re-enable the trigger. */
4407
4684
  historyLoadComplete(): void;
4408
4685
  get dataLayer(): DataLayer;
4686
+ /** Readonly source bars, without allocating a history copy on each live update. */
4687
+ primaryBars(): readonly Bar[];
4409
4688
  get timeScale(): TimeScale;
4410
4689
  /** Restore a saved logical range (e.g. preserve the user's zoom across a data reload). */
4411
4690
  setVisibleLogicalRange(range: LogicalRange): void;
@@ -4536,6 +4815,8 @@ declare class Chart {
4536
4815
  private _forgetIndicator;
4537
4816
  /** Optional instrument identity supplied by the host, never inferred from bars. */
4538
4817
  getDataContext(): Readonly<ChartDataContext> | undefined;
4818
+ /** Instrument capability from the host. A missing bar reading does not change it. */
4819
+ get hasOpenInterest(): boolean | undefined;
4539
4820
  /** Clear the previous source bars before changing context, then load the new source. */
4540
4821
  setDataContext(context: ChartDataContext | undefined): void;
4541
4822
  /**
@@ -4607,9 +4888,17 @@ declare class Chart {
4607
4888
  /**
4608
4889
  * Subscribe to crosshair movement for an OHLC legend / tooltip. The callback
4609
4890
  * 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.
4891
+ * with all-null fields when the pointer leaves the plot. A linked crosshair
4892
+ * also updates the readout, with source 'linked' and no pointer coordinates.
4611
4893
  */
4612
4894
  subscribeCrosshairMove(cb: (e: CrosshairMoveEvent) => void): void;
4895
+ /**
4896
+ * Update the readout under a link group's separately drawn crosshair. The
4897
+ * physical pointer takes precedence. This never emits a pointer move event,
4898
+ * so hosts do not interpret it as drawing input or echo it to another group.
4899
+ */
4900
+ setLinkedCrosshairIndex(index: number | null): void;
4901
+ private _readoutIndex;
4613
4902
  /**
4614
4903
  * Subscribe to drags of draggable primitives (order / SL / TP lines, drawing
4615
4904
  * handles). Fires per move and on release.
@@ -4800,6 +5089,16 @@ declare class Chart {
4800
5089
  */
4801
5090
  setStatusLineOptions(patch: LegendStatusLineOptions): void;
4802
5091
  statusLineOptions(): LegendStatusLineOptions;
5092
+ /**
5093
+ * How large a legend's action buttons are drawn, in media px.
5094
+ *
5095
+ * Chart-wide rather than per legend: the rows stack against the height the
5096
+ * buttons need, so two sizes on one pane would stack against two different
5097
+ * heights and overlap. The primitive holds it to a range it can actually
5098
+ * draw.
5099
+ */
5100
+ setLegendIconSize(size: number): void;
5101
+ legendIconSize(): number | undefined;
4803
5102
  /**
4804
5103
  * Turn the axis-strip chrome on or off, and hand it a clock. Merges field by
4805
5104
  * field, so switching the countdown on leaves the corner clock alone.
@@ -4809,6 +5108,8 @@ declare class Chart {
4809
5108
  axisChromeOptions(): AxisChromeOptions;
4810
5109
  /** Crosshair behaviour ('normal' or 'magnet'). Set it via `applyOptions`. */
4811
5110
  crosshairMode(): CrosshairMode;
5111
+ /** Whether the vertical crosshair snaps to an existing primary bar's center. */
5112
+ crosshairSnapToBar(): boolean;
4812
5113
  /** The active palette. Swap it with `setTheme`. */
4813
5114
  theme(): ChartTheme;
4814
5115
  /**
@@ -4937,11 +5238,13 @@ declare class Chart {
4937
5238
  grid?: Partial<GridOptions>;
4938
5239
  canvas?: CanvasOptions;
4939
5240
  statusLine?: LegendStatusLineOptions;
5241
+ legendIconSize?: number;
4940
5242
  priceScale?: Partial<PriceScaleOptions>;
4941
5243
  priceFormatter?: ((price: number) => string) | null;
4942
5244
  timeFormatter?: ((utcSeconds: number, tickMark?: TickMarkType) => string) | undefined;
4943
5245
  timezone?: string;
4944
5246
  crosshairMode?: CrosshairMode;
5247
+ crosshairSnapToBar?: boolean;
4945
5248
  }): void;
4946
5249
  panes(): readonly Pane[];
4947
5250
  /**
@@ -4971,12 +5274,17 @@ declare class Chart {
4971
5274
  * (or `setVisibleLogicalRange`) once the series are populated.
4972
5275
  */
4973
5276
  restoreState(state: unknown): RestoreReport;
5277
+ private _restoreState;
4974
5278
  /**
4975
5279
  * The opaque `drawings` slot in the chart state. The base engine only
4976
5280
  * round-trips it; the drawing tier reads and writes it.
4977
5281
  */
4978
5282
  drawingState(): unknown;
4979
5283
  setDrawingState(value: unknown): void;
5284
+ /** Detached JSON state, also available when no alert controller is attached. */
5285
+ alertState(): AlertsDocument | undefined;
5286
+ /** Runtime snapshot. JSON safety of opaque payloads is checked when state is read. */
5287
+ setAlertState(document: AlertsDocument | undefined): void;
4980
5288
  invalidate(build: (mask: InvalidateMask) => void): void;
4981
5289
  /**
4982
5290
  * Measure the container once more, a frame after construction.
@@ -5449,6 +5757,330 @@ declare class SvgContext {
5449
5757
  private _unsupported;
5450
5758
  }
5451
5759
 
5760
+ /**
5761
+ * Aligning a second instrument onto the primary series' bars.
5762
+ *
5763
+ * The x-axis is a gapless logical index over the times the shared DataLayer
5764
+ * holds (ARCHITECTURE.md §4.1, §5.3), so two instruments do not share a bar
5765
+ * index and cannot be laid side by side by position. They are matched by
5766
+ * timestamp, and the two directions of mismatch get opposite answers:
5767
+ *
5768
+ * - **A comparison bar with no primary bar is dropped.** The DataLayer merges
5769
+ * *every* series' times into one index space, so a time only the comparison
5770
+ * has would mint a new logical index: a column the primary instrument has no
5771
+ * candle for, inserted mid-chart, shifting every bar after it. That happens
5772
+ * for real (a different exchange's holiday calendar, a 24/7 instrument next
5773
+ * to an NSE one, a feed that emits a stray print), and warping the primary's
5774
+ * own axis to accommodate a comparison is never the right trade. The print is
5775
+ * counted in `dropped` so a host can say so rather than silently losing it.
5776
+ *
5777
+ * - **A primary bar with no comparison bar becomes whitespace**, which is a NaN
5778
+ * bar the line renderer breaks across, so the comparison shows a *gap*. The
5779
+ * alternative, carrying the last known value forward, draws a flat segment
5780
+ * through a session the instrument never traded and, worse, in percentage
5781
+ * mode it anchors the move on the far side of the gap to a print that does
5782
+ * not exist. Omitting the bar entirely is worse still: the renderer would
5783
+ * join the two sides with one straight line across the holiday.
5784
+ *
5785
+ * Matching is on the exact timestamp. Bar-open times are bucketed by the candle
5786
+ * builder (§10.2) and stored as UTC seconds (§4.0), so two instruments on the
5787
+ * same interval agree to the second; anything that does not agree is a
5788
+ * different interval, which no tolerance window could rescue.
5789
+ */
5790
+
5791
+ /** What one alignment pass did, for a host that wants to report coverage. */
5792
+ interface ComparisonAlignment {
5793
+ /** Items handed to the comparison series: one per primary bar. */
5794
+ bars: number;
5795
+ /** Primary bars the comparison also traded (a value is drawn). */
5796
+ matched: number;
5797
+ /** Primary bars with no comparison print (drawn as a gap). */
5798
+ gaps: number;
5799
+ /** Comparison prints discarded for having no primary bar at that time. */
5800
+ dropped: number;
5801
+ }
5802
+ /**
5803
+ * Project `comparison` onto `primary`'s bars: one item per primary bar, in the
5804
+ * primary's order, so the result occupies exactly the logical indices the chart
5805
+ * already has and adds none of its own.
5806
+ *
5807
+ * Neither input is mutated and neither has to be sorted: matching goes through
5808
+ * a time map, which also collapses a repeated timestamp to its last item, the
5809
+ * same rule the DataLayer applies when it merges (`sortedUniqueByTime`).
5810
+ */
5811
+ declare function alignToPrimary(primary: readonly Bar[], comparison: readonly SeriesDataItem[]): {
5812
+ items: SeriesDataItem[];
5813
+ alignment: ComparisonAlignment;
5814
+ };
5815
+
5816
+ /**
5817
+ * Multi-symbol comparison: put a second instrument on the primary one's pane
5818
+ * and read them together (NIFTY against BANKNIFTY, a stock against its index).
5819
+ *
5820
+ * Headless, in the spirit of `DrawingController` (src/draw/controller.ts) and
5821
+ * `ReplayController`: it owns the series, the alignment and the scales, and
5822
+ * ships no DOM, so the host draws its own symbol chips and legend rows from
5823
+ * `list()`.
5824
+ *
5825
+ * Three decisions carry the design:
5826
+ *
5827
+ * 1. **Each comparison owns a scale.** A free legacy overlay or left scale
5828
+ * preserves the first source's placement; additional sources use named
5829
+ * hidden scales so their different price units cannot affect one another.
5830
+ *
5831
+ * 2. **Comparability comes from the scale, not from the data.** The bars handed
5832
+ * over are stored as the instrument's own prices, so the legend, the
5833
+ * crosshair and any live update still speak in real prices. What makes the
5834
+ * lines readable together is the pane mode: `percentage` and
5835
+ * `indexed-to-100` give every scale its own baseline (its first visible
5836
+ * close, or an explicit common timestamp), and `_mirror` gives it the same
5837
+ * band of percent the primary's axis is showing. Without that mirror each
5838
+ * scale would autoscale to its own data and a 1% mover would look exactly
5839
+ * like a 10% mover, both filling the pane.
5840
+ *
5841
+ * 3. **Alignment is by timestamp** and lives in `./align`, which documents what
5842
+ * happens in each direction of mismatch.
5843
+ *
5844
+ * Each comparison owns a scale and baseline. Further instruments use keyed
5845
+ * hidden scales so their absolute prices cannot change another source's units.
5846
+ */
5847
+
5848
+ /**
5849
+ * How the pane quotes prices while a comparison is on it. The two rebasing
5850
+ * modes are the reason the lines are comparable at all; `none` leaves the
5851
+ * pane's own mode alone, for a host that wants the raw overlay.
5852
+ */
5853
+ type ComparisonMode = 'percentage' | 'indexed-to-100' | 'none';
5854
+ /** Independent first-visible closes, or the first visible timestamp shared by all visible sources. */
5855
+ type ComparisonBaseline = 'first-visible' | 'common';
5856
+ interface ComparisonOptions {
5857
+ /** Instrument label, e.g. 'BANKNIFTY'. Carried on the handle for the host's UI. */
5858
+ symbol: string;
5859
+ /** The instrument's own bars. Aligned to the primary series, see `./align`. */
5860
+ bars: readonly SeriesDataItem[];
5861
+ /** Line colour shorthand; `style.color` wins if both are given. */
5862
+ color?: string;
5863
+ /** Style overrides merged onto the chart type's defaults. */
5864
+ style?: SeriesStyle;
5865
+ /** Renderer for the comparison. Default 'line'. */
5866
+ type?: SeriesType;
5867
+ /** Pane to draw on. Default 0, the price pane. */
5868
+ paneIndex?: number;
5869
+ }
5870
+ interface ComparisonControllerOptions {
5871
+ /** Pane mode applied while any comparison is on it. Default 'percentage'. */
5872
+ mode?: ComparisonMode;
5873
+ /** Baseline policy. Default 'first-visible' preserves existing integrations. */
5874
+ baseline?: ComparisonBaseline;
5875
+ }
5876
+ /** What `addComparison` hands back: one instrument on the chart. */
5877
+ interface ComparisonHandle {
5878
+ readonly symbol: string;
5879
+ /**
5880
+ * The series this comparison draws through, for style patches and markers.
5881
+ * Data set on it directly skips alignment (use `setBars`), and removing it
5882
+ * directly leaves the pane rebased with nothing on it (use `remove`).
5883
+ */
5884
+ readonly series: SeriesApi;
5885
+ readonly paneIndex: number;
5886
+ /** The hidden scale it maps to. Never the pane's own price axis. */
5887
+ priceScale(): PriceScale;
5888
+ /** How the last alignment against the primary's bars went. */
5889
+ alignment(): ComparisonAlignment;
5890
+ /** Eligible aligned bar in its own price units; null for gaps, suppressed baselines or forming replay candles. */
5891
+ barAt(time: number): Readonly<Bar> | null;
5892
+ /** Replace the instrument's bars (a longer history, a refreshed fetch). */
5893
+ setBars(bars: readonly SeriesDataItem[]): void;
5894
+ /** Take this instrument off the chart. Safe to call twice. */
5895
+ remove(): void;
5896
+ /** Every comparison on this chart, in the order they were added. */
5897
+ list(): readonly ComparisonHandle[];
5898
+ }
5899
+ /**
5900
+ * The slice of a pane the controller reads. Declared structurally, like
5901
+ * `ReplayViewport`, so nothing here depends on `Pane` beyond the two members
5902
+ * that decide where a comparison can go.
5903
+ */
5904
+ interface ComparisonPane {
5905
+ readonly priceScale: PriceScale;
5906
+ series(): readonly {
5907
+ readonly scaleId: string;
5908
+ readonly style?: SeriesStyle;
5909
+ }[];
5910
+ }
5911
+ /**
5912
+ * The slice of the chart this controller needs. `Chart` satisfies it; declaring
5913
+ * it structurally keeps the controller testable against a stub.
5914
+ */
5915
+ interface ComparisonChartHost {
5916
+ addSeries(type: SeriesType, options: AddSeriesOptions): SeriesApi;
5917
+ panes(): readonly ComparisonPane[];
5918
+ addPrimitive(primitive: IPrimitive, paneIndex?: number): void;
5919
+ removePrimitive(primitive: IPrimitive): void;
5920
+ primarySeries(): SeriesApi | null;
5921
+ /** Avoids allocating a primary history copy when the host provides it. */
5922
+ primaryBars?(): readonly Bar[];
5923
+ getVisibleLogicalRange?(): {
5924
+ from: number;
5925
+ to: number;
5926
+ };
5927
+ on?(event: string, callback: (payload: unknown) => void): () => void;
5928
+ /** The shared logical axis locates visible primary bars even when another series adds times. */
5929
+ readonly dataLayer: {
5930
+ readonly length: number;
5931
+ indexToTime?(index: number): number | undefined;
5932
+ };
5933
+ }
5934
+ declare class ComparisonController {
5935
+ private readonly _chart;
5936
+ private _mode;
5937
+ private _baseline;
5938
+ private readonly _panes;
5939
+ /** Insertion-ordered, and the handle is the key so `remove` is a lookup. */
5940
+ private readonly _items;
5941
+ /** Axis length plus primary identity and boundaries avoid scanning history each frame. */
5942
+ private _alignedAt;
5943
+ /** Guards `realign` against re-entry through its own `setData` repaint. */
5944
+ private _realigning;
5945
+ private _nextScale;
5946
+ private _syncing;
5947
+ private _alignedPrimary;
5948
+ private _primaryLength;
5949
+ private _primaryFirst;
5950
+ private _primaryLast;
5951
+ private readonly _off;
5952
+ private _destroyed;
5953
+ constructor(chart: ComparisonChartHost, options?: ComparisonControllerOptions);
5954
+ /** Put an instrument on the chart alongside the primary series. */
5955
+ add(options: ComparisonOptions): ComparisonHandle;
5956
+ /** Take one instrument off. Returns false if it was already gone. */
5957
+ remove(handle: ComparisonHandle): boolean;
5958
+ /** Every comparison on the chart, in the order they were added. */
5959
+ list(): readonly ComparisonHandle[];
5960
+ /** Take them all off, putting every pane back the way it was found. */
5961
+ clear(): void;
5962
+ /**
5963
+ * Change the mode the panes are held in while comparisons are on them.
5964
+ * Panes that already have one switch immediately.
5965
+ */
5966
+ setMode(mode: ComparisonMode): void;
5967
+ get mode(): ComparisonMode;
5968
+ /** Select a shared timestamp without changing the instruments' stored price units. */
5969
+ setBaseline(baseline: ComparisonBaseline): void;
5970
+ get baseline(): ComparisonBaseline;
5971
+ /** Shared baseline timestamp, or null for no overlap, no visible sources, or independent baselines. */
5972
+ baselineTime(paneIndex?: number): number | null;
5973
+ /**
5974
+ * Re-project onto the primary's current bars. Chart handles data replacement
5975
+ * and replay automatically. A structural host without data events or primary
5976
+ * history identity can call this after replacing its timestamps.
5977
+ */
5978
+ realign(): void;
5979
+ /**
5980
+ * Bring the overlay scales in line with the primary axis, re-aligning first
5981
+ * if the shared time axis has moved under us (a live bar, history paged in,
5982
+ * a replay step). Runs once per base paint through the per-pane hook.
5983
+ *
5984
+ * Re-aligning here writes series data mid-frame, which is safe because the
5985
+ * hook is a `bottom` primitive: it runs after the pane has autoscaled and
5986
+ * before any series is drawn, so the frame that notices the change is also
5987
+ * the frame that draws it. The repaint that write asks for lands on the next
5988
+ * frame, or re-enters this one on a host that runs frames synchronously,
5989
+ * which is what `realign`'s guard is for.
5990
+ */
5991
+ sync(): void;
5992
+ /** Remove every comparison and forget the chart. */
5993
+ destroy(): void;
5994
+ private _dispose;
5995
+ private _needsAlignment;
5996
+ private _rememberAlignment;
5997
+ /**
5998
+ * Preserve the first comparison's legacy placement when the scale is free.
5999
+ * Additional comparisons need their own baselines, and an occupied volume
6000
+ * or left scale must keep its source and units.
6001
+ */
6002
+ private _scaleIdFor;
6003
+ private _openPane;
6004
+ private _closePane;
6005
+ private _applyMode;
6006
+ private _restoreScale;
6007
+ private _restoreMode;
6008
+ /**
6009
+ * Give the overlay the same band of percent the primary axis is showing, so
6010
+ * equal moves land on equal pixels and the divergence between two
6011
+ * instruments is the thing you see.
6012
+ *
6013
+ * Each scale has its own price baseline, and rebasing maps price to the
6014
+ * ratio `price / baseline`. So the
6015
+ * two scales agree exactly when their ranges hold the same ratios, which is
6016
+ * one multiplication: the primary's range times `baseline_overlay /
6017
+ * baseline_primary`. It holds for `indexed-to-100` and `percentage` alike,
6018
+ * since they are one ladder a hundred points apart.
6019
+ *
6020
+ * A null baseline means this scale is not rebasing (linear, logarithmic, or
6021
+ * a pane with nothing visible on it yet). Then there is no shared ladder to
6022
+ * join and the overlay keeps its own autoscale, which is what mode 'none'
6023
+ * asks for: two instruments each filling the pane, comparable in shape only.
6024
+ *
6025
+ * The overlay stays under autoscale throughout, even though the range it
6026
+ * measures is overwritten here every frame. That measurement is one pass over
6027
+ * bars the renderer is about to walk anyway, and paying for it buys the
6028
+ * failure mode we want: a chart that stops calling `sync` falls back to an
6029
+ * independently scaled overlay instead of freezing on a stale range.
6030
+ */
6031
+ private _mirror;
6032
+ private _primaryBars;
6033
+ private _record;
6034
+ private _visible;
6035
+ private _visiblePrimary;
6036
+ private _commonAnchor;
6037
+ private _setSuppressed;
6038
+ private _write;
6039
+ private _align;
6040
+ /**
6041
+ * Ask for a full repaint after a mode change. The pane hands a rebasing scale
6042
+ * its baseline during the autoscale pass, so a `Light` repaint (all a
6043
+ * primitive's `requestUpdate` raises) would paint the new mode before it has
6044
+ * anything to quote against. `applyOptions` is the series handle's own route
6045
+ * to a Full invalidation, and an empty patch changes no style. Several of
6046
+ * them coalesce into one frame, so asking every comparison costs nothing.
6047
+ */
6048
+ private _repaint;
6049
+ }
6050
+ /**
6051
+ * The controller for a chart, created on first use. Use it to change the mode
6052
+ * for the whole chart, to list what is on it, or to clear it.
6053
+ */
6054
+ declare function comparisonController(chart: ComparisonChartHost, options?: ComparisonControllerOptions): ComparisonController;
6055
+ /**
6056
+ * Put an instrument on the chart next to the primary one:
6057
+ *
6058
+ * ```ts
6059
+ * const bn = addComparison(chart, { symbol: 'BANKNIFTY', bars, color: '#f0b90b' });
6060
+ * ```
6061
+ *
6062
+ * The pane switches to percentage while any comparison is on it and goes back
6063
+ * to the mode it had when the last one leaves.
6064
+ */
6065
+ declare function addComparison(chart: ComparisonChartHost, options: ComparisonOptions): ComparisonHandle;
6066
+
6067
+ /** Options for a numeric snapshot of the chart's currently loaded data. */
6068
+ interface ChartDataCsvOptions {
6069
+ /** Include every configured study's declared plots, including hidden studies. Default true. */
6070
+ indicators?: boolean;
6071
+ /** Override the chart's registered comparisons, for example with an explicitly managed controller's list. */
6072
+ comparisons?: readonly Pick<ComparisonHandle, 'symbol' | 'barAt'>[];
6073
+ }
6074
+ /**
6075
+ * CSV with UTC seconds, unrounded OHLC/volume/OI, study values and aligned comparison closes.
6076
+ * Reads only installed primary rows, so replay exposes only its revealed prefix.
6077
+ * Missing or nonfinite values are blank. Study values precede visual plot offsets;
6078
+ * comparison values retain their own price units, regardless of axis rebasing.
6079
+ * Headers have fixed prefixes so custom names cannot become spreadsheet formulas.
6080
+ * Does not fetch data, include trading state or initiate a browser download.
6081
+ */
6082
+ declare function exportChartDataCsv(chart: Chart, options?: ChartDataCsvOptions): string;
6083
+
5452
6084
  type ChartObjectKind = 'source' | 'indicator' | 'drawing' | 'profile';
5453
6085
  interface ChartObjectCapabilities {
5454
6086
  readonly select: boolean;
@@ -5786,542 +6418,394 @@ declare class Canvas2dBackend implements IRenderBackend {
5786
6418
  * Nothing to do: the pane's `CanvasLayer` owns the backing store and sized it
5787
6419
  * already. Re-sizing it here would clear a bitmap that was just painted.
5788
6420
  */
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;
6421
+ resize(_widthPx: number, _heightPx: number, _dpr: number): void;
6422
+ /** The same clear `CanvasLayer.clearBitmap` does, so the op stream is unchanged. */
6423
+ beginFrame(clear: boolean): void;
6424
+ drawSeries(entry: RendererEntry, items: readonly DrawItem[], priceToY: (price: number) => number, barSpacing: number, dpr: number, style: SeriesStyle, rc: SeriesRenderContext): void;
6425
+ /** Every call above drew straight to the canvas; there is nothing to flush. */
6426
+ endFrame(): void;
6427
+ overlay2d(): CanvasRenderingContext2D | null;
6428
+ destroy(): void;
6042
6429
  }
6043
6430
 
6044
6431
  /**
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.
6432
+ * Histogram / column renderer (ARCHITECTURE.md §6). Used for the volume pane.
6433
+ * Bars are drawn from a base value (0) up to each bar's close.
6073
6434
  */
6074
6435
 
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;
6436
+ interface HistogramStyle {
6437
+ color: string;
6438
+ /** Optional separate colors keyed by an up/down flag set on the bar's volume sign. */
6439
+ base: number;
6440
+ }
6441
+ declare const DEFAULT_HISTOGRAM_STYLE: HistogramStyle;
6442
+
6443
+ /** UTC seconds when this recorded candle becomes complete and available. */
6444
+ type ReplayBarEndTime = (bar: Bar, index: number) => number;
6445
+ /** Explicit availability avoids treating a coarse candle's open as its close. */
6446
+ interface ReplayTiming {
6447
+ barEndTime: ReplayBarEndTime;
6448
+ /** Required with finer bars; their final prices also need a known availability time. */
6449
+ subBarEndTime?: ReplayBarEndTime;
6085
6450
  }
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
6451
 
6100
6452
  /**
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.
6453
+ * Market replay: walk a historical session forward bar by bar so a trader can
6454
+ * practise on it, and so every indicator redraws exactly as it stood at that
6455
+ * past moment.
6125
6456
  *
6126
- * 3. **Alignment is by timestamp** and lives in `./align`, which documents what
6127
- * happens in each direction of mismatch.
6457
+ * Headless, in the spirit of `DrawingController` (src/draw/controller.ts): it
6458
+ * owns the playhead and the transitions and ships no DOM, so the host renders
6459
+ * its own transport bar, scrub slider and clock from `state()` and the events.
6128
6460
  *
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`.
6461
+ * The mechanic is deliberately boring. Replay feeds the chart a **prefix** of
6462
+ * the full bar array through the ordinary `series.setData` path, and that is
6463
+ * what makes indicators free: `Chart._setData` calls `_recomputeIndicators` for
6464
+ * the primary series, and `IndicatorInstance.recompute` re-reads the whole
6465
+ * history from `sourceBars()`. Shorten that history and every indicator, level,
6466
+ * fill, marker and legend reconstructs itself as it was at that bar, with no
6467
+ * replay-aware code anywhere in the indicator tier.
6135
6468
  */
6136
6469
 
6470
+ /** Schedules a repeating callback and returns its canceller. Inject in tests. */
6471
+ type ReplayScheduler = (cb: () => void, intervalMs: number) => () => void;
6137
6472
  /**
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.
6473
+ * The slice of the time scale replay saves and restores. Declared structurally
6474
+ * rather than as `TimeScale` so nothing here depends on the scale's shape
6475
+ * beyond the two numbers that *are* the viewport.
6141
6476
  */
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;
6477
+ interface ReplayViewport {
6478
+ readonly barSpacing: number;
6479
+ readonly rightOffset: number;
6480
+ setBarSpacing(value: number): void;
6481
+ setRightOffset(value: number): void;
6160
6482
  }
6161
- /** What `addComparison` hands back: one instrument on the chart. */
6162
- interface ComparisonHandle {
6163
- readonly symbol: string;
6483
+ /**
6484
+ * The slice of the chart this controller needs. `Chart` satisfies it; declaring
6485
+ * it structurally keeps the controller testable against a stub and free of a
6486
+ * circular import back into core.
6487
+ */
6488
+ interface ReplayChartHost {
6489
+ emit(event: string, payload: unknown): void;
6490
+ readonly timeScale: ReplayViewport;
6164
6491
  /**
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`).
6492
+ * Optional. When the chart can name its own primary price series, `series`
6493
+ * may be omitted and replay drives that one.
6168
6494
  */
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[];
6495
+ primarySeries?(): SeriesApi | null;
6181
6496
  }
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
- }[];
6497
+ /** Everything a transport bar and a clock need, in one object. */
6498
+ interface ReplayState {
6499
+ /** 0-based newest bar; -1 before the first observation in timed replay. */
6500
+ index: number;
6501
+ /** Bars in the replay set (the scrub bar's maximum is `total - 1`). */
6502
+ total: number;
6503
+ playing: boolean;
6504
+ /** Multiplier over `barMs`: 2 plays two bars per `barMs`. */
6505
+ speed: number;
6506
+ /**
6507
+ * The bar at `index`, the replay clock's "now". Null when there is no data.
6508
+ *
6509
+ * Under intra-bar replay this is the **partial** bar as it stands this step,
6510
+ * not the completed one, which is what makes it the right thing to drive a
6511
+ * host's own forming volume bar or an OHLC readout from.
6512
+ */
6513
+ bar: Bar | null;
6514
+ /**
6515
+ * Which step of the forming bar this is, 0-based, and how many it takes. Both
6516
+ * are 0 and 1 without `subBars`, so a transport that shows them needs no
6517
+ * special case for the plain mode.
6518
+ */
6519
+ subIndex: number;
6520
+ subSteps: number;
6192
6521
  }
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
- };
6522
+ interface ReplayOptions {
6523
+ /** Explicit candle availability for time-aligned replay. Omitted preserves index-based replay. */
6524
+ timing?: ReplayTiming;
6525
+ /** Initial UTC availability time. Requires timing; otherwise startIndex selects a completed candle. */
6526
+ startTime?: number;
6527
+ /** False prepares and validates the snapshot without changing the chart. Default true. */
6528
+ autoStart?: boolean;
6529
+ /**
6530
+ * The series replay drives. The first one owns the timeline; any others (a
6531
+ * volume histogram, a comparison line) are truncated to the same instant by
6532
+ * time, because the shared DataLayer merges *every* series onto one axis and
6533
+ * a series left at full length would drag future timestamps back onto it.
6534
+ *
6535
+ * Optional only if the host chart implements `primarySeries()`.
6536
+ */
6537
+ series?: SeriesApi | readonly SeriesApi[];
6538
+ /**
6539
+ * The full session. Defaults to the primary series' current data. Never
6540
+ * mutated, and never handed to the chart as-is: each frame gets its own slice.
6541
+ */
6542
+ bars?: readonly Bar[];
6543
+ /**
6544
+ * Finer-grained bars the displayed ones are built from: the 1-minute session
6545
+ * under a 5-minute chart. Given these, the playhead advances a **sub-bar** at
6546
+ * a time and the newest bar forms in front of the user the way a live one
6547
+ * does, instead of appearing complete. A 5-minute bar over 1-minute data
6548
+ * takes five steps.
6549
+ *
6550
+ * They must cover the same session as `bars` and be sorted by time; sub-bars
6551
+ * outside any displayed bucket are ignored. The last step of a bucket emits
6552
+ * the displayed bar **verbatim** rather than the aggregate, so a bucket always
6553
+ * closes on exactly the number the chart would have shown without this option,
6554
+ * whatever the two feeds disagree about in between.
6555
+ *
6556
+ * Only the first driven series forms partially. Followers are cut to completed
6557
+ * buckets, because the controller cannot know how to half-aggregate an
6558
+ * arbitrary one: a volume histogram is summed, not OHLC-merged. The partial
6559
+ * bar reaches the host as `ReplayState.bar` on every frame, so a host that
6560
+ * wants a growing volume bar writes it from there.
6561
+ */
6562
+ subBars?: readonly Bar[];
6563
+ /** Bar to open at (0-based). Default 0. */
6564
+ startIndex?: number;
6565
+ /** Wall-clock milliseconds per bar at speed 1. Default 1000. */
6566
+ barMs?: number;
6567
+ /** Initial speed multiplier. Default 1. */
6568
+ speed?: number;
6569
+ /** Called on every playhead move, after the chart has been updated. */
6570
+ onFrame?: (state: ReplayState) => void;
6571
+ /** Playback clock. Default `performance.now`. */
6572
+ now?: () => number;
6573
+ /** Playback timer. Default `setInterval`. */
6574
+ scheduler?: ReplayScheduler;
6207
6575
  }
6208
- declare class ComparisonController {
6576
+ declare class ReplayController {
6209
6577
  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;
6578
+ private readonly _series;
6579
+ private readonly _bars;
6580
+ /** What each driven series held before replay took over, for `stop()`. */
6581
+ private readonly _restore;
6582
+ private readonly _view;
6583
+ private readonly _onFrame;
6584
+ private readonly _now;
6585
+ private readonly _schedule;
6586
+ private readonly _barMs;
6587
+ private readonly _startIndex;
6588
+ private readonly _timeline;
6589
+ private readonly _startTime;
6590
+ private _time;
6591
+ private _pointIndex;
6592
+ private readonly _subBars;
6214
6593
  /**
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`).
6594
+ * For each displayed bar, where its sub-bars start in `_subBars` and how many
6595
+ * there are. Built once: the alternative is a binary search per step, on a
6596
+ * path that runs on every played frame.
6597
+ *
6598
+ * A bucket with no sub-bars takes one step and shows the displayed bar, so a
6599
+ * gap in the finer feed costs that bar its formation, not its existence.
6218
6600
  */
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;
6601
+ private readonly _subStart;
6602
+ private readonly _subCount;
6603
+ private readonly _intra;
6604
+ private _speed;
6605
+ private _index;
6606
+ /** Step within the forming bar, 0-based. Always 0 without `subBars`. */
6607
+ private _sub;
6608
+ private _playing;
6609
+ /** True once replay owns the chart's data; false before start and after stop. */
6610
+ private _active;
6611
+ private _cancel;
6612
+ private _interval;
6613
+ /** Clock reading the last advance was charged to; keeps playback drift-free. */
6614
+ private _lastAdvance;
6231
6615
  /**
6232
- * Change the mode the panes are held in while comparisons are on them.
6233
- * Panes that already have one switch immediately.
6616
+ * Constructing the controller **enters replay**: it snapshots the chart's data
6617
+ * and viewport and immediately shows `startIndex`. That is the gesture a
6618
+ * replay button makes, and it means the snapshot is taken before anything has
6619
+ * moved, so `stop()` can put the user back exactly where they were.
6234
6620
  */
6235
- setMode(mode: ComparisonMode): void;
6236
- get mode(): ComparisonMode;
6621
+ constructor(chart: ReplayChartHost, options?: ReplayOptions);
6237
6622
  /**
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.
6623
+ * Map every sub-bar onto the displayed bar whose bucket it falls in.
6624
+ *
6625
+ * The displayed bars' own times are the bucket starts, so one forward pass
6626
+ * over both sorted arrays does it. A sub-bar before the first displayed bar
6627
+ * belongs to no bucket and is dropped rather than folded into bar 0, which
6628
+ * would open that bar at a price the chart never showed.
6242
6629
  */
6243
- realign(): void;
6630
+ private _buildBuckets;
6631
+ /** How many steps the bar at `index` takes. At least one, always. */
6632
+ private _steps;
6244
6633
  /**
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.
6634
+ * Jump the playhead to a bar. Out-of-range values clamp to the session.
6248
6635
  *
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.
6636
+ * A seek lands on a **complete** bar even under intra-bar replay: scrubbing to
6637
+ * a half-formed candle is not a position anyone asks for, and it would make
6638
+ * the same slider position mean different things on the way past.
6255
6639
  */
6256
- sync(): void;
6257
- /** Remove every comparison and forget the chart. */
6258
- destroy(): void;
6640
+ seek(index: number): void;
6641
+ /** Project only observations available by these UTC seconds. Requires timing. */
6642
+ seekTime(time: number): void;
6643
+ /** Availability clock in timed mode; the displayed bar's timestamp in legacy mode. */
6644
+ time(): number | null;
6645
+ /** Observation timestamps for a shared clock. Requires timing; returns a copy. */
6646
+ timePoints(): readonly number[];
6259
6647
  /**
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.
6648
+ * Move `n` steps forward. Stops dead at the end of the session.
6649
+ *
6650
+ * A step is one displayed bar normally, and one sub-bar under intra-bar
6651
+ * replay, so the transport button means "advance the chart by the smallest
6652
+ * amount it can show" in both modes.
6267
6653
  */
6268
- private _scaleIdFor;
6269
- private _openPane;
6270
- private _closePane;
6271
- private _applyMode;
6272
- private _restoreMode;
6654
+ step(n?: number): void;
6655
+ /** Move `n` steps back. Stops dead at the first bar. */
6656
+ stepBack(n?: number): void;
6273
6657
  /**
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.
6658
+ * Move the playhead `delta` steps, carrying across bar boundaries.
6289
6659
  *
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.
6660
+ * Written as a walk rather than arithmetic on a flat step count because
6661
+ * buckets hold different numbers of sub-bars: a session with a gap, or a
6662
+ * partial first bucket, has no constant steps-per-bar to divide by.
6295
6663
  */
6296
- private _mirror;
6297
- private _primaryBars;
6298
- private _align;
6664
+ private _advance;
6299
6665
  /**
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.
6666
+ * Start (or re-speed) playback. Calling it on the last bar plays nothing and
6667
+ * reports `replay:end` instead of arming a timer that could never advance.
6306
6668
  */
6307
- private _repaint;
6669
+ play(options?: {
6670
+ speed?: number;
6671
+ }): void;
6672
+ /** Halt playback, leaving the playhead (and the chart) where it is. */
6673
+ pause(): void;
6674
+ /**
6675
+ * Leave replay: every driven series gets its pre-replay data back and the
6676
+ * viewport is restored to the pixel, so a user who exits does not lose their
6677
+ * place. Safe to call twice; a later `seek`/`step`/`play` re-enters replay
6678
+ * from `startIndex`.
6679
+ */
6680
+ stop(): void;
6681
+ state(): ReplayState;
6682
+ /**
6683
+ * Put the chart at `index`. Every transition goes through here (seek, step
6684
+ * and each played frame alike), so arriving at a bar leaves the chart in the
6685
+ * same state however the user got there.
6686
+ */
6687
+ private _apply;
6688
+ private _write;
6689
+ /**
6690
+ * One timer tick. The number of bars owed comes from the clock, not from the
6691
+ * tick count, so a coarse or throttled timer still plays at the requested
6692
+ * speed, and a host may drive this from rAF instead with no change here.
6693
+ */
6694
+ private readonly _tick;
6695
+ /** True on the last step of the last bar, which is where playback stops. */
6696
+ private _atEnd;
6697
+ /** The session is over: stop the timer and tell the host once. */
6698
+ private _end;
6699
+ private _stopTimer;
6700
+ }
6701
+
6702
+ /** True while replay owns this chart, including when its playback clock is paused. */
6703
+ declare function isReplaying(chart: object): boolean;
6704
+
6705
+ type ReplayScope = 'focused' | 'all';
6706
+ /** Chart implements this; headless hosts may supply the lifecycle hooks too. */
6707
+ interface ReplayGroupChartHost extends ReplayChartHost {
6708
+ readonly isDestroyed?: boolean;
6709
+ on?(event: 'destroy', callback: () => void): () => void;
6710
+ }
6711
+ interface ReplayGroupMember {
6712
+ id: string;
6713
+ chart: ReplayGroupChartHost;
6714
+ /** Transport and clock options belong to the group. */
6715
+ options: Pick<ReplayOptions, 'series' | 'bars' | 'subBars' | 'onFrame'> & {
6716
+ timing: ReplayTiming;
6717
+ };
6718
+ }
6719
+ interface ReplayGroupState {
6720
+ active: boolean;
6721
+ destroyed: boolean;
6722
+ scope: ReplayScope;
6723
+ focusedId: string;
6724
+ /** UTC availability time, or null when the active histories have no observations. */
6725
+ time: number | null;
6726
+ /** Index in the union of active observation times; -1 before the first. */
6727
+ index: number;
6728
+ total: number;
6729
+ playing: boolean;
6730
+ speed: number;
6731
+ members: readonly {
6732
+ id: string;
6733
+ active: boolean;
6734
+ state: ReplayState;
6735
+ }[];
6736
+ }
6737
+ interface ReplayGroupOptions {
6738
+ /** Default focused. Changing toolbar focus alone does not redirect replay. */
6739
+ scope?: ReplayScope;
6740
+ /** Default the first member. */
6741
+ focusedId?: string;
6742
+ startTime?: number;
6743
+ /** Wall-clock milliseconds per observation at speed 1. Default 1000. */
6744
+ barMs?: number;
6745
+ speed?: number;
6746
+ now?: () => number;
6747
+ scheduler?: ReplayScheduler;
6748
+ /** Runs after all active charts reach a frame or transport transition. */
6749
+ onChange?: (state: ReplayGroupState) => void;
6750
+ }
6751
+ /** One opt-in clock driving validated ReplayControllers by availability time. */
6752
+ declare class ReplayGroup {
6753
+ private _members;
6754
+ private _activeIds;
6755
+ private _times;
6756
+ private _scope;
6757
+ private _focusedId;
6758
+ private _time;
6759
+ private _startTime;
6760
+ private _active;
6761
+ private _destroyed;
6762
+ private _playing;
6763
+ private _busy;
6764
+ private _closing;
6765
+ private _revision;
6766
+ private _speed;
6767
+ private readonly _barMs;
6768
+ private readonly _now;
6769
+ private readonly _scheduler;
6770
+ private readonly _onChange?;
6771
+ private _cancel;
6772
+ private _clockRevision;
6773
+ private _lastAdvance;
6774
+ private _pending;
6775
+ constructor(members: readonly ReplayGroupMember[], options?: ReplayGroupOptions);
6776
+ state(): ReplayGroupState;
6777
+ seekTime(time: number): void;
6778
+ /** Seek an observation index, clamped to the active timeline. */
6779
+ seek(index: number): void;
6780
+ step(n?: number): void;
6781
+ stepBack(n?: number): void;
6782
+ private _move;
6783
+ setScope(scope: ReplayScope, focusedId?: string): void;
6784
+ play(options?: {
6785
+ speed?: number;
6786
+ }): void;
6787
+ pause(): void;
6788
+ /** Restore data/viewports, retaining the captured session for later re-entry. */
6789
+ stop(): void;
6790
+ /** Restore surviving charts and release snapshots, listeners and ownership. */
6791
+ destroy(): void;
6792
+ private _validateScope;
6793
+ private _prepare;
6794
+ private _prepareEntering;
6795
+ private _install;
6796
+ private _wanted;
6797
+ private _rebuildTimes;
6798
+ private _check;
6799
+ private _apply;
6800
+ private _atEnd;
6801
+ private _tick;
6802
+ private _clearClock;
6803
+ private _emit;
6804
+ private _notify;
6805
+ private _execute;
6806
+ private _chartGone;
6807
+ private _cleanup;
6308
6808
  }
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
6809
 
6326
6810
  /**
6327
6811
  * Time alignment for chart linking: the one thing that makes a grid of charts
@@ -6409,7 +6893,7 @@ declare function followerRange(leader: LinkDataLayer, follower: LinkDataLayer, r
6409
6893
  * ships no DOM, so the host draws its own link badge / colour chips and decides
6410
6894
  * which charts belong to which group.
6411
6895
  *
6412
- * Three channels sync, each switchable on its own because a user routinely
6896
+ * Four channels sync, each switchable on its own because a user routinely
6413
6897
  * wants one without the others (mirror the cursor across four timeframes but
6414
6898
  * keep each zoom; or slave every chart's symbol but let each keep its own
6415
6899
  * window):
@@ -6417,6 +6901,7 @@ declare function followerRange(leader: LinkDataLayer, follower: LinkDataLayer, r
6417
6901
  * - **crosshair**: hovering one chart marks the same instant on the others.
6418
6902
  * - **viewport**: panning or zooming one moves the others to the same window.
6419
6903
  * - **symbol**: changing the instrument on one changes it on the others.
6904
+ * - **interval**: changing a timeframe asks each following host to adopt it.
6420
6905
  *
6421
6906
  * Four decisions carry the design.
6422
6907
  *
@@ -6476,6 +6961,8 @@ interface LinkChart {
6476
6961
  panes(): readonly unknown[];
6477
6962
  addPrimitive(primitive: IPrimitive, paneIndex?: number): void;
6478
6963
  removePrimitive(primitive: IPrimitive): void;
6964
+ /** Optional readout support alongside the group's vertical marker. */
6965
+ setLinkedCrosshairIndex?(index: number | null): void;
6479
6966
  }
6480
6967
  interface LinkOptions {
6481
6968
  /** Mirror the hovered instant onto every other member. Default true. */
@@ -6484,6 +6971,8 @@ interface LinkOptions {
6484
6971
  viewport?: boolean;
6485
6972
  /** Mirror the instrument, via each member's `onSymbol`. Default false. */
6486
6973
  symbol?: boolean;
6974
+ /** Mirror the timeframe, via each member's `onInterval`. Default false. */
6975
+ interval?: boolean;
6487
6976
  /** What a follower does with an instant it has no bar for. Default 'nearest'. */
6488
6977
  whenMissing?: LinkMissingPolicy;
6489
6978
  }
@@ -6496,6 +6985,10 @@ interface LinkMemberOptions {
6496
6985
  * it still broadcasts its own changes, it just never follows anyone else's.
6497
6986
  */
6498
6987
  onSymbol?: (symbol: string, chart: LinkChart) => void;
6988
+ /** The interval this chart is showing, if the host tracks one. */
6989
+ interval?: string;
6990
+ /** Apply the interval synchronously; return false to refuse an unsupported token. */
6991
+ onInterval?: (interval: string, chart: LinkChart) => boolean | void;
6499
6992
  }
6500
6993
  /** Every option resolved, as `options()` reports them. */
6501
6994
  type ResolvedLinkOptions = Required<LinkOptions>;
@@ -6505,6 +6998,7 @@ declare class LinkGroup {
6505
6998
  /** True while the group is applying a change to followers. See decision 3. */
6506
6999
  private _broadcasting;
6507
7000
  private _symbol;
7001
+ private _interval;
6508
7002
  private _destroyed;
6509
7003
  constructor(options?: LinkOptions);
6510
7004
  options(): ResolvedLinkOptions;
@@ -6524,6 +7018,8 @@ declare class LinkGroup {
6524
7018
  has(chart: LinkChart): boolean;
6525
7019
  /** The instrument the group has agreed on, or null if nobody declared one. */
6526
7020
  symbol(): string | null;
7021
+ /** Latest interval selected by a member, even while interval linking is off. */
7022
+ interval(): string | null;
6527
7023
  /**
6528
7024
  * The member's own logical index its linked crosshair is marking, or null
6529
7025
  * when it is showing none (it is the chart being hovered, the leader's
@@ -6551,6 +7047,8 @@ declare class LinkGroup {
6551
7047
  * symbol sync is on, loads it into every other member that can follow.
6552
7048
  */
6553
7049
  setSymbol(chart: LinkChart, symbol: string): void;
7050
+ /** Report a host-owned interval selection; followers opt in through `onInterval`. */
7051
+ setInterval(chart: LinkChart, interval: string): void;
6554
7052
  /** Unlink everything: no listeners, no linked crosshairs, no references. */
6555
7053
  destroy(): void;
6556
7054
  private _onCrosshair;
@@ -6559,6 +7057,9 @@ declare class LinkGroup {
6559
7057
  private _applySymbol;
6560
7058
  /** Push the group's agreed symbol onto everyone who is not already on it. */
6561
7059
  private _convergeSymbol;
7060
+ private _applyInterval;
7061
+ private _followInterval;
7062
+ private _convergeInterval;
6562
7063
  /**
6563
7064
  * Run `apply` on every member except the originator, exactly once, with the
6564
7065
  * echo guard held. `onLeader` (optional) runs on the originator itself.
@@ -6647,6 +7148,14 @@ interface Tick {
6647
7148
  ltq?: number;
6648
7149
  /** Cumulative day volume (Quote mode). */
6649
7150
  cumDayVolume?: number;
7151
+ /**
7152
+ * Open interest as at this tick, where the feed carries it. Unlike the two
7153
+ * quantities above it is not accumulated into the bar: it replaces, because
7154
+ * it is a level and not a flow (see `Bar.oi`). A feed that does not report it
7155
+ * leaves the built bar without one, which is the honest result rather than a
7156
+ * zero that reads as "nobody is holding this".
7157
+ */
7158
+ oi?: number;
6650
7159
  }
6651
7160
  interface CandleUpdate {
6652
7161
  bar: Bar;
@@ -7270,6 +7779,86 @@ interface TradeFeed {
7270
7779
  subscribePositions(cb: (positions: unknown[]) => void): UnsubscribeFn;
7271
7780
  }
7272
7781
 
7782
+ interface InstrumentCalendar {
7783
+ /** HHMM-HHMM[:days], with opening weekdays 1 (Sunday) through 7. */
7784
+ readonly sessions: readonly string[];
7785
+ /** Local opening dates replace weekly sessions. An empty list closes that date. */
7786
+ readonly exceptions?: Readonly<Record<string, readonly string[]>>;
7787
+ }
7788
+ /** Host-supplied market rules, separate from observations and saved user preferences. */
7789
+ interface InstrumentMetadata {
7790
+ readonly symbol: string;
7791
+ readonly exchange: string;
7792
+ readonly timezone: string;
7793
+ readonly priceTick: number;
7794
+ readonly pricePrecision: number;
7795
+ /** In the order adapter's units. This is a grid, never a lot conversion factor. */
7796
+ readonly quantityStep: number;
7797
+ readonly intervals: readonly string[];
7798
+ readonly calendar: InstrumentCalendar;
7799
+ readonly hasOpenInterest?: boolean;
7800
+ }
7801
+ interface InstrumentSession {
7802
+ /** Local opening date, including for an overnight window. */
7803
+ readonly date: string;
7804
+ /** UTC seconds, inclusive. */
7805
+ readonly open: number;
7806
+ /** UTC seconds, exclusive. */
7807
+ readonly close: number;
7808
+ }
7809
+ /** Validated, detached rules. Construction does not change global intervals or chart defaults. */
7810
+ declare class Instrument {
7811
+ readonly metadata: InstrumentMetadata;
7812
+ private readonly _sessions;
7813
+ private readonly _exceptions;
7814
+ constructor(input: unknown);
7815
+ /** Provider tokens are exact: a feed may distinguish monthly M from minute m. */
7816
+ supportsInterval(code: string): boolean;
7817
+ /** Formats display only. The source bars retain their unrounded values. */
7818
+ formatPrice(value: number): string;
7819
+ /** Resolve an active window. Closed dates and breaks return null, never inferred hours. */
7820
+ sessionAt(utcSeconds: number): InstrumentSession | null;
7821
+ /** The host clears old bars and owns source loading; only metadata is applied here. */
7822
+ applyTo(chart: Chart, interval: string): void;
7823
+ }
7824
+
7825
+ type TradingOperation = 'place' | 'modify' | 'cancel';
7826
+ /** Optional restrictions on existing write paths; omissions preserve legacy support. */
7827
+ interface TradingCapabilities {
7828
+ readonly place?: boolean | 'unknown';
7829
+ readonly modify?: boolean | 'unknown';
7830
+ readonly cancel?: boolean | 'unknown';
7831
+ /** Accepted types for new orders. An empty list disables placement. */
7832
+ readonly orderTypes?: readonly OrderType$1[];
7833
+ /** Accepted modes for new orders; does not change the broker's actual mode. */
7834
+ readonly modes?: readonly ('live' | 'analyzer')[];
7835
+ }
7836
+ interface TradingCapabilityRequest {
7837
+ readonly operation: TradingOperation;
7838
+ readonly symbol?: string;
7839
+ readonly exchange?: string;
7840
+ readonly orderId?: string;
7841
+ readonly type?: OrderType$1;
7842
+ readonly mode?: 'live' | 'analyzer';
7843
+ }
7844
+ /** A configured provider returning undefined declares that support is unavailable. */
7845
+ type TradingCapabilitySource = TradingCapabilities | ((request: Readonly<TradingCapabilityRequest>) => TradingCapabilities | undefined);
7846
+ type TradingCapabilityResult = {
7847
+ supported: true;
7848
+ } | {
7849
+ supported: false;
7850
+ reason: string;
7851
+ };
7852
+ /** Shared by host controls and the write boundary. Never grants broker authority. */
7853
+ declare function checkTradingCapability(source: TradingCapabilitySource | undefined, request: TradingCapabilityRequest): TradingCapabilityResult;
7854
+ /** Raised only before delivery, so an unsupported write does not become ambiguous. */
7855
+ declare class TradingCapabilityError extends Error {
7856
+ readonly preflight: true;
7857
+ readonly operation: TradingOperation;
7858
+ constructor(operation: TradingOperation, reason: string);
7859
+ }
7860
+ declare function assertTradingCapability(source: TradingCapabilitySource | undefined, request: TradingCapabilityRequest): void;
7861
+
7273
7862
  /** Limits apply to each pool, whose identity belongs to one data feed. */
7274
7863
  interface HistoryRequestPoolOptions {
7275
7864
  maxConcurrent?: number;
@@ -7443,6 +8032,12 @@ interface OpenAlgoConfig {
7443
8032
  apiKey: string;
7444
8033
  /** Injectable fetch (defaults to global fetch); lets the adapter be tested offline. */
7445
8034
  fetchImpl?: typeof fetch;
8035
+ /**
8036
+ * Instrument capability from host metadata. Explicit false omits the API's
8037
+ * placeholder OI column before it can become a misleading cash reading.
8038
+ * Missing/unknown capability preserves finite observations, including zero.
8039
+ */
8040
+ hasOpenInterest?(request: Readonly<BarsRequest>): boolean | undefined;
7446
8041
  }
7447
8042
  interface HistoryRow {
7448
8043
  timestamp?: number | string;
@@ -7452,6 +8047,8 @@ interface HistoryRow {
7452
8047
  low: number;
7453
8048
  close: number;
7454
8049
  volume?: number;
8050
+ /** Present on a derivatives response; the platform always sends the column. */
8051
+ oi?: number;
7455
8052
  }
7456
8053
  interface HistoryResponse {
7457
8054
  status?: string;
@@ -7461,7 +8058,7 @@ interface HistoryResponse {
7461
8058
  /** Pure: coerce a row timestamp (epoch s / epoch ms / IST string) to UTC seconds. */
7462
8059
  declare function rowTimeToUtcSeconds(value: number | string): number;
7463
8060
  /** Pure: map an OpenAlgo history response into sorted internal bars. */
7464
- declare function mapHistoryResponse(json: HistoryResponse): Bar[];
8061
+ declare function mapHistoryResponse(json: HistoryResponse, hasOpenInterest?: boolean): Bar[];
7465
8062
  declare class OpenAlgoDataFeed implements DataFeed {
7466
8063
  private readonly _config;
7467
8064
  private readonly _fetch;
@@ -7925,6 +8522,8 @@ interface ModifyPatch {
7925
8522
  * implement `OrderFeed` for the engine's write path.
7926
8523
  */
7927
8524
  interface OrderFeed {
8525
+ /** Optional support declaration; a configured provider can report unavailable metadata. */
8526
+ readonly capabilities?: TradingCapabilitySource$1;
7928
8527
  place(req: PlaceRequest & {
7929
8528
  mode: TradeMode;
7930
8529
  }): Promise<{
@@ -7960,6 +8559,8 @@ type ModeCheck = 'off' | 'auto' | 'always';
7960
8559
  interface OpenAlgoTradeConfig {
7961
8560
  baseUrl: string;
7962
8561
  apiKey: string;
8562
+ /** Optional host/broker restrictions; omitted preserves the existing OpenAlgo path. */
8563
+ capabilities?: TradingCapabilitySource;
7963
8564
  /**
7964
8565
  * Instrument constraints for the advisory pre-trade check on `place`, looked
7965
8566
  * up per order. Return undefined when the instrument is unknown; the
@@ -8085,7 +8686,8 @@ declare class OpenAlgoTradeFeed implements OrderFeed {
8085
8686
  * or a retried promise does not become two orders.
8086
8687
  */
8087
8688
  private _claimToken;
8088
- place(req: PlaceRequest & {
8689
+ get capabilities(): TradingCapabilitySource | undefined;
8690
+ place(request: PlaceRequest & {
8089
8691
  mode: TradeMode;
8090
8692
  }): Promise<{
8091
8693
  orderId: string;
@@ -8106,7 +8708,7 @@ declare class OpenAlgoTradeFeed implements OrderFeed {
8106
8708
  * that an ambiguous order did not reach the exchange.
8107
8709
  */
8108
8710
  releaseToken(token: string): void;
8109
- modify(orderId: string, patch: {
8711
+ modify(orderId: string, changes: {
8110
8712
  price?: number;
8111
8713
  triggerPrice?: number;
8112
8714
  qty?: number;
@@ -8533,6 +9135,11 @@ interface AggTick {
8533
9135
  time: number;
8534
9136
  price: number;
8535
9137
  qty: number;
9138
+ /**
9139
+ * Open interest as at this tick, where the feed carries it. Carried onto the
9140
+ * bar as the latest reading rather than accumulated: see `Bar.oi`.
9141
+ */
9142
+ oi?: number;
8536
9143
  }
8537
9144
  interface BarUpdate {
8538
9145
  bar: Bar;
@@ -8957,4 +9564,67 @@ declare function lerp(a: number, b: number, t: number): number;
8957
9564
  */
8958
9565
  declare function roundToTick(value: number, step: number): number;
8959
9566
 
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 };
9567
+ /** Headless, chart-owned trader alerts. Hosts subscribe to alert:triggered for delivery. */
9568
+ declare class AlertController {
9569
+ private readonly _chart;
9570
+ private readonly _records;
9571
+ private readonly _off;
9572
+ private readonly _now;
9573
+ private readonly _drawings;
9574
+ private readonly _visuals;
9575
+ private _timer;
9576
+ private _timerAt;
9577
+ private _paused;
9578
+ private _restoring;
9579
+ private _replay;
9580
+ private _destroyed;
9581
+ private _revision;
9582
+ constructor(_chart: AlertChartHost, options?: AlertControllerOptions);
9583
+ add(input: AlertInput): Alert;
9584
+ update(id: string, patch: AlertPatch): Alert | undefined;
9585
+ remove(id: string): boolean;
9586
+ private _remove;
9587
+ list(): Alert[];
9588
+ toJSON(): AlertsDocument;
9589
+ /** Validate the complete replacement before touching current alerts or their visuals. */
9590
+ fromJSON(input: unknown): void;
9591
+ private _saveState;
9592
+ /** Availability is independent of lifecycle state; disabled records can still have valid anchors. */
9593
+ availability(id: string): AlertAvailability;
9594
+ enable(id: string): Alert | undefined;
9595
+ disable(id: string): Alert | undefined;
9596
+ /** Explicit host pause is independent of replay and never delivers a backlog. */
9597
+ setPaused(paused: boolean): void;
9598
+ destroy(): void;
9599
+ private _assertAlive;
9600
+ private _seed;
9601
+ private _seedAll;
9602
+ private _onObjects;
9603
+ private _syncVisual;
9604
+ private _onData;
9605
+ private _plot;
9606
+ private _reading;
9607
+ private _closedMatch;
9608
+ private _touchMatch;
9609
+ private _barMatch;
9610
+ private _drawingValue;
9611
+ private _drawingTouch;
9612
+ private _trigger;
9613
+ private _expireDue;
9614
+ private _clearTimer;
9615
+ private _scheduleExpiry;
9616
+ }
9617
+
9618
+ /** Shared editor fields in source units. Drawing bands own their bounds. */
9619
+ declare function alertSettingsSchema(source: AlertSource, condition?: AlertCondition): readonly IndicatorInput[];
9620
+
9621
+ /** Validate and detach a version-1 document, or migrate a legacy bare list. */
9622
+ declare function parseAlertsDocument(input: unknown): AlertsDocument;
9623
+
9624
+ /** Register runtime code by stable id; persisted alerts store only that id. */
9625
+ declare function registerBarCondition(condition: BarCondition): void;
9626
+ declare function unregisterBarCondition(id: string): boolean;
9627
+ declare function getBarCondition(id: string): Readonly<BarCondition> | undefined;
9628
+ declare function registeredBarConditions(): readonly Readonly<BarCondition>[];
9629
+
9630
+ 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 };