@zuttora/charts 0.0.0-stage → 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.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -2
  3. package/dist/chart.d.ts +8 -0
  4. package/dist/chart.js +238 -0
  5. package/dist/chartState.d.ts +47 -0
  6. package/dist/chartState.js +64 -0
  7. package/dist/core/drawings.d.ts +21 -0
  8. package/dist/core/drawings.js +46 -0
  9. package/dist/core/emitter.d.ts +8 -0
  10. package/dist/core/emitter.js +26 -0
  11. package/dist/core/format.d.ts +9 -0
  12. package/dist/core/format.js +35 -0
  13. package/dist/core/heikinAshi.d.ts +2 -0
  14. package/dist/core/heikinAshi.js +14 -0
  15. package/dist/core/indicatorSet.d.ts +20 -0
  16. package/dist/core/indicatorSet.js +66 -0
  17. package/dist/core/indicators.d.ts +53 -0
  18. package/dist/core/indicators.js +187 -0
  19. package/dist/core/priceScale.d.ts +22 -0
  20. package/dist/core/priceScale.js +92 -0
  21. package/dist/core/series.d.ts +41 -0
  22. package/dist/core/series.js +221 -0
  23. package/dist/core/time.d.ts +25 -0
  24. package/dist/core/time.js +44 -0
  25. package/dist/core/timeLabels.d.ts +11 -0
  26. package/dist/core/timeLabels.js +52 -0
  27. package/dist/core/types.d.ts +134 -0
  28. package/dist/core/types.js +1 -0
  29. package/dist/core/viewport.d.ts +41 -0
  30. package/dist/core/viewport.js +116 -0
  31. package/dist/core/warn.d.ts +1 -0
  32. package/dist/core/warn.js +3 -0
  33. package/dist/createChart.d.ts +2 -0
  34. package/dist/createChart.js +7 -0
  35. package/dist/dom/environment.d.ts +11 -0
  36. package/dist/dom/environment.js +49 -0
  37. package/dist/dom/indicatorReadings.d.ts +7 -0
  38. package/dist/dom/indicatorReadings.js +26 -0
  39. package/dist/dom/legend.d.ts +19 -0
  40. package/dist/dom/legend.js +62 -0
  41. package/dist/dom/legendSync.d.ts +5 -0
  42. package/dist/dom/legendSync.js +30 -0
  43. package/dist/dom/root.d.ts +9 -0
  44. package/dist/dom/root.js +19 -0
  45. package/dist/draw/controller.d.ts +36 -0
  46. package/dist/draw/controller.js +151 -0
  47. package/dist/draw/geometry.d.ts +44 -0
  48. package/dist/draw/geometry.js +118 -0
  49. package/dist/draw/mapper.d.ts +30 -0
  50. package/dist/draw/mapper.js +32 -0
  51. package/dist/draw/pointer.d.ts +17 -0
  52. package/dist/draw/pointer.js +144 -0
  53. package/dist/draw/state.d.ts +61 -0
  54. package/dist/draw/state.js +8 -0
  55. package/dist/drawApi.d.ts +20 -0
  56. package/dist/drawApi.js +59 -0
  57. package/dist/host.d.ts +35 -0
  58. package/dist/host.js +63 -0
  59. package/dist/index.d.ts +7 -0
  60. package/dist/index.js +5 -0
  61. package/dist/interact/attach.d.ts +2 -0
  62. package/dist/interact/attach.js +142 -0
  63. package/dist/interact/gestures.d.ts +47 -0
  64. package/dist/interact/gestures.js +179 -0
  65. package/dist/interact/keyboard.d.ts +19 -0
  66. package/dist/interact/keyboard.js +34 -0
  67. package/dist/interact/wheel.d.ts +15 -0
  68. package/dist/interact/wheel.js +18 -0
  69. package/dist/render/bars.d.ts +17 -0
  70. package/dist/render/bars.js +55 -0
  71. package/dist/render/canvas.d.ts +2 -0
  72. package/dist/render/canvas.js +13 -0
  73. package/dist/render/drawAxes.d.ts +2 -0
  74. package/dist/render/drawAxes.js +34 -0
  75. package/dist/render/drawCandles.d.ts +3 -0
  76. package/dist/render/drawCandles.js +68 -0
  77. package/dist/render/drawCrosshair.d.ts +7 -0
  78. package/dist/render/drawCrosshair.js +42 -0
  79. package/dist/render/drawDrawings.d.ts +10 -0
  80. package/dist/render/drawDrawings.js +146 -0
  81. package/dist/render/drawGrid.d.ts +2 -0
  82. package/dist/render/drawGrid.js +22 -0
  83. package/dist/render/drawIndicators.d.ts +4 -0
  84. package/dist/render/drawIndicators.js +67 -0
  85. package/dist/render/drawLastPrice.d.ts +10 -0
  86. package/dist/render/drawLastPrice.js +53 -0
  87. package/dist/render/drawLine.d.ts +3 -0
  88. package/dist/render/drawLine.js +43 -0
  89. package/dist/render/drawMain.d.ts +2 -0
  90. package/dist/render/drawMain.js +43 -0
  91. package/dist/render/drawMarkers.d.ts +2 -0
  92. package/dist/render/drawMarkers.js +39 -0
  93. package/dist/render/drawPriceLines.d.ts +4 -0
  94. package/dist/render/drawPriceLines.js +60 -0
  95. package/dist/render/drawSubPanes.d.ts +2 -0
  96. package/dist/render/drawSubPanes.js +104 -0
  97. package/dist/render/frame.d.ts +57 -0
  98. package/dist/render/frame.js +5 -0
  99. package/dist/render/frameBuilder.d.ts +12 -0
  100. package/dist/render/frameBuilder.js +129 -0
  101. package/dist/render/indicatorFrame.d.ts +17 -0
  102. package/dist/render/indicatorFrame.js +55 -0
  103. package/dist/render/layout.d.ts +4 -0
  104. package/dist/render/layout.js +26 -0
  105. package/dist/render/paintFrame.d.ts +11 -0
  106. package/dist/render/paintFrame.js +33 -0
  107. package/dist/render/scheduler.d.ts +17 -0
  108. package/dist/render/scheduler.js +38 -0
  109. package/dist/render/tween.d.ts +12 -0
  110. package/dist/render/tween.js +38 -0
  111. package/dist/theme.d.ts +2 -0
  112. package/dist/theme.js +16 -0
  113. package/package.json +37 -4
@@ -0,0 +1,66 @@
1
+ import { isSubPane, makeCalc } from "./indicators.js";
2
+ export const MAX_INDICATORS = 8;
3
+ const keyOf = (spec) => JSON.stringify({ ...spec, color: undefined, signalColor: undefined });
4
+ export class IndicatorSet {
5
+ constructor() {
6
+ this.entries = [];
7
+ this.closes = [];
8
+ }
9
+ get subCount() {
10
+ let n = 0;
11
+ for (const e of this.entries)
12
+ if (isSubPane(e.spec))
13
+ n++;
14
+ return n;
15
+ }
16
+ main() {
17
+ return this.entries.filter((e) => !isSubPane(e.spec));
18
+ }
19
+ subs() {
20
+ return this.entries.filter((e) => isSubPane(e.spec));
21
+ }
22
+ setSpecs(specs, series) {
23
+ const old = new Map(this.entries.map((e) => [e.spec.id, e]));
24
+ const next = [];
25
+ const seen = new Set();
26
+ for (const spec of specs) {
27
+ if (seen.has(spec.id) || next.length >= MAX_INDICATORS)
28
+ continue;
29
+ seen.add(spec.id);
30
+ const key = keyOf(spec);
31
+ const kept = old.get(spec.id);
32
+ next.push(kept && kept.key === key ? { ...kept, spec } : { spec, key, calc: makeCalc(spec) });
33
+ }
34
+ const fresh = next.filter((e) => !this.entries.some((o) => o.calc === e.calc));
35
+ this.entries = next;
36
+ if (fresh.length > 0) {
37
+ this.loadCloses(series);
38
+ for (const e of fresh)
39
+ e.calc.computeFrom(this.closes, 0);
40
+ }
41
+ }
42
+ rebuild(series) {
43
+ if (this.entries.length === 0)
44
+ return;
45
+ this.loadCloses(series);
46
+ for (const e of this.entries)
47
+ e.calc.computeFrom(this.closes, 0);
48
+ }
49
+ onTick(series, result) {
50
+ if (this.entries.length === 0 || result.kind === 'ignored')
51
+ return;
52
+ const n = series.count;
53
+ if ((result.kind === 'closed' && result.shifted > 0) || this.closes.length < n - 1 || this.closes.length > n) {
54
+ this.rebuild(series);
55
+ return;
56
+ }
57
+ this.closes[n - 1] = series.closeAt(n - 1);
58
+ for (const e of this.entries)
59
+ e.calc.computeFrom(this.closes, n - 1);
60
+ }
61
+ loadCloses(series) {
62
+ this.closes = [];
63
+ for (let i = 0; i < series.count; i++)
64
+ this.closes.push(series.closeAt(i));
65
+ }
66
+ }
@@ -0,0 +1,53 @@
1
+ import type { IndicatorSpec } from './types.ts';
2
+ export type Values = (number | null)[];
3
+ export interface Calc {
4
+ computeFrom(closes: readonly number[], from: number): void;
5
+ lines: Record<string, Values>;
6
+ }
7
+ export declare class Sma implements Calc {
8
+ readonly values: Values;
9
+ readonly lines: Record<string, Values>;
10
+ private period;
11
+ constructor(period: number);
12
+ computeFrom(closes: readonly number[], from: number): void;
13
+ }
14
+ export declare class Ema implements Calc {
15
+ readonly values: Values;
16
+ readonly lines: Record<string, Values>;
17
+ private period;
18
+ private k;
19
+ constructor(period: number);
20
+ computeFrom(input: readonly (number | null)[], from: number): void;
21
+ }
22
+ export declare class Bollinger implements Calc {
23
+ readonly mid: Values;
24
+ readonly upper: Values;
25
+ readonly lower: Values;
26
+ readonly lines: Record<string, Values>;
27
+ private period;
28
+ private width;
29
+ constructor(period: number, stdDev: number);
30
+ computeFrom(closes: readonly number[], from: number): void;
31
+ }
32
+ export declare class Rsi implements Calc {
33
+ readonly values: Values;
34
+ readonly lines: Record<string, Values>;
35
+ private gain;
36
+ private loss;
37
+ private period;
38
+ constructor(period: number);
39
+ computeFrom(closes: readonly number[], from: number): void;
40
+ }
41
+ export declare class Macd implements Calc {
42
+ readonly macd: Values;
43
+ readonly hist: Values;
44
+ readonly lines: Record<string, Values>;
45
+ private fast;
46
+ private slow;
47
+ private signal;
48
+ constructor(fast: number, slow: number, signal: number);
49
+ computeFrom(closes: readonly number[], from: number): void;
50
+ }
51
+ export declare function makeCalc(spec: IndicatorSpec): Calc;
52
+ export declare function isSubPane(spec: IndicatorSpec): boolean;
53
+ export declare function indicatorTitle(spec: IndicatorSpec): string;
@@ -0,0 +1,187 @@
1
+ function cut(values, n) {
2
+ if (values.length > n)
3
+ values.length = n;
4
+ }
5
+ function whole(n) {
6
+ return Math.max(1, Math.floor(Number.isFinite(n) ? n : 1));
7
+ }
8
+ function windowMean(x, end, period) {
9
+ let sum = 0;
10
+ for (let j = end - period + 1; j <= end; j++)
11
+ sum += x[j];
12
+ return sum / period;
13
+ }
14
+ function firstValue(x) {
15
+ for (let i = 0; i < x.length; i++)
16
+ if (x[i] !== null)
17
+ return i;
18
+ return -1;
19
+ }
20
+ export class Sma {
21
+ constructor(period) {
22
+ this.values = [];
23
+ this.lines = { main: this.values };
24
+ this.period = whole(period);
25
+ }
26
+ computeFrom(closes, from) {
27
+ cut(this.values, from);
28
+ for (let i = this.values.length; i < closes.length; i++) {
29
+ this.values.push(i < this.period - 1 ? null : windowMean(closes, i, this.period));
30
+ }
31
+ }
32
+ }
33
+ export class Ema {
34
+ constructor(period) {
35
+ this.values = [];
36
+ this.lines = { main: this.values };
37
+ this.period = whole(period);
38
+ this.k = 2 / (this.period + 1);
39
+ }
40
+ computeFrom(input, from) {
41
+ cut(this.values, from);
42
+ const start = firstValue(input);
43
+ for (let i = this.values.length; i < input.length; i++) {
44
+ if (start < 0 || i < start + this.period - 1)
45
+ this.values.push(null);
46
+ else if (i === start + this.period - 1)
47
+ this.values.push(windowMean(input, i, this.period));
48
+ else {
49
+ const prev = this.values[i - 1];
50
+ this.values.push(prev + this.k * (input[i] - prev));
51
+ }
52
+ }
53
+ }
54
+ }
55
+ export class Bollinger {
56
+ constructor(period, stdDev) {
57
+ this.mid = [];
58
+ this.upper = [];
59
+ this.lower = [];
60
+ this.lines = { mid: this.mid, upper: this.upper, lower: this.lower };
61
+ this.period = whole(period);
62
+ this.width = Number.isFinite(stdDev) ? stdDev : 2;
63
+ }
64
+ computeFrom(closes, from) {
65
+ for (const v of [this.mid, this.upper, this.lower])
66
+ cut(v, from);
67
+ for (let i = this.mid.length; i < closes.length; i++) {
68
+ if (i < this.period - 1) {
69
+ this.mid.push(null);
70
+ this.upper.push(null);
71
+ this.lower.push(null);
72
+ continue;
73
+ }
74
+ const mean = windowMean(closes, i, this.period);
75
+ let sq = 0;
76
+ for (let j = i - this.period + 1; j <= i; j++)
77
+ sq += (closes[j] - mean) ** 2;
78
+ const band = this.width * Math.sqrt(sq / this.period);
79
+ this.mid.push(mean);
80
+ this.upper.push(mean + band);
81
+ this.lower.push(mean - band);
82
+ }
83
+ }
84
+ }
85
+ export class Rsi {
86
+ constructor(period) {
87
+ this.values = [];
88
+ this.lines = { rsi: this.values };
89
+ this.gain = [];
90
+ this.loss = [];
91
+ this.period = whole(period);
92
+ }
93
+ computeFrom(closes, from) {
94
+ for (const v of [this.values, this.gain, this.loss])
95
+ cut(v, from);
96
+ const p = this.period;
97
+ for (let i = this.values.length; i < closes.length; i++) {
98
+ if (i < p) {
99
+ this.values.push(null);
100
+ this.gain.push(null);
101
+ this.loss.push(null);
102
+ continue;
103
+ }
104
+ let g;
105
+ let l;
106
+ if (i === p) {
107
+ g = 0;
108
+ l = 0;
109
+ for (let j = 1; j <= p; j++) {
110
+ const d = closes[j] - closes[j - 1];
111
+ if (d > 0)
112
+ g += d;
113
+ else
114
+ l -= d;
115
+ }
116
+ g /= p;
117
+ l /= p;
118
+ }
119
+ else {
120
+ const d = closes[i] - closes[i - 1];
121
+ g = (this.gain[i - 1] * (p - 1) + (d > 0 ? d : 0)) / p;
122
+ l = (this.loss[i - 1] * (p - 1) + (d < 0 ? -d : 0)) / p;
123
+ }
124
+ this.gain.push(g);
125
+ this.loss.push(l);
126
+ this.values.push(l === 0 ? (g === 0 ? 50 : 100) : 100 - 100 / (1 + g / l));
127
+ }
128
+ }
129
+ }
130
+ export class Macd {
131
+ constructor(fast, slow, signal) {
132
+ this.macd = [];
133
+ this.hist = [];
134
+ this.fast = new Ema(fast);
135
+ this.slow = new Ema(slow);
136
+ this.signal = new Ema(signal);
137
+ this.lines = { macd: this.macd, signal: this.signal.values, hist: this.hist };
138
+ }
139
+ computeFrom(closes, from) {
140
+ this.fast.computeFrom(closes, from);
141
+ this.slow.computeFrom(closes, from);
142
+ cut(this.macd, from);
143
+ for (let i = this.macd.length; i < closes.length; i++) {
144
+ const f = this.fast.values[i];
145
+ const s = this.slow.values[i];
146
+ this.macd.push(f !== null && f !== undefined && s !== null && s !== undefined ? f - s : null);
147
+ }
148
+ this.signal.computeFrom(this.macd, from);
149
+ cut(this.hist, from);
150
+ for (let i = this.hist.length; i < closes.length; i++) {
151
+ const m = this.macd[i];
152
+ const g = this.signal.values[i];
153
+ this.hist.push(m !== null && m !== undefined && g !== null && g !== undefined ? m - g : null);
154
+ }
155
+ }
156
+ }
157
+ export function makeCalc(spec) {
158
+ switch (spec.type) {
159
+ case 'sma':
160
+ return new Sma(spec.period);
161
+ case 'ema':
162
+ return new Ema(spec.period);
163
+ case 'bollinger':
164
+ return new Bollinger(spec.period, spec.stdDev);
165
+ case 'rsi':
166
+ return new Rsi(spec.period);
167
+ case 'macd':
168
+ return new Macd(spec.fast, spec.slow, spec.signal);
169
+ }
170
+ }
171
+ export function isSubPane(spec) {
172
+ return spec.type === 'rsi' || spec.type === 'macd';
173
+ }
174
+ export function indicatorTitle(spec) {
175
+ switch (spec.type) {
176
+ case 'sma':
177
+ return `SMA ${spec.period}`;
178
+ case 'ema':
179
+ return `EMA ${spec.period}`;
180
+ case 'bollinger':
181
+ return `BB ${spec.period} ${spec.stdDev}`;
182
+ case 'rsi':
183
+ return `RSI ${spec.period}`;
184
+ case 'macd':
185
+ return `MACD ${spec.fast} ${spec.slow} ${spec.signal}`;
186
+ }
187
+ }
@@ -0,0 +1,22 @@
1
+ import type { PriceLine } from './types.ts';
2
+ export declare function niceStep(raw: number, minMove: number): number;
3
+ export interface PriceRange {
4
+ min: number;
5
+ max: number;
6
+ }
7
+ export declare function autoRange(bars: PriceRange, extraValues: readonly number[], lines: readonly PriceLine[], minMove: number): PriceRange;
8
+ export declare function pinSide(price: number, range: PriceRange): 'above' | 'below' | null;
9
+ export declare function axisTicks(range: PriceRange, paneHeight: number, minMove: number): number[];
10
+ export declare class PriceScale {
11
+ top: number;
12
+ height: number;
13
+ min: number;
14
+ max: number;
15
+ manual: boolean;
16
+ setPane(top: number, height: number): void;
17
+ setRange(range: PriceRange): void;
18
+ y(price: number): number;
19
+ priceAt(y: number): number;
20
+ scaleBy(dyPx: number): void;
21
+ resetAuto(): void;
22
+ }
@@ -0,0 +1,92 @@
1
+ export function niceStep(raw, minMove) {
2
+ const exp = Math.floor(Math.log10(raw));
3
+ const base = 10 ** exp;
4
+ const m = raw / base;
5
+ const nice = m <= 1 ? 1 : m <= 2 ? 2 : m <= 2.5 ? 2.5 : m <= 5 ? 5 : 10;
6
+ return Math.max(nice * base, minMove);
7
+ }
8
+ export function autoRange(bars, extraValues, lines, minMove) {
9
+ let min = bars.min;
10
+ let max = bars.max;
11
+ for (const v of extraValues) {
12
+ if (Number.isFinite(v)) {
13
+ if (v < min)
14
+ min = v;
15
+ if (v > max)
16
+ max = v;
17
+ }
18
+ }
19
+ const span = max - min;
20
+ const lo = min - 0.5 * span;
21
+ const hi = max + 0.5 * span;
22
+ for (const line of lines) {
23
+ if (line.price >= lo && line.price <= hi) {
24
+ if (line.price < min)
25
+ min = line.price;
26
+ if (line.price > max)
27
+ max = line.price;
28
+ }
29
+ }
30
+ if (max === min) {
31
+ const pad = Math.max(Math.abs(max) * 0.001, 10 * minMove);
32
+ return { min: min - pad, max: max + pad };
33
+ }
34
+ const pad = (max - min) * 0.08;
35
+ return { min: min - pad, max: max + pad };
36
+ }
37
+ export function pinSide(price, range) {
38
+ if (price > range.max)
39
+ return 'above';
40
+ if (price < range.min)
41
+ return 'below';
42
+ return null;
43
+ }
44
+ export function axisTicks(range, paneHeight, minMove) {
45
+ const target = Math.max(2, Math.floor(paneHeight / 40));
46
+ const span = range.max - range.min;
47
+ if (!(span > 0))
48
+ return [];
49
+ const step = niceStep(span / target, minMove);
50
+ const out = [];
51
+ const first = Math.ceil(range.min / step - 1e-9);
52
+ for (let k = first; k * step <= range.max + step * 1e-9; k++) {
53
+ out.push(Math.round(k * step * 1e8) / 1e8);
54
+ if (out.length > 200)
55
+ break;
56
+ }
57
+ return out;
58
+ }
59
+ export class PriceScale {
60
+ constructor() {
61
+ this.top = 0;
62
+ this.height = 1;
63
+ this.min = 0;
64
+ this.max = 1;
65
+ this.manual = false;
66
+ }
67
+ setPane(top, height) {
68
+ this.top = top;
69
+ this.height = Math.max(1, height);
70
+ }
71
+ setRange(range) {
72
+ this.min = range.min;
73
+ this.max = range.max;
74
+ }
75
+ y(price) {
76
+ return this.top + ((this.max - price) / (this.max - this.min)) * this.height;
77
+ }
78
+ priceAt(y) {
79
+ return this.max - ((y - this.top) / this.height) * (this.max - this.min);
80
+ }
81
+ scaleBy(dyPx) {
82
+ const factor = Math.exp(dyPx * 0.005);
83
+ const centre = (this.max + this.min) / 2;
84
+ const half = ((this.max - this.min) / 2) * factor;
85
+ this.min = centre - half;
86
+ this.max = centre + half;
87
+ this.manual = true;
88
+ }
89
+ resetAuto() {
90
+ this.manual = false;
91
+ }
92
+ }
@@ -0,0 +1,41 @@
1
+ import type { Candle } from './types.ts';
2
+ export declare const MAX_CLOSED = 5000;
3
+ export type TickResult = {
4
+ kind: 'ignored';
5
+ } | {
6
+ kind: 'updated';
7
+ } | {
8
+ kind: 'started';
9
+ } | {
10
+ kind: 'closed';
11
+ closed: Candle;
12
+ shifted: number;
13
+ };
14
+ export declare class CandleSeries {
15
+ t: number[];
16
+ o: number[];
17
+ h: number[];
18
+ l: number[];
19
+ c: number[];
20
+ forming: Candle | null;
21
+ hasMore: boolean;
22
+ get closedCount(): number;
23
+ get count(): number;
24
+ lastIndex(): number;
25
+ candleAt(i: number): Candle;
26
+ timeAt(i: number): number;
27
+ closeAt(i: number): number;
28
+ toArray(): Candle[];
29
+ setData(closed: readonly Candle[], forming: Candle | null, hasMore: boolean): void;
30
+ prependData(older: readonly Candle[], hasMore: boolean): number;
31
+ replaceClosed(candles: readonly Candle[]): number;
32
+ applyTick(price: number, time: number, intervalMs: number, offsetMs: number): TickResult;
33
+ visibleExtremes(from: number, to: number): {
34
+ min: number;
35
+ max: number;
36
+ } | null;
37
+ indexAtTime(time: number): number;
38
+ private closedIndexOf;
39
+ private push;
40
+ private dropOldest;
41
+ }
@@ -0,0 +1,221 @@
1
+ import { bucketStart } from "./time.js";
2
+ import { warn } from "./warn.js";
3
+ export const MAX_CLOSED = 5000;
4
+ function isValid(k) {
5
+ return (Number.isFinite(k.t) &&
6
+ Number.isFinite(k.o) &&
7
+ Number.isFinite(k.h) &&
8
+ Number.isFinite(k.l) &&
9
+ Number.isFinite(k.c) &&
10
+ k.l <= Math.min(k.o, k.c) &&
11
+ k.h >= Math.max(k.o, k.c));
12
+ }
13
+ export class CandleSeries {
14
+ constructor() {
15
+ this.t = [];
16
+ this.o = [];
17
+ this.h = [];
18
+ this.l = [];
19
+ this.c = [];
20
+ this.forming = null;
21
+ this.hasMore = false;
22
+ }
23
+ get closedCount() {
24
+ return this.t.length;
25
+ }
26
+ get count() {
27
+ return this.t.length + (this.forming ? 1 : 0);
28
+ }
29
+ lastIndex() {
30
+ return this.count - 1;
31
+ }
32
+ candleAt(i) {
33
+ if (i === this.t.length && this.forming)
34
+ return { ...this.forming };
35
+ return { t: this.t[i], o: this.o[i], h: this.h[i], l: this.l[i], c: this.c[i] };
36
+ }
37
+ timeAt(i) {
38
+ return i === this.t.length && this.forming ? this.forming.t : this.t[i];
39
+ }
40
+ closeAt(i) {
41
+ return i === this.t.length && this.forming ? this.forming.c : this.c[i];
42
+ }
43
+ toArray() {
44
+ const out = [];
45
+ for (let i = 0; i < this.count; i++)
46
+ out.push(this.candleAt(i));
47
+ return out;
48
+ }
49
+ setData(closed, forming, hasMore) {
50
+ this.t = [];
51
+ this.o = [];
52
+ this.h = [];
53
+ this.l = [];
54
+ this.c = [];
55
+ let dropped = 0;
56
+ let prevT = -Infinity;
57
+ for (const k of closed) {
58
+ if (!isValid(k) || k.t <= prevT) {
59
+ dropped++;
60
+ continue;
61
+ }
62
+ this.push(k);
63
+ prevT = k.t;
64
+ }
65
+ if (forming && (!isValid(forming) || forming.t <= prevT)) {
66
+ dropped++;
67
+ forming = null;
68
+ }
69
+ this.forming = forming ? { t: forming.t, o: forming.o, h: forming.h, l: forming.l, c: forming.c } : null;
70
+ this.hasMore = hasMore;
71
+ if (this.t.length > MAX_CLOSED) {
72
+ this.dropOldest(this.t.length - MAX_CLOSED);
73
+ this.hasMore = false;
74
+ }
75
+ if (dropped > 0)
76
+ warn(`dropped ${dropped} invalid candle(s)`);
77
+ }
78
+ prependData(older, hasMore) {
79
+ const oldest = this.count > 0 ? this.timeAt(0) : Infinity;
80
+ const keep = [];
81
+ let dropped = 0;
82
+ let prevT = -Infinity;
83
+ for (const k of older) {
84
+ if (!isValid(k) || k.t >= oldest || k.t <= prevT) {
85
+ dropped++;
86
+ continue;
87
+ }
88
+ keep.push(k);
89
+ prevT = k.t;
90
+ }
91
+ if (dropped > 0)
92
+ warn(`dropped ${dropped} invalid older candle(s)`);
93
+ this.hasMore = hasMore;
94
+ let room = MAX_CLOSED - this.t.length;
95
+ if (room < 0)
96
+ room = 0;
97
+ let added = keep;
98
+ if (added.length > room) {
99
+ added = added.slice(added.length - room);
100
+ this.hasMore = false;
101
+ }
102
+ if (added.length === 0)
103
+ return 0;
104
+ this.t = added.map((k) => k.t).concat(this.t);
105
+ this.o = added.map((k) => k.o).concat(this.o);
106
+ this.h = added.map((k) => k.h).concat(this.h);
107
+ this.l = added.map((k) => k.l).concat(this.l);
108
+ this.c = added.map((k) => k.c).concat(this.c);
109
+ return added.length;
110
+ }
111
+ replaceClosed(candles) {
112
+ let changed = 0;
113
+ for (const k of candles) {
114
+ if (!isValid(k))
115
+ continue;
116
+ const i = this.closedIndexOf(k.t);
117
+ if (i < 0)
118
+ continue;
119
+ this.o[i] = k.o;
120
+ this.h[i] = k.h;
121
+ this.l[i] = k.l;
122
+ this.c[i] = k.c;
123
+ changed++;
124
+ }
125
+ return changed;
126
+ }
127
+ applyTick(price, time, intervalMs, offsetMs) {
128
+ if (!Number.isFinite(price) || price <= 0)
129
+ return { kind: 'ignored' };
130
+ const b = bucketStart(time, intervalMs, offsetMs);
131
+ const f = this.forming;
132
+ if (f === null) {
133
+ const lastClosed = this.t.length > 0 ? this.t[this.t.length - 1] : -Infinity;
134
+ if (b <= lastClosed)
135
+ return { kind: 'ignored' };
136
+ this.forming = { t: b, o: price, h: price, l: price, c: price };
137
+ return { kind: 'started' };
138
+ }
139
+ if (b > f.t) {
140
+ const closed = { t: f.t, o: f.o, h: f.h, l: f.l, c: f.c };
141
+ this.push(closed);
142
+ this.forming = { t: b, o: price, h: price, l: price, c: price };
143
+ const over = this.t.length - MAX_CLOSED;
144
+ const shifted = over > 0 ? this.dropOldest(over) : 0;
145
+ if (shifted > 0)
146
+ this.hasMore = false;
147
+ return { kind: 'closed', closed, shifted };
148
+ }
149
+ if (b === f.t) {
150
+ if (price > f.h)
151
+ f.h = price;
152
+ if (price < f.l)
153
+ f.l = price;
154
+ f.c = price;
155
+ return { kind: 'updated' };
156
+ }
157
+ return { kind: 'ignored' };
158
+ }
159
+ visibleExtremes(from, to) {
160
+ const a = Math.max(0, Math.floor(from));
161
+ const z = Math.min(this.lastIndex(), Math.ceil(to));
162
+ if (a > z)
163
+ return null;
164
+ let min = Infinity;
165
+ let max = -Infinity;
166
+ const n = this.t.length;
167
+ for (let i = a; i <= z; i++) {
168
+ const lo = i === n && this.forming ? this.forming.l : this.l[i];
169
+ const hi = i === n && this.forming ? this.forming.h : this.h[i];
170
+ if (lo < min)
171
+ min = lo;
172
+ if (hi > max)
173
+ max = hi;
174
+ }
175
+ return { min, max };
176
+ }
177
+ indexAtTime(time) {
178
+ if (this.count === 0 || time < this.timeAt(0))
179
+ return -1;
180
+ let lo = 0;
181
+ let hi = this.count - 1;
182
+ while (lo < hi) {
183
+ const mid = (lo + hi + 1) >> 1;
184
+ if (this.timeAt(mid) <= time)
185
+ lo = mid;
186
+ else
187
+ hi = mid - 1;
188
+ }
189
+ return lo;
190
+ }
191
+ closedIndexOf(time) {
192
+ let lo = 0;
193
+ let hi = this.t.length - 1;
194
+ while (lo <= hi) {
195
+ const mid = (lo + hi) >> 1;
196
+ const v = this.t[mid];
197
+ if (v === time)
198
+ return mid;
199
+ if (v < time)
200
+ lo = mid + 1;
201
+ else
202
+ hi = mid - 1;
203
+ }
204
+ return -1;
205
+ }
206
+ push(k) {
207
+ this.t.push(k.t);
208
+ this.o.push(k.o);
209
+ this.h.push(k.h);
210
+ this.l.push(k.l);
211
+ this.c.push(k.c);
212
+ }
213
+ dropOldest(n) {
214
+ this.t.splice(0, n);
215
+ this.o.splice(0, n);
216
+ this.h.splice(0, n);
217
+ this.l.splice(0, n);
218
+ this.c.splice(0, n);
219
+ return n;
220
+ }
221
+ }