wickchart 0.3.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.
@@ -0,0 +1,572 @@
1
+ export type Bar = {
2
+ time: number;
3
+ open: number;
4
+ high: number;
5
+ low: number;
6
+ close: number;
7
+ volume?: number;
8
+ };
9
+ export type IndicatorLine = {
10
+ name?: string;
11
+ values: Array<number | null>;
12
+ /**
13
+ * #hex / rgb() / CSS name / palette key
14
+ */
15
+ color?: string;
16
+ };
17
+ export type IndicatorDef = {
18
+ /**
19
+ * overlay on the price pane, or a stacked sub-pane
20
+ */
21
+ kind?: 'overlay' | 'pane';
22
+ /**
23
+ * defaults; set via `name:p1/p2` tokens
24
+ */
25
+ params?: Record<string, number>;
26
+ compute: (bars: Bar[], params: Record<string, number>) => (Array<number | null> | {
27
+ lines?: IndicatorLine[];
28
+ histogram?: Array<number | null>;
29
+ });
30
+ /**
31
+ * pane only: dashed horizontal levels
32
+ */
33
+ guides?: number[];
34
+ /**
35
+ * pane only: fixed scale (else autoscale)
36
+ */
37
+ range?: [number, number];
38
+ /**
39
+ * legend/axis number format
40
+ */
41
+ fmt?: 'price' | 'fixed1';
42
+ color?: string;
43
+ };
44
+ export type Position = {
45
+ id: string;
46
+ side: 'long' | 'short';
47
+ entry: number;
48
+ stop: number | null;
49
+ target: number | null;
50
+ qty: number | null;
51
+ };
52
+ export type Alert = {
53
+ id: string;
54
+ price: number;
55
+ direction: 'above' | 'below' | 'cross';
56
+ once: boolean;
57
+ };
58
+ export type ChartState = {
59
+ type?: 'candles' | 'line' | 'area' | 'bars' | 'hollow' | 'heikin';
60
+ theme?: 'dark' | 'light';
61
+ log?: boolean;
62
+ stats?: boolean;
63
+ /**
64
+ * volume profile overlay (POC + value area)
65
+ */
66
+ profile?: boolean;
67
+ /**
68
+ * smart annotations (spikes/gaps/pivots/divergences)
69
+ */
70
+ annotations?: boolean;
71
+ label?: string;
72
+ indicators?: string;
73
+ /**
74
+ * visible time window (ms)
75
+ */
76
+ view?: {
77
+ from: number;
78
+ to: number;
79
+ };
80
+ /**
81
+ * partial positions to add
82
+ */
83
+ positions?: Array<Position & {
84
+ id?: string;
85
+ stop?: number;
86
+ target?: number;
87
+ qty?: number;
88
+ }>;
89
+ /**
90
+ * partial alerts to add
91
+ */
92
+ alerts?: Array<Alert & {
93
+ id?: string;
94
+ once?: boolean;
95
+ }>;
96
+ };
97
+ export type IndicatorEntry = {
98
+ name: string;
99
+ def: IndicatorDef;
100
+ params: Record<string, number>;
101
+ color: string | null;
102
+ key: string;
103
+ };
104
+ /**
105
+ * A single OHLCV bar. `time` is milliseconds (second-based input is
106
+ * auto-detected and converted).
107
+ * @typedef {object} Bar
108
+ * @property {number} time
109
+ * @property {number} open
110
+ * @property {number} high
111
+ * @property {number} low
112
+ * @property {number} close
113
+ * @property {number} [volume]
114
+ */
115
+ /**
116
+ * One plotted line of an indicator result.
117
+ * @typedef {object} IndicatorLine
118
+ * @property {string} [name]
119
+ * @property {Array<number|null>} values
120
+ * @property {string} [color] #hex / rgb() / CSS name / palette key
121
+ */
122
+ /**
123
+ * An indicator definition for {@link registerIndicator}.
124
+ * @typedef {object} IndicatorDef
125
+ * @property {'overlay'|'pane'} [kind] overlay on the price pane, or a stacked sub-pane
126
+ * @property {Record<string, number>} [params] defaults; set via `name:p1/p2` tokens
127
+ * @property {(bars: Bar[], params: Record<string, number>) => (Array<number|null>|{lines?: IndicatorLine[], histogram?: Array<number|null>})} compute
128
+ * @property {number[]} [guides] pane only: dashed horizontal levels
129
+ * @property {[number, number]} [range] pane only: fixed scale (else autoscale)
130
+ * @property {'price'|'fixed1'} [fmt] legend/axis number format
131
+ * @property {string} [color]
132
+ */
133
+ /**
134
+ * A position/order visualization.
135
+ * @typedef {object} Position
136
+ * @property {string} id
137
+ * @property {'long'|'short'} side
138
+ * @property {number} entry
139
+ * @property {number|null} stop
140
+ * @property {number|null} target
141
+ * @property {number|null} qty
142
+ */
143
+ /**
144
+ * A price alert (edge-triggered on streamed crossings).
145
+ * @typedef {object} Alert
146
+ * @property {string} id
147
+ * @property {number} price
148
+ * @property {'above'|'below'|'cross'} direction
149
+ * @property {boolean} once
150
+ */
151
+ /**
152
+ * Serializable chart snapshot (see `getState()` / `setState()`).
153
+ * @typedef {object} ChartState
154
+ * @property {'candles'|'line'|'area'|'bars'|'hollow'|'heikin'} [type]
155
+ * @property {'dark'|'light'} [theme]
156
+ * @property {boolean} [log]
157
+ * @property {boolean} [stats]
158
+ * @property {boolean} [profile] volume profile overlay (POC + value area)
159
+ * @property {boolean} [annotations] smart annotations (spikes/gaps/pivots/divergences)
160
+ * @property {string} [label]
161
+ * @property {string} [indicators]
162
+ * @property {{from: number, to: number}} [view] visible time window (ms)
163
+ * @property {Array<Position & {id?: string, stop?: number, target?: number, qty?: number}>} [positions] partial positions to add
164
+ * @property {Array<Alert & {id?: string, once?: boolean}>} [alerts] partial alerts to add
165
+ */
166
+ /**
167
+ * A parsed indicator entry (internal token → def binding).
168
+ * @typedef {object} IndicatorEntry
169
+ * @property {string} name
170
+ * @property {IndicatorDef} def
171
+ * @property {Record<string, number>} params
172
+ * @property {string|null} color
173
+ * @property {string} key
174
+ */
175
+ export declare const clamp: (v: any, lo: any, hi: any) => any;
176
+ export declare const isNum: (v: any) => boolean;
177
+ export declare const pad2: (n: any) => string;
178
+ export declare function numberFmt(p: any): any;
179
+ export declare function fmtCompact(v: any): any;
180
+ export declare function autoPrecision(v: any): 2 | 3 | 5 | 8;
181
+ /** Nice round step (1, 2, 5 × 10^n) covering `range` in ~`target` steps. */
182
+ export declare function niceStep(range: any, target: any): number;
183
+ export declare function hexToRgba(color: any, alpha: any): any;
184
+ export declare function safeColor(s: any): string;
185
+ export declare const FONT_STACK = "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
186
+ export declare const axisFont: (w?: number) => string;
187
+ export declare const pillFont: () => string;
188
+ export declare function roundRectPath(ctx: any, x: any, y: any, w: any, h: any, r: any): void;
189
+ export declare const SEC = 1000;
190
+ export declare const MIN: number;
191
+ export declare const HOUR: number;
192
+ export declare const DAY: number;
193
+ export declare const TIME_STEPS: {
194
+ ms: number;
195
+ label: string;
196
+ }[];
197
+ export declare const hhmm: (t: any) => string;
198
+ export declare const fmtDay: (t: any) => any;
199
+ export declare const fmtMonth: (t: any, withYear: any) => any;
200
+ export declare const fmtYear: (t: any) => any;
201
+ export declare const fmtFull: (t: any) => string;
202
+ export declare const THEMES: {
203
+ dark: {
204
+ bg: string;
205
+ text: string;
206
+ textStrong: string;
207
+ grid: string;
208
+ border: string;
209
+ up: string;
210
+ down: string;
211
+ accent: string;
212
+ crosshair: string;
213
+ crosshairBg: string;
214
+ crosshairText: string;
215
+ pillText: string;
216
+ rsi: string;
217
+ guide: string;
218
+ volAlpha: number;
219
+ overlay: string[];
220
+ };
221
+ light: {
222
+ bg: string;
223
+ text: string;
224
+ textStrong: string;
225
+ grid: string;
226
+ border: string;
227
+ up: string;
228
+ down: string;
229
+ accent: string;
230
+ crosshair: string;
231
+ crosshairBg: string;
232
+ crosshairText: string;
233
+ pillText: string;
234
+ rsi: string;
235
+ guide: string;
236
+ volAlpha: number;
237
+ overlay: string[];
238
+ };
239
+ };
240
+ /**
241
+ * @param {number[]} values
242
+ * @param {number} period
243
+ * @returns {Array<number|null>}
244
+ */
245
+ export declare function calcSMA(values: number[], period: number): Array<number | null>;
246
+ /**
247
+ * @param {number[]} values
248
+ * @param {number} period
249
+ * @returns {Array<number|null>}
250
+ */
251
+ export declare function calcEMA(values: number[], period: number): Array<number | null>;
252
+ /** EMA over a series that may contain leading nulls (e.g. MACD line).
253
+ * @param {Array<number|null>} values
254
+ * @param {number} period
255
+ * @returns {Array<number|null>}
256
+ */
257
+ export declare function calcEMASparse(values: Array<number | null>, period: number): Array<number | null>;
258
+ /**
259
+ * @param {number[]} closes
260
+ * @param {number} period
261
+ * @returns {Array<number|null>}
262
+ */
263
+ export declare function calcRSI(closes: number[], period: number): Array<number | null>;
264
+ /** Rolling standard deviation (population) over `period`, aligned like SMA. */
265
+ export declare function calcStdDev(values: any, period: any): any[];
266
+ /**
267
+ * Bollinger Bands.
268
+ * @param {number[]} closes
269
+ * @param {number} period
270
+ * @param {number} [mult]
271
+ * @returns {{mid:Array<number|null>, upper:Array<number|null>, lower:Array<number|null>}}
272
+ */
273
+ export declare function calcBollinger(closes: number[], period: number, mult?: number): {
274
+ mid: Array<number | null>;
275
+ upper: Array<number | null>;
276
+ lower: Array<number | null>;
277
+ };
278
+ /**
279
+ * MACD.
280
+ * @param {number[]} closes
281
+ * @param {number} [fast]
282
+ * @param {number} [slow]
283
+ * @param {number} [signal]
284
+ * @returns {{macd:Array<number|null>, signal:Array<number|null>, hist:Array<number|null>}}
285
+ */
286
+ export declare function calcMACD(closes: number[], fast?: number, slow?: number, signal?: number): {
287
+ macd: Array<number | null>;
288
+ signal: Array<number | null>;
289
+ hist: Array<number | null>;
290
+ };
291
+ /**
292
+ * Merge older (backfilled) bars in front of `existing`.
293
+ * Dedupes by time (existing bars win); only strictly older bars are prepended.
294
+ * @param {Bar[]} existing
295
+ * @param {Bar[]} older
296
+ * @returns {{bars: Bar[], added: number}} merged array and count prepended.
297
+ */
298
+ export declare function mergeOlderData(existing: Bar[], older: Bar[]): {
299
+ bars: Bar[];
300
+ added: number;
301
+ };
302
+ /**
303
+ * Indices of visible bars whose time jump from the previous bar exceeds
304
+ * `threshold × dt` (sessions breaks, weekends, missing data).
305
+ * @param {Bar[]} bars
306
+ * @param {number} i0
307
+ * @param {number} i1
308
+ * @param {number} dtMs
309
+ * @param {number} [threshold]
310
+ * @returns {number[]}
311
+ */
312
+ export declare function detectGaps(bars: Bar[], i0: number, i1: number, dtMs: number, threshold?: number): number[];
313
+ /**
314
+ * Aggregate a visible bar range into ~1px-wide columns for deep zoom-outs.
315
+ * `xOf(i)` must be non-decreasing in i (index-space x mapping guarantees it).
316
+ * Each column keeps first open / max high / min low / last close / volume sum.
317
+ * @param {Bar[]} bars
318
+ * @param {number} i0
319
+ * @param {number} i1
320
+ * @param {(i: number) => number} xOf
321
+ * @param {number} plotRight plot width in px (column count)
322
+ * @returns {Array<{x: number, i0: number, i1: number, open: number, high: number, low: number, close: number, volume: number}>}
323
+ */
324
+ export declare function buildColumns(bars: Bar[], i0: number, i1: number, xOf: (i: number) => number, plotRight: number): Array<{
325
+ x: number;
326
+ i0: number;
327
+ i1: number;
328
+ open: number;
329
+ high: number;
330
+ low: number;
331
+ close: number;
332
+ volume: number;
333
+ }>;
334
+ /**
335
+ * Detect notable events over a visible bar window.
336
+ * Volume spikes (> volMult × SMA(volume)), price gaps beyond the previous
337
+ * bar's range (> gapMult × average range), pivot highs/lows (± pivot bars),
338
+ * and RSI divergences (later higher high with weaker RSI, and symmetric lows).
339
+ *
340
+ * @param {Bar[]} bars full dataset (raw)
341
+ * @param {number} i0 first visible index
342
+ * @param {number} i1 last visible index
343
+ * @param {Array<number|null>|null} rsi precomputed RSI series (or null to skip divergences)
344
+ * @param {{pivot?: number, volMult?: number, gapMult?: number, divDist?: number}} [opts]
345
+ * @returns {Array<{type: string, side: 'high'|'low', i: number, note: string}>}
346
+ */
347
+ export declare function detectAnnotations(bars: Bar[], i0: number, i1: number, rsi: Array<number | null> | null, opts?: {
348
+ pivot?: number;
349
+ volMult?: number;
350
+ gapMult?: number;
351
+ divDist?: number;
352
+ }): Array<{
353
+ type: string;
354
+ side: 'high' | 'low';
355
+ i: number;
356
+ note: string;
357
+ }>;
358
+ /**
359
+ * Map a price to a sonification frequency over the visible scale.
360
+ * Logarithmic scales map through log-space; result clamped to [lo, hi] Hz.
361
+ * @param {number} price
362
+ * @param {{min: number, max: number, useLog?: boolean}} scale
363
+ * @param {number} [freqLo=180]
364
+ * @param {number} [freqHi=880]
365
+ * @returns {number} frequency in Hz
366
+ */
367
+ export declare function priceToFreq(price: number, scale: {
368
+ min: number;
369
+ max: number;
370
+ useLog?: boolean;
371
+ }, freqLo?: number, freqHi?: number): number;
372
+ /**
373
+ * Volume profile over a visible bar range: volume distributed into price
374
+ * rows, with POC and the value area (greedy expansion around the POC).
375
+ * @param {Bar[]} bars
376
+ * @param {number} i0
377
+ * @param {number} i1
378
+ * @param {{rows?: number, valueAreaPct?: number}} [opts]
379
+ * @returns {null|{
380
+ * rows: Array<{v: number, up: number, dn: number}>, maxV: number, total: number,
381
+ * rowH: number, priceMin: number, priceMax: number,
382
+ * pocIndex: number, valIndex: number, vahIndex: number,
383
+ * poc: number, val: number, vah: number
384
+ * }}
385
+ */
386
+ export declare function computeVolumeProfile(bars: Bar[], i0: number, i1: number, opts?: {
387
+ rows?: number;
388
+ valueAreaPct?: number;
389
+ }): null | {
390
+ rows: Array<{
391
+ v: number;
392
+ up: number;
393
+ dn: number;
394
+ }>;
395
+ maxV: number;
396
+ total: number;
397
+ rowH: number;
398
+ priceMin: number;
399
+ priceMax: number;
400
+ pocIndex: number;
401
+ valIndex: number;
402
+ vahIndex: number;
403
+ poc: number;
404
+ val: number;
405
+ vah: number;
406
+ };
407
+ /** Supported values for the `type` attribute. */
408
+ export declare const SERIES_TYPES: string[];
409
+ /**
410
+ * Heikin-Ashi transform (smoothed candles; time/volume pass through).
411
+ * @param {Bar[]} bars
412
+ * @returns {Bar[]}
413
+ */
414
+ export declare function calcHeikinAshi(bars: Bar[]): Bar[];
415
+ /**
416
+ * Normalize an indicator compute() result to
417
+ * `{ lines: [{name, values, color?}], histogram: number[] | null }`.
418
+ */
419
+ export declare function normalizeIndicatorResult(res: any): {
420
+ lines: any;
421
+ histogram: any;
422
+ };
423
+ /** Built-in indicator definitions (name → def). */
424
+ export declare const BUILTIN_INDICATORS: Map<string, {
425
+ kind: string;
426
+ params: {
427
+ period: number;
428
+ mult?: undefined;
429
+ fast?: undefined;
430
+ slow?: undefined;
431
+ signal?: undefined;
432
+ };
433
+ compute: (bars: any, p: any) => number[];
434
+ range?: undefined;
435
+ color?: undefined;
436
+ guides?: undefined;
437
+ fmt?: undefined;
438
+ } | {
439
+ kind: string;
440
+ params: {
441
+ period: number;
442
+ mult?: undefined;
443
+ fast?: undefined;
444
+ slow?: undefined;
445
+ signal?: undefined;
446
+ };
447
+ compute: (bars: any, p: any) => number[];
448
+ range?: undefined;
449
+ color?: undefined;
450
+ guides?: undefined;
451
+ fmt?: undefined;
452
+ } | {
453
+ kind: string;
454
+ params: {
455
+ period: number;
456
+ mult: number;
457
+ fast?: undefined;
458
+ slow?: undefined;
459
+ signal?: undefined;
460
+ };
461
+ compute: (bars: any, p: any) => {
462
+ lines: {
463
+ name: string;
464
+ values: number[];
465
+ }[];
466
+ };
467
+ range?: undefined;
468
+ color?: undefined;
469
+ guides?: undefined;
470
+ fmt?: undefined;
471
+ } | {
472
+ kind: string;
473
+ params: {
474
+ mult?: undefined;
475
+ period: number;
476
+ fast?: undefined;
477
+ slow?: undefined;
478
+ signal?: undefined;
479
+ };
480
+ guides: number[];
481
+ range: number[];
482
+ fmt: string;
483
+ color: string;
484
+ compute: (bars: any, p: any) => number[];
485
+ } | {
486
+ range?: undefined;
487
+ color?: undefined;
488
+ kind: string;
489
+ params: {
490
+ mult?: undefined;
491
+ period?: undefined;
492
+ fast: number;
493
+ slow: number;
494
+ signal: number;
495
+ };
496
+ guides: number[];
497
+ fmt: string;
498
+ compute: (bars: any, p: any) => {
499
+ lines: {
500
+ name: string;
501
+ values: number[];
502
+ }[];
503
+ histogram: number[];
504
+ };
505
+ }>;
506
+ /**
507
+ * Parse an `indicators` attribute string against a registry.
508
+ * Token: `name[:param[/param…]][@color]`, plus the `volume` keyword.
509
+ * @param {string|null|undefined} str
510
+ * @param {Map<string, IndicatorDef>} registry
511
+ * @returns {{overlays: IndicatorEntry[], panes: IndicatorEntry[], volume: boolean, unknown: string[]}}
512
+ */
513
+ export declare function parseIndicators(str: string | null | undefined, registry: Map<string, IndicatorDef>): {
514
+ overlays: IndicatorEntry[];
515
+ panes: IndicatorEntry[];
516
+ volume: boolean;
517
+ unknown: string[];
518
+ };
519
+ /**
520
+ * Unrealized P&L of a position at `price`.
521
+ * @param {{side?: 'long'|'short', entry: number, qty?: number}} pos
522
+ * @param {number} price
523
+ * @returns {number}
524
+ */
525
+ export declare function positionPnl(pos: {
526
+ side?: 'long' | 'short';
527
+ entry: number;
528
+ qty?: number;
529
+ }, price: number): number;
530
+ /**
531
+ * Edge-triggered alert crossing test between two consecutive prices.
532
+ * @param {{price: number, direction?: 'above'|'below'|'cross'}} alert
533
+ * @param {number} prevPrice
534
+ * @param {number} price
535
+ * @returns {boolean}
536
+ */
537
+ export declare function checkAlertCross(alert: {
538
+ price: number;
539
+ direction?: 'above' | 'below' | 'cross';
540
+ }, prevPrice: number, price: number): boolean;
541
+ /**
542
+ * Statistics over a visible slice of bars.
543
+ * @param {Bar[]} bars
544
+ * @param {number} i0
545
+ * @param {number} i1
546
+ * @param {number} dtMs
547
+ * @returns {null|{n:number, changePct:number, min:number, max:number, maxDDPct:number, annVolPct:number, up:number, dn:number, avgVolume:number}}
548
+ */
549
+ export declare function computeStats(bars: Bar[], i0: number, i1: number, dtMs: number): null | {
550
+ n: number;
551
+ changePct: number;
552
+ min: number;
553
+ max: number;
554
+ maxDDPct: number;
555
+ annVolPct: number;
556
+ up: number;
557
+ dn: number;
558
+ avgVolume: number;
559
+ };
560
+ /**
561
+ * Encode a chart state (from getState()) as a compact query string.
562
+ * View times are encoded in whole seconds.
563
+ * @param {ChartState|null} state
564
+ * @returns {string}
565
+ */
566
+ export declare function encodeStateQuery(state: ChartState | null): string;
567
+ /**
568
+ * Decode a query string (from encodeStateQuery) back into a partial state.
569
+ * @param {string} str
570
+ * @returns {ChartState}
571
+ */
572
+ export declare function decodeStateQuery(str: string): ChartState;
@@ -0,0 +1,58 @@
1
+ export declare const TF_SECONDS: {
2
+ '1m': number;
3
+ '3m': number;
4
+ '5m': number;
5
+ '15m': number;
6
+ '30m': number;
7
+ '1h': number;
8
+ '2h': number;
9
+ '4h': number;
10
+ '6h': number;
11
+ '12h': number;
12
+ '1d': number;
13
+ '3d': number;
14
+ '1w': number;
15
+ };
16
+ /** Seconds for a timeframe id ('15m', '1h', '1D'…); defaults to 1h. */
17
+ export declare function tfToSeconds(tf: any): any;
18
+ export declare const BASE_PRICES: {
19
+ BTC: number;
20
+ ETH: number;
21
+ SOL: number;
22
+ DEMO: number;
23
+ };
24
+ export declare function mulberry32(seed: any): () => number;
25
+ export declare function gauss(rnd: any): number;
26
+ export declare function hashStr(s: any): number;
27
+ /**
28
+ * Deterministic synthetic OHLCV history (random walk with volatility regimes
29
+ * and mild mean reversion). Same key → same series.
30
+ * @param {string} key seed key (symbol, symbol+tf, …)
31
+ * @param {number} sec timeframe in seconds
32
+ * @param {number} n bar count
33
+ * @param {number} [base=100] starting/base price
34
+ */
35
+ export declare function genSynthetic(key: string, sec: number, n: number, base?: number): any[];
36
+ /**
37
+ * Stateful synthetic live stream: mutates the current bar each tick and
38
+ * rolls over on timeframe boundaries. Bridges from `startPrice` when given.
39
+ * @param {number} sec timeframe in seconds
40
+ * @param {number} [startPrice] bridge continuity from the last known price
41
+ */
42
+ export declare function makeSynthStream(sec: number, startPrice?: number): () => any;
43
+ /**
44
+ * Fetch klines from Binance's public REST API.
45
+ * @param {string} symbol e.g. 'BTCUSDT'
46
+ * @param {string} tfId interval id ('15m','1h','1d'…)
47
+ * @param {number} [limit=500]
48
+ * @param {number} [endTime] fetch bars older than this (ms) — for backfill
49
+ */
50
+ export declare function fetchBinanceKlines(symbol: string, tfId: string, limit?: number, endTime?: number): Promise<any>;
51
+ /**
52
+ * Open a Binance kline WebSocket. `onDown(err)` fires on error/close/timeout
53
+ * (after which the socket is dead and the caller should fall back).
54
+ * @returns {{close(): void}}
55
+ */
56
+ export declare function openBinanceSocket(symbol: any, tfId: any, onBar: any, onDown: any, timeoutMs?: number): {
57
+ close(): void;
58
+ };