@agg-build/sdk 4.17.1 → 4.18.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,158 @@
1
+ import {
2
+ __spreadProps,
3
+ __spreadValues
4
+ } from "./chunk-WNQUEZJF.mjs";
5
+
6
+ // src/candle-builder.ts
7
+ var INTERVAL_SECS = {
8
+ "1m": 60,
9
+ "5m": 300,
10
+ "1h": 3600,
11
+ "1d": 86400
12
+ };
13
+ var ALL_INTERVALS = ["1m", "5m", "1h", "1d"];
14
+ var MAX_CLOSED = 500;
15
+ var CandleBuilder = class {
16
+ constructor() {
17
+ this.forming = /* @__PURE__ */ new Map();
18
+ this.closedMap = /* @__PURE__ */ new Map();
19
+ this.listeners = /* @__PURE__ */ new Set();
20
+ this._midpoint = null;
21
+ this.dirty = false;
22
+ this.scheduled = false;
23
+ for (const interval of ALL_INTERVALS) {
24
+ this.closedMap.set(interval, []);
25
+ }
26
+ }
27
+ /** Feed a midpoint (from orderbook delta). Updates O/H/L/C for all intervals. */
28
+ addMidpoint(midpoint, timestampSecs) {
29
+ if (!Number.isFinite(midpoint) || !Number.isFinite(timestampSecs)) return;
30
+ this._midpoint = midpoint;
31
+ for (const interval of ALL_INTERVALS) {
32
+ this.updateCandle(interval, midpoint, timestampSecs);
33
+ }
34
+ this.markDirty();
35
+ }
36
+ /** Feed a trade. Updates volume + refines H/L/C from trade price. */
37
+ addTrade(price, size, timestampSecs) {
38
+ if (!Number.isFinite(price) || !Number.isFinite(size) || !Number.isFinite(timestampSecs))
39
+ return;
40
+ for (const interval of ALL_INTERVALS) {
41
+ this.updateCandle(interval, price, timestampSecs, size);
42
+ }
43
+ this.markDirty();
44
+ }
45
+ /** Current forming candle for an interval. */
46
+ getForming(interval) {
47
+ var _a;
48
+ return (_a = this.forming.get(interval)) != null ? _a : null;
49
+ }
50
+ /** Closed candles for an interval (ring buffer, max 500, newest last). */
51
+ getClosed(interval) {
52
+ var _a;
53
+ return (_a = this.closedMap.get(interval)) != null ? _a : [];
54
+ }
55
+ /** Apply a bounded Worker view without recomputing candles from presentation samples. */
56
+ replaceTail(tail) {
57
+ var _a, _b;
58
+ for (const interval of ALL_INTERVALS) {
59
+ const view = tail[interval];
60
+ if (view.forming) this.forming.set(interval, __spreadValues({}, view.forming));
61
+ else this.forming.delete(interval);
62
+ if (!view.closed.length) continue;
63
+ const closed = new Map(this.getClosed(interval).map((candle) => [candle.openTime, candle]));
64
+ for (const candle of view.closed) closed.set(candle.openTime, __spreadValues({}, candle));
65
+ this.closedMap.set(
66
+ interval,
67
+ [...closed.values()].sort((a, b) => a.openTime - b.openTime).slice(-MAX_CLOSED)
68
+ );
69
+ }
70
+ this._midpoint = (_b = (_a = tail["1m"].forming) == null ? void 0 : _a.c) != null ? _b : null;
71
+ this.markDirty();
72
+ }
73
+ /** Current midpoint (latest value fed). */
74
+ get midpoint() {
75
+ return this._midpoint;
76
+ }
77
+ /**
78
+ * Subscribe to state changes. Returns unsubscribe function.
79
+ * BATCHED: callbacks fire at most once per rAF frame (~16ms).
80
+ * In non-browser environments, falls back to 16ms setTimeout.
81
+ */
82
+ onChange(listener) {
83
+ this.listeners.add(listener);
84
+ return () => {
85
+ this.listeners.delete(listener);
86
+ };
87
+ }
88
+ /** Clear all state (on reconnect). */
89
+ reset() {
90
+ this.forming.clear();
91
+ for (const interval of ALL_INTERVALS) {
92
+ this.closedMap.set(interval, []);
93
+ }
94
+ this._midpoint = null;
95
+ this.dirty = false;
96
+ }
97
+ /** Clear all state AND unsubscribe all listeners. Called on store eviction. */
98
+ dispose() {
99
+ this.reset();
100
+ this.listeners.clear();
101
+ }
102
+ updateCandle(interval, price, timestampSecs, tradeSize) {
103
+ const secs = INTERVAL_SECS[interval];
104
+ const openTime = Math.floor(timestampSecs / secs) * secs;
105
+ const current = this.forming.get(interval);
106
+ if (current && current.openTime !== openTime) {
107
+ const closed = this.closedMap.get(interval);
108
+ closed.push(__spreadProps(__spreadValues({}, current), { isFinal: true }));
109
+ if (closed.length > MAX_CLOSED) closed.shift();
110
+ this.forming.set(interval, {
111
+ openTime,
112
+ o: price,
113
+ h: price,
114
+ l: price,
115
+ c: price,
116
+ v: tradeSize != null ? tradeSize : 0,
117
+ tradeCount: tradeSize != null ? 1 : 0,
118
+ isFinal: false
119
+ });
120
+ } else if (!current) {
121
+ this.forming.set(interval, {
122
+ openTime,
123
+ o: price,
124
+ h: price,
125
+ l: price,
126
+ c: price,
127
+ v: tradeSize != null ? tradeSize : 0,
128
+ tradeCount: tradeSize != null ? 1 : 0,
129
+ isFinal: false
130
+ });
131
+ } else {
132
+ current.h = Math.max(current.h, price);
133
+ current.l = Math.min(current.l, price);
134
+ current.c = price;
135
+ if (tradeSize != null) {
136
+ current.v += tradeSize;
137
+ current.tradeCount += 1;
138
+ }
139
+ }
140
+ }
141
+ markDirty() {
142
+ this.dirty = true;
143
+ if (this.scheduled) return;
144
+ this.scheduled = true;
145
+ const tick = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 16);
146
+ tick(() => {
147
+ this.scheduled = false;
148
+ if (this.dirty) {
149
+ this.dirty = false;
150
+ this.listeners.forEach((l) => l());
151
+ }
152
+ });
153
+ }
154
+ };
155
+
156
+ export {
157
+ CandleBuilder
158
+ };