wickchart 1.6.0 → 1.7.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
@@ -1,5 +1,7 @@
1
1
  # WickChart
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/wickchart)](https://www.npmjs.com/package/wickchart)
4
+
3
5
  **`<wick-chart>` — a modern, simpler, more useful charting web component.**
4
6
 
5
7
  A TradingView-style financial chart as a single framework-agnostic Web Component.
@@ -62,6 +64,7 @@ with **zero JavaScript written**:
62
64
  | `tf` | timeframe: `1m 3m 5m 15m 30m 1h 2h 4h 6h 12h 1d 3d 1w` |
63
65
  | `limit` | initial bars (default 500) |
64
66
  | `live` | `live="false"` loads history without streaming |
67
+ | `aggregate` | information-based bars from a trade stream (see below) |
65
68
 
66
69
  The element reflects its state in the `status` attribute (`loading`, `live`,
67
70
  `polling`, `fallback`, `loaded`, `waiting`, `idle`) and emits
@@ -70,6 +73,78 @@ The element reflects its state in the `status` attribute (`loading`, `live`,
70
73
  synthetic stream bridged from the last real price, so the chart never goes
71
74
  blank. It also wires `chart.onloadmore` for infinite backfill automatically.
72
75
 
76
+ ### Information-based bars (advanced bars)
77
+
78
+ Add `aggregate` to any feed and bars close on *information*, not the clock:
79
+
80
+ ```html
81
+ <wick-feed for="c" binance="BTCUSDT" aggregate="dollar:50000"></wick-feed>
82
+ <wick-chart id="c" indicators="volume"></wick-chart>
83
+ ```
84
+
85
+ `tick:200` closes a bar every 200 prints, `volume:50` every 50 base units,
86
+ `dollar:50000` every $50k traded — the quant-grade alternative to time
87
+ candles, built client-side from the raw trade tape (Binance aggTrade
88
+ WebSocket + paginated REST backfill; offline synthetic prints with `demo=`;
89
+ your own JSON trades endpoint with `url=` + `poll=`). The value you pass *is*
90
+ the bar size — tune it per instrument. The machinery is exported too:
91
+ `import { TickBarAggregator, aggregateTrades } from 'wickchart/feed'` to pipe
92
+ any trade stream through the same aggregator.
93
+
94
+ ## Web Worker compute path (1M-bar histories)
95
+
96
+ One extra import, one attribute — and the built-in indicators compute in a
97
+ Web Worker, built for million-bar histories:
98
+
99
+ ```js
100
+ import 'wickchart/worker'; // once — wires a shared worker pool into the chart
101
+
102
+ <wick-chart worker indicators="sma:20 bb:20 rsi:14"></wick-chart>
103
+ ```
104
+
105
+ The dataset crosses once per bulk load as six transferable `Float64Array`s
106
+ (~25 ms per million bars; a structured clone of bar objects would cost ~1 s),
107
+ and indicator tasks reference it worker-side. First paint of every indicator
108
+ line happens off the main thread; `wick:worker` fires as results land.
109
+ Engages at 50k+ bars with built-in indicators (custom/scripted defs are
110
+ closures and stay sync, as does everything below the threshold); results are
111
+ cached per data epoch, so streamed ticks stop recomputing the full series per
112
+ bar. No worker available? Everything silently stays synchronous — the
113
+ attribute is an optimization, never a dependency. Live demo with freeze
114
+ numbers: **[demo/worker.html](./demo/worker.html)**.
115
+
116
+ **Incremental tick updates** (automatic, worker or not): a streamed tick —
117
+ appending a bar or replacing the forming one — patches every online-capable
118
+ series by recomputing a bounded tail with the *same* batch definition
119
+ (O(warm-up) ≈ 0.1 ms, not O(full history)) and only writing the last
120
+ `period` values, so history is never degraded by tail warm-up error. This
121
+ works on worker-computed bases too: the forming bar's indicator value stays
122
+ fresh instead of waiting for the next bulk load. Cumulative indicators
123
+ (`obv`, `vwap`) and the stateful `supertrend` are excluded and keep the
124
+ full-recompute behavior.
125
+
126
+ ## Report export (branded snapshots)
127
+
128
+ One shareable PNG — chart, visible-range stats, watermark — composed from
129
+ public surfaces only, as an opt-in entry:
130
+
131
+ ```js
132
+ import { exportReport, downloadReport } from 'wickchart/report';
133
+
134
+ const url = await exportReport(chart); // PNG data URL
135
+ const blob = await exportReport(chart, { as: 'blob' });
136
+ await downloadReport(chart, 'btc-1h.png', { source: 'binance: BTCUSDT' });
137
+ ```
138
+
139
+ The header carries the title (the chart's `label` by default), the visible
140
+ range and the brand; the chart keeps its full DPR resolution with a corner
141
+ watermark; the stats grid covers the visible window (return, annualized
142
+ vol, max drawdown, bars, up/down, average volume, high, low); the footer
143
+ credits your `source` and a timestamp. Theme follows the chart's own
144
+ `--wick-*` CSS variables (or `theme: 'dark' | 'light'`), scale 1–4.
145
+ `reportModel(chart, opts)` is exported too — plain data, if you want your
146
+ own layout.
147
+
73
148
  ---
74
149
 
75
150
  ## Why another chart library?
@@ -78,7 +153,8 @@ TradingView's charting library is powerful but heavy and enterprise-licensed;
78
153
  most wrappers add build steps and framework lock-in. WickChart takes the opposite
79
154
  bet:
80
155
 
81
- - **Zero dependencies, single file** (~40 KB unminified, no build step required)
156
+ - **Zero dependencies, no build step required** (~67 KB gzipped for the whole
157
+ component — `core.js` + `wick-chart.js`, held to a 68 KB CI budget)
82
158
  - **One tag, sane defaults** — drop it in and it renders; everything optional
83
159
  - **Built-in usefulness** — crosshair + OHLC legend, last-price line, wheel zoom,
84
160
  drag pan, pinch, keyboard navigation, live streaming, PNG export
@@ -97,7 +173,10 @@ chart, the full interactive demo, the zero-JavaScript declarative page, and a
97
173
  [benyblack.github.io/wickchart/docs.html](./docs.html)** — every attribute,
98
174
  method, event, the WickScript reference, overlays (with a live JSON
99
175
  playground), feeds, theming and framework bindings, each with runnable
100
- examples. This README covers the same ground in plain markdown.
176
+ examples. The **[Plugins hub](./plugins.html)** documents every opt-in
177
+ package — draw, sessions, replay, compare, navigator, alerts+, layouts,
178
+ signals, tape, grid, paper — each with its own live playground. This README covers
179
+ the same ground in plain markdown.
101
180
 
102
181
  ## Run the demo locally
103
182
 
@@ -886,7 +965,7 @@ npm install wickchart wickchart-signals // signals is a separate opt-in packag
886
965
 
887
966
  import { attachSignals } from 'wickchart-signals';
888
967
  const signals = attachSignals(chart);
889
- signals.setKinds(['engulfing', 'pinbar']); // subset (default: all three)
968
+ signals.setKinds(['engulfing', 'pinbar']); // subset (default: all three; [] = off)
890
969
  signals.setLabels(false); // hover explanations off
891
970
  chart.addEventListener('wick:signals', (e) => status.textContent = e.detail?.label || '');
892
971
  ```
@@ -894,6 +973,30 @@ chart.addEventListener('wick:signals', (e) => status.textContent = e.detail?.lab
894
973
  Detection is O(n), cached per dataset and kind subset — pan/zoom are pure
895
974
  repaints. Peer dependency: wickchart ≥ 1.4.
896
975
 
976
+ ### Tape — the `wickchart-tape` plugin
977
+
978
+ Time & sales (~4.6 KB gz, own CI budget): a live trade-print strip docked at
979
+ the bottom of the canvas through the `insetBottom` hook — `time · price ·
980
+ size` rows colored by side with proportional size bars, oversized prints
981
+ highlighted. Display-only: it never claims a pointer gesture. Prints carry
982
+ an optional side; without one the plugin applies the classic **tick rule**
983
+ (uptick → buy, downtick → sell), carried continuously across pushes. The
984
+ same stream drives the chart: `chart.setData(tape.toBars(60000))`.
985
+
986
+ ```js
987
+ npm install wickchart wickchart-tape // tape is a separate opt-in package
988
+
989
+ import { attachTape } from 'wickchart-tape';
990
+ const tape = attachTape(chart, { rows: 7, bigSize: 50 });
991
+ socket.onmessage = (m) => tape.push(m.trades); // single print or batch
992
+ tape.setRows(4); tape.hide(); tape.detach(); // rows 3–8; hide frees the dock
993
+ chart.addEventListener('wick:tape', (e) => status.textContent = e.detail.total + ' prints');
994
+ ```
995
+
996
+ Keeps the newest 500 prints. Peer dependency: wickchart ≥ 1.6 (the dock
997
+ hook); shares the bottom strip with wickchart-navigator, so attach one or
998
+ the other.
999
+
897
1000
  ## Methods
898
1001
 
899
1002
  | Method | Description |
@@ -947,6 +1050,9 @@ chart.addEventListener('wick:alert', (e) => {
947
1050
  // scripted alerts — any WickScript predicate, fired on its false→true edge
948
1051
  chart.addAlert({ when: 'crossup(rsi(close,14), 30)' });
949
1052
  chart.addAlert({ when: 'volume > sma(volume,20) * 3', once: false }); // re-arms
1053
+
1054
+ // evaluate only on final candles, so the signal cannot repaint
1055
+ chart.addAlert({ when: 'crossup(rsi(close,14), 30)', evaluate: 'close' });
950
1056
  ```
951
1057
 
952
1058
  The P&L chip recalculates on every streamed bar. Alerts are edge-triggered
@@ -955,6 +1061,43 @@ Scripted alerts are evaluated locally on every streamed bar — the event
955
1061
  carries the triggering close as `price` plus the `when` source; an invalid
956
1062
  predicate is rejected (`addAlert` returns `null`), never thrown.
957
1063
 
1064
+ **Live vs closed-candle evaluation.** Alerts evaluate on every update by
1065
+ default, the still-forming candle included — so a technical signal can
1066
+ repaint (RSI crosses 30 mid-candle, price reverses, the candle closes back
1067
+ above 30). Pass `evaluate: 'close'` to fire only on final candles, or set
1068
+ `<wick-chart alert-evaluate="close">` as the chart-wide default (per-alert
1069
+ `evaluate` still wins). A candle is final once a newer bar arrives, or as
1070
+ soon as the feed says so via `closed: true` on `update()` — `<wick-feed>`
1071
+ forwards Binance's `k.x` flag, so the signal lands at the close rather than
1072
+ one candle later. Historical corrections and backfilled candles never fire
1073
+ live alerts in either mode.
1074
+
1075
+ ### Timezone & VWAP sessions
1076
+
1077
+ Axis labels and the crosshair readout use the viewer's timezone by default.
1078
+ Pin them with `timezone` — `local`, `utc`, or any IANA zone, DST included:
1079
+
1080
+ ```html
1081
+ <wick-chart timezone="Europe/Stockholm"></wick-chart>
1082
+ <wick-chart timezone="America/New_York"></wick-chart>
1083
+ ```
1084
+
1085
+ Day dividers and month/year ticks follow the chosen zone, so a "1 Feb" tick
1086
+ is 1 February *there*. An unrecognised zone falls back to UTC and warns once.
1087
+
1088
+ VWAP's session boundary is deliberately **separate** from the display zone —
1089
+ changing the axis to Stockholm shouldn't silently re-anchor a BTC chart. It
1090
+ defaults to the UTC day (the crypto convention) and moves only when asked:
1091
+
1092
+ ```html
1093
+ <wick-chart indicators="vwap" vwap-anchor="America/New_York"></wick-chart>
1094
+ ```
1095
+
1096
+ `vwap-anchor` takes `utc` (default), `local`, an IANA zone, or a fixed offset
1097
+ in milliseconds. Equities, futures and FX rarely open at UTC midnight, so the
1098
+ default is right for crypto and wrong for most other markets — set it
1099
+ deliberately. `calcVWAP(bars, anchor)` takes the same values directly.
1100
+
958
1101
  ### Stats & measure
959
1102
 
960
1103
  `<wick-chart stats>` shows live statistics of the visible range — return %,
@@ -1022,7 +1165,9 @@ wick-chart {
1022
1165
  | Trackpad horizontal scroll | Pan |
1023
1166
  | Drag | Pan (auto-follow re-arms at the right edge) |
1024
1167
  | Pinch (touch) | Zoom |
1025
- | Double-click | Reset view |
1168
+ | Long press (touch) | Open the crosshair, then drag to scrub across bars |
1169
+ | Vertical swipe (touch) | Scrolls the page, not the chart |
1170
+ | Double-click / double-tap | Reset view |
1026
1171
  | `←` `→` (`+Shift` ×10) | Move crosshair |
1027
1172
  | `+` / `−` | Zoom in / out |
1028
1173
  | `Home` / `End` | Jump to oldest / newest |
@@ -1059,13 +1204,55 @@ column, and an offscreen layer so hover only repaints the crosshair.
1059
1204
  ## Architecture notes
1060
1205
 
1061
1206
  - Single ES module, Custom Element + Shadow DOM, Canvas 2D with
1062
- devicePixelRatio scaling and rAF-batched invalidation
1207
+ devicePixelRatio scaling and rAF-batched invalidation. The ratio is watched
1208
+ with a `resolution` media query, so moving a window between monitors
1209
+ re-renders at the new resolution rather than staying soft
1063
1210
  - Only visible bars are drawn; indicator series are computed lazily and cached
1064
1211
  per data version (prefix-sum SMA, Wilder RSI)
1065
1212
  - Time axis picks tick steps from bar interval (minutes → months) and labels
1066
1213
  day/month boundaries like a pro terminal
1067
1214
  - No dependencies, no build step required — but it bundles/tree-shakes fine
1068
1215
 
1216
+ ## Tests
1217
+
1218
+ Two suites, and they answer different questions.
1219
+
1220
+ ```bash
1221
+ npm test # Node: pure functions, indicator maths, parsing, plugins
1222
+ npm run test:e2e # Playwright: the chart in a real browser
1223
+ ```
1224
+
1225
+ `npm test` is the fast one and covers the bulk of the library. What it cannot
1226
+ reach is anything that only exists once a browser is involved: custom-element
1227
+ upgrade, a real canvas, wheel/pointer/touch input, `devicePixelRatio`,
1228
+ `ResizeObserver`, and React re-renders against a live DOM node. Bugs have
1229
+ shipped in exactly that gap — a React parent re-render used to silently reset
1230
+ the user's zoom, and a chart moved to a monitor with a different pixel ratio
1231
+ kept rendering at the old resolution. Both are covered in `e2e/` now.
1232
+
1233
+ The browser suite serves the repository over a small dependency-free static
1234
+ server (`e2e/server.mjs`) and loads the library from source, so it tests the
1235
+ files that ship rather than a build artifact. The React fixture pulls React
1236
+ from esm.sh, the same way `demo/react.html` does.
1237
+
1238
+ **Running it locally.** `npm run test:e2e` downloads Playwright's bundled
1239
+ Chromium the first time. If that CDN is blocked on your machine, point the
1240
+ suite at a browser you already have:
1241
+
1242
+ ```bash
1243
+ WICK_E2E_CHANNEL=chrome npm run test:e2e # or msedge
1244
+ ```
1245
+
1246
+ **Visual regression** is opt-in. Canvas output is not pixel-identical across
1247
+ operating systems, so a committed baseline from one machine red-lights
1248
+ everyone else; the rest of the suite compares the chart against *itself*
1249
+ instead (repaint X, assert only what should have moved did). To gate on real
1250
+ screenshots, generate baselines on the platform that will run them:
1251
+
1252
+ ```bash
1253
+ WICK_E2E_VISUAL=1 npm run test:e2e -- --update-snapshots
1254
+ ```
1255
+
1069
1256
  ## Roadmap ideas
1070
1257
 
1071
1258
  - More overlays (Bollinger, VWAP), MACD pane, drawing tools
@@ -1094,6 +1281,12 @@ Two behavioral notes: custom indicators registered via
1094
1281
  alias (one registry), and cross-tab co-view channels are now prefixed
1095
1282
  `wick-co-view:` (a 0.x tab and a 1.x tab won't pair — refresh both).
1096
1283
 
1284
+ ## Releases
1285
+
1286
+ Versioned per [semver](./CHANGELOG.md#how-this-project-versions); every
1287
+ release is a tagged GitHub Release with the changelog — see
1288
+ **[CHANGELOG.md](./CHANGELOG.md)**.
1289
+
1097
1290
  ## License
1098
1291
 
1099
1292
  MIT
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wickchart",
3
- "version": "1.6.0",
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.",
3
+ "version": "1.7.0",
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> (incl. tick/volume/dollar bars), a worker compute path for 1M-bar histories, report export, theming.",
5
5
  "type": "module",
6
6
  "main": "src/wick-chart.js",
7
7
  "module": "src/wick-chart.js",
@@ -22,6 +22,14 @@
22
22
  "./react": {
23
23
  "types": "./types/react.d.ts",
24
24
  "default": "./src/react.js"
25
+ },
26
+ "./worker": {
27
+ "types": "./types/worker.d.ts",
28
+ "default": "./src/worker.js"
29
+ },
30
+ "./report": {
31
+ "types": "./types/report.d.ts",
32
+ "default": "./src/report.js"
25
33
  }
26
34
  },
27
35
  "files": [
@@ -42,10 +50,11 @@
42
50
  },
43
51
  "scripts": {
44
52
  "dev": "npx --yes serve . -l 5173",
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\"",
53
+ "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\" \"plugins/tape/tests/*.test.mjs\" \"plugins/grid/tests/*.test.mjs\" \"plugins/paper/tests/*.test.mjs\"",
54
+ "test:e2e": "playwright test",
46
55
  "build:types": "node -e \"require('fs').rmSync('types', { recursive: true, force: true });\" && tsc -p tsconfig.json",
47
56
  "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 && 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"
57
+ "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 src/worker.js && node --check src/worker-core.js && node --check src/report.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 && node --check plugins/tape/core.mjs && node --check plugins/tape/tape.mjs && node --check plugins/grid/core.mjs && node --check plugins/grid/grid.mjs && node --check plugins/paper/core.mjs && node --check plugins/paper/paper.mjs"
49
58
  },
50
59
  "keywords": [
51
60
  "chart",
@@ -56,6 +65,9 @@
56
65
  "web-component",
57
66
  "custom-element",
58
67
  "canvas",
68
+ "paper-trading",
69
+ "equity-curve",
70
+ "web-worker",
59
71
  "zero-dependency",
60
72
  "tradingview",
61
73
  "vwap",
@@ -72,6 +84,7 @@
72
84
  "url": "git+https://github.com/benyblack/wickchart.git"
73
85
  },
74
86
  "devDependencies": {
87
+ "@playwright/test": "^1.63.0",
75
88
  "@types/react": "^19.1.0",
76
89
  "jsdom": "^26.1.0",
77
90
  "react": "^19.1.0",