@mmflow/charts 0.1.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,1617 @@
1
+ import REGL from 'regl';
2
+
3
+ type EngineEvent = {
4
+ type: "viewport:changed";
5
+ } | {
6
+ type: "data:updated";
7
+ series: string;
8
+ } | {
9
+ type: "crosshair:hover";
10
+ timeIdx: number | null;
11
+ px: number;
12
+ py: number;
13
+ } | {
14
+ type: "crosshair:leave";
15
+ } | {
16
+ type: "theme:changed";
17
+ };
18
+ type Listener = (e: EngineEvent) => void;
19
+ declare class EventBus {
20
+ private readonly listeners;
21
+ on(fn: Listener): () => void;
22
+ emit(e: EngineEvent): void;
23
+ clear(): void;
24
+ }
25
+
26
+ interface OhlcvBuffer {
27
+ time: Float64Array;
28
+ open: Float64Array;
29
+ high: Float64Array;
30
+ low: Float64Array;
31
+ close: Float64Array;
32
+ volume: Float64Array;
33
+ length: number;
34
+ capacity: number;
35
+ }
36
+ interface OhlcvColumns {
37
+ time: ArrayLike<number>;
38
+ open: ArrayLike<number>;
39
+ high: ArrayLike<number>;
40
+ low: ArrayLike<number>;
41
+ close: ArrayLike<number>;
42
+ volume: ArrayLike<number>;
43
+ /** Valid row count. Defaults to the shortest supplied column length. */
44
+ length?: number;
45
+ }
46
+ declare class DataStore {
47
+ private readonly bus;
48
+ private candles_;
49
+ private ready_;
50
+ constructor(bus: EventBus);
51
+ get candles(): OhlcvBuffer;
52
+ get ready(): boolean;
53
+ setCandles(rows: ReadonlyArray<{
54
+ t: number;
55
+ o: number;
56
+ h: number;
57
+ l: number;
58
+ c: number;
59
+ v: number;
60
+ }> | OhlcvColumns): void;
61
+ setCandleColumns(columns: OhlcvColumns): void;
62
+ applyTrade(bucketStart: number, price: number, size: number): boolean;
63
+ clear(): void;
64
+ priceExtent(tStart: number, tEnd: number): [number, number] | null;
65
+ barIndexContaining(t: number): number;
66
+ barTimeContaining(t: number): number | null;
67
+ firstIndexAtOrAfter(t: number): number;
68
+ requestRange(coin: string, interval: string, startTime: number, endTime: number, signal?: AbortSignal, fetcher?: RangeFetcher): Promise<boolean>;
69
+ }
70
+ interface RawCandleRow {
71
+ t: number;
72
+ o: string | number;
73
+ h: string | number;
74
+ l: string | number;
75
+ c: string | number;
76
+ v: string | number;
77
+ }
78
+ interface RangeFetchRequest {
79
+ coin: string;
80
+ interval: string;
81
+ startTime: number;
82
+ endTime: number;
83
+ signal?: AbortSignal;
84
+ }
85
+ type RangeFetcher = (req: RangeFetchRequest) => Promise<ReadonlyArray<RawCandleRow>>;
86
+
87
+ interface RendererGLOptions {
88
+ canvas: HTMLCanvasElement;
89
+ onContextLost?: () => void;
90
+ onContextRestored?: () => void;
91
+ }
92
+ declare class RendererGL {
93
+ readonly regl: REGL.Regl;
94
+ private readonly canvas;
95
+ private readonly handleLost;
96
+ private readonly handleRestored;
97
+ private disposed;
98
+ constructor(opts: RendererGLOptions);
99
+ resize(cssW: number, cssH: number, dpr: number): void;
100
+ clear(color: [number, number, number, number]): void;
101
+ dispose(): void;
102
+ }
103
+
104
+ interface Padding {
105
+ top: number;
106
+ right: number;
107
+ bottom: number;
108
+ left: number;
109
+ }
110
+ declare class Viewport {
111
+ private readonly bus;
112
+ width: number;
113
+ height: number;
114
+ dpr: number;
115
+ padding: Padding;
116
+ private t0;
117
+ private t1;
118
+ private p0;
119
+ private p1;
120
+ static readonly MIN_TIME_SPAN_MS = 100;
121
+ static readonly MIN_PRICE_SPAN_FRACTION = 1e-9;
122
+ priceAutoFit: boolean;
123
+ constructor(bus: EventBus);
124
+ setSize(cssWidth: number, cssHeight: number, dpr: number): void;
125
+ get plot(): {
126
+ x: number;
127
+ y: number;
128
+ w: number;
129
+ h: number;
130
+ };
131
+ setTimeRange(t0: number, t1: number): void;
132
+ setPriceRange(p0: number, p1: number, userInitiated?: boolean): void;
133
+ get timeRange(): [number, number];
134
+ get priceRange(): [number, number];
135
+ timeToX(t: number): number;
136
+ xToTime(x: number): number;
137
+ priceToY(p: number): number;
138
+ yToPrice(y: number): number;
139
+ timeToClipX(t: number): number;
140
+ priceToClipY(p: number): number;
141
+ pxXToClipX(cssX: number): number;
142
+ pxYToClipY(cssY: number): number;
143
+ pxWidthToClip(cssW: number): number;
144
+ pxHeightToClip(cssH: number): number;
145
+ get plotCss(): {
146
+ x: number;
147
+ y: number;
148
+ w: number;
149
+ h: number;
150
+ };
151
+ panPixels(dxCss: number, dyCss: number): void;
152
+ zoomTimeAt(cssX: number, factor: number): void;
153
+ zoomPriceAt(cssY: number, factor: number): void;
154
+ resetPriceAutoFit(): void;
155
+ }
156
+
157
+ interface ChartTheme {
158
+ background: [number, number, number, number];
159
+ text: [number, number, number, number];
160
+ textDim: [number, number, number, number];
161
+ grid: [number, number, number, number];
162
+ gridVert: [number, number, number, number];
163
+ border: [number, number, number, number];
164
+ candleUp: [number, number, number, number];
165
+ candleDown: [number, number, number, number];
166
+ volUp: [number, number, number, number];
167
+ volDown: [number, number, number, number];
168
+ crosshair: [number, number, number, number];
169
+ crosshairLabelBg: [number, number, number, number];
170
+ crosshairLabelText: [number, number, number, number];
171
+ fontFamily: string;
172
+ }
173
+ type RGBA = [number, number, number, number];
174
+ type RGB$1 = [number, number, number];
175
+ type CandleColorThemeKey = "mmflow" | "percept" | "classic" | "sourgum" | "carbon";
176
+ interface CandleColorThemeOption {
177
+ key: CandleColorThemeKey;
178
+ label: string;
179
+ upHex: string;
180
+ downHex: string;
181
+ }
182
+ declare const DEFAULT_CANDLE_COLOR_THEME: CandleColorThemeKey;
183
+ declare const CANDLE_COLOR_THEMES: CandleColorThemeOption[];
184
+ declare const isCandleColorThemeKey: (value: string | null | undefined) => value is CandleColorThemeKey;
185
+ declare const darkTheme: (candleColorTheme?: CandleColorThemeKey) => ChartTheme;
186
+ declare const CHART_COLORS: {
187
+ sma: RGBA;
188
+ ema: RGBA;
189
+ vwap: RGBA;
190
+ rsi: RGBA;
191
+ rsiThreshold: RGBA;
192
+ openInterest: RGBA;
193
+ whaleBuy: RGBA;
194
+ whaleSell: RGBA;
195
+ liqLong: RGBA;
196
+ liqShort: RGBA;
197
+ vpPoc: RGBA;
198
+ vpValueArea: RGBA;
199
+ volumeBars: RGBA;
200
+ volumeProfile: RGBA;
201
+ footprint: RGBA;
202
+ obh: RGBA;
203
+ keyLevels: RGBA;
204
+ liqLevels: RGBA;
205
+ gex: RGBA;
206
+ };
207
+ type ChartColorKey = keyof typeof CHART_COLORS;
208
+ declare const swatchOf: (key: ChartColorKey) => RGB$1;
209
+
210
+ interface GlyphInfo {
211
+ u0: number;
212
+ v0: number;
213
+ u1: number;
214
+ v1: number;
215
+ width: number;
216
+ height: number;
217
+ advance: number;
218
+ }
219
+ interface GlyphAtlasOptions {
220
+ fontSize: number;
221
+ fontFamily?: string;
222
+ lineHeight?: number;
223
+ chars?: string;
224
+ }
225
+ declare class GlyphAtlas {
226
+ readonly fontSize: number;
227
+ readonly fontFamily: string;
228
+ readonly lineHeight: number;
229
+ readonly chars: string;
230
+ private readonly glyphs;
231
+ texture: REGL.Texture2D | null;
232
+ atlasWidth: number;
233
+ atlasHeight: number;
234
+ private builtAtPixelRatio;
235
+ constructor(opts: GlyphAtlasOptions);
236
+ glyph(ch: string): GlyphInfo | undefined;
237
+ build(regl: REGL.Regl, pixelRatio?: number): void;
238
+ measure(s: string): number;
239
+ get pixelRatio(): number;
240
+ dispose(): void;
241
+ private destroyTexture;
242
+ }
243
+
244
+ interface LayerStats {
245
+ /** Median paint time in milliseconds. */
246
+ p50: number;
247
+ /** 95th-percentile paint time in milliseconds. */
248
+ p95: number;
249
+ /** Number of samples held in the ring buffer. */
250
+ count: number;
251
+ }
252
+ declare class PerfReporter {
253
+ private readonly windowSize;
254
+ private readonly samples;
255
+ constructor(windowSize?: number);
256
+ /**
257
+ * Record one paint-time sample for a layer. Called from
258
+ * Pane.paint / Scene.paint inside the per-layer try block;
259
+ * no-op if the layer is hidden because paint never ran.
260
+ */
261
+ record(layerId: string, paintMs: number): void;
262
+ /**
263
+ * Snapshot of the current per-layer stats. The returned Map's
264
+ * iteration order is the insertion order — typically matches
265
+ * the layer paint order (BackgroundLayer → CandleLayer → …)
266
+ * which is what the overlay wants to display.
267
+ */
268
+ getAllStats(): Map<string, LayerStats>;
269
+ /**
270
+ * Clear all samples — handy if the dev overlay is being
271
+ * re-armed mid-session and the user wants a fresh baseline.
272
+ */
273
+ reset(): void;
274
+ }
275
+
276
+ interface RenderContext {
277
+ regl: REGL.Regl;
278
+ viewport: Viewport;
279
+ theme: ChartTheme;
280
+ glyphAtlas: GlyphAtlas;
281
+ /**
282
+ * Optional dev-only sampler. When present, Pane.paint and
283
+ * Scene.paint wrap each layer's paint call with
284
+ * `performance.now()` bookends and feed the delta to the
285
+ * reporter. The `?perf=1` URL param activates it in dev
286
+ * builds; production renders never construct one.
287
+ */
288
+ perfReporter?: PerfReporter;
289
+ }
290
+ interface Layer {
291
+ readonly id: string;
292
+ dirty: boolean;
293
+ /**
294
+ * When `false`, Pane.paint and Scene.paint skip this layer — its
295
+ * `dirty` flag still clears so a later visibility toggle paints a
296
+ * single frame instead of replaying every queued dirty since the
297
+ * layer went dark. `undefined` is treated as visible (default-on)
298
+ * so existing layers don't need to set this field.
299
+ *
300
+ * The flag is mutated by the host (ChartEngine) in response to
301
+ * IndicatorPicker toggles, with a `scene.markAllDirty()` so the
302
+ * next rAF paints the new visibility state.
303
+ */
304
+ visible?: boolean;
305
+ paint(ctx: RenderContext): void;
306
+ onContextLost?(): void;
307
+ dispose?(): void;
308
+ }
309
+
310
+ interface PaneBounds {
311
+ yStartFraction: number;
312
+ yEndFraction: number;
313
+ }
314
+ interface PanePadding {
315
+ top: number;
316
+ bottom: number;
317
+ }
318
+ declare class Pane {
319
+ readonly id: string;
320
+ readonly viewport: Viewport;
321
+ readonly layers: Layer[];
322
+ bounds: PaneBounds;
323
+ paddingInternal: PanePadding;
324
+ priceInteractive: boolean;
325
+ /**
326
+ * Whether the pane participates in painting. Defaults true.
327
+ *
328
+ * When set to `false`, `Scene.paint` skips the pane entirely —
329
+ * no scissor region is allocated, none of its layers run paint,
330
+ * and the slice it would have occupied is unavailable to layers
331
+ * in other panes (the host re-distributes bounds across the
332
+ * remaining visible panes via Scene.relayout).
333
+ *
334
+ * Unlike `Layer.visible`, this is NOT consulted by paint loops
335
+ * inside the pane itself — Scene gates entry. Toggling this
336
+ * field requires the host to also recompute bounds + call
337
+ * `Scene.relayout()` so the remaining panes claim the freed
338
+ * space, otherwise the hidden pane leaves a blank stripe.
339
+ */
340
+ visible: boolean;
341
+ constructor(id: string, bus: EventBus, bounds: PaneBounds, paddingInternal?: PanePadding);
342
+ addLayer(layer: Layer): void;
343
+ removeLayer(id: string): void;
344
+ layout(cssWidth: number, cssHeight: number, dpr: number, globalPadding: {
345
+ top: number;
346
+ bottom: number;
347
+ }): void;
348
+ markAllDirty(): void;
349
+ notifyContextLost(): void;
350
+ paint(buildCtx: (viewport: Viewport) => RenderContext): void;
351
+ get hasDirtyLayer(): boolean;
352
+ dispose(): void;
353
+ }
354
+
355
+ interface ScenePadding {
356
+ top: number;
357
+ bottom: number;
358
+ }
359
+ declare class Scene {
360
+ private readonly renderer;
361
+ private readonly theme;
362
+ private readonly bus;
363
+ private readonly glyphAtlas;
364
+ private readonly panes;
365
+ private readonly globalLayers;
366
+ private raf;
367
+ private viewportDirty;
368
+ private detachBus;
369
+ private running;
370
+ private frameErrors;
371
+ private cssWidth;
372
+ private cssHeight;
373
+ private dpr;
374
+ private perfReporter;
375
+ globalPadding: ScenePadding;
376
+ constructor(renderer: RendererGL, theme: () => ChartTheme, bus: EventBus, glyphAtlas: GlyphAtlas);
377
+ addPane(pane: Pane): void;
378
+ addGlobalLayer(layer: Layer): void;
379
+ removePane(id: string): void;
380
+ getPane(id: string): Pane | undefined;
381
+ primary(): Pane | undefined;
382
+ bottom(): Pane | undefined;
383
+ setSize(cssWidth: number, cssHeight: number, dpr: number): void;
384
+ panTimePixels(dxCss: number): void;
385
+ zoomTimeAt(cssX: number, factor: number): void;
386
+ setTimeRange(t0: number, t1: number): void;
387
+ paneAt(cssY: number): Pane | undefined;
388
+ markAllDirty(): void;
389
+ /**
390
+ * Re-run the per-pane layout pass using the Scene's current size +
391
+ * DPR. The host calls this after mutating any pane's `bounds`
392
+ * (e.g. when redistributing space across remaining visible panes
393
+ * after a sub-pane toggle) so the viewport padding picks up the
394
+ * new slice. Doesn't call `markAllDirty()` itself — the host can
395
+ * batch a single dirty pass after multiple bound mutations.
396
+ *
397
+ * No-op when the Scene hasn't been sized yet (pre-first-frame);
398
+ * the next `setSize` call will pick up the new bounds naturally.
399
+ */
400
+ relayout(): void;
401
+ /**
402
+ * Attach a dev-only paint-time sampler. Pass `null` to detach.
403
+ * The reporter is threaded through the RenderContext built for
404
+ * each pane + global layer, where Pane.paint and Scene.paint
405
+ * wrap individual layer paints with `performance.now()` bookends
406
+ * when present. Cost when null: a single `?? undefined` per
407
+ * frame in the buildCtx call.
408
+ */
409
+ setPerfReporter(reporter: PerfReporter | null): void;
410
+ notifyContextLost(): void;
411
+ start(): void;
412
+ stop(): void;
413
+ private frame;
414
+ dispose(): void;
415
+ }
416
+
417
+ type Anchor = "left" | "center" | "right";
418
+ declare class TextBatch {
419
+ private readonly atlas;
420
+ private cmd;
421
+ private cornerBuf;
422
+ private rectBuf;
423
+ private uvBuf;
424
+ private colorBuf;
425
+ private rect;
426
+ private uv;
427
+ private color;
428
+ private count;
429
+ constructor(atlas: GlyphAtlas);
430
+ reset(): void;
431
+ addLabel(s: string, cssX: number, cssY: number, color: [number, number, number, number], viewport: Viewport, anchor?: Anchor, scale?: number): void;
432
+ flush(regl: REGL.Regl): void;
433
+ onContextLost(): void;
434
+ dispose(): void;
435
+ private ensureCapacity;
436
+ private compile;
437
+ }
438
+
439
+ declare const RGB: {
440
+ readonly surface: "15,17,23";
441
+ readonly line: "35,39,51";
442
+ readonly dim: "138,143,156";
443
+ readonly value: "205,210,220";
444
+ readonly black: "0,0,0";
445
+ readonly up: "30,199,122";
446
+ };
447
+ declare const alpha: (rgb: string, a: number) => string;
448
+ declare const UI: {
449
+ /** Translucent panel background — floating palettes / triggers. */
450
+ readonly overlayBg: string;
451
+ /** Slightly more opaque panel background — perf / info overlays. */
452
+ readonly panelBg: string;
453
+ /** Solid drawer surface — one step lighter than the chart bg so a
454
+ * portalled menu (the indicator picker) reads as raised. */
455
+ readonly drawerBg: "#13161f";
456
+ /** Hairline border on floating chrome. */
457
+ readonly border: string;
458
+ /** Primary readout text (legend values, perf "good" rows). */
459
+ readonly value: "#cdd2dc";
460
+ /** Up / down — match darkTheme candleUp / candleDown. */
461
+ readonly up: "#1ec77a";
462
+ readonly down: "#f6465d";
463
+ /** Status accents — perf overlay warn / danger thresholds. */
464
+ readonly warn: "#f5d94d";
465
+ readonly danger: "#ffa699";
466
+ };
467
+
468
+ declare function niceStep(rough: number): number;
469
+ declare function formatPrice(p: number, step: number): string;
470
+ declare function formatHoverPrice(p: number, step: number): string;
471
+ declare function formatVolume(v: number): string;
472
+ declare function paneLegendValue(value: number, format: (v: number) => string): string;
473
+ declare function stepForRange(p0: number, p1: number, targetTicks?: number): number;
474
+
475
+ declare const PLOT_GUTTER: {
476
+ readonly top: 8;
477
+ readonly right: 92;
478
+ readonly bottom: 32;
479
+ readonly left: 8;
480
+ };
481
+ declare const AXIS_LABEL_SCALE = 0.85;
482
+
483
+ type ChartTypeFamily = "Candle" | "Bars" | "Line" | "Area" | "Pro";
484
+ type ChartTypeKey = "candle" | "hollow-candle" | "heikin-ashi" | "bars" | "volume-bars" | "columns" | "high-low" | "line" | "line-markers" | "step-line" | "hlc-area" | "area" | "baseline" | "volume-footprint" | "tpo";
485
+ type ChartTypeAvailability = "available" | "partial" | "source_required";
486
+ interface ChartTypeOption {
487
+ key: ChartTypeKey;
488
+ family: ChartTypeFamily;
489
+ label: string;
490
+ short: string;
491
+ availability: ChartTypeAvailability;
492
+ }
493
+ declare const CHART_TYPE_FAMILIES: readonly ChartTypeFamily[];
494
+ declare const CHART_TYPE_OPTIONS: readonly ChartTypeOption[];
495
+ declare const DEFAULT_CHART_TYPE: ChartTypeKey;
496
+ declare const CHART_TYPE_BY_KEY: Record<ChartTypeKey, ChartTypeOption>;
497
+ declare function isChartTypeKey(value: string): value is ChartTypeKey;
498
+ declare function isChartTypeSelectable(option: ChartTypeOption): boolean;
499
+ declare function chartTypeAvailabilityLabel(availability: ChartTypeAvailability): "Live" | "Partial" | "Source";
500
+
501
+ /**
502
+ * Every toggleable indicator key understood by the picker. The
503
+ * literal-union shape (rather than an enum) lets `Record<IndicatorKey,
504
+ * X>` types in callers fail at compile time if a new key is added
505
+ * without an entry — the same exhaustiveness pattern used for
506
+ * DrawingTool / DrawnShape kind.
507
+ */
508
+ type IndicatorKey = "sma20" | "ema50" | "vwap" | "rsi" | "openInterest" | "obh" | "volumeBars" | "volumeProfile" | "vpLines" | "footprint" | "keyLevels" | "liqLevels" | "gex" | "liqEvents" | "whales";
509
+ /**
510
+ * Categories shown in the picker. Order is the display order; the
511
+ * picker renders one section per category in this order.
512
+ *
513
+ * Categories mirror the kiyotaka grouping in the P8 plan brief:
514
+ * Overlays (price-line indicators), Volume (anything reading volume
515
+ * across price levels), Order Flow (positioning / tape signals),
516
+ * Key Levels (static reference levels).
517
+ */
518
+ type IndicatorCategory = "Overlays" | "Oscillators" | "Volume" | "Order Flow" | "Funding & OI" | "Key Levels";
519
+ declare const INDICATOR_CATEGORIES: readonly IndicatorCategory[];
520
+ interface IndicatorEntry {
521
+ key: IndicatorKey;
522
+ /** Display label shown in the picker. */
523
+ label: string;
524
+ /** Short blurb shown as a hover tooltip on the picker row. */
525
+ description: string;
526
+ category: IndicatorCategory;
527
+ /** Display-only swatch RGB (0–1). Picker renders a tiny colored dot. */
528
+ swatch: [number, number, number];
529
+ }
530
+ declare const INDICATOR_CATALOG: readonly IndicatorEntry[];
531
+ declare const INDICATOR_BY_KEY: Record<IndicatorKey, IndicatorEntry>;
532
+ /**
533
+ * Default-on set. Keep the first paint chart-native: candles, volume,
534
+ * VWAP, visible-range volume profile, and the order-book heatmap.
535
+ * Heavier footprint/OI/liquidation overlays remain explicit opt-ins.
536
+ */
537
+ declare const DEFAULT_VISIBLE_INDICATORS: ReadonlySet<IndicatorKey>;
538
+ /**
539
+ * Map every IndicatorKey to the list of chart-layer ids it gates.
540
+ * Most are 1:1; the side-grouped ones (whales = buy+sell, liqEvents
541
+ * = long+short marker layers) are 1:N. Kept as a separate map (not
542
+ * embedded in INDICATOR_CATALOG) so the engine-side wiring concern
543
+ * stays out of the picker UI concerns — the picker doesn't need to
544
+ * know how many GL layers a checkbox controls.
545
+ *
546
+ * Layer ids match the `id` passed to each `new XxxLayer({id, ...})`
547
+ * in ChartEngine — when adding a new indicator, the engine-side
548
+ * lookup is by id, so the id strings here MUST match.
549
+ */
550
+ declare const CHART_LAYERS_BY_INDICATOR: Record<IndicatorKey, readonly string[]>;
551
+ /**
552
+ * Map every IndicatorKey to the list of sub-pane ids it gates.
553
+ * Most catalog keys are main-pane layers and have an empty list
554
+ * here; only sub-pane toggles (rsi → "rsi" pane, openInterest →
555
+ * "oi" pane) populate this map.
556
+ *
557
+ * Why a separate map from CHART_LAYERS_BY_INDICATOR: layers and
558
+ * panes are different rendering units. A layer hidden via
559
+ * Layer.visible just stops painting; a pane hidden via Pane.visible
560
+ * also needs its allocated slice redistributed via Scene.relayout —
561
+ * the engine treats these branches differently so the maps stay
562
+ * structurally separate too.
563
+ */
564
+ declare const CHART_PANES_BY_INDICATOR: Record<IndicatorKey, readonly string[]>;
565
+
566
+ type IndicatorLibraryGroup = "Aggregate Flow" | "Profiles" | "Order Flow" | "Exchange Data" | "Options & Macro" | "Sector" | "Technical";
567
+ type IndicatorAvailability = "available" | "partial" | "source_required";
568
+ interface IndicatorLibraryEntry {
569
+ id: string;
570
+ title: string;
571
+ short: string;
572
+ description: string;
573
+ group: IndicatorLibraryGroup;
574
+ key?: IndicatorKey;
575
+ availability?: IndicatorAvailability;
576
+ source?: string;
577
+ }
578
+ declare const INDICATOR_LIBRARY_GROUPS: readonly IndicatorLibraryGroup[];
579
+ declare const INDICATOR_LIBRARY: readonly IndicatorLibraryEntry[];
580
+ declare function indicatorAvailability(entry: IndicatorLibraryEntry): IndicatorAvailability;
581
+ declare function indicatorAvailabilityLabel(entry: IndicatorLibraryEntry): "LIVE" | "PARTIAL" | "SOURCE";
582
+ declare function indicatorCanToggle(entry: IndicatorLibraryEntry): boolean;
583
+
584
+ interface DrawnHLine {
585
+ id: string;
586
+ kind: "hline";
587
+ /** Price in instrument units. */
588
+ price: number;
589
+ /** Color RGBA in 0..1, premultiplied-safe (alpha applied at blend). */
590
+ color: [number, number, number, number];
591
+ }
592
+ /**
593
+ * Vertical line — single time anchor, spans the full main pane
594
+ * height. Typical use: marking news events, daily/weekly opens,
595
+ * earnings releases, anything where the chart needs a "this
596
+ * moment" reference. Single-click commit (mirrors hline; no
597
+ * preview phase).
598
+ */
599
+ interface DrawnVLine {
600
+ id: string;
601
+ kind: "vline";
602
+ /** Time in ms since epoch. */
603
+ t: number;
604
+ color: [number, number, number, number];
605
+ }
606
+ /**
607
+ * Trend line — segment between two (time, price) anchors. Anchored in
608
+ * data space (not pixel space) so the segment follows the data when
609
+ * the user pans or zooms. `t1`/`t2` are ms-since-epoch; `price1`/
610
+ * `price2` are in instrument units.
611
+ */
612
+ interface DrawnTrend {
613
+ id: string;
614
+ kind: "trend";
615
+ t1: number;
616
+ price1: number;
617
+ t2: number;
618
+ price2: number;
619
+ color: [number, number, number, number];
620
+ }
621
+ /**
622
+ * Rectangle outline between two (time, price) anchors — same anchor
623
+ * shape as DrawnTrend, but the renderer emits four edge segments to
624
+ * form a box. Outline-only (no fill) keeps the candles underneath
625
+ * legible. Hit-test treats any of the four edges as the click
626
+ * target; the interior is non-interactive.
627
+ */
628
+ interface DrawnRect {
629
+ id: string;
630
+ kind: "rect";
631
+ t1: number;
632
+ price1: number;
633
+ t2: number;
634
+ price2: number;
635
+ color: [number, number, number, number];
636
+ }
637
+ /**
638
+ * Fibonacci retracement — same two (time, price) anchors as trend/
639
+ * rect, renders as N horizontal levels at the standard retracement
640
+ * ratios between price1 and price2. Lines span x(t1)→x(t2) so the
641
+ * fib stays local to the user's selected range (matches the
642
+ * TradingView default; differs from "full-width hlines" which
643
+ * would lose the local-range signal). Hit-test treats any of the
644
+ * levels as the click target — deleting the fib removes the whole
645
+ * set, not a single level.
646
+ */
647
+ interface DrawnFib {
648
+ id: string;
649
+ kind: "fib";
650
+ t1: number;
651
+ price1: number;
652
+ t2: number;
653
+ price2: number;
654
+ color: [number, number, number, number];
655
+ }
656
+ /**
657
+ * Parallel channel — a base trend segment (t1,price1)→(t2,price2)
658
+ * plus a second line parallel to it, offset vertically in price by
659
+ * `priceOffset`. The parallel line runs
660
+ * (t1, price1+priceOffset)→(t2, price2+priceOffset), so both lines
661
+ * share the base slope (price2-price1)/(t2-t1) in data space and
662
+ * stay parallel through any pan/zoom.
663
+ *
664
+ * The offset is stored as a single vertical price delta rather than
665
+ * a third (t,price) anchor: the parallel constraint makes the shift
666
+ * one degree of freedom (a vertical price gap), so the third click's
667
+ * time coordinate is redundant for rendering. Storing the delta
668
+ * keeps the shape compact and the render math a straight reuse of
669
+ * the trend-line projection. A future "drag the parallel handle"
670
+ * edit slice recomputes `priceOffset` from the dragged point the
671
+ * same way the commit does.
672
+ *
673
+ * Three-click authoring (two for the base line, one for the offset)
674
+ * is the only multi-anchor-phase tool in the set; see the
675
+ * ChartEngine pending-draw state machine.
676
+ */
677
+ interface DrawnChannel {
678
+ id: string;
679
+ kind: "channel";
680
+ t1: number;
681
+ price1: number;
682
+ t2: number;
683
+ price2: number;
684
+ /** Vertical price offset of the parallel line from the base line. */
685
+ priceOffset: number;
686
+ color: [number, number, number, number];
687
+ }
688
+ type DrawnShape = DrawnHLine | DrawnTrend | DrawnRect | DrawnFib | DrawnVLine | DrawnChannel;
689
+ /**
690
+ * Standard fibonacci retracement ratios. 0.0 is the price1 anchor
691
+ * end; 1.0 is the price2 anchor end. The 0.618 "golden ratio" and
692
+ * 0.5 midpoint are the most-traded levels; 0.236 / 0.382 / 0.786
693
+ * are common secondary levels.
694
+ *
695
+ * Exported so a future fib label / hover-info slice can reuse the
696
+ * exact set the layer renders without redeclaring.
697
+ */
698
+ declare const FIB_RATIOS: readonly number[];
699
+ declare const DEFAULT_HLINE_COLOR: [number, number, number, number];
700
+ declare const DEFAULT_TREND_COLOR: [number, number, number, number];
701
+ declare const DEFAULT_RECT_COLOR: [number, number, number, number];
702
+ declare const DEFAULT_FIB_COLOR: [number, number, number, number];
703
+ declare const DEFAULT_VLINE_COLOR: [number, number, number, number];
704
+ declare const DEFAULT_CHANNEL_COLOR: [number, number, number, number];
705
+ declare const DEFAULT_COLOR_BY_KIND: Record<DrawnShape["kind"], [
706
+ number,
707
+ number,
708
+ number,
709
+ number
710
+ ]>;
711
+ declare const PREVIEW_SHAPE_ID = "__preview__";
712
+ /**
713
+ * Generate a stable shape id. Uses crypto.randomUUID when present,
714
+ * falls back to a Math.random base36 token for old browsers / SSR.
715
+ * IDs are opaque — code never parses them.
716
+ */
717
+ declare function makeShapeId(): string;
718
+ /**
719
+ * Load drawings for a coin. Returns [] if storage is unavailable,
720
+ * the entry is missing, or the payload fails the shape guard. Bad
721
+ * payloads are NOT auto-cleared — a transient parse failure during
722
+ * a multi-tab race shouldn't nuke the user's work.
723
+ *
724
+ * Multi-tab note: load is called on coin change and on mount. Two
725
+ * tabs editing the same coin will diverge until one of them
726
+ * triggers a reload; cross-tab sync via `storage` events is an
727
+ * explicit non-goal for the first slice (most users edit one chart
728
+ * at a time and multi-tab divergence is acceptable for v1).
729
+ */
730
+ declare function loadDrawings(coin: string): DrawnShape[];
731
+ /**
732
+ * Save drawings for a coin. No-op if storage is unavailable.
733
+ * Empty array deletes the storage entry so we don't accumulate
734
+ * keys for coins the user briefly browsed but never drew on.
735
+ */
736
+ declare function saveDrawings(coin: string, shapes: DrawnShape[]): void;
737
+
738
+ declare class BackgroundLayer implements Layer {
739
+ readonly id = "background";
740
+ dirty: boolean;
741
+ private cmd;
742
+ private horizontalBuffer;
743
+ private verticalBuffer;
744
+ private disposed;
745
+ private horizontalScratch;
746
+ private verticalScratch;
747
+ paint(ctx: RenderContext): void;
748
+ onContextLost(): void;
749
+ private compile;
750
+ dispose(): void;
751
+ }
752
+
753
+ declare class CandleLayer implements Layer {
754
+ private readonly store;
755
+ readonly id = "candles";
756
+ dirty: boolean;
757
+ private bodyCmd;
758
+ private wickCmd;
759
+ private lineCmd;
760
+ private fillCmd;
761
+ private cornerBuf;
762
+ private xCenterBuf;
763
+ private halfWidthBuf;
764
+ private openYBuf;
765
+ private closeYBuf;
766
+ private isUpBuf;
767
+ private wickPosBuf;
768
+ private wickIsUpBuf;
769
+ private lineBuf;
770
+ private fillBuf;
771
+ private xCenter;
772
+ private halfWidth;
773
+ private openY;
774
+ private closeY;
775
+ private isUp;
776
+ private wickPos;
777
+ private wickIsUp;
778
+ private lineScratch;
779
+ private fillScratch;
780
+ private haOpen;
781
+ private haHigh;
782
+ private haLow;
783
+ private haClose;
784
+ private disposed;
785
+ private liveAnim;
786
+ private chartType;
787
+ constructor(store: DataStore);
788
+ setChartType(next: ChartTypeKey): void;
789
+ paint(ctx: RenderContext): void;
790
+ onContextLost(): void;
791
+ dispose(): void;
792
+ private effectiveType;
793
+ private paintCandleBodies;
794
+ private paintMarkers;
795
+ private paintCloseLine;
796
+ private paintCloseArea;
797
+ private paintHlcArea;
798
+ private paintOhlcBars;
799
+ private paintColumns;
800
+ private paintBodyOutlines;
801
+ private uploadBodies;
802
+ private computeHeikinAshi;
803
+ private estimateInterval;
804
+ private liveCandle;
805
+ private sampleLiveCandle;
806
+ private ensureCapacity;
807
+ private ensureLineCapacity;
808
+ private ensureFillCapacity;
809
+ private compile;
810
+ }
811
+
812
+ type TimeSnap = (timeMs: number) => number | null;
813
+ interface SmoothCrosshairAxisResult {
814
+ value: number;
815
+ settled: boolean;
816
+ }
817
+ declare function smoothCrosshairAxis(current: number | null | undefined, target: number, factor?: number, epsilonPx?: number): SmoothCrosshairAxisResult;
818
+ declare class CrosshairLayer implements Layer {
819
+ readonly id = "crosshair";
820
+ dirty: boolean;
821
+ private cmd;
822
+ private buffer;
823
+ private fillCmd;
824
+ private fillBuffer;
825
+ private fillScratch;
826
+ private batch;
827
+ private scratch;
828
+ private detachBus;
829
+ private disposed;
830
+ private cssX;
831
+ private cssY;
832
+ private drawCssX;
833
+ private drawCssY;
834
+ private lastDpr;
835
+ private snap;
836
+ constructor(bus: EventBus, snap?: TimeSnap);
837
+ setSnap(snap: TimeSnap | null): void;
838
+ paint(ctx: RenderContext): void;
839
+ private pushRect;
840
+ onContextLost(): void;
841
+ dispose(): void;
842
+ private compile;
843
+ }
844
+
845
+ declare class DrawingsLayer implements Layer {
846
+ readonly id: string;
847
+ dirty: boolean;
848
+ private cmd;
849
+ private buffer;
850
+ private scratch;
851
+ private shapes;
852
+ private hlineGroups;
853
+ private trendGroups;
854
+ private rectGroups;
855
+ private fibGroups;
856
+ private vlineGroups;
857
+ private channelGroups;
858
+ private preview;
859
+ private disposed;
860
+ constructor(id: string);
861
+ setShapes(shapes: DrawnShape[]): void;
862
+ /**
863
+ * Set or clear the in-progress shape from a multi-click drawing
864
+ * tool. Pass `null` to clear (e.g. on commit or cancel). The
865
+ * preview always renders on top of committed shapes since it's
866
+ * fed last into the per-color emit loop.
867
+ */
868
+ setPreview(shape: DrawnShape | null): void;
869
+ clear(): void;
870
+ /**
871
+ * Generic per-color group builder. Filters by `kind`, applies the
872
+ * caller's `validate` predicate (per-shape finiteness), and
873
+ * groups by joined color string. Returned `items` are typed via
874
+ * the `Extract<DrawnShape, {kind: K}>` discriminator so the
875
+ * paint loops can read kind-specific fields without a runtime
876
+ * narrowing check.
877
+ *
878
+ * Three near-identical builders (one per shape kind) collapsed
879
+ * into this generic at the fib slice — the audit explicitly
880
+ * called the fourth tool as the right moment to take the
881
+ * refactor since the rule-of-three was met.
882
+ */
883
+ private static buildGroupsBy;
884
+ /**
885
+ * Hit-test the user's committed shapes for right-click delete.
886
+ * Walks the shape array newest-first so a hit on the topmost
887
+ * shape wins. Preview shapes are not hit-tested.
888
+ *
889
+ * `thresholdPx` defaults to 6 — wider than the rendered 1-px line
890
+ * so click targets stay forgiving on hi-DPR displays.
891
+ */
892
+ hitTest(viewport: Viewport, cssX: number, cssY: number, thresholdPx?: number): DrawnShape | null;
893
+ paint(ctx: RenderContext): void;
894
+ private emitPreview;
895
+ onContextLost(): void;
896
+ dispose(): void;
897
+ private compile;
898
+ }
899
+
900
+ declare class ExtremaAnnotationLayer implements Layer {
901
+ private readonly store;
902
+ readonly id = "extrema-annotations";
903
+ dirty: boolean;
904
+ private lineCmd;
905
+ private fillCmd;
906
+ private lineBuffer;
907
+ private fillBuffer;
908
+ private batch;
909
+ private lineScratch;
910
+ private fillScratch;
911
+ private disposed;
912
+ constructor(store: DataStore);
913
+ paint(ctx: RenderContext): void;
914
+ onContextLost(): void;
915
+ dispose(): void;
916
+ private makeCallout;
917
+ private pushRect;
918
+ private compile;
919
+ }
920
+
921
+ interface HLineSpec {
922
+ value: number;
923
+ color: [number, number, number, number];
924
+ }
925
+ declare class HorizontalLineLayer implements Layer {
926
+ readonly id: string;
927
+ dirty: boolean;
928
+ private cmd;
929
+ private buffer;
930
+ private scratch;
931
+ private disposed;
932
+ private groups;
933
+ private lines;
934
+ constructor(id: string, lines: HLineSpec[]);
935
+ setLines(lines: HLineSpec[]): void;
936
+ private static buildGroups;
937
+ paint(ctx: RenderContext): void;
938
+ onContextLost(): void;
939
+ dispose(): void;
940
+ private compile;
941
+ }
942
+
943
+ interface IndicatorOptions {
944
+ id: string;
945
+ compute: (closes: number[], b: OhlcvBuffer) => number[];
946
+ color: [number, number, number, number];
947
+ }
948
+ declare class IndicatorLayer implements Layer {
949
+ private readonly store;
950
+ private readonly opts;
951
+ readonly id: string;
952
+ dirty: boolean;
953
+ private cmd;
954
+ private buffer;
955
+ private scratch;
956
+ private disposed;
957
+ private closes;
958
+ private closesLength;
959
+ private lastValues;
960
+ constructor(store: DataStore, opts: IndicatorOptions);
961
+ paint(ctx: RenderContext): void;
962
+ valueAt(i: number): number;
963
+ onContextLost(): void;
964
+ dispose(): void;
965
+ private ensureCapacity;
966
+ private compile;
967
+ }
968
+
969
+ interface MarkerSpec {
970
+ t: number;
971
+ price: number;
972
+ /** USD notional; drives marker size via log scale. */
973
+ sizeUsd: number;
974
+ }
975
+ declare class MarkerLayer implements Layer {
976
+ private readonly opts;
977
+ readonly id: string;
978
+ dirty: boolean;
979
+ private markers;
980
+ private cmd;
981
+ private cornerBuf;
982
+ private xCenterBuf;
983
+ private yCenterBuf;
984
+ private halfWidthBuf;
985
+ private halfHeightBuf;
986
+ private xCenter;
987
+ private yCenter;
988
+ private halfWidth;
989
+ private halfHeight;
990
+ private disposed;
991
+ private readonly logMin;
992
+ private readonly logMax;
993
+ constructor(opts: {
994
+ id: string;
995
+ sideSign: 1 | -1;
996
+ color: [number, number, number, number];
997
+ /** Override the log10(USD) lower anchor for marker sizing. */
998
+ logMinUsd?: number;
999
+ /** Override the log10(USD) upper anchor for marker sizing. */
1000
+ logMaxUsd?: number;
1001
+ });
1002
+ setMarkers(markers: MarkerSpec[]): void;
1003
+ clear(): void;
1004
+ paint(ctx: RenderContext): void;
1005
+ onContextLost(): void;
1006
+ dispose(): void;
1007
+ private ensureCapacity;
1008
+ private compile;
1009
+ }
1010
+
1011
+ interface OiHistoryPoint {
1012
+ t: number;
1013
+ v: number;
1014
+ }
1015
+ declare class OiHistoryLayer implements Layer {
1016
+ private readonly opts;
1017
+ readonly id: string;
1018
+ dirty: boolean;
1019
+ private points;
1020
+ private cmd;
1021
+ private buffer;
1022
+ private scratch;
1023
+ private disposed;
1024
+ constructor(opts: {
1025
+ id: string;
1026
+ color: [number, number, number, number];
1027
+ });
1028
+ setPoints(points: OiHistoryPoint[]): void;
1029
+ clear(): void;
1030
+ valueAt(t: number): number;
1031
+ paint(ctx: RenderContext): void;
1032
+ onContextLost(): void;
1033
+ dispose(): void;
1034
+ private ensureCapacity;
1035
+ private compile;
1036
+ }
1037
+
1038
+ interface OrderBookHeatmapLevel {
1039
+ px: number;
1040
+ sz: number;
1041
+ }
1042
+ interface OrderBookHeatmapSnapshot {
1043
+ time: number;
1044
+ bids: OrderBookHeatmapLevel[];
1045
+ asks: OrderBookHeatmapLevel[];
1046
+ }
1047
+ interface OrderBookHeatmapHover {
1048
+ time: number;
1049
+ price: number;
1050
+ bucketLow: number;
1051
+ bucketHigh: number;
1052
+ bidUsd: number;
1053
+ askUsd: number;
1054
+ totalUsd: number;
1055
+ side: "bid" | "ask" | "both";
1056
+ snapshotAgeMs: number;
1057
+ }
1058
+ interface OrderBookHeatmapSummary {
1059
+ snapshotCount: number;
1060
+ oldestTime: number;
1061
+ newestTime: number;
1062
+ newestBidUsd: number;
1063
+ newestAskUsd: number;
1064
+ newestTotalUsd: number;
1065
+ maxBucketUsd: number;
1066
+ visibleBucketCount: number;
1067
+ }
1068
+ declare class OrderBookHeatmapLayer implements Layer {
1069
+ private readonly opts;
1070
+ readonly id = "obh";
1071
+ dirty: boolean;
1072
+ private snapshots;
1073
+ private bins;
1074
+ private texture;
1075
+ private cmd;
1076
+ private cornerBuf;
1077
+ private disposed;
1078
+ private lastSnapshotGen;
1079
+ private snapshotGen;
1080
+ private lastP0;
1081
+ private lastP1;
1082
+ constructor(opts?: {
1083
+ alpha?: number;
1084
+ });
1085
+ addSnapshot(snap: OrderBookHeatmapSnapshot): void;
1086
+ clear(): void;
1087
+ hoverAt(time: number, price: number, priceRange: readonly [number, number]): OrderBookHeatmapHover | null;
1088
+ summary(priceRange: readonly [number, number]): OrderBookHeatmapSummary | null;
1089
+ paint(ctx: RenderContext): void;
1090
+ onContextLost(): void;
1091
+ dispose(): void;
1092
+ private rebin;
1093
+ private binSide;
1094
+ private compile;
1095
+ }
1096
+
1097
+ declare class PaneLegendLayer implements Layer {
1098
+ private readonly opts;
1099
+ readonly id: string;
1100
+ dirty: boolean;
1101
+ private value;
1102
+ private batch;
1103
+ private disposed;
1104
+ constructor(opts: {
1105
+ id: string;
1106
+ label: string;
1107
+ labelColor: [number, number, number, number];
1108
+ });
1109
+ setValue(value: string): void;
1110
+ paint(ctx: RenderContext): void;
1111
+ onContextLost(): void;
1112
+ dispose(): void;
1113
+ }
1114
+
1115
+ declare class PriceAxisLayer implements Layer {
1116
+ readonly id = "price-axis";
1117
+ dirty: boolean;
1118
+ private batch;
1119
+ private disposed;
1120
+ paint(ctx: RenderContext): void;
1121
+ onContextLost(): void;
1122
+ dispose(): void;
1123
+ }
1124
+
1125
+ declare class TimeAxisLayer implements Layer {
1126
+ readonly id = "time-axis";
1127
+ dirty: boolean;
1128
+ private batch;
1129
+ private disposed;
1130
+ paint(ctx: RenderContext): void;
1131
+ onContextLost(): void;
1132
+ dispose(): void;
1133
+ }
1134
+
1135
+ interface FootprintLevel {
1136
+ price: number;
1137
+ buyVol: number;
1138
+ sellVol: number;
1139
+ }
1140
+ interface FootprintBar {
1141
+ t: number;
1142
+ open: number;
1143
+ high: number;
1144
+ low: number;
1145
+ close: number;
1146
+ levels: FootprintLevel[];
1147
+ }
1148
+ declare class VolumeFootprintLayer implements Layer {
1149
+ private readonly opts;
1150
+ readonly id = "footprint";
1151
+ dirty: boolean;
1152
+ private bars;
1153
+ private intervalMs;
1154
+ private cmd;
1155
+ private cornerBuf;
1156
+ private xCenterBuf;
1157
+ private halfWidthBuf;
1158
+ private bottomYBuf;
1159
+ private topYBuf;
1160
+ private intensityBuf;
1161
+ private xCenter;
1162
+ private halfWidth;
1163
+ private bottomY;
1164
+ private topY;
1165
+ private intensity;
1166
+ private disposed;
1167
+ constructor(opts?: {
1168
+ alpha?: number;
1169
+ });
1170
+ setBars(bars: FootprintBar[], intervalMs: number): void;
1171
+ clear(): void;
1172
+ paint(ctx: RenderContext): void;
1173
+ onContextLost(): void;
1174
+ dispose(): void;
1175
+ private ensureCapacity;
1176
+ private compile;
1177
+ }
1178
+
1179
+ declare class VolumeLayer implements Layer {
1180
+ private readonly store;
1181
+ readonly id = "volume";
1182
+ dirty: boolean;
1183
+ private cmd;
1184
+ private cornerBuf;
1185
+ private xCenterBuf;
1186
+ private halfWidthBuf;
1187
+ private openYBuf;
1188
+ private closeYBuf;
1189
+ private isUpBuf;
1190
+ private xCenter;
1191
+ private halfWidth;
1192
+ private openY;
1193
+ private closeY;
1194
+ private isUp;
1195
+ private disposed;
1196
+ private liveAnim;
1197
+ constructor(store: DataStore);
1198
+ paint(ctx: RenderContext): void;
1199
+ onContextLost(): void;
1200
+ dispose(): void;
1201
+ private estimateInterval;
1202
+ private liveVolume;
1203
+ private sampleLiveVolume;
1204
+ private ensureCapacity;
1205
+ private compile;
1206
+ }
1207
+
1208
+ interface VolumeProfileBin {
1209
+ px: number;
1210
+ totalUsd: number;
1211
+ }
1212
+ interface VolumeProfileSnapshot {
1213
+ bins: VolumeProfileBin[];
1214
+ /** Bin width in price units — both edges of each bin sit ± binWidth/2. */
1215
+ binWidth: number;
1216
+ /** Inclusive value-area bounds. Bins in [val, vah] render at full
1217
+ * intensity; bins outside fade. */
1218
+ val: number;
1219
+ vah: number;
1220
+ }
1221
+ declare class VolumeProfileLayer implements Layer {
1222
+ readonly id = "volume-profile";
1223
+ dirty: boolean;
1224
+ private snapshot;
1225
+ private cmd;
1226
+ private cornerBuf;
1227
+ private xCenterBuf;
1228
+ private halfWidthBuf;
1229
+ private bottomYBuf;
1230
+ private topYBuf;
1231
+ private intensityBuf;
1232
+ private xCenter;
1233
+ private halfWidth;
1234
+ private bottomY;
1235
+ private topY;
1236
+ private intensity;
1237
+ private disposed;
1238
+ private readonly widthFraction;
1239
+ constructor(opts?: {
1240
+ alpha?: number;
1241
+ widthFraction?: number;
1242
+ });
1243
+ private alpha;
1244
+ setProfile(snapshot: VolumeProfileSnapshot): void;
1245
+ clear(): void;
1246
+ paint(ctx: RenderContext): void;
1247
+ onContextLost(): void;
1248
+ dispose(): void;
1249
+ private ensureCapacity;
1250
+ private compile;
1251
+ }
1252
+
1253
+ interface Candle {
1254
+ time: number;
1255
+ open: number;
1256
+ high: number;
1257
+ low: number;
1258
+ close: number;
1259
+ volume: number;
1260
+ }
1261
+ interface CandleRange {
1262
+ symbol: string;
1263
+ resolution: string;
1264
+ from: number;
1265
+ to: number;
1266
+ }
1267
+ interface FeedUpdate {
1268
+ symbol: string;
1269
+ price: number;
1270
+ size: number;
1271
+ time: number;
1272
+ side?: "buy" | "sell";
1273
+ }
1274
+ type Unsubscribe = () => void;
1275
+ interface DataFeed {
1276
+ fetchCandles(range: CandleRange): Promise<Candle[]>;
1277
+ fetchCandlesColumns?(range: CandleRange): Promise<OhlcvColumns>;
1278
+ subscribe(symbol: string, resolution: string, onUpdate: (u: FeedUpdate) => void): Unsubscribe;
1279
+ subscribeOrderBook?(symbol: string, onBook: (b: OrderBookHeatmapSnapshot) => void): Unsubscribe;
1280
+ fetchFootprint?(symbol: string, opts: {
1281
+ resolution: string;
1282
+ from: number;
1283
+ to: number;
1284
+ intervalMs: number;
1285
+ }): Promise<FootprintBar[]>;
1286
+ fetchVolumeProfile?(symbol: string, opts: {
1287
+ resolution: string;
1288
+ from: number;
1289
+ to: number;
1290
+ intervalMs: number;
1291
+ }): Promise<VolumeProfileSnapshot>;
1292
+ subscribeMarkers?(symbol: string, onMarkers: (m: MarkerSpec[] | {
1293
+ buys?: MarkerSpec[];
1294
+ sells?: MarkerSpec[];
1295
+ }) => void): Unsubscribe;
1296
+ }
1297
+ declare function createDataFeed(impl: DataFeed): DataFeed;
1298
+ declare function composeFeeds(parts: {
1299
+ candles?: DataFeed;
1300
+ orderbook?: DataFeed;
1301
+ footprint?: DataFeed;
1302
+ volumeProfile?: DataFeed;
1303
+ markers?: DataFeed;
1304
+ }): DataFeed;
1305
+
1306
+ type ParamSpec = {
1307
+ type: "number";
1308
+ label: string;
1309
+ default: number;
1310
+ min?: number;
1311
+ max?: number;
1312
+ step?: number;
1313
+ } | {
1314
+ type: "int";
1315
+ label: string;
1316
+ default: number;
1317
+ min?: number;
1318
+ max?: number;
1319
+ } | {
1320
+ type: "bool";
1321
+ label: string;
1322
+ default: boolean;
1323
+ } | {
1324
+ type: "color";
1325
+ label: string;
1326
+ default: string;
1327
+ } | {
1328
+ type: "select";
1329
+ label: string;
1330
+ default: string;
1331
+ options: {
1332
+ value: string;
1333
+ label: string;
1334
+ }[];
1335
+ };
1336
+ type ParamValues<P extends Record<string, ParamSpec>> = {
1337
+ [K in keyof P]: P[K] extends {
1338
+ type: "bool";
1339
+ } ? boolean : P[K] extends {
1340
+ type: "color" | "select";
1341
+ } ? string : number;
1342
+ };
1343
+ interface PlotLine {
1344
+ id: string;
1345
+ data: number[];
1346
+ color?: string;
1347
+ lineWidth?: number;
1348
+ }
1349
+ interface IndicatorResult {
1350
+ lines: PlotLine[];
1351
+ histogram?: number[];
1352
+ bands?: [number, number][];
1353
+ }
1354
+ interface CandleView {
1355
+ readonly time: Float64Array | number[];
1356
+ readonly open: Float64Array | number[];
1357
+ readonly high: Float64Array | number[];
1358
+ readonly low: Float64Array | number[];
1359
+ readonly close: Float64Array | number[];
1360
+ readonly volume: Float64Array | number[];
1361
+ readonly length: number;
1362
+ }
1363
+ interface IndicatorDefinition<P extends Record<string, ParamSpec>> {
1364
+ name: string;
1365
+ overlay: boolean;
1366
+ params: P;
1367
+ compute(candles: CandleView, params: ParamValues<P>): IndicatorResult;
1368
+ }
1369
+ interface IndicatorInstance {
1370
+ name: string;
1371
+ overlay: boolean;
1372
+ params: Record<string, ParamSpec>;
1373
+ values: Record<string, unknown>;
1374
+ compute(candles: CandleView): IndicatorResult;
1375
+ }
1376
+ interface ControlDescriptor {
1377
+ key: string;
1378
+ kind: string;
1379
+ label: string;
1380
+ value: unknown;
1381
+ min?: number;
1382
+ max?: number;
1383
+ step?: number;
1384
+ options?: {
1385
+ value: string;
1386
+ label: string;
1387
+ }[];
1388
+ }
1389
+ declare function defineIndicator<P extends Record<string, ParamSpec>>(def: IndicatorDefinition<P>): (overrides?: Partial<ParamValues<P>>) => IndicatorInstance;
1390
+ declare function getControls(inst: IndicatorInstance): ControlDescriptor[];
1391
+
1392
+ interface ThemeTokens {
1393
+ background?: string;
1394
+ text?: string;
1395
+ grid?: string;
1396
+ border?: string;
1397
+ candleUp?: string;
1398
+ candleDown?: string;
1399
+ volumeUp?: string;
1400
+ volumeDown?: string;
1401
+ crosshair?: string;
1402
+ fontFamily?: string;
1403
+ palette?: Record<string, string>;
1404
+ }
1405
+ declare function resolveTheme(tokens: ThemeTokens, base?: "dark" | "light"): ChartTheme;
1406
+ declare function defineTheme(tokens: ThemeTokens): ChartTheme;
1407
+ declare const presets: {
1408
+ dark: ChartTheme;
1409
+ light: ChartTheme;
1410
+ };
1411
+
1412
+ declare const smaStudy: (overrides?: Partial<ParamValues<{
1413
+ period: {
1414
+ type: "int";
1415
+ label: string;
1416
+ default: number;
1417
+ min: number;
1418
+ max: number;
1419
+ };
1420
+ }>> | undefined) => IndicatorInstance;
1421
+ declare const emaStudy: (overrides?: Partial<ParamValues<{
1422
+ period: {
1423
+ type: "int";
1424
+ label: string;
1425
+ default: number;
1426
+ min: number;
1427
+ max: number;
1428
+ };
1429
+ }>> | undefined) => IndicatorInstance;
1430
+ declare const vwapStudy: (overrides?: Partial<ParamValues<{}>> | undefined) => IndicatorInstance;
1431
+ declare const rsiStudy: (overrides?: Partial<ParamValues<{
1432
+ period: {
1433
+ type: "int";
1434
+ label: string;
1435
+ default: number;
1436
+ min: number;
1437
+ max: number;
1438
+ };
1439
+ }>> | undefined) => IndicatorInstance;
1440
+ declare const macdStudy: (overrides?: Partial<ParamValues<{
1441
+ fast: {
1442
+ type: "int";
1443
+ label: string;
1444
+ default: number;
1445
+ min: number;
1446
+ max: number;
1447
+ };
1448
+ slow: {
1449
+ type: "int";
1450
+ label: string;
1451
+ default: number;
1452
+ min: number;
1453
+ max: number;
1454
+ };
1455
+ signal: {
1456
+ type: "int";
1457
+ label: string;
1458
+ default: number;
1459
+ min: number;
1460
+ max: number;
1461
+ };
1462
+ }>> | undefined) => IndicatorInstance;
1463
+ /** Registry of the built-in study factories, keyed by short name. */
1464
+ declare const builtinStudies: {
1465
+ readonly sma: (overrides?: Partial<ParamValues<{
1466
+ period: {
1467
+ type: "int";
1468
+ label: string;
1469
+ default: number;
1470
+ min: number;
1471
+ max: number;
1472
+ };
1473
+ }>> | undefined) => IndicatorInstance;
1474
+ readonly ema: (overrides?: Partial<ParamValues<{
1475
+ period: {
1476
+ type: "int";
1477
+ label: string;
1478
+ default: number;
1479
+ min: number;
1480
+ max: number;
1481
+ };
1482
+ }>> | undefined) => IndicatorInstance;
1483
+ readonly vwap: (overrides?: Partial<ParamValues<{}>> | undefined) => IndicatorInstance;
1484
+ readonly rsi: (overrides?: Partial<ParamValues<{
1485
+ period: {
1486
+ type: "int";
1487
+ label: string;
1488
+ default: number;
1489
+ min: number;
1490
+ max: number;
1491
+ };
1492
+ }>> | undefined) => IndicatorInstance;
1493
+ readonly macd: (overrides?: Partial<ParamValues<{
1494
+ fast: {
1495
+ type: "int";
1496
+ label: string;
1497
+ default: number;
1498
+ min: number;
1499
+ max: number;
1500
+ };
1501
+ slow: {
1502
+ type: "int";
1503
+ label: string;
1504
+ default: number;
1505
+ min: number;
1506
+ max: number;
1507
+ };
1508
+ signal: {
1509
+ type: "int";
1510
+ label: string;
1511
+ default: number;
1512
+ min: number;
1513
+ max: number;
1514
+ };
1515
+ }>> | undefined) => IndicatorInstance;
1516
+ };
1517
+
1518
+ interface ChartOptions {
1519
+ container: HTMLElement;
1520
+ theme?: ThemeTokens;
1521
+ autosize?: boolean;
1522
+ devicePixelRatio?: number;
1523
+ perf?: boolean;
1524
+ }
1525
+ interface IChartApi {
1526
+ addCandleSeries(): CandleLayer;
1527
+ addVolumeSeries(): VolumeLayer;
1528
+ addFootprintSeries(opts?: FootprintSeriesOptions): FootprintSeriesApi;
1529
+ addVolumeProfile(opts?: VolumeProfileOptions): VolumeProfileApi;
1530
+ addOrderBookHeatmap(opts?: OrderBookHeatmapOptions): OrderBookHeatmapApi;
1531
+ addMarkers(opts?: MarkerSeriesOptions): MarkerSeriesApi;
1532
+ addIndicator(inst: IndicatorInstance): {
1533
+ remove(): void;
1534
+ setParams(v: Record<string, unknown>): void;
1535
+ };
1536
+ addPane(opts?: PaneOptions): PaneApi;
1537
+ setData(feed: DataFeed, opts?: {
1538
+ symbol?: string;
1539
+ resolution?: string;
1540
+ }): void;
1541
+ applyTheme(theme: ThemeTokens): void;
1542
+ resize(w: number, h: number): void;
1543
+ takeScreenshot(): HTMLCanvasElement;
1544
+ subscribeCrosshairMove(cb: (p: unknown) => void): Unsubscribe;
1545
+ on(ev: "contextlost" | "contextrestored", cb: () => void): Unsubscribe;
1546
+ dispose(): void;
1547
+ }
1548
+ interface FootprintSeriesOptions {
1549
+ bars?: FootprintBar[];
1550
+ intervalMs?: number;
1551
+ alpha?: number;
1552
+ }
1553
+ interface FootprintSeriesApi {
1554
+ layer: VolumeFootprintLayer;
1555
+ setBars(bars: FootprintBar[], intervalMs?: number): void;
1556
+ clear(): void;
1557
+ remove(): void;
1558
+ }
1559
+ interface VolumeProfileOptions {
1560
+ profile?: VolumeProfileSnapshot;
1561
+ alpha?: number;
1562
+ widthFraction?: number;
1563
+ }
1564
+ interface VolumeProfileApi {
1565
+ layer: VolumeProfileLayer;
1566
+ setProfile(profile: VolumeProfileSnapshot): void;
1567
+ clear(): void;
1568
+ remove(): void;
1569
+ }
1570
+ interface OrderBookHeatmapOptions {
1571
+ snapshots?: OrderBookHeatmapSnapshot[];
1572
+ alpha?: number;
1573
+ }
1574
+ interface OrderBookHeatmapApi {
1575
+ layer: OrderBookHeatmapLayer;
1576
+ addSnapshot(snapshot: OrderBookHeatmapSnapshot): void;
1577
+ setSnapshots(snapshots: OrderBookHeatmapSnapshot[]): void;
1578
+ clear(): void;
1579
+ remove(): void;
1580
+ }
1581
+ interface MarkerSeriesOptions {
1582
+ id?: string;
1583
+ markers?: MarkerSpec[];
1584
+ buys?: MarkerSpec[];
1585
+ sells?: MarkerSpec[];
1586
+ sideSign?: 1 | -1;
1587
+ color?: RGBA;
1588
+ buyColor?: RGBA;
1589
+ sellColor?: RGBA;
1590
+ logMinUsd?: number;
1591
+ logMaxUsd?: number;
1592
+ }
1593
+ interface MarkerSeriesApi {
1594
+ layers: MarkerLayer[];
1595
+ setMarkers(markers: MarkerSpec[] | {
1596
+ buys?: MarkerSpec[];
1597
+ sells?: MarkerSpec[];
1598
+ }): void;
1599
+ clear(): void;
1600
+ remove(): void;
1601
+ }
1602
+ interface PaneOptions {
1603
+ id?: string;
1604
+ interactive?: boolean;
1605
+ padding?: {
1606
+ top?: number;
1607
+ bottom?: number;
1608
+ };
1609
+ priceRange?: [number, number];
1610
+ }
1611
+ interface PaneApi {
1612
+ pane: Pane;
1613
+ remove(): void;
1614
+ }
1615
+ declare function createChart(options: ChartOptions): IChartApi;
1616
+
1617
+ export { AXIS_LABEL_SCALE, BackgroundLayer, CANDLE_COLOR_THEMES, CHART_COLORS, CHART_LAYERS_BY_INDICATOR, CHART_PANES_BY_INDICATOR, CHART_TYPE_BY_KEY, CHART_TYPE_FAMILIES, CHART_TYPE_OPTIONS, type Candle, type CandleColorThemeKey, type CandleColorThemeOption, CandleLayer, type CandleRange, type CandleView, type ChartColorKey, type ChartOptions, type ChartTheme, type ChartTypeAvailability, type ChartTypeFamily, type ChartTypeKey, type ChartTypeOption, type ControlDescriptor, CrosshairLayer, DEFAULT_CANDLE_COLOR_THEME, DEFAULT_CHANNEL_COLOR, DEFAULT_CHART_TYPE, DEFAULT_COLOR_BY_KIND, DEFAULT_FIB_COLOR, DEFAULT_HLINE_COLOR, DEFAULT_RECT_COLOR, DEFAULT_TREND_COLOR, DEFAULT_VISIBLE_INDICATORS, DEFAULT_VLINE_COLOR, type DataFeed, DataStore, DrawingsLayer, type DrawnChannel, type DrawnFib, type DrawnHLine, type DrawnRect, type DrawnShape, type DrawnTrend, type DrawnVLine, type EngineEvent, EventBus, ExtremaAnnotationLayer, FIB_RATIOS, type FeedUpdate, type FootprintBar, type FootprintLevel, type FootprintSeriesApi, type FootprintSeriesOptions, GlyphAtlas, type GlyphAtlasOptions, type HLineSpec, HorizontalLineLayer, type IChartApi, INDICATOR_BY_KEY, INDICATOR_CATALOG, INDICATOR_CATEGORIES, INDICATOR_LIBRARY, INDICATOR_LIBRARY_GROUPS, type IndicatorAvailability, type IndicatorCategory, type IndicatorDefinition, type IndicatorEntry, type IndicatorInstance, type IndicatorKey, IndicatorLayer, type IndicatorLibraryEntry, type IndicatorLibraryGroup, type IndicatorOptions, type IndicatorResult, type Layer, type LayerStats, MarkerLayer, type MarkerSeriesApi, type MarkerSeriesOptions, type MarkerSpec, type OhlcvBuffer, type OhlcvColumns, OiHistoryLayer, type OiHistoryPoint, type OrderBookHeatmapApi, type OrderBookHeatmapHover, OrderBookHeatmapLayer, type OrderBookHeatmapLevel, type OrderBookHeatmapOptions, type OrderBookHeatmapSnapshot, type OrderBookHeatmapSummary, PLOT_GUTTER, PREVIEW_SHAPE_ID, type Padding, Pane, type PaneApi, type PaneBounds, PaneLegendLayer, type PaneOptions, type PanePadding, type ParamSpec, type ParamValues, PerfReporter, type PlotLine, PriceAxisLayer, RGB, type RGBA, type RangeFetchRequest, type RangeFetcher, type RawCandleRow, type RenderContext, RendererGL, type RendererGLOptions, Scene, type SmoothCrosshairAxisResult, TextBatch, type ThemeTokens, TimeAxisLayer, type TimeSnap, UI, type Unsubscribe, Viewport, VolumeFootprintLayer, VolumeLayer, type VolumeProfileApi, type VolumeProfileBin, VolumeProfileLayer, type VolumeProfileOptions, type VolumeProfileSnapshot, alpha, builtinStudies, chartTypeAvailabilityLabel, composeFeeds, createChart, createDataFeed, darkTheme, defineIndicator, defineTheme, emaStudy, formatHoverPrice, formatPrice, formatVolume, getControls, indicatorAvailability, indicatorAvailabilityLabel, indicatorCanToggle, isCandleColorThemeKey, isChartTypeKey, isChartTypeSelectable, loadDrawings, macdStudy, makeShapeId, niceStep, paneLegendValue, presets, resolveTheme, rsiStudy, saveDrawings, smaStudy, smoothCrosshairAxis, stepForRange, swatchOf, vwapStudy };