wickchart 0.4.0 → 1.0.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 +75 -48
- package/package.json +12 -12
- package/src/core.js +9 -7
- package/src/feeds.js +1 -1
- package/src/{hab-chart.js → wick-chart.js} +91 -82
- package/src/{hab-feed.js → wick-feed.js} +34 -19
- package/types/core.d.ts +3 -3
- package/types/{hab-chart.d.ts → wick-chart.d.ts} +92 -7
- package/types/{hab-feed.d.ts → wick-feed.d.ts} +8 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# WickChart
|
|
2
2
|
|
|
3
|
-
**`<
|
|
3
|
+
**`<wick-chart>` — a modern, simpler, more useful charting web component.**
|
|
4
4
|
|
|
5
5
|
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
|
|
@@ -14,8 +14,8 @@ npm install wickchart
|
|
|
14
14
|
|
|
15
15
|
```js
|
|
16
16
|
// any bundler / framework — TypeScript types included
|
|
17
|
-
import 'wickchart'; // registers <
|
|
18
|
-
import
|
|
17
|
+
import 'wickchart'; // registers <wick-chart>
|
|
18
|
+
import WickChart from 'wickchart'; // for WickChart.registerIndicator(...)
|
|
19
19
|
import { encodeStateQuery } from 'wickchart/core'; // pure helpers
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -24,10 +24,10 @@ Or straight from a CDN — no install, no build:
|
|
|
24
24
|
```html
|
|
25
25
|
<script type="module" src="https://unpkg.com/wickchart"></script>
|
|
26
26
|
|
|
27
|
-
<
|
|
27
|
+
<wick-chart label="BTC · 1h" type="candles" indicators="sma:20 volume"></wick-chart>
|
|
28
28
|
|
|
29
29
|
<script type="module">
|
|
30
|
-
const chart = document.querySelector('
|
|
30
|
+
const chart = document.querySelector('wick-chart');
|
|
31
31
|
chart.setData(bars); // [{ time, open, high, low, close, volume }]
|
|
32
32
|
chart.update(bar); // stream live updates
|
|
33
33
|
</script>
|
|
@@ -37,7 +37,7 @@ Works in plain HTML, React, Vue, Svelte, Angular — anywhere a `<div>` works.
|
|
|
37
37
|
TypeScript declarations ship inside the package (generated at pack time from
|
|
38
38
|
the JSDoc-annotated source — the repo itself stays 100% dependency-free JS).
|
|
39
39
|
|
|
40
|
-
## Declarative live charts with `<
|
|
40
|
+
## Declarative live charts with `<wick-feed>`
|
|
41
41
|
|
|
42
42
|
One more script tag and your chart is fully live — data, backfill, streaming —
|
|
43
43
|
with **zero JavaScript written**:
|
|
@@ -45,13 +45,13 @@ with **zero JavaScript written**:
|
|
|
45
45
|
```html
|
|
46
46
|
<script type="module" src="https://unpkg.com/wickchart/feed"></script>
|
|
47
47
|
|
|
48
|
-
<
|
|
49
|
-
<
|
|
48
|
+
<wick-feed for="chart" binance="BTCUSDT" tf="1h"></wick-feed>
|
|
49
|
+
<wick-chart id="chart" indicators="sma:20 volume" profile></wick-chart>
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
| Attribute | Meaning |
|
|
53
53
|
| ----------- | ----------------------------------------------------------------------- |
|
|
54
|
-
| `for` | target `<
|
|
54
|
+
| `for` | target `<wick-chart>` id (auto-pairs with the first chart when omitted) |
|
|
55
55
|
| `binance` | Binance symbol (`BTCUSDT`) — REST load + WebSocket live + backfill |
|
|
56
56
|
| `demo` | deterministic offline synthetic feed (`demo="ETH"` picks a base price) |
|
|
57
57
|
| `url` | generic REST endpoint returning a JSON array of bars (+ `poll="10"` sec) |
|
|
@@ -61,7 +61,7 @@ with **zero JavaScript written**:
|
|
|
61
61
|
|
|
62
62
|
The element reflects its state in the `status` attribute (`loading`, `live`,
|
|
63
63
|
`polling`, `fallback`, `loaded`, `waiting`, `idle`) and emits
|
|
64
|
-
`
|
|
64
|
+
`wick-feed:status` / `wick-feed:fallback` events. When Binance is unreachable
|
|
65
65
|
(geo-blocked, offline), it degrades gracefully: WebSocket → REST polling → a
|
|
66
66
|
synthetic stream bridged from the last real price, so the chart never goes
|
|
67
67
|
blank. It also wires `chart.onloadmore` for infinite backfill automatically.
|
|
@@ -71,7 +71,7 @@ blank. It also wires `chart.onloadmore` for infinite backfill automatically.
|
|
|
71
71
|
## Why another chart library?
|
|
72
72
|
|
|
73
73
|
TradingView's charting library is powerful but heavy and enterprise-licensed;
|
|
74
|
-
most wrappers add build steps and framework lock-in.
|
|
74
|
+
most wrappers add build steps and framework lock-in. WickChart takes the opposite
|
|
75
75
|
bet:
|
|
76
76
|
|
|
77
77
|
- **Zero dependencies, single file** (~40 KB unminified, no build step required)
|
|
@@ -82,7 +82,13 @@ bet:
|
|
|
82
82
|
outside the component (Shadow DOM friendly)
|
|
83
83
|
- **Accessible** — focusable, arrow-key crosshair, ARIA summary of the data
|
|
84
84
|
|
|
85
|
-
##
|
|
85
|
+
## Try it online
|
|
86
|
+
|
|
87
|
+
The demo site is deployed to GitHub Pages:
|
|
88
|
+
**https://benyblack.github.io/wickchart/** — a landing page with a live hero
|
|
89
|
+
chart, the full interactive demo, and the zero-JavaScript declarative page.
|
|
90
|
+
|
|
91
|
+
## Run the demo locally
|
|
86
92
|
|
|
87
93
|
```bash
|
|
88
94
|
npm run dev # serves on http://localhost:5173
|
|
@@ -147,7 +153,7 @@ Register your own — anything from a one-liner moving average to a multi-line
|
|
|
147
153
|
pane:
|
|
148
154
|
|
|
149
155
|
```js
|
|
150
|
-
|
|
156
|
+
WickChart.registerIndicator('vwap', {
|
|
151
157
|
kind: 'overlay', // or 'pane'
|
|
152
158
|
params: { period: 20 }, // defaults; set via indicators="vwap:30"
|
|
153
159
|
compute(bars, params) { // bars: normalized {time,open,high,low,close,volume}
|
|
@@ -165,11 +171,11 @@ HabChart.registerIndicator('vwap', {
|
|
|
165
171
|
chart.indicators = 'vwap:20';
|
|
166
172
|
```
|
|
167
173
|
|
|
168
|
-
`import
|
|
169
|
-
`
|
|
174
|
+
`import WickChart from 'wickchart'` gives you the class for
|
|
175
|
+
`WickChart.registerIndicator(...)` (the element is registered as a side effect
|
|
170
176
|
of importing the package).
|
|
171
177
|
|
|
172
|
-
###
|
|
178
|
+
### WickScript — custom indicators as expressions
|
|
173
179
|
|
|
174
180
|
No build step, no JS: write an indicator inline in the attribute. `expr:{…}`
|
|
175
181
|
draws on the price chart; `pexpr:{…}` gets its own pane. Add an optional
|
|
@@ -177,10 +183,10 @@ draws on the price chart; `pexpr:{…}` gets its own pane. Add an optional
|
|
|
177
183
|
shareable URLs.
|
|
178
184
|
|
|
179
185
|
```html
|
|
180
|
-
<
|
|
186
|
+
<wick-chart indicators="sma:20 expr:{(close - sma(close,20)) / sma(close,20) * 100}@ff6a00"></wick-chart>
|
|
181
187
|
|
|
182
188
|
<!-- oscillator in its own pane -->
|
|
183
|
-
<
|
|
189
|
+
<wick-chart indicators="pexpr:{rsi(close,14)} pexpr:{change(close) / close * 100}"></wick-chart>
|
|
184
190
|
```
|
|
185
191
|
|
|
186
192
|
| Series variables | |
|
|
@@ -213,7 +219,7 @@ attribute syntax:
|
|
|
213
219
|
```js
|
|
214
220
|
import { scriptIndicator } from 'wickchart/core';
|
|
215
221
|
|
|
216
|
-
|
|
222
|
+
WickChart.registerIndicator('spread', scriptIndicator('close - ema(close,21)'));
|
|
217
223
|
chart.indicators = 'spread'; // now usable like any built-in
|
|
218
224
|
```
|
|
219
225
|
|
|
@@ -222,7 +228,7 @@ press **+ Expr** — invalid expressions show the compiler's error inline).
|
|
|
222
228
|
|
|
223
229
|
### Volatility-regime shading
|
|
224
230
|
|
|
225
|
-
`<
|
|
231
|
+
`<wick-chart volshading>` tints the price pane background by realized
|
|
226
232
|
volatility — the rolling stddev of log returns (20 bars by default),
|
|
227
233
|
classified against its own full-history percentiles: **calm** (≤ 30th
|
|
228
234
|
percentile, subtle blue), **normal** (untinted), **hot** (≥ 70th percentile,
|
|
@@ -231,9 +237,9 @@ read instantly, and the legend shows the hovered bar's regime and
|
|
|
231
237
|
percentile (`VOL 30/70 · hot · 94%ile`).
|
|
232
238
|
|
|
233
239
|
```html
|
|
234
|
-
<
|
|
235
|
-
<
|
|
236
|
-
<
|
|
240
|
+
<wick-chart volshading></wick-chart> <!-- defaults 30/70, 20 bars -->
|
|
241
|
+
<wick-chart volshading="20/85"></wick-chart> <!-- custom cutoffs -->
|
|
242
|
+
<wick-chart volshading="20/85/50"></wick-chart> <!-- + 50-bar vol window -->
|
|
237
243
|
```
|
|
238
244
|
|
|
239
245
|
Cutoffs are clamped so the low percentile always stays at least 2 points
|
|
@@ -277,7 +283,7 @@ from `wickchart/core` for server-side use.
|
|
|
277
283
|
|
|
278
284
|
### Sonification — the chart by ear
|
|
279
285
|
|
|
280
|
-
`<
|
|
286
|
+
`<wick-chart sonify>` maps price to pitch (180–880 Hz across the visible
|
|
281
287
|
scale, log-aware): moving the crosshair with the mouse or **arrow keys** plays
|
|
282
288
|
a short tone per bar, so trend and shape are audible — a rare accessibility
|
|
283
289
|
win for screen-reader users. `chart.playRange()` sweeps the whole visible
|
|
@@ -291,7 +297,7 @@ Tag charts with the same channel and they share pointers — across browser
|
|
|
291
297
|
tabs, or between multiple charts on one page:
|
|
292
298
|
|
|
293
299
|
```html
|
|
294
|
-
<
|
|
300
|
+
<wick-chart co-view="btc-room"></wick-chart>
|
|
295
301
|
```
|
|
296
302
|
|
|
297
303
|
Hovering in one tab draws a ghost crosshair (accent, dotted, with the time
|
|
@@ -302,12 +308,12 @@ stops moving. Same-origin only (BroadcastChannel); the connection follows the
|
|
|
302
308
|
|
|
303
309
|
### Smart annotations
|
|
304
310
|
|
|
305
|
-
`<
|
|
311
|
+
`<wick-chart annotations>` marks notable events on the visible range — volume
|
|
306
312
|
spikes (>3× average), price gaps, 41-bar pivot highs/lows, and RSI
|
|
307
313
|
divergences — with lettered badges (V/G/H/L/D). Hover a badged bar and the
|
|
308
314
|
legend shows a one-line insight ("Volume 4.2× average", "Bearish RSI
|
|
309
315
|
divergence"). The current set is emitted on every recompute via the
|
|
310
|
-
`
|
|
316
|
+
`wick:annotations` event, so hosts can build their own UI from it. Badges are
|
|
311
317
|
hidden at extreme zoom-out, where bars collapse into columns.
|
|
312
318
|
|
|
313
319
|
### Example: VWAP via the registry
|
|
@@ -316,9 +322,9 @@ VWAP ships in the demo but *not* as a builtin — it's the reference for writing
|
|
|
316
322
|
your own (session-anchored, resets each trading day):
|
|
317
323
|
|
|
318
324
|
```js
|
|
319
|
-
import
|
|
325
|
+
import WickChart from 'wickchart';
|
|
320
326
|
|
|
321
|
-
|
|
327
|
+
WickChart.registerIndicator('vwap', {
|
|
322
328
|
kind: 'overlay',
|
|
323
329
|
params: {},
|
|
324
330
|
compute(bars) {
|
|
@@ -381,7 +387,7 @@ chart.addPosition({ id: 'x1', side: 'short', entry: 66000, qty: 1 });
|
|
|
381
387
|
chart.removePosition('x1');
|
|
382
388
|
|
|
383
389
|
chart.addAlert({ price: 65000, direction: 'above' }); // 'above' | 'below' | 'cross'
|
|
384
|
-
chart.addEventListener('
|
|
390
|
+
chart.addEventListener('wick:alert', (e) => {
|
|
385
391
|
console.log('crossed!', e.detail.id, e.detail.price);
|
|
386
392
|
});
|
|
387
393
|
```
|
|
@@ -391,12 +397,12 @@ The P&L chip recalculates on every streamed bar. Alerts are edge-triggered
|
|
|
391
397
|
|
|
392
398
|
### Stats & measure
|
|
393
399
|
|
|
394
|
-
`<
|
|
400
|
+
`<wick-chart stats>` shows live statistics of the visible range — return %,
|
|
395
401
|
max drawdown, annualized volatility, up/down bar counts, average volume —
|
|
396
402
|
recalculated as you pan and zoom.
|
|
397
403
|
|
|
398
404
|
Hold **Shift and drag** across the chart to measure a move: an overlay shows
|
|
399
|
-
Δprice, Δ%, bar count and elapsed time, and a `
|
|
405
|
+
Δprice, Δ%, bar count and elapsed time, and a `wick:measure` event fires on
|
|
400
406
|
release (`detail.from` / `detail.to` carry index, time and price). Click or
|
|
401
407
|
press `Esc` to clear.
|
|
402
408
|
|
|
@@ -423,9 +429,9 @@ Reflected properties (`chart.type = 'line'`) work for `theme`, `type`, `label`,
|
|
|
423
429
|
|
|
424
430
|
| Event | Detail |
|
|
425
431
|
| --------------- | ---------------------------------------------------------- |
|
|
426
|
-
| `
|
|
427
|
-
| `
|
|
428
|
-
| `
|
|
432
|
+
| `wick:crosshair` | `{ index, bar, x, y, price }` on hover / arrows, `null` on leave |
|
|
433
|
+
| `wick:range` | `{ from, to }` after zoom / pan / jump |
|
|
434
|
+
| `wick:select` | `{ index, bar, price }` on click/tap (e.g. open an order form at that price) |
|
|
429
435
|
|
|
430
436
|
## Theming
|
|
431
437
|
|
|
@@ -433,18 +439,18 @@ All colors are CSS custom properties settable on the element (they pierce the
|
|
|
433
439
|
Shadow DOM):
|
|
434
440
|
|
|
435
441
|
```css
|
|
436
|
-
|
|
437
|
-
--
|
|
438
|
-
--
|
|
439
|
-
--
|
|
440
|
-
--
|
|
441
|
-
--
|
|
442
|
-
--
|
|
443
|
-
--
|
|
444
|
-
--
|
|
445
|
-
--
|
|
446
|
-
--
|
|
447
|
-
--
|
|
442
|
+
wick-chart {
|
|
443
|
+
--wick-bg: #0d1117; /* transparent works too */
|
|
444
|
+
--wick-up: #16c784;
|
|
445
|
+
--wick-down: #ea3943;
|
|
446
|
+
--wick-accent: #4c8dff; /* line & area color */
|
|
447
|
+
--wick-text: #8b949e; /* axis text */
|
|
448
|
+
--wick-text-strong: #e6edf3; /* legend values */
|
|
449
|
+
--wick-grid: rgba(230,237,243,.05);
|
|
450
|
+
--wick-border: rgba(230,237,243,.09);
|
|
451
|
+
--wick-crosshair: rgba(230,237,243,.42);
|
|
452
|
+
--wick-rsi: #a78bfa;
|
|
453
|
+
--wick-overlay-0: #f0b429; /* SMA color, …-1, -2, … for more overlays */
|
|
448
454
|
}
|
|
449
455
|
```
|
|
450
456
|
|
|
@@ -507,6 +513,27 @@ column, and an offscreen layer so hover only repaints the crosshair.
|
|
|
507
513
|
- Incremental (O(1)) indicator updates for high-frequency streaming
|
|
508
514
|
- Min/max downsampling and/or an offscreen hover layer if profiling ever demands
|
|
509
515
|
|
|
516
|
+
## Migrating from 0.x (HabView)
|
|
517
|
+
|
|
518
|
+
1.0 renames the public surface to the WickChart brand. The 0.x names keep
|
|
519
|
+
working as **deprecated aliases** (removed in 2.0), so upgrading is safe to
|
|
520
|
+
do lazily:
|
|
521
|
+
|
|
522
|
+
| 0.x (deprecated alias) | 1.0 canonical |
|
|
523
|
+
|---|---|
|
|
524
|
+
| `<hab-chart>` / `<hab-feed>` | `<wick-chart>` / `<wick-feed>` |
|
|
525
|
+
| `hab:range`, `hab:select`, `hab:alert`, `hab:crosshair`, `hab:measure`, `hab:annotations` | `wick:*` of the same name (both fire during 1.x) |
|
|
526
|
+
| `hab-feed:status` / `hab-feed:fallback` | `wick-feed:status` / `wick-feed:fallback` (both fire during 1.x) |
|
|
527
|
+
| `--hab-bg`, `--hab-up`, … | `--wick-*` of the same name (`--wick-*` wins; `--hab-*` is the fallback) |
|
|
528
|
+
| `HabChart` / `HabFeed` classes | `WickChart` / `WickFeed` (also as named exports) |
|
|
529
|
+
| HabScript (the `expr:{…}` language) | WickScript — syntax unchanged |
|
|
530
|
+
| `import … from 'wickchart/src/hab-chart.js'` | use the package entry points (`wickchart`, `wickchart/core`, `wickchart/feed`) — module files are renamed |
|
|
531
|
+
|
|
532
|
+
Two behavioral notes: custom indicators registered via
|
|
533
|
+
`WickChart.registerIndicator()` are shared with the legacy `<hab-chart>`
|
|
534
|
+
alias (one registry), and cross-tab co-view channels are now prefixed
|
|
535
|
+
`wick-co-view:` (a 0.x tab and a 1.x tab won't pair — refresh both).
|
|
536
|
+
|
|
510
537
|
## License
|
|
511
538
|
|
|
512
539
|
MIT
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wickchart",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "<
|
|
3
|
+
"version": "1.0.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.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "src/
|
|
7
|
-
"module": "src/
|
|
8
|
-
"types": "types/
|
|
6
|
+
"main": "src/wick-chart.js",
|
|
7
|
+
"module": "src/wick-chart.js",
|
|
8
|
+
"types": "types/wick-chart.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"types": "./types/
|
|
12
|
-
"default": "./src/
|
|
11
|
+
"types": "./types/wick-chart.d.ts",
|
|
12
|
+
"default": "./src/wick-chart.js"
|
|
13
13
|
},
|
|
14
14
|
"./core": {
|
|
15
15
|
"types": "./types/core.d.ts",
|
|
16
16
|
"default": "./src/core.js"
|
|
17
17
|
},
|
|
18
18
|
"./feed": {
|
|
19
|
-
"types": "./types/
|
|
20
|
-
"default": "./src/
|
|
19
|
+
"types": "./types/wick-feed.d.ts",
|
|
20
|
+
"default": "./src/wick-feed.js"
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
"types"
|
|
26
26
|
],
|
|
27
27
|
"sideEffects": [
|
|
28
|
-
"src/
|
|
28
|
+
"src/wick-chart.js"
|
|
29
29
|
],
|
|
30
30
|
"scripts": {
|
|
31
31
|
"dev": "npx --yes serve . -l 5173",
|
|
32
32
|
"test": "node --test \"tests/*.test.mjs\"",
|
|
33
|
-
"build:types": "tsc -p tsconfig.json",
|
|
33
|
+
"build:types": "node -e \"require('fs').rmSync('types', { recursive: true, force: true });\" && tsc -p tsconfig.json",
|
|
34
34
|
"prepack": "npm run build:types",
|
|
35
|
-
"ci": "npm run build:types && npm test && node --check src/
|
|
35
|
+
"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 demo/app.js"
|
|
36
36
|
},
|
|
37
37
|
"keywords": [
|
|
38
38
|
"chart",
|
package/src/core.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
*
|
|
2
|
+
* WickChart core — pure, DOM-free functions shared by <wick-chart> and tests.
|
|
3
3
|
* Importable in the browser (ESM) and in Node (`node --test`).
|
|
4
4
|
* MIT License.
|
|
5
5
|
* ========================================================================== */
|
|
@@ -252,7 +252,8 @@ export const fmtFull = (t) => {
|
|
|
252
252
|
};
|
|
253
253
|
|
|
254
254
|
/* ------------------------------------------------------------------ *
|
|
255
|
-
* Themes (every key overridable via --
|
|
255
|
+
* Themes (every key overridable via --wick-* CSS custom properties;
|
|
256
|
+
* the 0.x --hab-* names still work as fallbacks)
|
|
256
257
|
* ------------------------------------------------------------------ */
|
|
257
258
|
|
|
258
259
|
export const THEMES = {
|
|
@@ -867,7 +868,7 @@ export const BUILTIN_INDICATORS = new Map(
|
|
|
867
868
|
/**
|
|
868
869
|
* Parse an `indicators` attribute string against a registry.
|
|
869
870
|
* Token: `name[:param[/param…]][@color]`, the `volume` keyword, and
|
|
870
|
-
*
|
|
871
|
+
* WickScript blobs `expr:{…}` (overlay) / `pexpr:{…}` (separate pane).
|
|
871
872
|
* @param {string|null|undefined} str
|
|
872
873
|
* @param {Map<string, IndicatorDef>} registry
|
|
873
874
|
* @returns {{overlays: IndicatorEntry[], panes: IndicatorEntry[], volume: boolean, unknown: string[]}}
|
|
@@ -940,11 +941,12 @@ export function parseIndicators(str, registry) {
|
|
|
940
941
|
}
|
|
941
942
|
|
|
942
943
|
/* ------------------------------------------------------------------ *
|
|
943
|
-
*
|
|
944
|
+
* WickScript — safe expression mini-language for custom indicators
|
|
944
945
|
*
|
|
945
946
|
* `expr:{(close - sma(close,20)) / sma(close,20)}` compiles through a
|
|
946
947
|
* hand-written tokenizer + recursive-descent parser (no eval / Function)
|
|
947
|
-
* and evaluates element-wise over the bar series.
|
|
948
|
+
* and evaluates element-wise over the bar series. (Called "HabScript" in
|
|
949
|
+
* 0.x releases.)
|
|
948
950
|
* ------------------------------------------------------------------ */
|
|
949
951
|
|
|
950
952
|
const SCRIPT_MAX_LEN = 512;
|
|
@@ -1156,7 +1158,7 @@ function validateScriptNode(n) {
|
|
|
1156
1158
|
}
|
|
1157
1159
|
|
|
1158
1160
|
/**
|
|
1159
|
-
* Compile a
|
|
1161
|
+
* Compile a WickScript expression. Throws a descriptive error on any syntax
|
|
1160
1162
|
* or semantic problem — never evaluates strings at runtime.
|
|
1161
1163
|
* @param {string} src
|
|
1162
1164
|
* @returns {{src: string, ast: object}}
|
|
@@ -1303,7 +1305,7 @@ export function evalScript(compiled, bars) {
|
|
|
1303
1305
|
}
|
|
1304
1306
|
|
|
1305
1307
|
/**
|
|
1306
|
-
* Build an indicator definition from a
|
|
1308
|
+
* Build an indicator definition from a WickScript expression — used inline by
|
|
1307
1309
|
* `indicators="expr:{…}"` / `pexpr:{…}"`, or register it under a name:
|
|
1308
1310
|
* `HabChart.registerIndicator('myspread', scriptIndicator('close - ema(close,21)'))`.
|
|
1309
1311
|
* @param {string} src
|
package/src/feeds.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
*
|
|
2
|
+
* WickChart feeds — data-source helpers shared by <wick-feed> and the demo app.
|
|
3
3
|
* Browser module (uses fetch/WebSocket inside functions); importable in Node
|
|
4
4
|
* for unit-testing the pure generators.
|
|
5
5
|
* MIT License.
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
* <
|
|
2
|
+
* <wick-chart> — a modern, dependency-free financial charting web component.
|
|
3
3
|
*
|
|
4
|
-
* <
|
|
5
|
-
* </
|
|
4
|
+
* <wick-chart label="BTC · 1h" type="candles" indicators="sma:20 volume">
|
|
5
|
+
* </wick-chart>
|
|
6
6
|
* <script type="module">
|
|
7
|
-
* const chart = document.querySelector('
|
|
7
|
+
* const chart = document.querySelector('wick-chart');
|
|
8
8
|
* chart.setData(bars); // [{ time, open, high, low, close, volume }]
|
|
9
9
|
* chart.update(bar); // streaming update / append
|
|
10
10
|
* </script>
|
|
11
11
|
*
|
|
12
12
|
* Zero dependencies. Canvas-rendered. Framework-agnostic (works in React,
|
|
13
|
-
* Vue,
|
|
13
|
+
* Vue, plain HTML). Themeable with --wick-* CSS custom properties.
|
|
14
|
+
*
|
|
15
|
+
* The 0.x names (<hab-chart>, --hab-*, hab:* events) still work as
|
|
16
|
+
* deprecated aliases — see the README migration notes.
|
|
14
17
|
*
|
|
15
18
|
* MIT License.
|
|
16
19
|
* ========================================================================== */
|
|
@@ -29,14 +32,18 @@ import {
|
|
|
29
32
|
} from './core.js';
|
|
30
33
|
|
|
31
34
|
/* ------------------------------------------------------------------ *
|
|
32
|
-
* <
|
|
35
|
+
* <wick-chart>
|
|
33
36
|
* ------------------------------------------------------------------ */
|
|
34
37
|
|
|
38
|
+
/** Indicator registry — module scope so registrations are shared by
|
|
39
|
+
* <wick-chart> and the deprecated <hab-chart> alias element. */
|
|
40
|
+
const REGISTRY = new Map(BUILTIN_INDICATORS);
|
|
41
|
+
|
|
35
42
|
/* SSR safety: importing this module under Node (Next.js/Nuxt server render)
|
|
36
43
|
* must not throw — the element simply registers only in browsers. */
|
|
37
44
|
const HTMLElementBase = typeof HTMLElement !== 'undefined' ? HTMLElement : class {};
|
|
38
45
|
|
|
39
|
-
class
|
|
46
|
+
class WickChart extends HTMLElementBase {
|
|
40
47
|
static get observedAttributes() {
|
|
41
48
|
return ['theme', 'type', 'log', 'auto', 'indicators', 'precision', 'label', 'stats', 'profile', 'annotations', 'volshading', 'co-view', 'sonify'];
|
|
42
49
|
}
|
|
@@ -55,7 +62,7 @@ class HabChart extends HTMLElementBase {
|
|
|
55
62
|
contain: content;
|
|
56
63
|
}
|
|
57
64
|
:host(:focus-visible) {
|
|
58
|
-
outline: 2px solid var(--hab-accent, #4c8dff);
|
|
65
|
+
outline: 2px solid var(--wick-accent, var(--hab-accent, #4c8dff));
|
|
59
66
|
outline-offset: -2px;
|
|
60
67
|
}
|
|
61
68
|
.wrap { position: absolute; inset: 0; overflow: hidden; }
|
|
@@ -78,26 +85,26 @@ class HabChart extends HTMLElementBase {
|
|
|
78
85
|
}
|
|
79
86
|
.legend .row { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
|
|
80
87
|
.legend .sym {
|
|
81
|
-
color: var(--hab-text-strong, #e6edf3);
|
|
88
|
+
color: var(--wick-text-strong, var(--hab-text-strong, #e6edf3));
|
|
82
89
|
font-weight: 700;
|
|
83
90
|
font-size: 13px;
|
|
84
91
|
letter-spacing: 0.02em;
|
|
85
92
|
}
|
|
86
93
|
.legend .kv { display: inline-flex; gap: 5px; align-items: baseline; white-space: nowrap; }
|
|
87
|
-
.legend .k { color: var(--hab-text, #8b949e); font-size: 11px; }
|
|
88
|
-
.legend .v { color: var(--hab-text-strong, #e6edf3); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
94
|
+
.legend .k { color: var(--wick-text, var(--hab-text, #8b949e)); font-size: 11px; }
|
|
95
|
+
.legend .v { color: var(--wick-text-strong, var(--hab-text-strong, #e6edf3)); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
89
96
|
.legend .pct { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
90
|
-
.legend .up { color: var(--hab-up, #16c784); }
|
|
91
|
-
.legend .dn { color: var(--hab-down, #ea3943); }
|
|
97
|
+
.legend .up { color: var(--wick-up, var(--hab-up, #16c784)); }
|
|
98
|
+
.legend .dn { color: var(--wick-down, var(--hab-down, #ea3943)); }
|
|
92
99
|
.legend .ind {
|
|
93
100
|
display: inline-flex; align-items: center; gap: 6px;
|
|
94
|
-
color: var(--hab-text, #8b949e); font-size: 11.5px; white-space: nowrap;
|
|
101
|
+
color: var(--wick-text, var(--hab-text, #8b949e)); font-size: 11.5px; white-space: nowrap;
|
|
95
102
|
}
|
|
96
103
|
.legend .ind i { width: 8px; height: 2.5px; border-radius: 2px; display: inline-block; }
|
|
97
104
|
.legend .ind .v { font-size: 12px; }
|
|
98
105
|
.legend .insight {
|
|
99
|
-
color: var(--hab-accent, #4c8dff);
|
|
100
|
-
background: var(--hab-chip, rgba(127, 137, 153, 0.12));
|
|
106
|
+
color: var(--wick-accent, var(--hab-accent, #4c8dff));
|
|
107
|
+
background: var(--wick-chip, var(--hab-chip, rgba(127, 137, 153, 0.12)));
|
|
101
108
|
border-radius: 6px;
|
|
102
109
|
padding: 1px 8px;
|
|
103
110
|
font-size: 11.5px;
|
|
@@ -106,7 +113,7 @@ class HabChart extends HTMLElementBase {
|
|
|
106
113
|
.nodata {
|
|
107
114
|
position: absolute; inset: 0;
|
|
108
115
|
display: flex; align-items: center; justify-content: center;
|
|
109
|
-
color: var(--hab-text, #8b949e);
|
|
116
|
+
color: var(--wick-text, var(--hab-text, #8b949e));
|
|
110
117
|
font: 500 13px ${FONT_STACK};
|
|
111
118
|
pointer-events: none;
|
|
112
119
|
}
|
|
@@ -119,25 +126,25 @@ class HabChart extends HTMLElementBase {
|
|
|
119
126
|
}
|
|
120
127
|
.hud .pos {
|
|
121
128
|
display: inline-flex; gap: 9px; align-items: baseline; white-space: nowrap;
|
|
122
|
-
background: var(--hab-chip, rgba(127, 137, 153, 0.12));
|
|
123
|
-
border: 1px solid var(--hab-border, rgba(148, 163, 184, 0.2));
|
|
129
|
+
background: var(--wick-chip, var(--hab-chip, rgba(127, 137, 153, 0.12)));
|
|
130
|
+
border: 1px solid var(--wick-border, var(--hab-border, rgba(148, 163, 184, 0.2)));
|
|
124
131
|
border-radius: 7px;
|
|
125
132
|
padding: 3px 9px;
|
|
126
133
|
}
|
|
127
134
|
.hud .statsrow {
|
|
128
135
|
display: inline-flex; gap: 12px; white-space: nowrap;
|
|
129
|
-
background: var(--hab-chip, rgba(127, 137, 153, 0.12));
|
|
130
|
-
border: 1px solid var(--hab-border, rgba(148, 163, 184, 0.2));
|
|
136
|
+
background: var(--wick-chip, var(--hab-chip, rgba(127, 137, 153, 0.12)));
|
|
137
|
+
border: 1px solid var(--wick-border, var(--hab-border, rgba(148, 163, 184, 0.2)));
|
|
131
138
|
border-radius: 7px;
|
|
132
139
|
padding: 3px 10px;
|
|
133
|
-
color: var(--hab-text, #8b949e);
|
|
140
|
+
color: var(--wick-text, var(--hab-text, #8b949e));
|
|
134
141
|
font-variant-numeric: tabular-nums;
|
|
135
142
|
}
|
|
136
|
-
.hud .statsrow b { color: var(--hab-text-strong, #e6edf3); font-weight: 600; }
|
|
137
|
-
.hud .k { color: var(--hab-text, #8b949e); font-weight: 500; }
|
|
138
|
-
.hud .v { color: var(--hab-text-strong, #e6edf3); font-variant-numeric: tabular-nums; }
|
|
139
|
-
.hud .up { color: var(--hab-up, #16c784); }
|
|
140
|
-
.hud .dn { color: var(--hab-down, #ea3943); }
|
|
143
|
+
.hud .statsrow b { color: var(--wick-text-strong, var(--hab-text-strong, #e6edf3)); font-weight: 600; }
|
|
144
|
+
.hud .k { color: var(--wick-text, var(--hab-text, #8b949e)); font-weight: 500; }
|
|
145
|
+
.hud .v { color: var(--wick-text-strong, var(--hab-text-strong, #e6edf3)); font-variant-numeric: tabular-nums; }
|
|
146
|
+
.hud .up { color: var(--wick-up, var(--hab-up, #16c784)); }
|
|
147
|
+
.hud .dn { color: var(--wick-down, var(--hab-down, #ea3943)); }
|
|
141
148
|
</style>
|
|
142
149
|
<div class="wrap" part="wrap">
|
|
143
150
|
<canvas part="canvas" role="img"></canvas>
|
|
@@ -305,7 +312,7 @@ class HabChart extends HTMLElementBase {
|
|
|
305
312
|
this._label = val || '';
|
|
306
313
|
break;
|
|
307
314
|
case 'indicators':
|
|
308
|
-
this._ind = parseIndicators(val,
|
|
315
|
+
this._ind = parseIndicators(val, WickChart._registry());
|
|
309
316
|
break;
|
|
310
317
|
case 'stats':
|
|
311
318
|
this._stats = val != null && val !== 'false';
|
|
@@ -339,20 +346,15 @@ class HabChart extends HTMLElementBase {
|
|
|
339
346
|
* Indicator registry
|
|
340
347
|
* ------------------------------------------------------------ */
|
|
341
348
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
/** Lazily-built registry, seeded with the built-in indicators. */
|
|
349
|
+
/** Indicator registry (module scope — shared with the legacy alias tag). */
|
|
345
350
|
static _registry() {
|
|
346
|
-
|
|
347
|
-
HabChart._registryMap = new Map(BUILTIN_INDICATORS);
|
|
348
|
-
}
|
|
349
|
-
return HabChart._registryMap;
|
|
351
|
+
return REGISTRY;
|
|
350
352
|
}
|
|
351
353
|
|
|
352
354
|
/**
|
|
353
355
|
* Register a custom indicator.
|
|
354
356
|
*
|
|
355
|
-
*
|
|
357
|
+
* WickChart.registerIndicator('vwap', {
|
|
356
358
|
* kind: 'overlay', // or 'pane'
|
|
357
359
|
* params: { period: 20 }, // defaults; settable via name:period
|
|
358
360
|
* compute(bars, params) { // bars: normalized {time,o,h,l,c,v}
|
|
@@ -373,7 +375,7 @@ class HabChart extends HTMLElementBase {
|
|
|
373
375
|
if (!def || typeof def.compute !== 'function') {
|
|
374
376
|
throw new Error('registerIndicator: def.compute must be a function');
|
|
375
377
|
}
|
|
376
|
-
|
|
378
|
+
REGISTRY.set(name.toLowerCase(), {
|
|
377
379
|
kind: def.kind === 'pane' ? 'pane' : 'overlay',
|
|
378
380
|
...def,
|
|
379
381
|
});
|
|
@@ -403,7 +405,7 @@ class HabChart extends HTMLElementBase {
|
|
|
403
405
|
}
|
|
404
406
|
const norm = [];
|
|
405
407
|
for (const b of bars) {
|
|
406
|
-
const nb =
|
|
408
|
+
const nb = WickChart._normBar(b);
|
|
407
409
|
if (nb) norm.push(nb);
|
|
408
410
|
}
|
|
409
411
|
// skip the O(n log n) sort when already ascending (typical for feeds)
|
|
@@ -432,7 +434,7 @@ class HabChart extends HTMLElementBase {
|
|
|
432
434
|
* @param {import('./core.js').Bar} bar
|
|
433
435
|
*/
|
|
434
436
|
update(bar) {
|
|
435
|
-
const b =
|
|
437
|
+
const b = WickChart._normBar(bar);
|
|
436
438
|
if (!b) return;
|
|
437
439
|
const d = this._data;
|
|
438
440
|
const last = d[d.length - 1];
|
|
@@ -492,7 +494,7 @@ class HabChart extends HTMLElementBase {
|
|
|
492
494
|
}
|
|
493
495
|
const older = [];
|
|
494
496
|
for (const b of bars) {
|
|
495
|
-
const nb =
|
|
497
|
+
const nb = WickChart._normBar(b);
|
|
496
498
|
if (nb) older.push(nb);
|
|
497
499
|
}
|
|
498
500
|
const { bars: merged, added } = mergeOlderData(this._data, older);
|
|
@@ -540,14 +542,14 @@ class HabChart extends HTMLElementBase {
|
|
|
540
542
|
setVisibleRange(range) {
|
|
541
543
|
const d = this._data;
|
|
542
544
|
if (!d.length || !range || !this._ly) return;
|
|
543
|
-
const from =
|
|
544
|
-
const to =
|
|
545
|
-
let i0 =
|
|
546
|
-
let i1 =
|
|
545
|
+
const from = WickChart._timeToMs(range.from);
|
|
546
|
+
const to = WickChart._timeToMs(range.to);
|
|
547
|
+
let i0 = WickChart._indexForTime(d, from);
|
|
548
|
+
let i1 = WickChart._indexForTime(d, to);
|
|
547
549
|
if (i0 > i1) [i0, i1] = [i1, i0];
|
|
548
550
|
if (i1 - i0 < 2) return;
|
|
549
551
|
const { plotRight } = this._ly;
|
|
550
|
-
this._view.spacing = clamp(plotRight / (i1 - i0), this._minSpacing(),
|
|
552
|
+
this._view.spacing = clamp(plotRight / (i1 - i0), this._minSpacing(), WickChart._MAX_SP);
|
|
551
553
|
this._view.rightIndex = i1;
|
|
552
554
|
this._auto = false;
|
|
553
555
|
this._clampView();
|
|
@@ -725,7 +727,8 @@ class HabChart extends HTMLElementBase {
|
|
|
725
727
|
}
|
|
726
728
|
|
|
727
729
|
/**
|
|
728
|
-
* Price alert. Fires `
|
|
730
|
+
* Price alert. Fires `wick:alert` ({id, price, bar}) on an edge crossing
|
|
731
|
+
* (plus the deprecated `hab:alert` alias)
|
|
729
732
|
* during streaming updates.
|
|
730
733
|
* @param {{id?: string, price: number, direction?: 'above'|'below'|'cross',
|
|
731
734
|
* once?: boolean}} alert
|
|
@@ -764,9 +767,7 @@ class HabChart extends HTMLElementBase {
|
|
|
764
767
|
if (a.fired) continue;
|
|
765
768
|
if (checkAlertCross(a, prevClose, bar.close)) {
|
|
766
769
|
a.fired = true;
|
|
767
|
-
this.
|
|
768
|
-
new CustomEvent('hab:alert', { detail: { id: a.id, price: a.price, bar } })
|
|
769
|
-
);
|
|
770
|
+
this._fire('alert', { id: a.id, price: a.price, bar });
|
|
770
771
|
if (a.once) this._alerts = this._alerts.filter((x) => x !== a);
|
|
771
772
|
}
|
|
772
773
|
}
|
|
@@ -808,7 +809,7 @@ class HabChart extends HTMLElementBase {
|
|
|
808
809
|
if (!b) return null;
|
|
809
810
|
const t = b.time != null ? b.time : b.t;
|
|
810
811
|
if (!isNum(t)) return null;
|
|
811
|
-
const time =
|
|
812
|
+
const time = WickChart._timeToMs(t);
|
|
812
813
|
const close = isNum(b.close) ? b.close : isNum(b.value) ? b.value : NaN;
|
|
813
814
|
if (!isNum(close)) return null;
|
|
814
815
|
const open = isNum(b.open) ? b.open : close;
|
|
@@ -887,8 +888,9 @@ class HabChart extends HTMLElementBase {
|
|
|
887
888
|
if (this._pal && this._palKey === this._theme) return this._pal;
|
|
888
889
|
const base = THEMES[this._theme] || THEMES.dark;
|
|
889
890
|
const cs = getComputedStyle(this);
|
|
891
|
+
// --wick-* is canonical; --hab-* still honored as the 0.x fallback
|
|
890
892
|
const get = (name, fallback) => {
|
|
891
|
-
const v = cs.getPropertyValue('--hab-' + name).trim();
|
|
893
|
+
const v = cs.getPropertyValue('--wick-' + name).trim() || cs.getPropertyValue('--hab-' + name).trim();
|
|
892
894
|
return v || fallback;
|
|
893
895
|
};
|
|
894
896
|
const pal = {};
|
|
@@ -899,7 +901,8 @@ class HabChart extends HTMLElementBase {
|
|
|
899
901
|
o.push(get('overlay-' + i, base.overlay[i]));
|
|
900
902
|
}
|
|
901
903
|
// allow single overlay color
|
|
902
|
-
const single =
|
|
904
|
+
const single =
|
|
905
|
+
cs.getPropertyValue('--wick-overlay').trim() || cs.getPropertyValue('--hab-overlay').trim();
|
|
903
906
|
pal.overlay = single ? base.overlay.map(() => single) : o;
|
|
904
907
|
} else {
|
|
905
908
|
pal[k] = get(k.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase()), base[k]);
|
|
@@ -1018,7 +1021,7 @@ class HabChart extends HTMLElementBase {
|
|
|
1018
1021
|
if (!d.length || !this._ly) return;
|
|
1019
1022
|
const { plotRight } = this._ly;
|
|
1020
1023
|
const target = Math.min(d.length, 150);
|
|
1021
|
-
this._view.spacing = clamp(plotRight / target, this._minSpacing(),
|
|
1024
|
+
this._view.spacing = clamp(plotRight / target, this._minSpacing(), WickChart._MAX_SP);
|
|
1022
1025
|
this._view.rightIndex = d.length - 1 + this._rightMargin();
|
|
1023
1026
|
}
|
|
1024
1027
|
|
|
@@ -1027,7 +1030,7 @@ class HabChart extends HTMLElementBase {
|
|
|
1027
1030
|
const ly = this._ly;
|
|
1028
1031
|
if (!d.length || !ly) return;
|
|
1029
1032
|
const v = this._view;
|
|
1030
|
-
v.spacing = clamp(v.spacing, this._minSpacing(),
|
|
1033
|
+
v.spacing = clamp(v.spacing, this._minSpacing(), WickChart._MAX_SP);
|
|
1031
1034
|
const visible = ly.plotRight / v.spacing;
|
|
1032
1035
|
const maxRight = d.length - 1 + Math.max(6, visible * 0.5);
|
|
1033
1036
|
const minRight = Math.min(2, d.length - 1);
|
|
@@ -1457,9 +1460,7 @@ class HabChart extends HTMLElementBase {
|
|
|
1457
1460
|
this._annoList = detectAnnotations(this._data, i0, i1, this._cachedRSI14());
|
|
1458
1461
|
this._annoKey = akey;
|
|
1459
1462
|
this._legendKey = ''; // legend may now show insights at the hovered bar
|
|
1460
|
-
this.
|
|
1461
|
-
new CustomEvent('hab:annotations', { detail: { annotations: this._annoList } })
|
|
1462
|
-
);
|
|
1463
|
+
this._fire('annotations', { annotations: this._annoList });
|
|
1463
1464
|
}
|
|
1464
1465
|
const A = this._annoList;
|
|
1465
1466
|
if (A.length) {
|
|
@@ -2353,7 +2354,7 @@ class HabChart extends HTMLElementBase {
|
|
|
2353
2354
|
const s = clamp(
|
|
2354
2355
|
(this._pinch.spacing * dist) / this._pinch.dist,
|
|
2355
2356
|
this._minSpacing(),
|
|
2356
|
-
|
|
2357
|
+
WickChart._MAX_SP
|
|
2357
2358
|
);
|
|
2358
2359
|
this._view.spacing = s;
|
|
2359
2360
|
this._view.rightIndex = this._pinch.idxAtMid + (ly.plotRight - mid.x) / s;
|
|
@@ -2406,15 +2407,11 @@ class HabChart extends HTMLElementBase {
|
|
|
2406
2407
|
const d = this._data;
|
|
2407
2408
|
const barA = d[clamp(m.iA, 0, d.length - 1)];
|
|
2408
2409
|
const barB = d[clamp(m.iB, 0, d.length - 1)];
|
|
2409
|
-
this.
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
bars: Math.abs(m.iB - m.iA),
|
|
2415
|
-
},
|
|
2416
|
-
})
|
|
2417
|
-
);
|
|
2410
|
+
this._fire('measure', {
|
|
2411
|
+
from: { index: m.iA, time: barA.time, price: m.pA },
|
|
2412
|
+
to: { index: m.iB, time: barB.time, price: m.pB },
|
|
2413
|
+
bars: Math.abs(m.iB - m.iA),
|
|
2414
|
+
});
|
|
2418
2415
|
}
|
|
2419
2416
|
} else if (this._pan && had && !this._pan.moved && this._data.length) {
|
|
2420
2417
|
if (this._measure) {
|
|
@@ -2426,11 +2423,7 @@ class HabChart extends HTMLElementBase {
|
|
|
2426
2423
|
const pt = this._localPoint(e);
|
|
2427
2424
|
const idx = clamp(Math.round(this._indexForX(pt.x)), 0, this._data.length - 1);
|
|
2428
2425
|
const price = this._yToPrice(pt.y);
|
|
2429
|
-
this.
|
|
2430
|
-
new CustomEvent('hab:select', {
|
|
2431
|
-
detail: { index: idx, bar: this._data[idx], price },
|
|
2432
|
-
})
|
|
2433
|
-
);
|
|
2426
|
+
this._fire('select', { index: idx, bar: this._data[idx], price });
|
|
2434
2427
|
}
|
|
2435
2428
|
}
|
|
2436
2429
|
this._pan = null;
|
|
@@ -2467,7 +2460,7 @@ class HabChart extends HTMLElementBase {
|
|
|
2467
2460
|
|
|
2468
2461
|
const factor = Math.exp(-dy * (e.ctrlKey ? 0.008 : 0.0016));
|
|
2469
2462
|
const oldSp = this._view.spacing;
|
|
2470
|
-
const newSp = clamp(oldSp * factor, this._minSpacing(),
|
|
2463
|
+
const newSp = clamp(oldSp * factor, this._minSpacing(), WickChart._MAX_SP);
|
|
2471
2464
|
if (newSp === oldSp) return;
|
|
2472
2465
|
const idxAtCursor = this._indexForX(pt.x);
|
|
2473
2466
|
this._view.spacing = newSp;
|
|
@@ -2504,12 +2497,12 @@ class HabChart extends HTMLElementBase {
|
|
|
2504
2497
|
this._invalidate();
|
|
2505
2498
|
this._emitRange();
|
|
2506
2499
|
} else if (key === '+' || key === '=') {
|
|
2507
|
-
this._view.spacing = clamp(this._view.spacing * 1.25, this._minSpacing(),
|
|
2500
|
+
this._view.spacing = clamp(this._view.spacing * 1.25, this._minSpacing(), WickChart._MAX_SP);
|
|
2508
2501
|
this._clampView();
|
|
2509
2502
|
this._invalidate();
|
|
2510
2503
|
this._emitRange();
|
|
2511
2504
|
} else if (key === '-' || key === '_') {
|
|
2512
|
-
this._view.spacing = clamp(this._view.spacing / 1.25, this._minSpacing(),
|
|
2505
|
+
this._view.spacing = clamp(this._view.spacing / 1.25, this._minSpacing(), WickChart._MAX_SP);
|
|
2513
2506
|
this._clampView();
|
|
2514
2507
|
this._invalidate();
|
|
2515
2508
|
this._emitRange();
|
|
@@ -2636,7 +2629,7 @@ class HabChart extends HTMLElementBase {
|
|
|
2636
2629
|
price: this._yToPrice(hover.y),
|
|
2637
2630
|
};
|
|
2638
2631
|
}
|
|
2639
|
-
this.
|
|
2632
|
+
this._fire('crosshair', detail);
|
|
2640
2633
|
|
|
2641
2634
|
// co-view: share the pointer with peer charts (leave events bypass throttle)
|
|
2642
2635
|
if (this._coviewCh) {
|
|
@@ -2678,7 +2671,7 @@ class HabChart extends HTMLElementBase {
|
|
|
2678
2671
|
if (!name || !this._connected || typeof BroadcastChannel === 'undefined') return;
|
|
2679
2672
|
if (!this._coviewPeer) this._coviewPeer = 'p' + Math.random().toString(36).slice(2, 8);
|
|
2680
2673
|
try {
|
|
2681
|
-
const ch = new BroadcastChannel('
|
|
2674
|
+
const ch = new BroadcastChannel('wick-co-view:' + name);
|
|
2682
2675
|
ch.onmessage = (ev) => this._onCoMessage(ev.data);
|
|
2683
2676
|
this._coviewCh = ch;
|
|
2684
2677
|
} catch (_) {}
|
|
@@ -2703,7 +2696,7 @@ class HabChart extends HTMLElementBase {
|
|
|
2703
2696
|
}
|
|
2704
2697
|
if (!isNum(m.time) || !this._data.length) return;
|
|
2705
2698
|
this._ghost = {
|
|
2706
|
-
index:
|
|
2699
|
+
index: WickChart._indexForTime(this._data, m.time),
|
|
2707
2700
|
yFrac: isNum(m.yFrac) ? clamp(m.yFrac, 0, 1) : null,
|
|
2708
2701
|
at: Date.now(),
|
|
2709
2702
|
};
|
|
@@ -2715,15 +2708,31 @@ class HabChart extends HTMLElementBase {
|
|
|
2715
2708
|
this._invalidate();
|
|
2716
2709
|
}
|
|
2717
2710
|
|
|
2711
|
+
/** Dispatch `wick:name` (canonical) plus the deprecated `hab:name` alias,
|
|
2712
|
+
* so 0.x listeners keep working until 2.0. */
|
|
2713
|
+
_fire(name, detail) {
|
|
2714
|
+
this.dispatchEvent(new CustomEvent('wick:' + name, { detail }));
|
|
2715
|
+
this.dispatchEvent(new CustomEvent('hab:' + name, { detail }));
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
2718
|
_emitRange() {
|
|
2719
2719
|
const r = this.getVisibleRange();
|
|
2720
2720
|
if (!r) return;
|
|
2721
|
-
this.
|
|
2721
|
+
this._fire('range', r);
|
|
2722
2722
|
}
|
|
2723
2723
|
}
|
|
2724
2724
|
|
|
2725
|
-
if (typeof customElements !== 'undefined'
|
|
2726
|
-
customElements.
|
|
2725
|
+
if (typeof customElements !== 'undefined') {
|
|
2726
|
+
if (!customElements.get('wick-chart')) {
|
|
2727
|
+
customElements.define('wick-chart', WickChart);
|
|
2728
|
+
}
|
|
2729
|
+
// 0.x alias: same element under its old tag name (deprecated, removed in 2.0)
|
|
2730
|
+
if (!customElements.get('hab-chart')) {
|
|
2731
|
+
/** @deprecated use <wick-chart> */
|
|
2732
|
+
class HabChart extends WickChart {}
|
|
2733
|
+
customElements.define('hab-chart', HabChart);
|
|
2734
|
+
}
|
|
2727
2735
|
}
|
|
2728
2736
|
|
|
2729
|
-
export default
|
|
2737
|
+
export default WickChart;
|
|
2738
|
+
export { WickChart };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
* <
|
|
2
|
+
* <wick-feed> — declarative data feeds for <wick-chart>.
|
|
3
3
|
*
|
|
4
|
-
* <script type="module" src="https://unpkg.com/
|
|
4
|
+
* <script type="module" src="https://unpkg.com/wickchart/feed"></script>
|
|
5
5
|
*
|
|
6
|
-
* <
|
|
7
|
-
* <
|
|
6
|
+
* <wick-feed for="chart" binance="BTCUSDT" tf="1h"></wick-feed>
|
|
7
|
+
* <wick-chart id="chart" indicators="sma:20 volume"></wick-chart>
|
|
8
8
|
*
|
|
9
9
|
* A fully live chart with zero JavaScript written. Sources:
|
|
10
10
|
* binance="SYMBOL" live via WebSocket (REST klines + backfill; falls back
|
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
* Attributes: for (chart id; auto-pairs with the first chart otherwise),
|
|
18
18
|
* tf (1m…1w), limit (initial bars, default 500), live="false" to disable
|
|
19
19
|
* streaming. Status is reflected in the `status` attribute and via
|
|
20
|
-
* `
|
|
21
|
-
* waiting / idle). `
|
|
20
|
+
* `wick-feed:status` events (loading / live / polling / fallback / loaded /
|
|
21
|
+
* waiting / idle). `wick-feed:fallback` fires when a live source degrades.
|
|
22
|
+
* (The 0.x event names `hab-feed:*` still fire as deprecated aliases.)
|
|
22
23
|
* ========================================================================== */
|
|
23
24
|
|
|
24
|
-
import './
|
|
25
|
+
import './wick-chart.js';
|
|
25
26
|
import {
|
|
26
27
|
genSynthetic,
|
|
27
28
|
makeSynthStream,
|
|
@@ -35,7 +36,7 @@ const LIVE_TICK_MS = 650;
|
|
|
35
36
|
|
|
36
37
|
const HTMLElementBase = typeof HTMLElement !== 'undefined' ? HTMLElement : class {};
|
|
37
38
|
|
|
38
|
-
class
|
|
39
|
+
class WickFeed extends HTMLElementBase {
|
|
39
40
|
static get observedAttributes() {
|
|
40
41
|
return ['for', 'binance', 'demo', 'url', 'tf', 'limit', 'poll', 'live'];
|
|
41
42
|
}
|
|
@@ -86,17 +87,24 @@ class HabFeed extends HTMLElementBase {
|
|
|
86
87
|
_setStatus(status, detail) {
|
|
87
88
|
if (!this.isConnected) return;
|
|
88
89
|
this.setAttribute('status', status);
|
|
89
|
-
this.
|
|
90
|
+
this._fire('status', { status, ...detail });
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
/**
|
|
93
|
+
/** Dispatch `wick-feed:name` plus the deprecated `hab-feed:name` alias. */
|
|
94
|
+
_fire(name, detail) {
|
|
95
|
+
this.dispatchEvent(new CustomEvent('wick-feed:' + name, { detail }));
|
|
96
|
+
this.dispatchEvent(new CustomEvent('hab-feed:' + name, { detail }));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Resolve the target chart (by `for` id, else the first chart element —
|
|
100
|
+
* <wick-chart> or the deprecated <hab-chart>). */
|
|
93
101
|
_resolveChart() {
|
|
94
102
|
const id = this.getAttribute('for');
|
|
95
103
|
if (id) {
|
|
96
104
|
const el = document.getElementById(id);
|
|
97
|
-
return el && el.tagName === 'HAB-CHART' ? el : null;
|
|
105
|
+
return el && (el.tagName === 'WICK-CHART' || el.tagName === 'HAB-CHART') ? el : null;
|
|
98
106
|
}
|
|
99
|
-
return document.querySelector('hab-chart');
|
|
107
|
+
return document.querySelector('wick-chart') || document.querySelector('hab-chart');
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
_restart() {
|
|
@@ -105,7 +113,7 @@ class HabFeed extends HTMLElementBase {
|
|
|
105
113
|
if (!chart || typeof chart.setData !== 'function') {
|
|
106
114
|
// chart not in the DOM yet (or not upgraded) — watch for it
|
|
107
115
|
this._setStatus('waiting');
|
|
108
|
-
customElements.whenDefined('
|
|
116
|
+
customElements.whenDefined('wick-chart').then(() => {
|
|
109
117
|
if (!this.isConnected) return;
|
|
110
118
|
this._observer = this._observer || new MutationObserver(() => {
|
|
111
119
|
const c = this._resolveChart();
|
|
@@ -208,9 +216,7 @@ class HabFeed extends HTMLElementBase {
|
|
|
208
216
|
}
|
|
209
217
|
|
|
210
218
|
_degrade(gen, chart, sym, tfId, limit, live, err) {
|
|
211
|
-
this.
|
|
212
|
-
new CustomEvent('hab-feed:fallback', { detail: { reason: err && err.message } })
|
|
213
|
-
);
|
|
219
|
+
this._fire('fallback', { reason: err && err.message });
|
|
214
220
|
this._synthetic(gen, chart, sym, tfId, limit, live, 'fallback');
|
|
215
221
|
}
|
|
216
222
|
|
|
@@ -248,8 +254,17 @@ class HabFeed extends HTMLElementBase {
|
|
|
248
254
|
}
|
|
249
255
|
}
|
|
250
256
|
|
|
251
|
-
if (typeof customElements !== 'undefined'
|
|
252
|
-
customElements.
|
|
257
|
+
if (typeof customElements !== 'undefined') {
|
|
258
|
+
if (!customElements.get('wick-feed')) {
|
|
259
|
+
customElements.define('wick-feed', WickFeed);
|
|
260
|
+
}
|
|
261
|
+
// 0.x alias: same element under its old tag name (deprecated, removed in 2.0)
|
|
262
|
+
if (!customElements.get('hab-feed')) {
|
|
263
|
+
/** @deprecated use <wick-feed> */
|
|
264
|
+
class HabFeed extends WickFeed {}
|
|
265
|
+
customElements.define('hab-feed', HabFeed);
|
|
266
|
+
}
|
|
253
267
|
}
|
|
254
268
|
|
|
255
|
-
export default
|
|
269
|
+
export default WickFeed;
|
|
270
|
+
export { WickFeed };
|
package/types/core.d.ts
CHANGED
|
@@ -512,7 +512,7 @@ export declare const BUILTIN_INDICATORS: Map<string, {
|
|
|
512
512
|
/**
|
|
513
513
|
* Parse an `indicators` attribute string against a registry.
|
|
514
514
|
* Token: `name[:param[/param…]][@color]`, the `volume` keyword, and
|
|
515
|
-
*
|
|
515
|
+
* WickScript blobs `expr:{…}` (overlay) / `pexpr:{…}` (separate pane).
|
|
516
516
|
* @param {string|null|undefined} str
|
|
517
517
|
* @param {Map<string, IndicatorDef>} registry
|
|
518
518
|
* @returns {{overlays: IndicatorEntry[], panes: IndicatorEntry[], volume: boolean, unknown: string[]}}
|
|
@@ -533,7 +533,7 @@ export declare function parseIndicators(str: string | null | undefined, registry
|
|
|
533
533
|
*/
|
|
534
534
|
export declare function splitIndicatorTokens(str: string | null | undefined): string[];
|
|
535
535
|
/**
|
|
536
|
-
* Compile a
|
|
536
|
+
* Compile a WickScript expression. Throws a descriptive error on any syntax
|
|
537
537
|
* or semantic problem — never evaluates strings at runtime.
|
|
538
538
|
* @param {string} src
|
|
539
539
|
* @returns {{src: string, ast: object}}
|
|
@@ -553,7 +553,7 @@ export declare function evalScript(compiled: {
|
|
|
553
553
|
ast: object;
|
|
554
554
|
} | string, bars: Bar[]): number[];
|
|
555
555
|
/**
|
|
556
|
-
* Build an indicator definition from a
|
|
556
|
+
* Build an indicator definition from a WickScript expression — used inline by
|
|
557
557
|
* `indicators="expr:{…}"` / `pexpr:{…}"`, or register it under a name:
|
|
558
558
|
* `HabChart.registerIndicator('myspread', scriptIndicator('close - ema(close,21)'))`.
|
|
559
559
|
* @param {string} src
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
declare const HTMLElementBase: {
|
|
2
2
|
new (): {};
|
|
3
3
|
};
|
|
4
|
-
declare class
|
|
4
|
+
declare class WickChart extends HTMLElementBase {
|
|
5
5
|
_canvas: any;
|
|
6
6
|
_ctx: any;
|
|
7
7
|
_legend: any;
|
|
@@ -172,13 +172,93 @@ declare class HabChart extends HTMLElementBase {
|
|
|
172
172
|
connectedCallback(): void;
|
|
173
173
|
disconnectedCallback(): void;
|
|
174
174
|
attributeChangedCallback(name: any, _old: any, val: any): void;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
/** Indicator registry (module scope — shared with the legacy alias tag). */
|
|
176
|
+
static _registry(): Map<string, {
|
|
177
|
+
kind: string;
|
|
178
|
+
params: {
|
|
179
|
+
period: number;
|
|
180
|
+
mult?: undefined;
|
|
181
|
+
fast?: undefined;
|
|
182
|
+
slow?: undefined;
|
|
183
|
+
signal?: undefined;
|
|
184
|
+
};
|
|
185
|
+
compute: (bars: any, p: any) => number[];
|
|
186
|
+
range?: undefined;
|
|
187
|
+
color?: undefined;
|
|
188
|
+
guides?: undefined;
|
|
189
|
+
fmt?: undefined;
|
|
190
|
+
} | {
|
|
191
|
+
kind: string;
|
|
192
|
+
params: {
|
|
193
|
+
period: number;
|
|
194
|
+
mult?: undefined;
|
|
195
|
+
fast?: undefined;
|
|
196
|
+
slow?: undefined;
|
|
197
|
+
signal?: undefined;
|
|
198
|
+
};
|
|
199
|
+
compute: (bars: any, p: any) => number[];
|
|
200
|
+
range?: undefined;
|
|
201
|
+
color?: undefined;
|
|
202
|
+
guides?: undefined;
|
|
203
|
+
fmt?: undefined;
|
|
204
|
+
} | {
|
|
205
|
+
kind: string;
|
|
206
|
+
params: {
|
|
207
|
+
period: number;
|
|
208
|
+
mult: number;
|
|
209
|
+
fast?: undefined;
|
|
210
|
+
slow?: undefined;
|
|
211
|
+
signal?: undefined;
|
|
212
|
+
};
|
|
213
|
+
compute: (bars: any, p: any) => {
|
|
214
|
+
lines: {
|
|
215
|
+
name: string;
|
|
216
|
+
values: number[];
|
|
217
|
+
}[];
|
|
218
|
+
};
|
|
219
|
+
range?: undefined;
|
|
220
|
+
color?: undefined;
|
|
221
|
+
guides?: undefined;
|
|
222
|
+
fmt?: undefined;
|
|
223
|
+
} | {
|
|
224
|
+
kind: string;
|
|
225
|
+
params: {
|
|
226
|
+
mult?: undefined;
|
|
227
|
+
period: number;
|
|
228
|
+
fast?: undefined;
|
|
229
|
+
slow?: undefined;
|
|
230
|
+
signal?: undefined;
|
|
231
|
+
};
|
|
232
|
+
guides: number[];
|
|
233
|
+
range: number[];
|
|
234
|
+
fmt: string;
|
|
235
|
+
color: string;
|
|
236
|
+
compute: (bars: any, p: any) => number[];
|
|
237
|
+
} | {
|
|
238
|
+
range?: undefined;
|
|
239
|
+
color?: undefined;
|
|
240
|
+
kind: string;
|
|
241
|
+
params: {
|
|
242
|
+
mult?: undefined;
|
|
243
|
+
period?: undefined;
|
|
244
|
+
fast: number;
|
|
245
|
+
slow: number;
|
|
246
|
+
signal: number;
|
|
247
|
+
};
|
|
248
|
+
guides: number[];
|
|
249
|
+
fmt: string;
|
|
250
|
+
compute: (bars: any, p: any) => {
|
|
251
|
+
lines: {
|
|
252
|
+
name: string;
|
|
253
|
+
values: number[];
|
|
254
|
+
}[];
|
|
255
|
+
histogram: number[];
|
|
256
|
+
};
|
|
257
|
+
}>;
|
|
178
258
|
/**
|
|
179
259
|
* Register a custom indicator.
|
|
180
260
|
*
|
|
181
|
-
*
|
|
261
|
+
* WickChart.registerIndicator('vwap', {
|
|
182
262
|
* kind: 'overlay', // or 'pane'
|
|
183
263
|
* params: { period: 20 }, // defaults; settable via name:period
|
|
184
264
|
* compute(bars, params) { // bars: normalized {time,o,h,l,c,v}
|
|
@@ -269,7 +349,8 @@ declare class HabChart extends HTMLElementBase {
|
|
|
269
349
|
removePosition(id: any): void;
|
|
270
350
|
clearPositions(): void;
|
|
271
351
|
/**
|
|
272
|
-
* Price alert. Fires `
|
|
352
|
+
* Price alert. Fires `wick:alert` ({id, price, bar}) on an edge crossing
|
|
353
|
+
* (plus the deprecated `hab:alert` alias)
|
|
273
354
|
* during streaming updates.
|
|
274
355
|
* @param {{id?: string, price: number, direction?: 'above'|'below'|'cross',
|
|
275
356
|
* once?: boolean}} alert
|
|
@@ -386,6 +467,10 @@ declare class HabChart extends HTMLElementBase {
|
|
|
386
467
|
_setupCoView(): void;
|
|
387
468
|
_coviewSend(msg: any): void;
|
|
388
469
|
_onCoMessage(m: any): void;
|
|
470
|
+
/** Dispatch `wick:name` (canonical) plus the deprecated `hab:name` alias,
|
|
471
|
+
* so 0.x listeners keep working until 2.0. */
|
|
472
|
+
_fire(name: any, detail: any): void;
|
|
389
473
|
_emitRange(): void;
|
|
390
474
|
}
|
|
391
|
-
export default
|
|
475
|
+
export default WickChart;
|
|
476
|
+
export { WickChart };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import './
|
|
1
|
+
import './wick-chart.js';
|
|
2
2
|
declare const HTMLElementBase: {
|
|
3
3
|
new (): {};
|
|
4
4
|
};
|
|
5
|
-
declare class
|
|
5
|
+
declare class WickFeed extends HTMLElementBase {
|
|
6
6
|
_gen: number;
|
|
7
7
|
_closers: any[];
|
|
8
8
|
_timer: number;
|
|
@@ -15,7 +15,10 @@ declare class HabFeed extends HTMLElementBase {
|
|
|
15
15
|
_scheduleRestart(): void;
|
|
16
16
|
_teardown(): void;
|
|
17
17
|
_setStatus(status: any, detail: any): void;
|
|
18
|
-
/**
|
|
18
|
+
/** Dispatch `wick-feed:name` plus the deprecated `hab-feed:name` alias. */
|
|
19
|
+
_fire(name: any, detail: any): void;
|
|
20
|
+
/** Resolve the target chart (by `for` id, else the first chart element —
|
|
21
|
+
* <wick-chart> or the deprecated <hab-chart>). */
|
|
19
22
|
_resolveChart(): Element;
|
|
20
23
|
_restart(): void;
|
|
21
24
|
_synthetic(gen: any, chart: any, key: any, tfId: any, limit: any, live: any, status?: string): void;
|
|
@@ -24,4 +27,5 @@ declare class HabFeed extends HTMLElementBase {
|
|
|
24
27
|
_degrade(gen: any, chart: any, sym: any, tfId: any, limit: any, live: any, err: any): void;
|
|
25
28
|
_rest(gen: any, chart: any, url: any, limit: any, live: any): Promise<void>;
|
|
26
29
|
}
|
|
27
|
-
export default
|
|
30
|
+
export default WickFeed;
|
|
31
|
+
export { WickFeed };
|