openalgo-charts 2.4.5 → 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/README.md CHANGED
@@ -4,12 +4,12 @@
4
4
 
5
5
  **A from-scratch, dependency-free HTML5-canvas charting engine for OpenAlgo.**
6
6
 
7
- Professional interactive charts, 105 built-in indicators plus your own custom ones, 85 drawing tools, order flow, market replay, linked chart grids, on-chart trading, vector SVG export and an optional WebGL2 backend. Nine lazy-loaded tiers, zero runtime dependencies, 91.00 kB Brotli for the base engine, and a one-call widget tier that adds the toolbar, drawing rail, dialogs and shortcuts.
7
+ Professional interactive charts, 105 built-in indicators plus your own custom ones, 85 drawing tools, order flow, market replay, linked chart grids, on-chart trading, vector SVG export and an optional WebGL2 backend. Nine lazy-loaded tiers, zero runtime dependencies, 91.55 kB Brotli for the base engine, and a one-call widget tier that adds the toolbar, drawing rail, dialogs and shortcuts.
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/openalgo-charts.svg?color=cb3837&label=npm)](https://www.npmjs.com/package/openalgo-charts)
10
10
  [![license](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)
11
- [![bundle](https://img.shields.io/badge/brotli-91.00%20kB%20base%20%C2%B7%20243.96%20kB%20all%20tiers-brightgreen.svg)](#size-budget)
12
- [![tests](https://img.shields.io/badge/engine%20tests-5734%20passing-brightgreen.svg)](#develop)
11
+ [![bundle](https://img.shields.io/badge/brotli-91.55%20kB%20base%20%C2%B7%20245.21%20kB%20all%20tiers-brightgreen.svg)](#size-budget)
12
+ [![tests](https://img.shields.io/badge/engine%20tests-5804%20passing-brightgreen.svg)](#develop)
13
13
  [![dependencies](https://img.shields.io/badge/runtime%20deps-0-brightgreen.svg)](#principles)
14
14
 
15
15
  [**Documentation**](https://marketcalls.github.io/openalgo-charts/)  ·  [**Live examples**](https://marketcalls.github.io/openalgo-charts/examples)  ·  [**Getting started**](./docs/getting-started.md)  ·  [**Migrating to 2.0**](./docs/migrating-to-2.md)  ·  [**Architecture**](./ARCHITECTURE.md)
@@ -53,13 +53,17 @@ Every chart in the [live gallery](https://marketcalls.github.io/openalgo-charts/
53
53
 
54
54
  ## Install
55
55
 
56
- Current version: **2.4.5**.
56
+ Current version: **2.4.6**.
57
57
 
58
- This release adds validated instrument metadata, adapter conformance checks,
59
- browser endurance tooling, translated widget controls and trading capability
60
- checks. Named workspace documents and indicator templates use an optional
61
- asynchronous storage tier. Existing hosts can adopt these contracts incrementally.
62
- See the [2.4.5 changelog](./CHANGELOG.md#245).
58
+ This release adds optional study source buttons, candle-anchored signal markers
59
+ and configurable legend controls. It fixes missing markers at plot gaps, marker
60
+ price scales and transparent legend readings. Existing studies keep plot anchoring
61
+ by default; hosts opt in to price anchoring and supply their own source editor.
62
+ See the [2.4.6 changelog](./CHANGELOG.md#246).
63
+
64
+ To try these controls locally, run the [yfinance reference host](./examples/yfinance/README.md)
65
+ and add **Source signal sample** from Indicators. Its source button opens the
66
+ host-owned code, and Readout settings include **Legend button size** for each chart.
63
67
 
64
68
  ```bash
65
69
  npm install openalgo-charts
@@ -88,7 +92,7 @@ in front of npm rather than being places you upload to. A chart is one HTML file
88
92
  ```html
89
93
  <div id="chart" style="width:100vw;height:100vh"></div>
90
94
  <script type="module">
91
- import { createChart } from 'https://unpkg.com/openalgo-charts@2.4.5/dist/openalgo-charts.mjs';
95
+ import { createChart } from 'https://unpkg.com/openalgo-charts@2.4.6/dist/openalgo-charts.mjs';
92
96
  const chart = createChart(document.getElementById('chart'), { timezone: 'Asia/Kolkata' });
93
97
  chart.addSeries('candlestick').setData(bars);
94
98
  </script>
@@ -130,17 +134,17 @@ Import only what you use. Each tier is a separate bundle that registers into the
130
134
 
131
135
  | Import | Contents | Brotli |
132
136
  |---|---|---|
133
- | `openalgo-charts` | Engine, 13 chart types, panes &amp; scales, primitives, registries, chart state, chart linking, bar cache, interval registry, trading overlay, SVG export, render backend port, OpenAlgo feeds | 91.00 kB |
137
+ | `openalgo-charts` | Engine, 13 chart types, panes &amp; scales, primitives, registries, chart state, chart linking, bar cache, interval registry, trading overlay, SVG export, render backend port, OpenAlgo feeds | 91.55 kB |
134
138
  | `openalgo-charts/indicators` | 105 built-in indicators, the `registerIndicator` contract for your own, and the Tier-2 (external-data) contract | 29.84 kB |
135
139
  | `openalgo-charts/draw` | 85 drawing tools + a headless drawing controller, clipboard, settings schema, level palette, freehand geometry and SVG icons | 35.43 kB |
136
140
  | `openalgo-charts/transform` | Heikin Ashi, Renko, Range bars, Line Break, Point &amp; Figure, Kagi, and symbol arithmetic (`AAPL/MSFT`) | 4.50 kB |
137
141
  | `openalgo-charts/profile` | Volume Profile, Market Profile (TPO) with compact pixel letters, Footprint, order flow | 14.96 kB |
138
142
  | `openalgo-charts/trade` | Order / position / bracket tools + DOM ladder | 8.01 kB |
139
143
  | `openalgo-charts/webgl` | WebGL2 series backend: batched, analytically anti-aliased GPU rendering of the standard chart types behind `renderer: 'auto'`, with a session-long fallback to the 2D path | 6.39 kB |
140
- | `openalgo-charts/widget` | The chart with its chrome in one call: `createWidget` adds a top bar, the drawing rail, a status line, the settings and indicator dialogs, drawing properties, a right-click menu, a keymap with a `?` panel and optional layout persistence. The only tier that ships DOM | 48.30 kB |
144
+ | `openalgo-charts/widget` | The chart with its chrome in one call: `createWidget` adds a top bar, the drawing rail, a status line, the settings and indicator dialogs, drawing properties, a right-click menu, a keymap with a `?` panel and optional layout persistence. The only tier that ships DOM | 49.00 kB |
141
145
  | `openalgo-charts/workspace` | Validated workspace and indicator-template documents, named catalogs with revision checks, asynchronous storage and an IndexedDB adapter; no DOM | 5.52 kB |
142
146
 
143
- Everything together is **243.96 kB Brotli**; a widget terminal with built-in indicators (base + draw + indicators + widget) is 204.58 kB. Figures are measured from the 2.4.5 release build. The trade tier is 8.01 kB on its own; base + trade costs 99.01 kB. Sizes use decimal kB.
147
+ Everything together is **245.21 kB Brotli**; a widget terminal with built-in indicators (base + draw + indicators + widget) is 205.83 kB. Figures are measured from the 2.4.6 release build. The trade tier is 8.01 kB on its own; base + trade costs 99.56 kB. Sizes use decimal kB.
144
148
 
145
149
  ## What's built
146
150
 
@@ -445,17 +449,17 @@ Enforced in CI by [`size-limit`](./.size-limit.json). Nothing is excluded, becau
445
449
 
446
450
  | Bundle | Limit | Actual |
447
451
  |---|---|---|
448
- | Base engine | 91.5 kB | 91.00 kB |
449
- | Base + trade | 100 kB | 99.01 kB |
452
+ | Base engine | 92 kB | 91.55 kB |
453
+ | Base + trade | 100 kB | 99.56 kB |
450
454
  | Indicators tier | 30 kB | 29.84 kB |
451
455
  | Draw tier | 36 kB | 35.43 kB |
452
456
  | Transform tier | 6 kB | 4.50 kB |
453
457
  | Profile tier | 15 kB | 14.96 kB |
454
458
  | WebGL2 tier | 7 kB | 6.39 kB |
455
- | Widget tier | 48.75 kB | 48.30 kB |
456
- | Widget terminal (base + draw + indicators + widget) | 205 kB | 204.58 kB |
459
+ | Widget tier | 49.25 kB | 49.00 kB |
460
+ | Widget terminal (base + draw + indicators + widget) | 206 kB | 205.83 kB |
457
461
  | Workspace tier | 6 kB | 5.52 kB |
458
- | **Everything** | 245 kB | 243.96 kB |
462
+ | **Everything** | 245.5 kB | 245.21 kB |
459
463
 
460
464
  ## Documentation
461
465
 
@@ -500,8 +504,8 @@ See [Contributing](./CONTRIBUTING.md) for setup, targeted checks, documentation
500
504
  ```bash
501
505
  npm install # install dev toolchain
502
506
  npm run typecheck # strict TypeScript check
503
- npm test # engine unit tests (Vitest): 5734 across 242 files
504
- npm run test:demo # reference-host tests: 389 across 30 files
507
+ npm test # engine unit tests (Vitest): 5804 across 246 files
508
+ npm run test:demo # reference-host tests: 401 across 31 files
505
509
  npm run test:endurance # node endurance-harness tests: 7 cases
506
510
  npm run build # Rollup -> dist/ (minified ESM per tier + types)
507
511
  npm run size # size-limit (Brotli) against the budget
@@ -519,7 +523,7 @@ npm run verify # lint + types + unit + endurance harness + build + demo + dt
519
523
 
520
524
  ## Status &amp; limitations
521
525
 
522
- Version **2.4.5**. All engine build phases are implemented. Upgrading a 1.9.x host: [Migrating to 2.0](./docs/migrating-to-2.md).
526
+ Version **2.4.6**. All engine build phases are implemented. Upgrading a 1.9.x host: [Migrating to 2.0](./docs/migrating-to-2.md).
523
527
 
524
528
  Known gaps, stated plainly:
525
529
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { TradingCapabilitySource as TradingCapabilitySource$1 } from 'openalgo-charts';
2
2
 
3
3
  /** Library version string. Matches package.json (including locally prepared releases). */
4
- declare const VERSION = "2.4.5";
4
+ declare const VERSION = "2.4.6";
5
5
  /** Returns the current library version. */
6
6
  declare function version(): string;
7
7
 
@@ -859,6 +859,11 @@ interface IPrimitive {
859
859
  /** Pick the best hit across primitives: nearest distance, then z-order priority. */
860
860
  declare function bestHit(hits: readonly (PrimitiveHit | null)[]): PrimitiveHit | null;
861
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
+
862
867
  /**
863
868
  * `labelUp` / `labelDown` are text plates with a tail, for named signals ("Buy",
864
869
  * "Sell") rather than bare glyphs. The tail points *at* the anchor price and the
@@ -900,10 +905,23 @@ declare function drawShape(ctx: CanvasRenderingContext2D, shape: MarkerShape, cx
900
905
  declare function drawLabel(ctx: CanvasRenderingContext2D, up: boolean, cx: number, anchorY: number, text: string, color: string, fontPx: number): void;
901
906
  declare class SeriesMarkers implements IPrimitive {
902
907
  private readonly _seriesId;
908
+ private readonly _fallbackBars;
909
+ private readonly _priceScale;
903
910
  private _markers;
904
911
  private _host;
905
912
  private _lastPositions;
906
- 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);
907
925
  attached(host: PrimitiveHost): void;
908
926
  detached(): void;
909
927
  zOrder(): ZOrder;
@@ -975,8 +993,14 @@ interface SeriesApi {
975
993
  remove(): void;
976
994
  /** The price scale this series maps to (call `.setOptions({ marginTop, marginBottom })` on it). */
977
995
  priceScale(): PriceScale;
978
- /** Create a markers layer (buy/sell signals, shapes) bound to this series. */
979
- 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;
980
1004
  }
981
1005
 
982
1006
  /**
@@ -1679,7 +1703,7 @@ declare class PriceLine implements IPrimitive {
1679
1703
  * placeholder.
1680
1704
  */
1681
1705
 
1682
- type PaneLegendAction = 'hide' | 'settings' | 'up' | 'down' | 'maximize' | 'close';
1706
+ type PaneLegendAction = 'hide' | 'settings' | 'source' | 'up' | 'down' | 'maximize' | 'close';
1683
1707
  /**
1684
1708
  * One reading on a legend row. Multi-plot sources show one per plot, each in
1685
1709
  * that plot's own color (an MA ribbon's four averages, MACD's three lines) —
@@ -1796,6 +1820,7 @@ interface PaneLegendOptions {
1796
1820
  * `${id}::<action>`:
1797
1821
  * - `up` / `down` — move this pane one slot (`::up` / `::down`)
1798
1822
  * - `hide` — toggle visibility (`::hide`)
1823
+ * - `source`: show the code this source was written from (`::source`)
1799
1824
  * - `maximize` — expand this pane to fill the chart (`::maximize`)
1800
1825
  * - `close` — remove the source, and its pane if it empties (`::close`)
1801
1826
  *
@@ -1810,6 +1835,15 @@ interface PaneLegendOptions {
1810
1835
  maximized?: boolean;
1811
1836
  /** Text size in media px. Default 11. */
1812
1837
  font?: number;
1838
+ /**
1839
+ * Square side of one action button in media px. Default 16.
1840
+ *
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.
1845
+ */
1846
+ iconSize?: number;
1813
1847
  /** Left inset from the plot edge in media px. Default 8. */
1814
1848
  left?: number;
1815
1849
  /** Top inset in media px. Default 6. */
@@ -2018,10 +2052,14 @@ interface IndicatorHost {
2018
2052
  color?: string;
2019
2053
  row: number;
2020
2054
  paneIndex: number;
2055
+ /** The descriptor's `hasSource`, so the row can offer a source button. */
2056
+ hasSource?: boolean;
2021
2057
  }): PaneLegend;
2022
2058
  removeIndicatorLegend(legend: PaneLegend): void;
2023
2059
  /** How many legends already sit on this pane, so rows stack. */
2024
2060
  legendRowsOn(paneIndex: number): number;
2061
+ /** The instrument's own series, for a descriptor anchoring marks to price. */
2062
+ primarySeries?(): SeriesApi | null;
2025
2063
  addIndicatorSeries(type: string, paneIndex: number, style: Record<string, unknown> | undefined, priceScaleId: string | undefined,
2026
2064
  /** Axis/crosshair formatting for the scale this plot maps to. */
2027
2065
  priceFormat?: PriceFormat): SeriesApi;
@@ -2920,6 +2958,20 @@ interface IndicatorDescriptor {
2920
2958
  category?: string;
2921
2959
  /** `'onchart'` overlays the price pane; `'pane'` gets its own pane. */
2922
2960
  placement: 'onchart' | 'pane';
2961
+ /**
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.
2973
+ */
2974
+ hasSource?: boolean;
2923
2975
  inputs: readonly IndicatorInput[];
2924
2976
  plots: readonly IndicatorPlot[];
2925
2977
  /**
@@ -2973,6 +3025,24 @@ interface IndicatorDescriptor {
2973
3025
  values: IndicatorValues;
2974
3026
  settings: Readonly<IndicatorSettings>;
2975
3027
  }): readonly SeriesMarker[];
3028
+ /**
3029
+ * What `aboveBar` and `belowBar` are measured against.
3030
+ *
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.
3044
+ */
3045
+ markerAnchor?: 'plot' | 'price';
2976
3046
  /**
2977
3047
  * Optional summary grid pinned to a corner of the pane.
2978
3048
  *
@@ -4112,6 +4182,11 @@ interface ChartOptions {
4112
4182
  canvas?: CanvasOptions;
4113
4183
  /** Per-field status-line switches applied to every pane legend on the chart. */
4114
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;
4115
4190
  /** Accessible label for the chart container (screen readers). */
4116
4191
  ariaLabel?: string;
4117
4192
  /**
@@ -4458,6 +4533,8 @@ declare class Chart {
4458
4533
  private readonly _canvas;
4459
4534
  /** Status-line switches pushed onto every pane legend, host-added ones included. */
4460
4535
  private readonly _statusLine;
4536
+ /** Legend action-button side in media px; undefined leaves the primitive's default. */
4537
+ private _legendIconSize;
4461
4538
  /** Axis-strip chrome switches. Empty is the shipped chart: neither drawn. */
4462
4539
  private readonly _axisChrome;
4463
4540
  /**
@@ -5012,6 +5089,16 @@ declare class Chart {
5012
5089
  */
5013
5090
  setStatusLineOptions(patch: LegendStatusLineOptions): void;
5014
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;
5015
5102
  /**
5016
5103
  * Turn the axis-strip chrome on or off, and hand it a clock. Merges field by
5017
5104
  * field, so switching the countdown on leaves the corner clock alone.
@@ -5151,6 +5238,7 @@ declare class Chart {
5151
5238
  grid?: Partial<GridOptions>;
5152
5239
  canvas?: CanvasOptions;
5153
5240
  statusLine?: LegendStatusLineOptions;
5241
+ legendIconSize?: number;
5154
5242
  priceScale?: Partial<PriceScaleOptions>;
5155
5243
  priceFormatter?: ((price: number) => string) | null;
5156
5244
  timeFormatter?: ((utcSeconds: number, tickMark?: TickMarkType) => string) | undefined;