@agg-build/sdk 4.25.0 → 4.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agg-websocket-BstVWtLA.d.mts +6004 -0
- package/dist/agg-websocket-BstVWtLA.d.mts.map +1 -0
- package/dist/agg-websocket-BstVWtLA.d.ts +6004 -0
- package/dist/agg-websocket-BstVWtLA.d.ts.map +1 -0
- package/dist/agg-websocket-CK2iXfti.mjs +1427 -0
- package/dist/agg-websocket-CK2iXfti.mjs.map +1 -0
- package/dist/agg-websocket-DJbIfxKb.js +1478 -0
- package/dist/browser.d.mts +67 -66
- package/dist/browser.d.mts.map +1 -0
- package/dist/browser.d.ts +67 -66
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +720 -2047
- package/dist/browser.mjs +423 -799
- package/dist/browser.mjs.map +1 -0
- package/dist/candle-builder-BNewIe9K.mjs +148 -0
- package/dist/candle-builder-BNewIe9K.mjs.map +1 -0
- package/dist/candle-builder-BULYdxB4.js +151 -0
- package/dist/index.d.mts +2 -1342
- package/dist/index.d.ts +2 -1342
- package/dist/index.js +2794 -4656
- package/dist/index.mjs +2788 -2486
- package/dist/index.mjs.map +1 -0
- package/dist/market-data.d.mts +6 -6
- package/dist/market-data.d.mts.map +1 -0
- package/dist/market-data.d.ts +6 -6
- package/dist/market-data.d.ts.map +1 -0
- package/dist/market-data.js +314 -1774
- package/dist/market-data.mjs +314 -350
- package/dist/market-data.mjs.map +1 -0
- package/dist/server.d.mts +235 -234
- package/dist/server.d.mts.map +1 -0
- package/dist/server.d.ts +235 -234
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +168 -296
- package/dist/server.mjs +163 -234
- package/dist/server.mjs.map +1 -0
- package/package.json +4 -4
- package/dist/agg-websocket-Bxt9PzTz.d.mts +0 -4479
- package/dist/agg-websocket-Bxt9PzTz.d.ts +0 -4479
- package/dist/chunk-QXBVA5TW.mjs +0 -2073
- package/dist/chunk-VD5UTAVD.mjs +0 -158
- package/dist/chunk-WNQUEZJF.mjs +0 -58
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.mjs","names":[],"sources":["../src/market-data/worker-adapter.ts"],"sourcesContent":["import { proxy, releaseProxy, wrap, type Endpoint, type Remote } from \"comlink\";\nimport {\n AggWebSocket,\n type AggWebSocketCallbacks,\n type AggWebSocketOptions,\n type AggWebSocketDiagnostics,\n} from \"../agg-websocket\";\nimport type { ChartHistoryManifest, ChartCandle, ChartBarsResponse } from \"../types\";\nimport type { OrderbookState } from \"../orderbook-utils\";\nimport type { WsArbFeedBatch, WsArbMarketUpdate } from \"../ws-types\";\nimport type { MarketDataEngine, MarketSubscription, MarketViewBatch } from \"./engine\";\n\n/** The application owns the native module Worker URL so its static bundler fingerprints it. */\nexport type MarketDataWorkerFactory = () => Worker;\n\nexport class WorkerAggWebSocket extends AggWebSocket {\n private worker: Worker | null;\n private remote: Remote<MarketDataEngine> | null;\n private endpointListeners = new Map<EventListenerOrEventListenerObject, string>();\n private historyOperations = 0;\n private hostProvider?: () => Record<string, unknown>;\n private handlers: AggWebSocketCallbacks;\n private currentBooks = new Map<string, OrderbookState>();\n private recovering = new Set<string>();\n private diagnosticRevisions = new Map<string, number>();\n private deliveredRevisions = new Map<string, number>();\n private resolvedRevisions = new Map<string, number>();\n private refs = new Map<string, { sub: MarketSubscription; count: number }>();\n private arbListeners = new Map<string, Set<(msg: WsArbMarketUpdate) => void>>();\n private feedListeners = new Set<(msg: WsArbFeedBatch) => void>();\n private latest = new Map<string, WsArbMarketUpdate>();\n private generation = 0;\n private online = false;\n private stopped = false;\n private queued = false;\n private ready: Promise<void>;\n private resolveFailure?: () => void;\n private historyFailures = new Set<(error: Error) => void>();\n private fallback: AggWebSocket | null = null;\n private fallbackUnsubs = new Map<string, () => void>();\n get processesCandles(): boolean {\n return !this.fallback;\n }\n\n constructor(\n private readonly config: AggWebSocketOptions,\n factory: MarketDataWorkerFactory,\n ) {\n super(config);\n if (config.callbacks?.onTrade && !config.callbacks.onMarketView)\n throw new Error(\n \"Raw trade callbacks require AggWebSocket; Worker views require onMarketView opt-in\",\n );\n this.handlers = config.callbacks ?? {};\n this.worker = factory();\n const worker = this.worker;\n const endpoint: Endpoint = {\n postMessage: worker.postMessage.bind(worker),\n addEventListener: (type, listener) => {\n this.endpointListeners.set(listener, type);\n worker.addEventListener(type, listener);\n },\n removeEventListener: (type, listener) => {\n this.endpointListeners.delete(listener);\n worker.removeEventListener(type, listener);\n },\n };\n this.remote = wrap<MarketDataEngine>(endpoint);\n this.worker.addEventListener(\"error\", this.fail);\n this.worker.addEventListener(\"messageerror\", this.fail);\n const { callbacks: _callbacks, ...options } = config;\n const startup = this.remote\n .start(\n options,\n proxy((event) => {\n if (!this.fallback) this.event(event.name, event.args, event.revision);\n }),\n )\n .then(() => {\n if (this.stopped || this.fallback || !this.remote) return;\n return this.remote.observeViews(\n proxy((batch: MarketViewBatch) => {\n if (batch.generation < this.generation || this.stopped || this.fallback) return;\n for (const view of batch.views) {\n if (\n !this.refs.has(`orderbook:${view.outcomeId}`) &&\n !this.refs.has(`trades:${view.outcomeId}`)\n )\n continue;\n if ((this.resolvedRevisions.get(view.outcomeId) ?? -1) >= view.revision) continue;\n const integrity =\n (this.diagnosticRevisions.get(view.outcomeId) ?? -1) > view.revision\n ? \"resyncing\"\n : view.integrity;\n this.deliveredRevisions.set(view.outcomeId, view.revision);\n this.handlers.onMarketView?.({ ...view, integrity });\n if (view.book) {\n this.currentBooks.set(view.outcomeId, view.book);\n if (integrity === \"ok\") {\n this.recovering.delete(view.outcomeId);\n this.diagnosticRevisions.delete(view.outcomeId);\n } else this.recovering.add(view.outcomeId);\n if (view.snapshot) this.handlers.onSnapshot?.(view.outcomeId, view.book);\n else this.handlers.onDelta?.(view.outcomeId, view.book);\n }\n for (const trade of [...view.trades].reverse()) this.handlers.onTrade?.(trade);\n }\n }),\n );\n })\n .catch(this.fail);\n const failed = new Promise<void>((resolve) => {\n this.resolveFailure = resolve;\n });\n this.ready = Promise.race([startup, failed]);\n const startupTimeout = setTimeout(this.fail, 3000);\n void this.ready.then(() => clearTimeout(startupTimeout));\n if (typeof document !== \"undefined\")\n document.addEventListener(\"visibilitychange\", this.visibility);\n }\n\n override async decodeHistory(payload: string): Promise<ChartBarsResponse> {\n this.historyOperations++;\n try {\n return await this.decodeHistoryRequest(payload);\n } finally {\n this.historyOperations--;\n }\n }\n private async decodeHistoryRequest(payload: string): Promise<ChartBarsResponse> {\n if (this.stopped) throw new Error(\"Market-data worker disposed\");\n await this.ready;\n if (this.stopped) throw new Error(\"Market-data worker disposed\");\n if (this.fallback) return this.fallback.decodeHistory(payload);\n const remote = this.remote;\n if (!remote) throw new Error(\"Market-data worker unavailable\");\n let rejectFailure!: (error: Error) => void;\n const failed = new Promise<never>((_resolve, reject) => {\n rejectFailure = reject;\n this.historyFailures.add(reject);\n });\n try {\n return await Promise.race([remote.decodeHistory(payload), failed]);\n } finally {\n this.historyFailures.delete(rejectFailure);\n }\n }\n\n private historyGeneration = 0;\n override async loadHistory(\n manifest: ChartHistoryManifest,\n options?: { signal?: AbortSignal },\n ): Promise<ChartCandle[]> {\n this.historyOperations++;\n try {\n return await this.loadHistoryRequest(manifest, options);\n } finally {\n this.historyOperations--;\n }\n }\n private async loadHistoryRequest(\n manifest: ChartHistoryManifest,\n options?: { signal?: AbortSignal },\n ): Promise<ChartCandle[]> {\n if (this.stopped) throw new Error(\"Market-data worker disposed\");\n if (this.fallback) return this.fallback.loadHistory(manifest, options);\n const generation = ++this.historyGeneration;\n const key = `${manifest.venueMarketOutcomeId}:${manifest.resolution}:${manifest.from}:${manifest.to}`;\n options?.signal?.throwIfAborted();\n await this.ready;\n if (this.stopped) throw new Error(\"Market-data worker disposed\");\n options?.signal?.throwIfAborted();\n // Startup may switch transports during the await.\n const fallbackAfterStartup = this.fallback as AggWebSocket | null;\n if (fallbackAfterStartup) return fallbackAfterStartup.loadHistory(manifest, options);\n const remote = this.remote;\n if (!remote) throw new Error(\"Market-data worker unavailable\");\n let rejectFailure!: (error: Error) => void;\n const failed = new Promise<never>((_resolve, reject) => {\n rejectFailure = reject;\n this.historyFailures.add(reject);\n });\n const abort = () => {\n void this.remote?.cancelHistoryRange(key, generation);\n };\n options?.signal?.addEventListener(\"abort\", abort, { once: true });\n try {\n const result = await Promise.race([\n remote.loadImmutableRange(key, generation, manifest),\n failed,\n ]);\n options?.signal?.throwIfAborted();\n return result;\n } finally {\n options?.signal?.removeEventListener(\"abort\", abort);\n this.historyFailures.delete(rejectFailure);\n }\n }\n override setHostDiagnostics(provider?: () => Record<string, unknown>): void {\n this.hostProvider = provider;\n }\n\n override getDiagnostics(): AggWebSocketDiagnostics {\n const subscriptions = [...this.refs.values()].map((entry) => entry.sub);\n return this.fallback\n ? {\n ...this.fallback.getDiagnostics(),\n host: this.hostProvider?.(),\n pendingHistoryRequests: this.historyOperations,\n workerAttached: false,\n bridgeListeners: this.endpointListeners.size,\n }\n : {\n connected: this.connected,\n host: this.hostProvider?.(),\n transport: this.stopped ? \"disposed\" : \"worker\",\n orderbookOutcomeIds: subscriptions\n .filter((sub) => sub.channel === \"orderbook\")\n .map((sub) => sub.id),\n tradeOutcomeIds: subscriptions\n .filter((sub) => sub.channel === \"trades\")\n .map((sub) => sub.id),\n arbMarketIds: [...this.arbListeners.keys()],\n arbFeedSubscribers: this.feedListeners.size,\n cachedBooks: this.currentBooks.size,\n cachedArb: this.latest.size,\n resyncingBooks: this.recovering.size,\n pendingResnapshots: 0,\n pendingHistoryRequests: this.historyOperations,\n workerAttached: this.worker !== null,\n bridgeListeners: this.endpointListeners.size,\n };\n }\n async getWorkerDiagnostics(): Promise<ReturnType<MarketDataEngine[\"getMeasurements\"]> | null> {\n await this.ready;\n const remote = this.remote;\n if (this.stopped || !remote || this.fallback) return null;\n let rejectFailure!: (error: Error) => void;\n const failed = new Promise<never>((_resolve, reject) => {\n rejectFailure = reject;\n this.historyFailures.add(reject);\n });\n try {\n return await Promise.race([remote.getMeasurements(), failed]);\n } catch {\n return null;\n } finally {\n this.historyFailures.delete(rejectFailure);\n }\n }\n\n override get connected(): boolean {\n return !this.stopped && (this.fallback?.connected ?? this.online);\n }\n override getBook(id: string): OrderbookState | null {\n return this.fallback?.getBook(id) ?? this.currentBooks.get(id) ?? null;\n }\n override isResyncing(id: string): boolean {\n return this.fallback?.isResyncing(id) ?? this.recovering.has(id);\n }\n override getArb(id: string): WsArbMarketUpdate | null {\n return this.fallback?.getArb(id) ?? this.latest.get(id) ?? null;\n }\n override subscribe(id: string, channel: \"orderbook\" | \"trades\" = \"orderbook\"): () => void {\n return this.ref({ id, channel });\n }\n override subscribeTrades(id: string): () => void {\n return this.subscribe(id, \"trades\");\n }\n override subscribeArb(id: string, cb: (msg: WsArbMarketUpdate) => void): () => void {\n const listeners = this.arbListeners.get(id) ?? new Set();\n listeners.add(cb);\n this.arbListeners.set(id, listeners);\n const unsubscribe = this.ref({ id, channel: \"arb\" });\n return () => {\n unsubscribe();\n listeners.delete(cb);\n if (!listeners.size) this.arbListeners.delete(id);\n };\n }\n override subscribeArbFeed(cb: (msg: WsArbFeedBatch) => void): () => void {\n this.feedListeners.add(cb);\n const unsubscribe = this.ref({ id: \"*\", channel: \"arb-feed\" });\n return () => {\n unsubscribe();\n this.feedListeners.delete(cb);\n };\n }\n override setCallbacks(callbacks: AggWebSocketCallbacks): void {\n this.handlers = callbacks;\n if (callbacks.onTrade && !callbacks.onMarketView && !this.fallback) this.fail();\n this.fallback?.setCallbacks(this.fallbackCallbacks());\n }\n override authenticate(token: string): void {\n this.config.initialToken = token;\n this.run((engine) => engine.authenticate(token));\n }\n override connect(): void {\n this.run((engine) => engine.connect());\n }\n override disconnect(): void {\n this.run((engine) => engine.disconnect());\n }\n override resnapshot(id: string | string[]): void {\n const ids = Array.isArray(id) ? id : [id];\n this.run((engine) => engine.resnapshot(ids));\n }\n override destroy(): void {\n if (this.stopped) return;\n this.stopped = true;\n this.online = false;\n this.resolveFailure?.();\n for (const reject of this.historyFailures) reject(new Error(\"Market-data worker disposed\"));\n this.historyFailures.clear();\n this.refs.clear();\n this.currentBooks.clear();\n this.latest.clear();\n this.arbListeners.clear();\n this.feedListeners.clear();\n this.recovering.clear();\n this.diagnosticRevisions.clear();\n this.deliveredRevisions.clear();\n this.resolvedRevisions.clear();\n this.fallback?.destroy();\n this.fallbackUnsubs.clear();\n this.hostProvider = undefined;\n this.handlers = {};\n if (typeof document !== \"undefined\")\n document.removeEventListener(\"visibilitychange\", this.visibility);\n this.releaseWorker();\n }\n private releaseWorker(): void {\n const worker = this.worker;\n const remote = this.remote;\n this.remote = null;\n this.worker = null;\n if (remote) remote[releaseProxy]();\n if (!worker) return;\n for (const [listener, type] of this.endpointListeners)\n worker.removeEventListener(type, listener);\n this.endpointListeners.clear();\n worker.removeEventListener(\"error\", this.fail);\n worker.removeEventListener(\"messageerror\", this.fail);\n worker.terminate();\n }\n\n private event(name: keyof AggWebSocketCallbacks, args: unknown[], revision = 0): void {\n if (this.stopped) return;\n if (name === \"onConnectionStateChange\") this.online = args[0] as boolean;\n if (name === \"onDiagnostics\") {\n const diagnostic = args[0] as { type: string; outcomeId?: string };\n if (\n diagnostic.outcomeId &&\n revision < (this.deliveredRevisions.get(diagnostic.outcomeId) ?? -1)\n )\n return;\n if (\n diagnostic.outcomeId &&\n (this.refs.has(`orderbook:${diagnostic.outcomeId}`) ||\n this.refs.has(`trades:${diagnostic.outcomeId}`)) &&\n [\"seq_gap\", \"checksum_mismatch\"].includes(diagnostic.type)\n ) {\n this.recovering.add(diagnostic.outcomeId);\n this.diagnosticRevisions.set(diagnostic.outcomeId, revision);\n }\n }\n if (name === \"onMarketResolved\") {\n const id = (args[0] as { outcomeId: string }).outcomeId;\n if (this.refs.has(`orderbook:${id}`) || this.refs.has(`trades:${id}`))\n this.resolvedRevisions.set(id, revision);\n this.currentBooks.delete(id);\n }\n if (name === \"onArbMarket\") {\n const msg = args[0] as WsArbMarketUpdate;\n this.latest.set(msg.marketId, msg);\n for (const cb of this.arbListeners.get(msg.marketId) ?? []) cb(msg);\n }\n if (name === \"onArbFeed\") for (const cb of this.feedListeners) cb(args[0] as WsArbFeedBatch);\n (this.handlers[name] as ((...args: unknown[]) => void) | undefined)?.(...args);\n }\n private ref(sub: MarketSubscription): () => void {\n const key = `${sub.channel}:${sub.id}`;\n const entry = this.refs.get(key) ?? { sub, count: 0 };\n entry.count++;\n this.refs.set(key, entry);\n this.sync();\n let released = false;\n return () => {\n if (released) return;\n released = true;\n if (--entry.count === 0) {\n this.refs.delete(key);\n if (sub.channel === \"orderbook\") this.recovering.delete(sub.id);\n if (!this.refs.has(`orderbook:${sub.id}`) && !this.refs.has(`trades:${sub.id}`)) {\n this.currentBooks.delete(sub.id);\n this.recovering.delete(sub.id);\n this.diagnosticRevisions.delete(sub.id);\n this.deliveredRevisions.delete(sub.id);\n this.resolvedRevisions.delete(sub.id);\n }\n }\n this.sync();\n };\n }\n private sync(): void {\n if (this.queued || this.stopped) return;\n this.queued = true;\n queueMicrotask(() => {\n this.queued = false;\n if (this.stopped) return;\n if (this.fallback) {\n this.syncFallback();\n return;\n }\n const generation = ++this.generation;\n const subscriptions = [...this.refs.values()].map((entry) => entry.sub);\n void this.ready\n .then(() => {\n if (!this.stopped && !this.fallback)\n return this.remote?.setSubscriptions(subscriptions, generation);\n })\n .catch(this.fail);\n });\n }\n private run(action: (engine: Remote<MarketDataEngine> | AggWebSocket) => unknown): void {\n if (this.stopped) return;\n if (this.fallback) {\n action(this.fallback);\n return;\n }\n void this.ready\n .then(() => {\n if (this.stopped) return;\n const engine = this.fallback ?? this.remote;\n if (engine) return action(engine);\n })\n .catch(this.fail);\n }\n private visibility = (): void => {\n if (document.visibilityState === \"visible\")\n this.resnapshot(\n [...this.refs.values()]\n .filter((entry) => entry.sub.channel === \"orderbook\")\n .map((entry) => entry.sub.id),\n );\n };\n private fallbackCallbacks(): AggWebSocketCallbacks {\n return {\n ...this.handlers,\n onArbMarket: (msg) => this.event(\"onArbMarket\", [msg]),\n onArbFeed: (msg) => this.event(\"onArbFeed\", [msg]),\n };\n }\n private syncFallback(): void {\n for (const [key, unsubscribe] of this.fallbackUnsubs) {\n if (!this.refs.has(key)) {\n unsubscribe();\n this.fallbackUnsubs.delete(key);\n }\n }\n for (const [key, { sub }] of this.refs) {\n if (this.fallbackUnsubs.has(key)) continue;\n this.fallbackUnsubs.set(\n key,\n sub.channel === \"arb\"\n ? this.fallback!.subscribeArb(sub.id, () => {})\n : sub.channel === \"arb-feed\"\n ? this.fallback!.subscribeArbFeed(() => {})\n : this.fallback!.subscribe(sub.id, sub.channel),\n );\n }\n }\n\n private fail = (): void => {\n if (this.stopped || this.fallback) return;\n this.releaseWorker();\n this.currentBooks.clear();\n this.latest.clear();\n this.recovering.clear();\n this.diagnosticRevisions.clear();\n this.deliveredRevisions.clear();\n this.resolvedRevisions.clear();\n this.online = false;\n this.handlers.onConnectionStateChange?.(false);\n this.fallback = new AggWebSocket({ ...this.config, callbacks: this.fallbackCallbacks() });\n for (const reject of this.historyFailures) reject(new Error(\"Market-data worker failed\"));\n this.historyFailures.clear();\n this.resolveFailure?.();\n this.syncFallback();\n };\n}\n\nexport function createWorkerWebSocket(\n options: AggWebSocketOptions,\n factory: MarketDataWorkerFactory,\n): AggWebSocket {\n // Raw trade callbacks keep the original lossless synchronous delivery contract.\n // View consumers explicitly opt into bounded presentation batches via onMarketView.\n if (options.callbacks?.onTrade && !options.callbacks.onMarketView)\n return new AggWebSocket(options);\n try {\n return new WorkerAggWebSocket(options, factory);\n } catch {\n return new AggWebSocket(options);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
//#region src/candle-builder.ts
|
|
2
|
+
const INTERVAL_SECS = {
|
|
3
|
+
"1m": 60,
|
|
4
|
+
"5m": 300,
|
|
5
|
+
"1h": 3600,
|
|
6
|
+
"1d": 86400
|
|
7
|
+
};
|
|
8
|
+
const ALL_INTERVALS = [
|
|
9
|
+
"1m",
|
|
10
|
+
"5m",
|
|
11
|
+
"1h",
|
|
12
|
+
"1d"
|
|
13
|
+
];
|
|
14
|
+
const MAX_CLOSED = 500;
|
|
15
|
+
/**
|
|
16
|
+
* Accumulates OHLCV candles from orderbook delta midpoints and trades.
|
|
17
|
+
* All 4 intervals computed simultaneously. onChange batched via rAF (~16ms).
|
|
18
|
+
*/
|
|
19
|
+
var CandleBuilder = class {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.forming = /* @__PURE__ */ new Map();
|
|
22
|
+
this.closedMap = /* @__PURE__ */ new Map();
|
|
23
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
24
|
+
this._midpoint = null;
|
|
25
|
+
this.dirty = false;
|
|
26
|
+
this.scheduled = false;
|
|
27
|
+
for (const interval of ALL_INTERVALS) this.closedMap.set(interval, []);
|
|
28
|
+
}
|
|
29
|
+
/** Feed a midpoint (from orderbook delta). Updates O/H/L/C for all intervals. */
|
|
30
|
+
addMidpoint(midpoint, timestampSecs) {
|
|
31
|
+
if (!Number.isFinite(midpoint) || !Number.isFinite(timestampSecs)) return;
|
|
32
|
+
this._midpoint = midpoint;
|
|
33
|
+
for (const interval of ALL_INTERVALS) this.updateCandle(interval, midpoint, timestampSecs);
|
|
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)) return;
|
|
39
|
+
for (const interval of ALL_INTERVALS) this.updateCandle(interval, price, timestampSecs, size);
|
|
40
|
+
this.markDirty();
|
|
41
|
+
}
|
|
42
|
+
/** Current forming candle for an interval. */
|
|
43
|
+
getForming(interval) {
|
|
44
|
+
return this.forming.get(interval) ?? null;
|
|
45
|
+
}
|
|
46
|
+
/** Closed candles for an interval (ring buffer, max 500, newest last). */
|
|
47
|
+
getClosed(interval) {
|
|
48
|
+
return this.closedMap.get(interval) ?? [];
|
|
49
|
+
}
|
|
50
|
+
/** Apply a bounded Worker view without recomputing candles from presentation samples. */
|
|
51
|
+
replaceTail(tail) {
|
|
52
|
+
for (const interval of ALL_INTERVALS) {
|
|
53
|
+
const view = tail[interval];
|
|
54
|
+
if (view.forming) this.forming.set(interval, { ...view.forming });
|
|
55
|
+
else this.forming.delete(interval);
|
|
56
|
+
if (!view.closed.length) continue;
|
|
57
|
+
const closed = new Map(this.getClosed(interval).map((candle) => [candle.openTime, candle]));
|
|
58
|
+
for (const candle of view.closed) closed.set(candle.openTime, { ...candle });
|
|
59
|
+
this.closedMap.set(interval, [...closed.values()].sort((a, b) => a.openTime - b.openTime).slice(-500));
|
|
60
|
+
}
|
|
61
|
+
this._midpoint = tail["1m"].forming?.c ?? null;
|
|
62
|
+
this.markDirty();
|
|
63
|
+
}
|
|
64
|
+
/** Current midpoint (latest value fed). */
|
|
65
|
+
get midpoint() {
|
|
66
|
+
return this._midpoint;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Subscribe to state changes. Returns unsubscribe function.
|
|
70
|
+
* BATCHED: callbacks fire at most once per rAF frame (~16ms).
|
|
71
|
+
* In non-browser environments, falls back to 16ms setTimeout.
|
|
72
|
+
*/
|
|
73
|
+
onChange(listener) {
|
|
74
|
+
this.listeners.add(listener);
|
|
75
|
+
return () => {
|
|
76
|
+
this.listeners.delete(listener);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Clear all state (on reconnect). */
|
|
80
|
+
reset() {
|
|
81
|
+
this.forming.clear();
|
|
82
|
+
for (const interval of ALL_INTERVALS) this.closedMap.set(interval, []);
|
|
83
|
+
this._midpoint = null;
|
|
84
|
+
this.dirty = false;
|
|
85
|
+
}
|
|
86
|
+
/** Clear all state AND unsubscribe all listeners. Called on store eviction. */
|
|
87
|
+
dispose() {
|
|
88
|
+
this.reset();
|
|
89
|
+
this.listeners.clear();
|
|
90
|
+
}
|
|
91
|
+
updateCandle(interval, price, timestampSecs, tradeSize) {
|
|
92
|
+
const secs = INTERVAL_SECS[interval];
|
|
93
|
+
const openTime = Math.floor(timestampSecs / secs) * secs;
|
|
94
|
+
const current = this.forming.get(interval);
|
|
95
|
+
if (current && current.openTime !== openTime) {
|
|
96
|
+
const closed = this.closedMap.get(interval);
|
|
97
|
+
closed.push({
|
|
98
|
+
...current,
|
|
99
|
+
isFinal: true
|
|
100
|
+
});
|
|
101
|
+
if (closed.length > MAX_CLOSED) closed.shift();
|
|
102
|
+
this.forming.set(interval, {
|
|
103
|
+
openTime,
|
|
104
|
+
o: price,
|
|
105
|
+
h: price,
|
|
106
|
+
l: price,
|
|
107
|
+
c: price,
|
|
108
|
+
v: tradeSize ?? 0,
|
|
109
|
+
tradeCount: tradeSize != null ? 1 : 0,
|
|
110
|
+
isFinal: false
|
|
111
|
+
});
|
|
112
|
+
} else if (!current) this.forming.set(interval, {
|
|
113
|
+
openTime,
|
|
114
|
+
o: price,
|
|
115
|
+
h: price,
|
|
116
|
+
l: price,
|
|
117
|
+
c: price,
|
|
118
|
+
v: tradeSize ?? 0,
|
|
119
|
+
tradeCount: tradeSize != null ? 1 : 0,
|
|
120
|
+
isFinal: false
|
|
121
|
+
});
|
|
122
|
+
else {
|
|
123
|
+
current.h = Math.max(current.h, price);
|
|
124
|
+
current.l = Math.min(current.l, price);
|
|
125
|
+
current.c = price;
|
|
126
|
+
if (tradeSize != null) {
|
|
127
|
+
current.v += tradeSize;
|
|
128
|
+
current.tradeCount += 1;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
markDirty() {
|
|
133
|
+
this.dirty = true;
|
|
134
|
+
if (this.scheduled) return;
|
|
135
|
+
this.scheduled = true;
|
|
136
|
+
(typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 16))(() => {
|
|
137
|
+
this.scheduled = false;
|
|
138
|
+
if (this.dirty) {
|
|
139
|
+
this.dirty = false;
|
|
140
|
+
this.listeners.forEach((l) => l());
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
//#endregion
|
|
146
|
+
export { CandleBuilder as t };
|
|
147
|
+
|
|
148
|
+
//# sourceMappingURL=candle-builder-BNewIe9K.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"candle-builder-BNewIe9K.mjs","names":[],"sources":["../src/candle-builder.ts"],"sourcesContent":["export type CandleInterval = \"1m\" | \"5m\" | \"1h\" | \"1d\";\n\nconst INTERVAL_SECS: Record<CandleInterval, number> = {\n \"1m\": 60,\n \"5m\": 300,\n \"1h\": 3600,\n \"1d\": 86400,\n};\n\nconst ALL_INTERVALS: CandleInterval[] = [\"1m\", \"5m\", \"1h\", \"1d\"];\nconst MAX_CLOSED = 500;\n\n/** Universal OHLCV candle. Chart-library-agnostic. */\nexport interface Candle {\n /** Interval-aligned open time in seconds. */\n openTime: number;\n o: number;\n h: number;\n l: number;\n c: number;\n /** Accumulated trade volume. 0 if no trades in interval. */\n v: number;\n tradeCount: number;\n isFinal: boolean;\n}\n\n/**\n * Accumulates OHLCV candles from orderbook delta midpoints and trades.\n * All 4 intervals computed simultaneously. onChange batched via rAF (~16ms).\n */\nexport class CandleBuilder {\n private forming = new Map<CandleInterval, Candle>();\n private closedMap = new Map<CandleInterval, Candle[]>();\n private listeners = new Set<() => void>();\n private _midpoint: number | null = null;\n private dirty = false;\n private scheduled = false;\n\n constructor() {\n for (const interval of ALL_INTERVALS) {\n this.closedMap.set(interval, []);\n }\n }\n\n /** Feed a midpoint (from orderbook delta). Updates O/H/L/C for all intervals. */\n addMidpoint(midpoint: number, timestampSecs: number): void {\n if (!Number.isFinite(midpoint) || !Number.isFinite(timestampSecs)) return;\n this._midpoint = midpoint;\n for (const interval of ALL_INTERVALS) {\n this.updateCandle(interval, midpoint, timestampSecs);\n }\n this.markDirty();\n }\n\n /** Feed a trade. Updates volume + refines H/L/C from trade price. */\n addTrade(price: number, size: number, timestampSecs: number): void {\n if (!Number.isFinite(price) || !Number.isFinite(size) || !Number.isFinite(timestampSecs))\n return;\n for (const interval of ALL_INTERVALS) {\n this.updateCandle(interval, price, timestampSecs, size);\n }\n this.markDirty();\n }\n\n /** Current forming candle for an interval. */\n getForming(interval: CandleInterval): Candle | null {\n return this.forming.get(interval) ?? null;\n }\n\n /** Closed candles for an interval (ring buffer, max 500, newest last). */\n getClosed(interval: CandleInterval): Candle[] {\n return this.closedMap.get(interval) ?? [];\n }\n\n /** Apply a bounded Worker view without recomputing candles from presentation samples. */\n replaceTail(tail: Record<CandleInterval, { forming: Candle | null; closed: Candle[] }>): void {\n for (const interval of ALL_INTERVALS) {\n const view = tail[interval];\n if (view.forming) this.forming.set(interval, { ...view.forming });\n else this.forming.delete(interval);\n if (!view.closed.length) continue;\n const closed = new Map(this.getClosed(interval).map((candle) => [candle.openTime, candle]));\n for (const candle of view.closed) closed.set(candle.openTime, { ...candle });\n this.closedMap.set(\n interval,\n [...closed.values()].sort((a, b) => a.openTime - b.openTime).slice(-MAX_CLOSED),\n );\n }\n this._midpoint = tail[\"1m\"].forming?.c ?? null;\n this.markDirty();\n }\n\n /** Current midpoint (latest value fed). */\n get midpoint(): number | null {\n return this._midpoint;\n }\n\n /**\n * Subscribe to state changes. Returns unsubscribe function.\n * BATCHED: callbacks fire at most once per rAF frame (~16ms).\n * In non-browser environments, falls back to 16ms setTimeout.\n */\n onChange(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /** Clear all state (on reconnect). */\n reset(): void {\n this.forming.clear();\n for (const interval of ALL_INTERVALS) {\n this.closedMap.set(interval, []);\n }\n this._midpoint = null;\n this.dirty = false;\n }\n\n /** Clear all state AND unsubscribe all listeners. Called on store eviction. */\n dispose(): void {\n this.reset();\n this.listeners.clear();\n }\n\n private updateCandle(\n interval: CandleInterval,\n price: number,\n timestampSecs: number,\n tradeSize?: number,\n ): void {\n const secs = INTERVAL_SECS[interval];\n const openTime = Math.floor(timestampSecs / secs) * secs;\n const current = this.forming.get(interval);\n\n if (current && current.openTime !== openTime) {\n // New interval bucket — close the old candle\n const closed = this.closedMap.get(interval)!;\n closed.push({ ...current, isFinal: true });\n if (closed.length > MAX_CLOSED) closed.shift();\n this.forming.set(interval, {\n openTime,\n o: price,\n h: price,\n l: price,\n c: price,\n v: tradeSize ?? 0,\n tradeCount: tradeSize != null ? 1 : 0,\n isFinal: false,\n });\n } else if (!current) {\n this.forming.set(interval, {\n openTime,\n o: price,\n h: price,\n l: price,\n c: price,\n v: tradeSize ?? 0,\n tradeCount: tradeSize != null ? 1 : 0,\n isFinal: false,\n });\n } else {\n current.h = Math.max(current.h, price);\n current.l = Math.min(current.l, price);\n current.c = price;\n if (tradeSize != null) {\n current.v += tradeSize;\n current.tradeCount += 1;\n }\n }\n }\n\n private markDirty(): void {\n this.dirty = true;\n if (this.scheduled) return;\n this.scheduled = true;\n const tick =\n typeof requestAnimationFrame === \"function\"\n ? requestAnimationFrame\n : (fn: () => void) => setTimeout(fn, 16);\n tick(() => {\n this.scheduled = false;\n if (this.dirty) {\n this.dirty = false;\n this.listeners.forEach((l) => l());\n }\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
//#region src/candle-builder.ts
|
|
2
|
+
const INTERVAL_SECS = {
|
|
3
|
+
"1m": 60,
|
|
4
|
+
"5m": 300,
|
|
5
|
+
"1h": 3600,
|
|
6
|
+
"1d": 86400
|
|
7
|
+
};
|
|
8
|
+
const ALL_INTERVALS = [
|
|
9
|
+
"1m",
|
|
10
|
+
"5m",
|
|
11
|
+
"1h",
|
|
12
|
+
"1d"
|
|
13
|
+
];
|
|
14
|
+
const MAX_CLOSED = 500;
|
|
15
|
+
/**
|
|
16
|
+
* Accumulates OHLCV candles from orderbook delta midpoints and trades.
|
|
17
|
+
* All 4 intervals computed simultaneously. onChange batched via rAF (~16ms).
|
|
18
|
+
*/
|
|
19
|
+
var CandleBuilder = class {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.forming = /* @__PURE__ */ new Map();
|
|
22
|
+
this.closedMap = /* @__PURE__ */ new Map();
|
|
23
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
24
|
+
this._midpoint = null;
|
|
25
|
+
this.dirty = false;
|
|
26
|
+
this.scheduled = false;
|
|
27
|
+
for (const interval of ALL_INTERVALS) this.closedMap.set(interval, []);
|
|
28
|
+
}
|
|
29
|
+
/** Feed a midpoint (from orderbook delta). Updates O/H/L/C for all intervals. */
|
|
30
|
+
addMidpoint(midpoint, timestampSecs) {
|
|
31
|
+
if (!Number.isFinite(midpoint) || !Number.isFinite(timestampSecs)) return;
|
|
32
|
+
this._midpoint = midpoint;
|
|
33
|
+
for (const interval of ALL_INTERVALS) this.updateCandle(interval, midpoint, timestampSecs);
|
|
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)) return;
|
|
39
|
+
for (const interval of ALL_INTERVALS) this.updateCandle(interval, price, timestampSecs, size);
|
|
40
|
+
this.markDirty();
|
|
41
|
+
}
|
|
42
|
+
/** Current forming candle for an interval. */
|
|
43
|
+
getForming(interval) {
|
|
44
|
+
return this.forming.get(interval) ?? null;
|
|
45
|
+
}
|
|
46
|
+
/** Closed candles for an interval (ring buffer, max 500, newest last). */
|
|
47
|
+
getClosed(interval) {
|
|
48
|
+
return this.closedMap.get(interval) ?? [];
|
|
49
|
+
}
|
|
50
|
+
/** Apply a bounded Worker view without recomputing candles from presentation samples. */
|
|
51
|
+
replaceTail(tail) {
|
|
52
|
+
for (const interval of ALL_INTERVALS) {
|
|
53
|
+
const view = tail[interval];
|
|
54
|
+
if (view.forming) this.forming.set(interval, { ...view.forming });
|
|
55
|
+
else this.forming.delete(interval);
|
|
56
|
+
if (!view.closed.length) continue;
|
|
57
|
+
const closed = new Map(this.getClosed(interval).map((candle) => [candle.openTime, candle]));
|
|
58
|
+
for (const candle of view.closed) closed.set(candle.openTime, { ...candle });
|
|
59
|
+
this.closedMap.set(interval, [...closed.values()].sort((a, b) => a.openTime - b.openTime).slice(-500));
|
|
60
|
+
}
|
|
61
|
+
this._midpoint = tail["1m"].forming?.c ?? null;
|
|
62
|
+
this.markDirty();
|
|
63
|
+
}
|
|
64
|
+
/** Current midpoint (latest value fed). */
|
|
65
|
+
get midpoint() {
|
|
66
|
+
return this._midpoint;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Subscribe to state changes. Returns unsubscribe function.
|
|
70
|
+
* BATCHED: callbacks fire at most once per rAF frame (~16ms).
|
|
71
|
+
* In non-browser environments, falls back to 16ms setTimeout.
|
|
72
|
+
*/
|
|
73
|
+
onChange(listener) {
|
|
74
|
+
this.listeners.add(listener);
|
|
75
|
+
return () => {
|
|
76
|
+
this.listeners.delete(listener);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Clear all state (on reconnect). */
|
|
80
|
+
reset() {
|
|
81
|
+
this.forming.clear();
|
|
82
|
+
for (const interval of ALL_INTERVALS) this.closedMap.set(interval, []);
|
|
83
|
+
this._midpoint = null;
|
|
84
|
+
this.dirty = false;
|
|
85
|
+
}
|
|
86
|
+
/** Clear all state AND unsubscribe all listeners. Called on store eviction. */
|
|
87
|
+
dispose() {
|
|
88
|
+
this.reset();
|
|
89
|
+
this.listeners.clear();
|
|
90
|
+
}
|
|
91
|
+
updateCandle(interval, price, timestampSecs, tradeSize) {
|
|
92
|
+
const secs = INTERVAL_SECS[interval];
|
|
93
|
+
const openTime = Math.floor(timestampSecs / secs) * secs;
|
|
94
|
+
const current = this.forming.get(interval);
|
|
95
|
+
if (current && current.openTime !== openTime) {
|
|
96
|
+
const closed = this.closedMap.get(interval);
|
|
97
|
+
closed.push({
|
|
98
|
+
...current,
|
|
99
|
+
isFinal: true
|
|
100
|
+
});
|
|
101
|
+
if (closed.length > MAX_CLOSED) closed.shift();
|
|
102
|
+
this.forming.set(interval, {
|
|
103
|
+
openTime,
|
|
104
|
+
o: price,
|
|
105
|
+
h: price,
|
|
106
|
+
l: price,
|
|
107
|
+
c: price,
|
|
108
|
+
v: tradeSize ?? 0,
|
|
109
|
+
tradeCount: tradeSize != null ? 1 : 0,
|
|
110
|
+
isFinal: false
|
|
111
|
+
});
|
|
112
|
+
} else if (!current) this.forming.set(interval, {
|
|
113
|
+
openTime,
|
|
114
|
+
o: price,
|
|
115
|
+
h: price,
|
|
116
|
+
l: price,
|
|
117
|
+
c: price,
|
|
118
|
+
v: tradeSize ?? 0,
|
|
119
|
+
tradeCount: tradeSize != null ? 1 : 0,
|
|
120
|
+
isFinal: false
|
|
121
|
+
});
|
|
122
|
+
else {
|
|
123
|
+
current.h = Math.max(current.h, price);
|
|
124
|
+
current.l = Math.min(current.l, price);
|
|
125
|
+
current.c = price;
|
|
126
|
+
if (tradeSize != null) {
|
|
127
|
+
current.v += tradeSize;
|
|
128
|
+
current.tradeCount += 1;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
markDirty() {
|
|
133
|
+
this.dirty = true;
|
|
134
|
+
if (this.scheduled) return;
|
|
135
|
+
this.scheduled = true;
|
|
136
|
+
(typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 16))(() => {
|
|
137
|
+
this.scheduled = false;
|
|
138
|
+
if (this.dirty) {
|
|
139
|
+
this.dirty = false;
|
|
140
|
+
this.listeners.forEach((l) => l());
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
//#endregion
|
|
146
|
+
Object.defineProperty(exports, "CandleBuilder", {
|
|
147
|
+
enumerable: true,
|
|
148
|
+
get: function() {
|
|
149
|
+
return CandleBuilder;
|
|
150
|
+
}
|
|
151
|
+
});
|