openalgo-charts 2.4.5 → 2.4.7

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, 92.83 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-92.83%20kB%20base%20%C2%B7%20246.52%20kB%20all%20tiers-brightgreen.svg)](#size-budget)
12
+ [![tests](https://img.shields.io/badge/engine%20tests-5841%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,16 @@ 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.7**.
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
+ Drag a price or study-threshold alert line to preview a new level, then release
59
+ to save it once. Escape cancels the draft; range bounds stay ordered. Stored
60
+ thresholds and live evaluation remain unchanged during preview. See the
61
+ [2.4.7 changelog](./CHANGELOG.md#247) and the [live example](https://marketcalls.github.io/openalgo-charts/examples/#alert-threshold-dragging).
62
+
63
+ To try dragging locally, run the [yfinance reference host](./examples/yfinance/README.md),
64
+ create a price alert from the chart context menu, and drag its dashed line or
65
+ **Alert** badge. Study and range alerts use the same controls.
63
66
 
64
67
  ```bash
65
68
  npm install openalgo-charts
@@ -88,7 +91,7 @@ in front of npm rather than being places you upload to. A chart is one HTML file
88
91
  ```html
89
92
  <div id="chart" style="width:100vw;height:100vh"></div>
90
93
  <script type="module">
91
- import { createChart } from 'https://unpkg.com/openalgo-charts@2.4.5/dist/openalgo-charts.mjs';
94
+ import { createChart } from 'https://unpkg.com/openalgo-charts@2.4.7/dist/openalgo-charts.mjs';
92
95
  const chart = createChart(document.getElementById('chart'), { timezone: 'Asia/Kolkata' });
93
96
  chart.addSeries('candlestick').setData(bars);
94
97
  </script>
@@ -130,17 +133,17 @@ Import only what you use. Each tier is a separate bundle that registers into the
130
133
 
131
134
  | Import | Contents | Brotli |
132
135
  |---|---|---|
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 |
136
+ | `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 | 92.83 kB |
134
137
  | `openalgo-charts/indicators` | 105 built-in indicators, the `registerIndicator` contract for your own, and the Tier-2 (external-data) contract | 29.84 kB |
135
138
  | `openalgo-charts/draw` | 85 drawing tools + a headless drawing controller, clipboard, settings schema, level palette, freehand geometry and SVG icons | 35.43 kB |
136
139
  | `openalgo-charts/transform` | Heikin Ashi, Renko, Range bars, Line Break, Point &amp; Figure, Kagi, and symbol arithmetic (`AAPL/MSFT`) | 4.50 kB |
137
140
  | `openalgo-charts/profile` | Volume Profile, Market Profile (TPO) with compact pixel letters, Footprint, order flow | 14.96 kB |
138
141
  | `openalgo-charts/trade` | Order / position / bracket tools + DOM ladder | 8.01 kB |
139
142
  | `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 |
143
+ | `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.03 kB |
141
144
  | `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
145
 
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.
146
+ Everything together is **246.52 kB Brotli**; a widget terminal with built-in indicators (base + draw + indicators + widget) is 207.13 kB. Figures are measured from the 2.4.7 release build. The trade tier is 8.01 kB on its own; base + trade costs 100.84 kB. Sizes use decimal kB.
144
147
 
145
148
  ## What's built
146
149
 
@@ -445,17 +448,17 @@ Enforced in CI by [`size-limit`](./.size-limit.json). Nothing is excluded, becau
445
448
 
446
449
  | Bundle | Limit | Actual |
447
450
  |---|---|---|
448
- | Base engine | 91.5 kB | 91.00 kB |
449
- | Base + trade | 100 kB | 99.01 kB |
451
+ | Base engine | 93.5 kB | 92.83 kB |
452
+ | Base + trade | 101.5 kB | 100.84 kB |
450
453
  | Indicators tier | 30 kB | 29.84 kB |
451
454
  | Draw tier | 36 kB | 35.43 kB |
452
455
  | Transform tier | 6 kB | 4.50 kB |
453
456
  | Profile tier | 15 kB | 14.96 kB |
454
457
  | 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 |
458
+ | Widget tier | 49.25 kB | 49.03 kB |
459
+ | Widget terminal (base + draw + indicators + widget) | 207.75 kB | 207.13 kB |
457
460
  | Workspace tier | 6 kB | 5.52 kB |
458
- | **Everything** | 245 kB | 243.96 kB |
461
+ | **Everything** | 247.25 kB | 246.52 kB |
459
462
 
460
463
  ## Documentation
461
464
 
@@ -500,8 +503,8 @@ See [Contributing](./CONTRIBUTING.md) for setup, targeted checks, documentation
500
503
  ```bash
501
504
  npm install # install dev toolchain
502
505
  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
506
+ npm test # engine unit tests (Vitest): 5841 across 246 files
507
+ npm run test:demo # reference-host tests: 401 across 31 files
505
508
  npm run test:endurance # node endurance-harness tests: 7 cases
506
509
  npm run build # Rollup -> dist/ (minified ESM per tier + types)
507
510
  npm run size # size-limit (Brotli) against the budget
@@ -519,7 +522,7 @@ npm run verify # lint + types + unit + endurance harness + build + demo + dt
519
522
 
520
523
  ## Status &amp; limitations
521
524
 
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).
525
+ Version **2.4.7**. All engine build phases are implemented. Upgrading a 1.9.x host: [Migrating to 2.0](./docs/migrating-to-2.md).
523
526
 
524
527
  Known gaps, stated plainly:
525
528
 
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.7";
5
5
  /** Returns the current library version. */
6
6
  declare function version(): string;
7
7
 
@@ -812,6 +812,8 @@ interface PrimitiveHit {
812
812
  * declares it here, and the drag callbacks receive time as well as price.
813
813
  */
814
814
  draggable?: boolean;
815
+ /** Opt into Escape cancellation without a release. Listen for `drag:cancel` to discard the preview. */
816
+ cancelOnEscape?: boolean;
815
817
  }
816
818
  /** Injected when a primitive is attached; lets it request a repaint. */
817
819
  interface PrimitiveHost {
@@ -859,6 +861,11 @@ interface IPrimitive {
859
861
  /** Pick the best hit across primitives: nearest distance, then z-order priority. */
860
862
  declare function bestHit(hits: readonly (PrimitiveHit | null)[]): PrimitiveHit | null;
861
863
 
864
+ /**
865
+ * Series markers (ARCHITECTURE.md §8.1): buy/sell signals and shapes anchored
866
+ * to bars. Visible-range culled, per-bar stacked, four discrete sizes.
867
+ */
868
+
862
869
  /**
863
870
  * `labelUp` / `labelDown` are text plates with a tail, for named signals ("Buy",
864
871
  * "Sell") rather than bare glyphs. The tail points *at* the anchor price and the
@@ -900,10 +907,23 @@ declare function drawShape(ctx: CanvasRenderingContext2D, shape: MarkerShape, cx
900
907
  declare function drawLabel(ctx: CanvasRenderingContext2D, up: boolean, cx: number, anchorY: number, text: string, color: string, fontPx: number): void;
901
908
  declare class SeriesMarkers implements IPrimitive {
902
909
  private readonly _seriesId;
910
+ private readonly _fallbackBars;
911
+ private readonly _priceScale;
903
912
  private _markers;
904
913
  private _host;
905
914
  private _lastPositions;
906
- constructor(seriesId: SeriesId);
915
+ /**
916
+ * @param seriesId The series whose pane and price scale the marks live on.
917
+ * @param fallbackBars Bars to position against where that series has none.
918
+ *
919
+ * The second argument exists because a marker's series decides *where* it is
920
+ * drawn while the bar under it decides *how high*, and those are not always
921
+ * the same row of data. An indicator that draws one line in an uptrend and
922
+ * another in a downtrend has a gap in each, and a mark that lands in a gap
923
+ * had no bar to measure from and was dropped without a word. The caller
924
+ * passes the instrument's own bars, which have no gaps.
925
+ */
926
+ constructor(seriesId: SeriesId, fallbackBars?: () => readonly Bar[], priceScale?: () => PriceScale);
907
927
  attached(host: PrimitiveHost): void;
908
928
  detached(): void;
909
929
  zOrder(): ZOrder;
@@ -975,8 +995,14 @@ interface SeriesApi {
975
995
  remove(): void;
976
996
  /** The price scale this series maps to (call `.setOptions({ marginTop, marginBottom })` on it). */
977
997
  priceScale(): PriceScale;
978
- /** Create a markers layer (buy/sell signals, shapes) bound to this series. */
979
- createMarkers(): SeriesMarkers;
998
+ /**
999
+ * Create a markers layer (buy/sell signals, shapes) bound to this series.
1000
+ *
1001
+ * `fallbackBars` positions a mark whose time this series has no point for,
1002
+ * which happens whenever the series is drawn with gaps. Without it such a
1003
+ * mark is dropped silently.
1004
+ */
1005
+ createMarkers(fallbackBars?: () => readonly Bar[]): SeriesMarkers;
980
1006
  }
981
1007
 
982
1008
  /**
@@ -1679,7 +1705,7 @@ declare class PriceLine implements IPrimitive {
1679
1705
  * placeholder.
1680
1706
  */
1681
1707
 
1682
- type PaneLegendAction = 'hide' | 'settings' | 'up' | 'down' | 'maximize' | 'close';
1708
+ type PaneLegendAction = 'hide' | 'settings' | 'source' | 'up' | 'down' | 'maximize' | 'close';
1683
1709
  /**
1684
1710
  * One reading on a legend row. Multi-plot sources show one per plot, each in
1685
1711
  * that plot's own color (an MA ribbon's four averages, MACD's three lines) —
@@ -1796,6 +1822,7 @@ interface PaneLegendOptions {
1796
1822
  * `${id}::<action>`:
1797
1823
  * - `up` / `down` — move this pane one slot (`::up` / `::down`)
1798
1824
  * - `hide` — toggle visibility (`::hide`)
1825
+ * - `source`: show the code this source was written from (`::source`)
1799
1826
  * - `maximize` — expand this pane to fill the chart (`::maximize`)
1800
1827
  * - `close` — remove the source, and its pane if it empties (`::close`)
1801
1828
  *
@@ -1810,6 +1837,15 @@ interface PaneLegendOptions {
1810
1837
  maximized?: boolean;
1811
1838
  /** Text size in media px. Default 11. */
1812
1839
  font?: number;
1840
+ /**
1841
+ * Square side of one action button in media px. Default 16.
1842
+ *
1843
+ * The row grows to hold it, so raising this moves every legend row below it
1844
+ * down by the same amount and nothing overlaps. It is a chart-wide setting
1845
+ * for that reason: two legends on one pane with different button sizes would
1846
+ * stack against different row heights and collide.
1847
+ */
1848
+ iconSize?: number;
1813
1849
  /** Left inset from the plot edge in media px. Default 8. */
1814
1850
  left?: number;
1815
1851
  /** Top inset in media px. Default 6. */
@@ -2018,10 +2054,14 @@ interface IndicatorHost {
2018
2054
  color?: string;
2019
2055
  row: number;
2020
2056
  paneIndex: number;
2057
+ /** The descriptor's `hasSource`, so the row can offer a source button. */
2058
+ hasSource?: boolean;
2021
2059
  }): PaneLegend;
2022
2060
  removeIndicatorLegend(legend: PaneLegend): void;
2023
2061
  /** How many legends already sit on this pane, so rows stack. */
2024
2062
  legendRowsOn(paneIndex: number): number;
2063
+ /** The instrument's own series, for a descriptor anchoring marks to price. */
2064
+ primarySeries?(): SeriesApi | null;
2025
2065
  addIndicatorSeries(type: string, paneIndex: number, style: Record<string, unknown> | undefined, priceScaleId: string | undefined,
2026
2066
  /** Axis/crosshair formatting for the scale this plot maps to. */
2027
2067
  priceFormat?: PriceFormat): SeriesApi;
@@ -2920,6 +2960,20 @@ interface IndicatorDescriptor {
2920
2960
  category?: string;
2921
2961
  /** `'onchart'` overlays the price pane; `'pane'` gets its own pane. */
2922
2962
  placement: 'onchart' | 'pane';
2963
+ /**
2964
+ * This indicator was written from code the host can show the user.
2965
+ *
2966
+ * Its legend row then carries a source button beside the gear, and pressing
2967
+ * it emits `indicatorSource` with the same payload `indicatorSettings`
2968
+ * carries. The engine does not hold the code and does not want to: a
2969
+ * descriptor may be compiled from a script, generated, or written by hand in
2970
+ * the host's own bundle, and only the host knows which of those it can put in
2971
+ * front of somebody. So this says a button is worth offering, and the host
2972
+ * decides what the button opens.
2973
+ *
2974
+ * Absent or false draws no button, which is every built-in study.
2975
+ */
2976
+ hasSource?: boolean;
2923
2977
  inputs: readonly IndicatorInput[];
2924
2978
  plots: readonly IndicatorPlot[];
2925
2979
  /**
@@ -2973,6 +3027,24 @@ interface IndicatorDescriptor {
2973
3027
  values: IndicatorValues;
2974
3028
  settings: Readonly<IndicatorSettings>;
2975
3029
  }): readonly SeriesMarker[];
3030
+ /**
3031
+ * What `aboveBar` and `belowBar` are measured against.
3032
+ *
3033
+ * `'plot'`, the default, is this study's own first plot, which is right for a
3034
+ * mark that belongs to the line: an arrow on a moving average sits against
3035
+ * the average.
3036
+ *
3037
+ * `'price'` is the instrument's candles, so above is above the high and below
3038
+ * is below the low. That is what a buy or sell signal on an overlay study
3039
+ * means, and anchoring one to the study's own column instead puts it wherever
3040
+ * that column happens to sit: a study that anchors its marks to a mid-body
3041
+ * line draws every "below" mark through the middle of the candle.
3042
+ *
3043
+ * Ignored by a study in its own pane, which has no candles to measure
3044
+ * against, and ignored when the chart has no primary series yet. Both fall
3045
+ * back to the first plot rather than dropping the marker.
3046
+ */
3047
+ markerAnchor?: 'plot' | 'price';
2976
3048
  /**
2977
3049
  * Optional summary grid pinned to a corner of the pane.
2978
3050
  *
@@ -4112,6 +4184,11 @@ interface ChartOptions {
4112
4184
  canvas?: CanvasOptions;
4113
4185
  /** Per-field status-line switches applied to every pane legend on the chart. */
4114
4186
  statusLine?: LegendStatusLineOptions;
4187
+ /**
4188
+ * Square side of a legend action button in media px. Default 16, held to
4189
+ * 12..28. Applied to every pane legend, because the rows stack against it.
4190
+ */
4191
+ legendIconSize?: number;
4115
4192
  /** Accessible label for the chart container (screen readers). */
4116
4193
  ariaLabel?: string;
4117
4194
  /**
@@ -4264,7 +4341,7 @@ interface PointerSample {
4264
4341
  }
4265
4342
  /**
4266
4343
  * What the engine reports about the physical pointer behind a gesture.
4267
- * `crosshair:move`, `click`, `drag` and `drag:end` all carry these keys.
4344
+ * `crosshair:move`, `click`, `drag:start`, `drag` and `drag:end` all carry these keys.
4268
4345
  */
4269
4346
  interface PointerInfo {
4270
4347
  modifiers: PointerModifiers;
@@ -4318,7 +4395,7 @@ interface ChartDragEvent extends PointerInfo {
4318
4395
  */
4319
4396
  samples: PointerSample[];
4320
4397
  }
4321
- /** Payload of the `drag:end` event: the release that finished a primitive drag. */
4398
+ /** Payload of `drag:start` (press) and `drag:end` (release) for a primitive drag. */
4322
4399
  interface ChartDragEndEvent extends PointerInfo {
4323
4400
  id: string;
4324
4401
  price: number;
@@ -4458,6 +4535,8 @@ declare class Chart {
4458
4535
  private readonly _canvas;
4459
4536
  /** Status-line switches pushed onto every pane legend, host-added ones included. */
4460
4537
  private readonly _statusLine;
4538
+ /** Legend action-button side in media px; undefined leaves the primitive's default. */
4539
+ private _legendIconSize;
4461
4540
  /** Axis-strip chrome switches. Empty is the shipped chart: neither drawn. */
4462
4541
  private readonly _axisChrome;
4463
4542
  /**
@@ -4557,6 +4636,7 @@ declare class Chart {
4557
4636
  /** Pressure at the press; a click reports this, since its release always reads 0. */
4558
4637
  private _downPressure;
4559
4638
  private _dragId;
4639
+ private _dragCancelOnEscape;
4560
4640
  private _hoverId;
4561
4641
  /** Whether that primitive draws below the overlay, so leaving it must repaint the base. */
4562
4642
  private _hoverOnBase;
@@ -5012,6 +5092,16 @@ declare class Chart {
5012
5092
  */
5013
5093
  setStatusLineOptions(patch: LegendStatusLineOptions): void;
5014
5094
  statusLineOptions(): LegendStatusLineOptions;
5095
+ /**
5096
+ * How large a legend's action buttons are drawn, in media px.
5097
+ *
5098
+ * Chart-wide rather than per legend: the rows stack against the height the
5099
+ * buttons need, so two sizes on one pane would stack against two different
5100
+ * heights and overlap. The primitive holds it to a range it can actually
5101
+ * draw.
5102
+ */
5103
+ setLegendIconSize(size: number): void;
5104
+ legendIconSize(): number | undefined;
5015
5105
  /**
5016
5106
  * Turn the axis-strip chrome on or off, and hand it a clock. Merges field by
5017
5107
  * field, so switching the countdown on leaves the corner clock alone.
@@ -5151,6 +5241,7 @@ declare class Chart {
5151
5241
  grid?: Partial<GridOptions>;
5152
5242
  canvas?: CanvasOptions;
5153
5243
  statusLine?: LegendStatusLineOptions;
5244
+ legendIconSize?: number;
5154
5245
  priceScale?: Partial<PriceScaleOptions>;
5155
5246
  priceFormatter?: ((price: number) => string) | null;
5156
5247
  timeFormatter?: ((utcSeconds: number, tickMark?: TickMarkType) => string) | undefined;
@@ -5418,6 +5509,7 @@ declare class Chart {
5418
5509
  private readonly _onDblClick;
5419
5510
  private _beginPinch;
5420
5511
  private _updatePinch;
5512
+ private _cancelPrimitiveDrag;
5421
5513
  private readonly _onKeyDown;
5422
5514
  /** Scope gating: hover keeps keys chart-local; global always acts. */
5423
5515
  private _shortcutsActive;
@@ -9491,7 +9583,14 @@ declare class AlertController {
9491
9583
  private _replay;
9492
9584
  private _destroyed;
9493
9585
  private _revision;
9586
+ private _drag;
9494
9587
  constructor(_chart: AlertChartHost, options?: AlertControllerOptions);
9588
+ private _dragAvailability;
9589
+ private _dragPrice;
9590
+ private _startDrag;
9591
+ private _cancelDrag;
9592
+ /** Market evaluation keeps the committed source until a moved, owned gesture ends. */
9593
+ private _onDrag;
9495
9594
  add(input: AlertInput): Alert;
9496
9595
  update(id: string, patch: AlertPatch): Alert | undefined;
9497
9596
  remove(id: string): boolean;