wickchart 1.0.0 → 1.2.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
@@ -17,6 +17,7 @@ npm install wickchart
17
17
  import 'wickchart'; // registers <wick-chart>
18
18
  import WickChart from 'wickchart'; // for WickChart.registerIndicator(...)
19
19
  import { encodeStateQuery } from 'wickchart/core'; // pure helpers
20
+ import { WickChart } from 'wickchart/react'; // React bindings (optional)
20
21
  ```
21
22
 
22
23
  Or straight from a CDN — no install, no build:
@@ -86,7 +87,14 @@ bet:
86
87
 
87
88
  The demo site is deployed to GitHub Pages:
88
89
  **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
+ chart, the full interactive demo, the zero-JavaScript declarative page, and a
91
+ [React demo](./demo/react.html) driven entirely by React state.
92
+
93
+ **Full documentation lives at
94
+ [benyblack.github.io/wickchart/docs.html](./docs.html)** — every attribute,
95
+ method, event, the WickScript reference, overlays (with a live JSON
96
+ playground), feeds, theming and framework bindings, each with runnable
97
+ examples. This README covers the same ground in plain markdown.
90
98
 
91
99
  ## Run the demo locally
92
100
 
@@ -105,6 +113,101 @@ with graceful fallback to synthetic data if it isn't.
105
113
 
106
114
  ---
107
115
 
116
+ ## Frameworks
117
+
118
+ `<wick-chart>` is framework-agnostic — attributes, one `data` property,
119
+ standard DOM events. The one opinionated wrapper ships as `wickchart/react`,
120
+ which turns that contract into idiomatic React with proper event
121
+ subscription/cleanup. `react` is an **optional** peer dependency: nothing
122
+ changes if you never import `wickchart/react`.
123
+
124
+ ### React
125
+
126
+ ```bash
127
+ npm install wickchart react
128
+ ```
129
+
130
+ ```jsx
131
+ import { WickChart, useWickChart } from 'wickchart/react';
132
+
133
+ // drop-in component — props map 1:1 onto the element
134
+ export function PriceChart({ bars, onRange }) {
135
+ return (
136
+ <WickChart
137
+ type="candles"
138
+ indicators="sma:20 ema:50 volume"
139
+ volshading
140
+ label="BTC · 1h"
141
+ data={bars} // bars are assigned as a property
142
+ onRange={onRange} // subscribes to wick:range
143
+ onAlert={(e) => toast(`crossed ${e.detail.price}`)}
144
+ style={{ height: 420 }}
145
+ />
146
+ );
147
+ }
148
+
149
+ // or the hook, when you need the imperative API
150
+ function PracticeChart({ bars }) {
151
+ const { ref, chart } = useWickChart({ data: bars, indicators: 'sma:20' });
152
+ // chart.getDataWindow(), chart.addAlert(...), chart.getState() … after mount
153
+ return <wick-chart ref={ref} style={{ height: 420 }} />;
154
+ }
155
+ ```
156
+
157
+ Rules of thumb:
158
+
159
+ - **Pass a fresh array** to `data` when the bars change — the binding compares
160
+ by reference, and reassignment is what triggers a redraw (don't mutate).
161
+ The same rule applies to `overlays` (see
162
+ [Server-side overlays](#server-side-overlays-zones--levels)).
163
+ - **String/number/boolean props become attributes** (`type`, `indicators`,
164
+ `volshading`, …); `className`/`style`/`id` reach React as usual.
165
+ - **`onXxx` subscribes to `wick:xxx`** with cleanup on unmount; an
166
+ `events={{ range: fn }}` object works too.
167
+ - Works the same on React 16.8 → 19 — no custom-element event caveats.
168
+
169
+ No build step? The [React demo](./demo/react.html) runs straight off a CDN
170
+ import map — `react` and `react-dom` from esm.sh, the bindings from the
171
+ package source.
172
+
173
+ ### Vue 3
174
+
175
+ ```vue
176
+ <script setup>
177
+ import { ref, onMounted } from 'vue';
178
+ import 'wickchart';
179
+ const chart = ref(null);
180
+ const bars = ref([]);
181
+ onMounted(async () => {
182
+ bars.value = await loadBars();
183
+ chart.value.data = bars.value;
184
+ chart.value.addEventListener('wick:range', (e) => console.log(e.detail));
185
+ });
186
+ </script>
187
+
188
+ <template>
189
+ <wick-chart ref="chart" type="candles" indicators="sma:20"
190
+ style="height: 420px"></wick-chart>
191
+ </template>
192
+ ```
193
+
194
+ ### Svelte
195
+
196
+ ```svelte
197
+ <script>
198
+ import 'wickchart';
199
+ let el;
200
+ let bars = [];
201
+ $: if (el && bars.length) el.data = bars;
202
+ </script>
203
+
204
+ <wick-chart bind:this={el} type="candles" indicators="sma:20"
205
+ on:wick:alert={(e) => console.log(e.detail)}
206
+ style="height: 420px"></wick-chart>
207
+ ```
208
+
209
+ ---
210
+
108
211
  ## Data format
109
212
 
110
213
  Bars are plain objects; `time` accepts **milliseconds or seconds** (auto-detected).
@@ -132,6 +235,7 @@ chart.setData([
132
235
  | `profile` | off | Volume profile overlay (POC + 70% value area) |
133
236
  | `annotations` | off | Smart annotations (volume spikes, gaps, pivots, RSI divergences) |
134
237
  | `volshading` | off | Volatility-regime background shading (see below) |
238
+ | `overlays` | – | JSON array of server-side zones & levels (see below) |
135
239
 
136
240
  \* `indicators=""` disables everything, including volume. Token syntax:
137
241
  `name[:param[/param…]][@color]` — e.g. `sma:20@#ff0000`, `macd:12/26/9`.
@@ -249,6 +353,69 @@ classifies everything as normal. The pieces are exported from
249
353
  `wickchart/core` (`calcRealizedVol`, `volRegimeBands`, `percentileOfSorted`)
250
354
  if you want to build on them.
251
355
 
356
+ ### Server-side overlays (zones & levels)
357
+
358
+ Draw analysis from your own API straight onto the chart: supply/demand
359
+ **zones** (time × price rectangles) and horizontal **levels**, rendered
360
+ behind the candles. Zones without a `to` extend into future space past the
361
+ last bar, like TradingView drawings.
362
+
363
+ ```js
364
+ const res = await fetch('https://api.example.com/analysis?symbol=BTC');
365
+ chart.setOverlays(await res.json());
366
+ ```
367
+
368
+ ```js
369
+ [
370
+ // zone: from/to are timestamps (ms or s); null → chart edge
371
+ { type: 'zone', from: 1753920000000, priceFrom: 33000, priceTo: 35600,
372
+ color: '#ef5350', alpha: 0.25, label: 'demand' },
373
+ { type: 'zone', from: 1753920000000, // no `to` → extends
374
+ priceFrom: 37700, priceTo: 40900, color: '#26a69a' }, // to the right edge
375
+ // level: horizontal price line, full width by default
376
+ { type: 'level', price: 28700, color: '#3f51b5', label: 'S1' },
377
+ { type: 'level', price: 22800, color: '#3f51b5', dash: true },
378
+ ]
379
+ ```
380
+
381
+ - `addOverlay(o)` upserts one (by `id`), `removeOverlay(id)`,
382
+ `clearOverlays()`, and `chart.overlays` reads them back.
383
+ - Colors accept hex / `rgb()` / CSS names plus the palette keys
384
+ `up` | `down` | `accent`; `alpha` clamps to 0.02–0.8 (default 0.22).
385
+ - Timestamps snap to bars (before the first bar clamps left, after the last
386
+ clamps right); invalid entries are dropped, never thrown — it's API data.
387
+ - Fully declarative, too — the same JSON as an attribute:
388
+
389
+ ```html
390
+ <wick-chart overlays='[{"type":"level","price":28700,"color":"#3f51b5","label":"S1"}]'></wick-chart>
391
+ ```
392
+
393
+ The React binding takes `overlays` as a prop (fresh array → re-apply), and
394
+ `normalizeOverlays` / `barIndexForTime` / `resolveOverlayColor` are exported
395
+ from `wickchart/core`.
396
+
397
+ ### Scenario mode — ghost paths & volatility cones
398
+
399
+ Project what-if into future space: a ghost path of hypothetical prices plus
400
+ a volatility cone (±1σ/±2σ bands widening with √h from realized vol).
401
+
402
+ ```js
403
+ chart.setScenario({
404
+ path: [64000, 65500, 66800, 68000], // prices for future bars 1..N
405
+ cone: true, // σ-bands from realized vol (default)
406
+ label: 'bull case',
407
+ color: 'up', // up | down | accent or safe colors
408
+ });
409
+ chart.setScenario({ horizon: 48 }); // cone-only projection
410
+ chart.clearScenario();
411
+ ```
412
+
413
+ Setting a scenario reserves future space on the right so the cone stays
414
+ visible; the horizon defaults to the path length (1–500) and `levels` are σ
415
+ multipliers (default `[1, 2]`). Like overlays, scenarios are analysis data —
416
+ excluded from shareable state, and the same shape a server-side model could
417
+ push. `calcVolCone` / `normalizeScenario` are exported from `wickchart/core`.
418
+
252
419
  ### AI-ready data window — `getDataWindow()`
253
420
 
254
421
  One call turns whatever is on screen into a compact, LLM-pasteable summary.
@@ -266,6 +433,31 @@ s.volPctile; // 84 → hot regime relative to the window itself
266
433
  s.patterns; // [{ time, note }] — most recent first
267
434
  ```
268
435
 
436
+ ### AI agent interface — the chart as a tool surface
437
+
438
+ The chart can publish its own **tool manifest** and accept validated
439
+ tool-calls, so any LLM can operate it with zero glue code — the chart never
440
+ touches the network; you supply the model call.
441
+
442
+ ```js
443
+ chart.aiTools(); // manifest: get_data_window, set_indicators, set_overlays, add_alert, …
444
+ chart.aiPrompt(); // system prompt demanding JSON [{tool, args}] ops
445
+ chart.aiContext(); // grounding: current state + visible-window summary
446
+ chart.applyAI(ops); // validated dispatcher — per-op {ok, result} / {ok:false, error}
447
+
448
+ const { results } = await chart.ask(
449
+ 'add RSI, mark the demand zone, and alert me on volume spikes',
450
+ { run: async (payload) => (await callMyLLM(payload)).ops }
451
+ );
452
+ ```
453
+
454
+ Every op is whitelisted and its args validated (indicator names checked
455
+ against the registry, overlays through the sanitizer, enums enforced) — LLM
456
+ output is treated as untrusted input, and a bad op returns an error the
457
+ model can self-correct from instead of throwing. The
458
+ [docs page](./docs.html) has a live playground driving `applyAI()` with an
459
+ offline demo agent (no network, no keys).
460
+
269
461
  `text` renders like:
270
462
 
271
463
  ```
@@ -390,10 +582,17 @@ chart.addAlert({ price: 65000, direction: 'above' }); // 'above' | 'below' | 'cr
390
582
  chart.addEventListener('wick:alert', (e) => {
391
583
  console.log('crossed!', e.detail.id, e.detail.price);
392
584
  });
585
+
586
+ // scripted alerts — any WickScript predicate, fired on its false→true edge
587
+ chart.addAlert({ when: 'crossup(rsi(close,14), 30)' });
588
+ chart.addAlert({ when: 'volume > sma(volume,20) * 3', once: false }); // re-arms
393
589
  ```
394
590
 
395
591
  The P&L chip recalculates on every streamed bar. Alerts are edge-triggered
396
592
  (fire once per crossing) and one-shot by default (`once: false` to re-arm).
593
+ Scripted alerts are evaluated locally on every streamed bar — the event
594
+ carries the triggering close as `price` plus the `when` source; an invalid
595
+ predicate is rejected (`addAlert` returns `null`), never thrown.
397
596
 
398
597
  ### Stats & measure
399
598
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wickchart",
3
- "version": "1.0.0",
3
+ "version": "1.2.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",
@@ -18,6 +18,10 @@
18
18
  "./feed": {
19
19
  "types": "./types/wick-feed.d.ts",
20
20
  "default": "./src/wick-feed.js"
21
+ },
22
+ "./react": {
23
+ "types": "./types/react.d.ts",
24
+ "default": "./src/react.js"
21
25
  }
22
26
  },
23
27
  "files": [
@@ -25,14 +29,23 @@
25
29
  "types"
26
30
  ],
27
31
  "sideEffects": [
28
- "src/wick-chart.js"
32
+ "src/wick-chart.js",
33
+ "src/react.js"
29
34
  ],
35
+ "peerDependencies": {
36
+ "react": ">=16.8"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "react": {
40
+ "optional": true
41
+ }
42
+ },
30
43
  "scripts": {
31
44
  "dev": "npx --yes serve . -l 5173",
32
45
  "test": "node --test \"tests/*.test.mjs\"",
33
46
  "build:types": "node -e \"require('fs').rmSync('types', { recursive: true, force: true });\" && tsc -p tsconfig.json",
34
47
  "prepack": "npm run build:types",
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"
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"
36
49
  },
37
50
  "keywords": [
38
51
  "chart",
@@ -52,6 +65,10 @@
52
65
  "url": "git+https://github.com/benyblack/wickchart.git"
53
66
  },
54
67
  "devDependencies": {
68
+ "@types/react": "^19.1.0",
69
+ "jsdom": "^26.1.0",
70
+ "react": "^19.1.0",
71
+ "react-dom": "^19.1.0",
55
72
  "typescript": "^7.0.2"
56
73
  }
57
74
  }