wickchart 1.4.0 → 1.6.0

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
@@ -6,6 +6,9 @@ A TradingView-style financial chart as a single framework-agnostic Web Component
6
6
  One file, zero dependencies, one HTML tag. Canvas-rendered, fast, themeable, and
7
7
  streaming-ready.
8
8
 
9
+ <img width="759" height="390" alt="image" src="https://github.com/user-attachments/assets/f335cecc-d5b3-4d27-a982-600b6bc72d6f" />
10
+
11
+
9
12
  ## Install
10
13
 
11
14
  ```bash
@@ -226,7 +229,7 @@ chart.setData([
226
229
  | ------------- | ---------- | ------------------------------------------------------------------ |
227
230
  | `theme` | `dark` | `dark` or `light` |
228
231
  | `type` | `candles` | `candles`, `line`, `area`, `bars` (OHLC), `hollow` (hollow up-candles), `heikin` (Heikin-Ashi) |
229
- | `indicators` | `volume`* | Space/comma-separated: `sma:20`, `ema:50`, `bb:20`, `rsi:14`, `macd:12/26/9`, `volume`, or any registered indicator |
232
+ | `indicators` | `volume`* | Space/comma-separated: `sma:20`, `ema:50`, `bb:20`, `vwap`, `supertrend:10/3`, `donchian:20`, `keltner:20/2`, `rsi:14`, `macd:12/26/9`, `stoch:14/3`, `atr:14`, `obv`, `cci:20`, `wr:14`, `volume`, or any registered indicator |
230
233
  | `label` | – | Text shown in the legend (e.g. `"BTC · 1h"`) |
231
234
  | `log` | off | Logarithmic price scale |
232
235
  | `auto` | on | Keep the right edge pinned to the latest bar while streaming |
@@ -247,8 +250,17 @@ chart.setData([
247
250
  | `sma` | overlay | `period` (20) | |
248
251
  | `ema` | overlay | `period` (50) | |
249
252
  | `bb` | overlay | `period`, `mult` (20, 2) | Bollinger bands (3 lines) |
253
+ | `vwap` | overlay | – | hlc3 VWAP, resets each UTC day |
254
+ | `supertrend` | overlay | `period`, `mult` (10, 3) | ATR trend line, breaks at flips |
255
+ | `donchian` | overlay | `period` (20) | high/low channel + mid |
256
+ | `keltner` | overlay | `period`, `mult` (20, 2) | EMA ± mult×ATR channel |
250
257
  | `rsi` | pane | `period` (14) | fixed 0–100 scale, 30/70 guides |
251
258
  | `macd` | pane | `fast/slow/signal` (12/26/9) | 2 lines + histogram |
259
+ | `stoch` | pane | `period`, `smooth` (14, 3) | %K + %D, fixed 0–100, 20/80 guides |
260
+ | `atr` | pane | `period` (14) | Wilder ATR |
261
+ | `obv` | pane | – | on-balance volume |
262
+ | `cci` | pane | `period` (20) | ±100 guides |
263
+ | `wr` | pane | `period` (14) | Williams %R, fixed −100–0, −80/−20 guides |
252
264
  | `volume` | overlay | – | histogram at the bottom of the price pane |
253
265
 
254
266
  ### Custom indicators
@@ -257,9 +269,9 @@ Register your own — anything from a one-liner moving average to a multi-line
257
269
  pane:
258
270
 
259
271
  ```js
260
- WickChart.registerIndicator('vwap', {
272
+ WickChart.registerIndicator('cvwap', { // cumulative VWAP over the whole dataset
261
273
  kind: 'overlay', // or 'pane'
262
- params: { period: 20 }, // defaults; set via indicators="vwap:30"
274
+ params: { period: 20 }, // defaults; set via indicators="cvwap:30"
263
275
  compute(bars, params) { // bars: normalized {time,open,high,low,close,volume}
264
276
  const out = new Array(bars.length).fill(null);
265
277
  let pv = 0, vv = 0;
@@ -270,9 +282,9 @@ WickChart.registerIndicator('vwap', {
270
282
  }
271
283
  return out; // single series — or { lines:[{name,values}], histogram }
272
284
  },
273
- // pane-only extras: guides:[30,70], range:[0,100], fmt:'price'|'fixed1'
285
+ // pane-only extras: guides:[30,70], range:[0,100], fmt:'price'|'fixed1'|'compact'
274
286
  });
275
- chart.indicators = 'vwap:20';
287
+ chart.indicators = 'cvwap';
276
288
  ```
277
289
 
278
290
  `import WickChart from 'wickchart'` gives you the class for
@@ -306,6 +318,7 @@ shareable URLs.
306
318
  | `prev(x[,k])` `change(x)` | shifted series / bar-to-bar delta |
307
319
  | `abs(x)` `sqrt(x)` `log(x)` `min(a,b)` `max(a,b)` | element-wise math |
308
320
  | `crossup(a,b)` `crossdown(a,b)` | 1 on a strict cross, else 0 |
321
+ | `vwap()` `obv()` `atr(n)` | bar-level series — callable anywhere, e.g. `crossup(close, vwap())` in alerts |
309
322
 
310
323
  Operators are `+ - * / %` with usual precedence, unary `-`, and parentheses.
311
324
  Values before a window fills are `NaN` (not drawn), division by zero yields
@@ -661,7 +674,7 @@ chart.removeLayer('flags'); // detach by handle or id
661
674
  A claimed gesture delivers `move`/`up` (and `cancel` on Escape) to the layer
662
675
  while the chart suppresses pan/brush/measure. Markers, watermarks, signal
663
676
  badges — or a whole drawing toolkit — plug in without the core growing a
664
- single tool. The main entry is covered by a CI gzip budget (64 KB) so it
677
+ single tool. The main entry is covered by a CI gzip budget (68 KB) so it
665
678
  stays that way.
666
679
 
667
680
  ### Drawings — the `wickchart-draw` plugin
@@ -673,19 +686,213 @@ data that rides zoom & pan, survives reloads, extrapolates into future
673
686
  space, and serializes to JSON. Anchors magnet-snap to bar times and OHLC.
674
687
 
675
688
  ```js
689
+ npm install wickchart wickchart-draw // drawings are a separate opt-in package
690
+
676
691
  import { attachDrawings } from 'wickchart-draw';
677
692
 
678
693
  const draw = attachDrawings(chart);
679
694
  draw.setTool('trendline'); // drag to draw; setTool(null) = select/move mode
680
695
  draw.getDrawings(); // → JSON array (save it); setDrawings(saved)
681
696
  draw.undo(); draw.clear();
697
+ draw.setShare(true); // shared drawings: draw on one tab, appears on all
682
698
  chart.addEventListener('wick:drawings', (e) => save(e.detail.drawings));
683
699
  ```
684
700
 
685
701
  Select mode: click a drawing to select it, drag to move, drag the square
686
702
  handles to re-anchor, `Delete` removes, `Esc` cancels a gesture; clicks on
687
- empty space fall through to the chart. Peer dependency: wickchart 1.4.
688
- See the live playground in the docs (Drawings section).
703
+ empty space fall through to the chart. Placing a note opens an inline editor
704
+ (type + `Enter`); click a selected note again to re-edit. `setShare(true)`
705
+ reuses the chart's `co-view` room (or pass an explicit room name) — last
706
+ writer wins, remote updates never touch the local undo stack. Peer
707
+ dependency: wickchart ≥ 1.4. See the live playground in the docs (Drawings
708
+ section — it shares a room, so open it twice and draw on either chart).
709
+
710
+ ### Sessions — the `wickchart-sessions` plugin
711
+
712
+ Market session shading as opt-in bytes (~6 KB gz, own CI budget): Asia /
713
+ London / New York and other sessions drawn as translucent bands, with labels,
714
+ closed-weekend shading for equities/futures, and crosshair hover events.
715
+ Presets for crypto & forex use the common UTC convention; equity/futures
716
+ presets use IANA timezones, so 09:30 is the real 09:30 across DST changes.
717
+ Custom defs (`{ name, start, end, tz?, days?, color?, alpha? }`) cover
718
+ midnight-crossing sessions and weekday filters.
719
+
720
+ ```js
721
+ npm install wickchart wickchart-sessions // sessions are a separate opt-in package
722
+
723
+ import { attachSessions } from 'wickchart-sessions';
724
+
725
+ const sessions = attachSessions(chart, { preset: 'crypto' });
726
+ sessions.setPreset('nyse'); // 'crypto' | 'forex' | 'nyse' | 'cme' | null
727
+ sessions.setSessions([...]); // custom defs (validated; getSessions() → JSON)
728
+ sessions.setWeekends(true); // shade closed Sat+Sun (default for nyse/cme)
729
+ chart.addEventListener('wick:sessions', (e) => status.textContent = e.detail.hover || '');
730
+ ```
731
+
732
+ The hover bridge listens to the chart's own crosshair events, so shading
733
+ never claims a pointer gesture — pan/zoom/measure work untouched. Peer
734
+ dependency: wickchart ≥ 1.4.
735
+
736
+ ### Replay — the `wickchart-replay` plugin
737
+
738
+ Bar replay as opt-in bytes (~3 KB gz, own CI budget): play history forward
739
+ bar-by-bar or at speed while the future stays hidden. The whole engine runs
740
+ on the public data API — a `setData` slice hides the future, `update()`
741
+ appends one bar per step — so the core stays replay-free. A badge layer shows
742
+ the mode and position at a glance.
743
+
744
+ ```js
745
+ npm install wickchart wickchart-replay // replay is a separate opt-in package
746
+
747
+ import { attachReplay } from 'wickchart-replay';
748
+
749
+ const replay = attachReplay(chart);
750
+ replay.start(); // head at ~70% of the data (or pass a time/index)
751
+ replay.play(); // 4 bars/sec — play(15) for faster, pause() stops
752
+ replay.step(); // reveal one bar
753
+ replay.seek('2026-03-06'); // jump the head
754
+ replay.setLoop(true); // wrap to the anchor at the end
755
+ replay.stop(); // exit — the full dataset is restored
756
+ chart.addEventListener('wick:replay', (e) => progress.textContent =
757
+ e.detail.active ? `${e.detail.index + 1}/${e.detail.total}` : '');
758
+ ```
759
+
760
+ Anchors accept bar indices, timestamps (ms/s) or date strings; every change
761
+ fires `wick:replay` with the full state. Pause live feeds while replaying —
762
+ an external `update()`/`setData()` aborts replay instead of corrupting the
763
+ chart (the demo pauses its feed automatically). Paper trading and an equity
764
+ curve are the planned 0.2 follow-up. Peer dependency: wickchart ≥ 1.4.
765
+
766
+ ### Compare — the `wickchart-compare` plugin
767
+
768
+ Normalized multi-asset overlays as opt-in bytes (~4 KB gz, own CI budget):
769
+ percent-rebased compare lines (ETH against BTC, TradingView-style) plus
770
+ derived **ratio** and **diff** lines (`BTC/ETH`, `BTC−ETH`), drawn over the
771
+ main pane against their own invisible scale so the price axis is untouched.
772
+ A legend chip row shows each series with its live value.
773
+
774
+ ```js
775
+ npm install wickchart wickchart-compare // compare is a separate opt-in package
776
+
777
+ import { attachCompare } from 'wickchart-compare';
778
+
779
+ const cmp = attachCompare(chart);
780
+ cmp.setSeries([
781
+ { label: 'ETH', data: ethBars }, // OHLC or {time, value}
782
+ { label: 'BTC/ETH', op: 'ratio', a: btcBars, b: ethBars }, // derived
783
+ ]);
784
+ cmp.setRebase('visible'); // 0% at the window edge, re-normalized while
785
+ // panning; 'first' or an epoch anchor also work
786
+ cmp.clear(); cmp.detach();
787
+ ```
788
+
789
+ Series are sampled onto the main chart's bar times, so timeframes can mix
790
+ and gaps break the line instead of bridging. Rebased values share one
791
+ invisible scale inset 8% from the pane edges; the price scale is never
792
+ distorted. Validated, capped at 6 series, invalid entries dropped. Peer
793
+ dependency: wickchart ≥ 1.4.
794
+
795
+ ### Navigator — the `wickchart-navigator` plugin
796
+
797
+ The most-missed TradingView affordance: a silhouette of the whole dataset
798
+ docked below the chart with a draggable viewport window (~3 KB gz, own CI
799
+ budget). Drag the window to pan, grab an edge to resize, click outside it to
800
+ jump — pan/zoom and the window stay in sync live, both directions.
801
+
802
+ ```js
803
+ npm install wickchart wickchart-navigator // navigator is a separate opt-in package
804
+
805
+ import { attachNavigator } from 'wickchart-navigator';
806
+ const nav = attachNavigator(chart, { height: 46 }); // strip height, 24..120
807
+ nav.detach(); // remove the strip again
808
+ ```
809
+
810
+ The strip needs bottom space, so this plugin pairs with a small core hook:
811
+ a layer may declare `insetBottom` (px) — the largest declared inset reserves
812
+ a docked strip at the bottom of the canvas, panes and the time axis shrink
813
+ above it, and layers draw it as `api.layout.dock`. On charts without the
814
+ hook the navigator degrades silently. The silhouette is O(n) once per
815
+ (dataset, width) and cached. Peer dependency: wickchart ≥ 1.6.
816
+
817
+ ### Alerts+ — the `wickchart-alerts-plus` plugin
818
+
819
+ The "pro" alert tier (~3 KB gz, own CI budget). Core alerts are runtime-only
820
+ by design; this adds what a trading tool actually needs, without the core
821
+ growing any of it: **persistence** (the alert list mirrors into
822
+ localStorage and re-arms on reload), **desktop notifications + a WebAudio
823
+ beep** while the tab is hidden, and an optional **webhook** that receives
824
+ every fire as `POST { id, price, when, time, bar, key }`.
825
+
826
+ ```js
827
+ npm install wickchart wickchart-alerts-plus // alerts-plus is a separate opt-in package
828
+
829
+ import { attachAlertsPlus } from 'wickchart-alerts-plus';
830
+ const ap = attachAlertsPlus(chart, {
831
+ key: 'BTC:1h', // one storage key per symbol+timeframe
832
+ notify: true, sound: true, // hidden-tab surfacing
833
+ webhook: 'https://example.com/hook', // optional
834
+ });
835
+ await ap.requestNotify(); // ask for the notification permission
836
+ ap.add({ price: 100, direction: 'above' }); // persisted, re-armed on reload
837
+ ap.add({ when: 'rsi(close,14) < 30' }); // scripted alerts persist too
838
+ ap.list(); ap.remove(id); ap.clear(); ap.sync(); ap.detach();
839
+ ```
840
+
841
+ Once-fired alerts drop out of storage automatically; alerts added directly
842
+ on the chart are captured at the next save point; storage/fetch are
843
+ injectable and every storage failure degrades to memory-only, never
844
+ throwing. Peer dependency: wickchart ≥ 1.4.
845
+
846
+ ### Layouts — the `wickchart-layouts` plugin
847
+
848
+ Named workspace persistence (~3 KB gz, own CI budget): save and restore
849
+ whole chart setups by name — type, theme, log scale, toggles, indicators,
850
+ view range, positions, alerts — plus the drawing list when wickchart-draw
851
+ is attached. Everything rides the core's public `getState()`/`setState()`.
852
+
853
+ ```js
854
+ npm install wickchart wickchart-layouts // layouts is a separate opt-in package
855
+
856
+ import { attachLayouts } from 'wickchart-layouts';
857
+ const layouts = attachLayouts(chart, {
858
+ key: 'my-desk', // storage key (default 'wickchart-layouts')
859
+ drawings: draw, // optional wickchart-draw handle — include drawings
860
+ });
861
+ layouts.save('swing'); // capture the current setup under a name
862
+ layouts.load('swing'); // apply it back
863
+ layouts.list(); // → [{ name, at, drawingCount }] newest first
864
+ layouts.export(); // → JSON string — share it, store it anywhere
865
+ layouts.import(json); // merge layouts back (replaces same names)
866
+ chart.addEventListener('wick:layouts', (e) => console.log(e.detail.action, e.detail.name));
867
+ ```
868
+
869
+ Entries are capped (oldest evicted), `storage` is injectable, storage
870
+ failures degrade to an in-memory store for the session and never throw.
871
+ Pair a `load` with `wickchart-alerts-plus`'s `sync()` if you also persist
872
+ alerts, since a layout load replaces the chart's alert list. Peer
873
+ dependency: wickchart ≥ 1.4.
874
+
875
+ ### Signals — the `wickchart-signals` plugin
876
+
877
+ Candlestick pattern badges (~4 KB gz, own CI budget): bullish/bearish
878
+ **engulfing**, **pin bars** (hammer / shooting star) and **inside bars**
879
+ drawn as direction-colored letter chips above/below the bar. Hover a badged
880
+ bar and the plugin draws the explanation ("Bullish engulfing") and fires
881
+ `wick:signals` — the same passive crosshair bridge as wickchart-sessions,
882
+ so badges never claim a pointer gesture.
883
+
884
+ ```js
885
+ npm install wickchart wickchart-signals // signals is a separate opt-in package
886
+
887
+ import { attachSignals } from 'wickchart-signals';
888
+ const signals = attachSignals(chart);
889
+ signals.setKinds(['engulfing', 'pinbar']); // subset (default: all three)
890
+ signals.setLabels(false); // hover explanations off
891
+ chart.addEventListener('wick:signals', (e) => status.textContent = e.detail?.label || '');
892
+ ```
893
+
894
+ Detection is O(n), cached per dataset and kind subset — pan/zoom are pure
895
+ repaints. Peer dependency: wickchart ≥ 1.4.
689
896
 
690
897
  ## Methods
691
898
 
@@ -701,7 +908,7 @@ See the live playground in the docs (Drawings section).
701
908
  | `getDataWindow()` | → AI-ready summary of the visible window (see below) |
702
909
  | `getState()` | → serializable snapshot (type, indicators, view, positions, alerts) |
703
910
  | `setState(state)` | Apply a snapshot; a pending view applies after the next `setData()` |
704
- | `addLayer(layer)` / `removeLayer(idOrHandle)` | Register/detach a plugin layer (draw hook + optional pointer claim) |
911
+ | `addLayer(layer)` / `removeLayer(idOrHandle)` | Register/detach a plugin layer (draw hook + optional pointer claim + optional `insetBottom` dock strip) |
705
912
  | `requestDraw()` | Repaint on the next frame (interactive layers) |
706
913
  | `timeToX(t)` / `xToTime(x)` | Bar time ⇄ x-pixel; extrapolates into future space |
707
914
  | `priceToY(p)` / `yToPrice(y)` | Price ⇄ y-pixel in the main pane (log-aware) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wickchart",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "<wick-chart> — a modern, dependency-free financial charting web component. Candles, line & area charts, crosshair, zoom/pan, indicators (incl. a safe expression mini-language), live streaming via <wick-feed>, theming.",
5
5
  "type": "module",
6
6
  "main": "src/wick-chart.js",
@@ -42,10 +42,10 @@
42
42
  },
43
43
  "scripts": {
44
44
  "dev": "npx --yes serve . -l 5173",
45
- "test": "node --test \"tests/*.test.mjs\" \"plugins/draw/tests/*.test.mjs\"",
45
+ "test": "node --test \"tests/*.test.mjs\" \"plugins/draw/tests/*.test.mjs\" \"plugins/sessions/tests/*.test.mjs\" \"plugins/replay/tests/*.test.mjs\" \"plugins/compare/tests/*.test.mjs\" \"plugins/navigator/tests/*.test.mjs\" \"plugins/alerts-plus/tests/*.test.mjs\" \"plugins/layouts/tests/*.test.mjs\" \"plugins/signals/tests/*.test.mjs\"",
46
46
  "build:types": "node -e \"require('fs').rmSync('types', { recursive: true, force: true });\" && tsc -p tsconfig.json",
47
47
  "prepack": "npm run build:types",
48
- "ci": "npm run build:types && npm test && node --check src/wick-chart.js && node --check src/wick-feed.js && node --check src/core.js && node --check src/react.js && node --check src/react-core.js && node --check demo/app.js && node --check plugins/draw/core.mjs && node --check plugins/draw/draw.mjs"
48
+ "ci": "npm run build:types && npm test && node --check src/wick-chart.js && node --check src/wick-feed.js && node --check src/core.js && node --check src/react.js && node --check src/react-core.js && node --check demo/app.js && node --check plugins/draw/core.mjs && node --check plugins/draw/draw.mjs && node --check plugins/sessions/core.mjs && node --check plugins/sessions/sessions.mjs && node --check plugins/replay/replay.mjs && node --check plugins/compare/core.mjs && node --check plugins/compare/compare.mjs && node --check plugins/navigator/core.mjs && node --check plugins/navigator/navigator.mjs && node --check plugins/alerts-plus/alerts-plus.mjs && node --check plugins/layouts/layouts.mjs && node --check plugins/signals/core.mjs && node --check plugins/signals/signals.mjs"
49
49
  },
50
50
  "keywords": [
51
51
  "chart",
@@ -57,7 +57,14 @@
57
57
  "custom-element",
58
58
  "canvas",
59
59
  "zero-dependency",
60
- "tradingview"
60
+ "tradingview",
61
+ "vwap",
62
+ "supertrend",
63
+ "bollinger-bands",
64
+ "macd",
65
+ "rsi",
66
+ "stochastic",
67
+ "technical-analysis"
61
68
  ],
62
69
  "license": "MIT",
63
70
  "repository": {