@tradejs/cli 2.0.12 → 2.0.14

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,326 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/lib/hyperliquidWhaleStream.ts
21
+ var hyperliquidWhaleStream_exports = {};
22
+ __export(hyperliquidWhaleStream_exports, {
23
+ parseHyperliquidTradesMessage: () => parseHyperliquidTradesMessage,
24
+ parseHyperliquidUserFillsMessage: () => parseHyperliquidUserFillsMessage,
25
+ runHyperliquidWhaleStream: () => runHyperliquidWhaleStream
26
+ });
27
+ module.exports = __toCommonJS(hyperliquidWhaleStream_exports);
28
+ var import_ws = require("ws");
29
+ var import_timescale = require("@tradejs/infra/timescale");
30
+ var import_strategies = require("@tradejs/node/strategies");
31
+
32
+ // src/lib/hyperliquidWhaleData.ts
33
+ var HYPERLIQUID_WHALE_BUCKET_MS = 6e4;
34
+ var finiteNumber = (value) => {
35
+ const numeric = typeof value === "number" ? value : Number(value);
36
+ return Number.isFinite(numeric) ? numeric : null;
37
+ };
38
+ var POSITION_EPSILON = 1e-12;
39
+ var normalizePosition = (value) => Math.abs(value) <= POSITION_EPSILON ? 0 : value;
40
+ var classifyHyperliquidPositionChange = (params) => {
41
+ const startValue = finiteNumber(params.startPosition);
42
+ const size = finiteNumber(params.size);
43
+ const side = String(params.side ?? "").toUpperCase();
44
+ if (startValue == null || size == null || size <= 0) return null;
45
+ if (side !== "B" && side !== "A") return null;
46
+ const startPosition = normalizePosition(startValue);
47
+ const endPosition = normalizePosition(
48
+ startPosition + (side === "B" ? size : -size)
49
+ );
50
+ const startLong = Math.max(startPosition, 0);
51
+ const endLong = Math.max(endPosition, 0);
52
+ const startShort = Math.max(-startPosition, 0);
53
+ const endShort = Math.max(-endPosition, 0);
54
+ const longEntrySize = Math.max(0, endLong - startLong);
55
+ const shortEntrySize = Math.max(0, endShort - startShort);
56
+ const longExitSize = Math.max(0, startLong - endLong);
57
+ const shortExitSize = Math.max(0, startShort - endShort);
58
+ const flipped = startPosition * endPosition < 0;
59
+ const opened = startPosition === 0;
60
+ const closed = endPosition === 0;
61
+ const increased = Math.sign(startPosition) === Math.sign(endPosition) && Math.abs(endPosition) > Math.abs(startPosition);
62
+ return {
63
+ startPosition,
64
+ endPosition,
65
+ action: flipped ? "flip" : opened ? "open" : closed ? "close" : increased ? "increase" : "reduce",
66
+ longEntrySize,
67
+ shortEntrySize,
68
+ longExitSize,
69
+ shortExitSize
70
+ };
71
+ };
72
+ var normalizeAddress = (value) => typeof value === "string" && /^0x[0-9a-fA-F]{40}$/.test(value) ? value.toLowerCase() : null;
73
+ var normalizeTradeFields = (value) => {
74
+ const symbol = typeof value.coin === "string" ? value.coin.trim() : "";
75
+ const price = finiteNumber(value.px);
76
+ const size = finiteNumber(value.sz);
77
+ const timestamp = finiteNumber(value.time);
78
+ const tid = typeof value.tid === "string" || typeof value.tid === "number" ? String(value.tid) : "";
79
+ if (!symbol || price == null || size == null || timestamp == null || !tid || price <= 0 || size <= 0) {
80
+ return null;
81
+ }
82
+ return {
83
+ symbol,
84
+ price,
85
+ size,
86
+ timestamp,
87
+ tid,
88
+ notionalUsd: price * size
89
+ };
90
+ };
91
+ var normalizeHyperliquidUserFill = (params) => {
92
+ const normalized = normalizeTradeFields(params.fill);
93
+ if (!normalized || !params.trackedSymbols.has(normalized.symbol)) return null;
94
+ const address = normalizeAddress(params.address);
95
+ if (!address) return null;
96
+ const side = String(params.fill.side ?? "").toUpperCase();
97
+ if (side !== "B" && side !== "A") return null;
98
+ const positionChange = classifyHyperliquidPositionChange({
99
+ startPosition: params.fill.startPosition,
100
+ side,
101
+ size: normalized.size
102
+ });
103
+ const closedPnl = finiteNumber(params.fill.closedPnl);
104
+ const liquidation = params.fill.liquidation != null && typeof params.fill.liquidation === "object";
105
+ const positionFields = positionChange ? side === "B" ? {
106
+ buyerStartPosition: positionChange.startPosition,
107
+ buyerEndPosition: positionChange.endPosition,
108
+ buyerPositionAction: positionChange.action,
109
+ buyerClosedPnl: closedPnl,
110
+ buyerLiquidation: liquidation
111
+ } : {
112
+ sellerStartPosition: positionChange.startPosition,
113
+ sellerEndPosition: positionChange.endPosition,
114
+ sellerPositionAction: positionChange.action,
115
+ sellerClosedPnl: closedPnl,
116
+ sellerLiquidation: liquidation
117
+ } : {};
118
+ return {
119
+ symbol: normalized.symbol,
120
+ ts: new Date(normalized.timestamp),
121
+ tid: normalized.tid,
122
+ price: normalized.price,
123
+ size: normalized.size,
124
+ notionalUsd: normalized.notionalUsd,
125
+ buyerAddress: side === "B" ? address : null,
126
+ sellerAddress: side === "A" ? address : null,
127
+ buyerTracked: side === "B",
128
+ sellerTracked: side === "A",
129
+ ...positionFields,
130
+ ...params.identity,
131
+ source: "hyperliquid_user_fills"
132
+ };
133
+ };
134
+
135
+ // src/lib/hyperliquidWhaleStream.ts
136
+ var DEFAULT_WS_URL = "wss://api.hyperliquid.xyz/ws";
137
+ var EVENT_BATCH_SIZE = 500;
138
+ var FLUSH_INTERVAL_MS = 2e3;
139
+ var FINALIZE_INTERVAL_MS = 15e3;
140
+ var FINALIZE_LAG_MS = 5e3;
141
+ var REBUILD_LOOKBACK_MS = 10 * 6e4;
142
+ var RAW_EVENT_RETENTION_MS = 48 * 60 * 6e4;
143
+ var sleep = (ms, signal) => new Promise((resolve) => {
144
+ if (signal.aborted) return resolve();
145
+ const timer = setTimeout(resolve, ms);
146
+ signal.addEventListener(
147
+ "abort",
148
+ () => {
149
+ clearTimeout(timer);
150
+ resolve();
151
+ },
152
+ { once: true }
153
+ );
154
+ });
155
+ var parseHyperliquidTradesMessage = (raw) => {
156
+ try {
157
+ const message = JSON.parse(raw);
158
+ return message.channel === "trades" && Array.isArray(message.data) ? message.data : [];
159
+ } catch {
160
+ return [];
161
+ }
162
+ };
163
+ var parseHyperliquidUserFillsMessage = (raw) => {
164
+ try {
165
+ const message = JSON.parse(raw);
166
+ if (message.channel !== "userFills" || !message.data || typeof message.data !== "object" || Array.isArray(message.data)) {
167
+ return null;
168
+ }
169
+ const data = message.data;
170
+ if (typeof data.user !== "string" || !Array.isArray(data.fills)) {
171
+ return null;
172
+ }
173
+ return {
174
+ address: data.user,
175
+ isSnapshot: data.isSnapshot === true,
176
+ fills: data.fills
177
+ };
178
+ } catch {
179
+ return null;
180
+ }
181
+ };
182
+ var runHyperliquidWhaleStream = async (params) => {
183
+ const universe = (0, import_strategies.getHyperliquidPerpUniverseSnapshot)();
184
+ const whales = (0, import_strategies.getHyperliquidWhaleRegistrySnapshot)();
185
+ const trackedSymbols = new Set(universe.symbols);
186
+ const identity = {
187
+ universeFingerprint: universe.fingerprint,
188
+ whaleRegistryFingerprint: whales.fingerprint
189
+ };
190
+ const buffer = [];
191
+ let writes = Promise.resolve();
192
+ let streamCoverageFromMs = null;
193
+ let streamCoverageToMs = 0;
194
+ const flush = () => {
195
+ if (!buffer.length) return writes;
196
+ const batch = buffer.splice(0, buffer.length);
197
+ writes = writes.then(() => (0, import_timescale.upsertHyperliquidWhaleTradeEvents)(batch));
198
+ return writes;
199
+ };
200
+ const recordStreamCoverage = (toMs) => {
201
+ if (streamCoverageFromMs == null) return writes;
202
+ const fromMs = Math.max(streamCoverageFromMs, streamCoverageToMs);
203
+ if (toMs <= fromMs) return writes;
204
+ const rows = Array.from(
205
+ { length: Math.floor((toMs - fromMs) / HYPERLIQUID_WHALE_BUCKET_MS) },
206
+ (_, index) => ({
207
+ ts: new Date(fromMs + index * HYPERLIQUID_WHALE_BUCKET_MS),
208
+ coveredWhales: whales.addresses.length,
209
+ expectedWhales: whales.addresses.length,
210
+ coveragePct: 1,
211
+ source: "hyperliquid_user_fills_ws",
212
+ ...identity
213
+ })
214
+ );
215
+ streamCoverageToMs = toMs;
216
+ writes = writes.then(() => (0, import_timescale.upsertHyperliquidWhaleCoverageRows)(rows));
217
+ return writes;
218
+ };
219
+ const finalize = () => {
220
+ const toMs = Math.floor((Date.now() - FINALIZE_LAG_MS) / HYPERLIQUID_WHALE_BUCKET_MS) * HYPERLIQUID_WHALE_BUCKET_MS;
221
+ const fromMs = Math.max(0, toMs - REBUILD_LOOKBACK_MS);
222
+ recordStreamCoverage(toMs);
223
+ writes = writes.then(
224
+ () => (0, import_timescale.rebuildHyperliquidWhaleFlowRows)({
225
+ fromMs,
226
+ toMs,
227
+ deleteEventsBeforeMs: toMs - RAW_EVENT_RETENTION_MS,
228
+ ...identity
229
+ }).then((buckets) => {
230
+ params.log?.(`Hyperliquid whale-flow finalized buckets=${buckets}`);
231
+ })
232
+ );
233
+ return writes;
234
+ };
235
+ const flushTimer = setInterval(() => void flush(), FLUSH_INTERVAL_MS);
236
+ const finalizeTimer = setInterval(
237
+ () => void flush().then(finalize),
238
+ FINALIZE_INTERVAL_MS
239
+ );
240
+ let reconnectDelayMs = 1e3;
241
+ try {
242
+ while (!params.signal.aborted) {
243
+ await new Promise((resolve) => {
244
+ const ws = new import_ws.WebSocket(
245
+ process.env.HYPERLIQUID_WS_URL?.trim() || DEFAULT_WS_URL
246
+ );
247
+ let settled = false;
248
+ const finish = () => {
249
+ if (settled) return;
250
+ settled = true;
251
+ resolve();
252
+ };
253
+ const pingTimer = setInterval(() => {
254
+ if (ws.readyState === import_ws.WebSocket.OPEN) {
255
+ ws.send(JSON.stringify({ method: "ping" }));
256
+ }
257
+ }, 3e4);
258
+ const close = () => {
259
+ clearInterval(pingTimer);
260
+ if (ws.readyState === import_ws.WebSocket.OPEN) ws.close();
261
+ else if (ws.readyState === import_ws.WebSocket.CONNECTING) ws.terminate();
262
+ finish();
263
+ };
264
+ params.signal.addEventListener("abort", close, { once: true });
265
+ ws.on("open", () => {
266
+ reconnectDelayMs = 1e3;
267
+ streamCoverageFromMs = Math.ceil(Date.now() / HYPERLIQUID_WHALE_BUCKET_MS) * HYPERLIQUID_WHALE_BUCKET_MS;
268
+ streamCoverageToMs = streamCoverageFromMs;
269
+ for (const address of whales.addresses) {
270
+ ws.send(
271
+ JSON.stringify({
272
+ method: "subscribe",
273
+ subscription: { type: "userFills", user: address }
274
+ })
275
+ );
276
+ }
277
+ params.log?.(
278
+ `Hyperliquid whale stream connected userFills subscriptions=${whales.addresses.length}`
279
+ );
280
+ });
281
+ ws.on("message", (data) => {
282
+ const message = parseHyperliquidUserFillsMessage(data.toString());
283
+ if (!message) return;
284
+ for (const fill of message.fills) {
285
+ const event = normalizeHyperliquidUserFill({
286
+ fill,
287
+ address: message.address,
288
+ trackedSymbols,
289
+ identity
290
+ });
291
+ if (event) buffer.push(event);
292
+ }
293
+ if (buffer.length >= EVENT_BATCH_SIZE) void flush();
294
+ });
295
+ ws.on("error", (error) => {
296
+ params.log?.(`Hyperliquid whale stream error: ${String(error)}`);
297
+ close();
298
+ });
299
+ ws.on("close", () => {
300
+ recordStreamCoverage(
301
+ Math.floor(Date.now() / HYPERLIQUID_WHALE_BUCKET_MS) * HYPERLIQUID_WHALE_BUCKET_MS
302
+ );
303
+ streamCoverageFromMs = null;
304
+ clearInterval(pingTimer);
305
+ params.signal.removeEventListener("abort", close);
306
+ finish();
307
+ });
308
+ });
309
+ if (!params.signal.aborted) {
310
+ await sleep(reconnectDelayMs, params.signal);
311
+ reconnectDelayMs = Math.min(3e4, reconnectDelayMs * 2);
312
+ }
313
+ }
314
+ } finally {
315
+ clearInterval(flushTimer);
316
+ clearInterval(finalizeTimer);
317
+ await flush();
318
+ await finalize();
319
+ }
320
+ };
321
+ // Annotate the CommonJS export names for ESM import in node:
322
+ 0 && (module.exports = {
323
+ parseHyperliquidTradesMessage,
324
+ parseHyperliquidUserFillsMessage,
325
+ runHyperliquidWhaleStream
326
+ });