@tradejs/infra 2.0.21 → 3.0.1

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 (55) hide show
  1. package/dist/{chunk-YVIHTUV5.mjs → chunk-OLB6NQRG.mjs} +1 -1
  2. package/dist/{chunk-I2J6YDBD.mjs → chunk-ZXU7OYFU.mjs} +65 -38
  3. package/dist/internal-BuHqoCCV.d.mts +9 -0
  4. package/dist/internal-q48Torp4.d.ts +9 -0
  5. package/dist/ml.mjs +3 -3
  6. package/dist/query-tkHVD4Xo.d.mts +12 -0
  7. package/dist/query-tkHVD4Xo.d.ts +12 -0
  8. package/dist/timescale/candles.js +6 -4
  9. package/dist/timescale/candles.mjs +2 -2
  10. package/dist/timescale/client.d.mts +3 -2
  11. package/dist/timescale/client.d.ts +3 -2
  12. package/dist/timescale/client.js +40 -15
  13. package/dist/timescale/client.mjs +2 -2
  14. package/dist/timescale/derivatives.d.mts +4 -2
  15. package/dist/timescale/derivatives.d.ts +4 -2
  16. package/dist/timescale/derivatives.js +20 -12
  17. package/dist/timescale/derivatives.mjs +479 -13
  18. package/dist/timescale/hyperliquidWhales.d.mts +29 -25
  19. package/dist/timescale/hyperliquidWhales.d.ts +29 -25
  20. package/dist/timescale/hyperliquidWhales.js +84 -71
  21. package/dist/timescale/hyperliquidWhales.mjs +1103 -14
  22. package/dist/timescale/marketContext.d.mts +15 -11
  23. package/dist/timescale/marketContext.d.ts +15 -11
  24. package/dist/timescale/marketContext.js +67 -57
  25. package/dist/timescale/marketContext.mjs +1259 -28
  26. package/dist/timescale/spread.js +6 -4
  27. package/dist/timescale/spread.mjs +105 -5
  28. package/dist/userSettings.js +3 -316
  29. package/dist/userSettings.mjs +3 -21
  30. package/package.json +2 -22
  31. package/dist/aiEndpoints.d.mts +0 -10
  32. package/dist/aiEndpoints.d.ts +0 -10
  33. package/dist/aiEndpoints.js +0 -159
  34. package/dist/aiEndpoints.mjs +0 -12
  35. package/dist/aiLanguages.d.mts +0 -11
  36. package/dist/aiLanguages.d.ts +0 -11
  37. package/dist/aiLanguages.js +0 -71
  38. package/dist/aiLanguages.mjs +0 -12
  39. package/dist/aiModels.d.mts +0 -12
  40. package/dist/aiModels.d.ts +0 -12
  41. package/dist/aiModels.js +0 -272
  42. package/dist/aiModels.mjs +0 -17
  43. package/dist/chunk-2CZREG43.mjs +0 -112
  44. package/dist/chunk-CCC7DX2T.mjs +0 -44
  45. package/dist/chunk-DFMKDB2R.mjs +0 -1285
  46. package/dist/chunk-DTCLZIBM.mjs +0 -163
  47. package/dist/chunk-NWXFWTWU.mjs +0 -1114
  48. package/dist/chunk-SZQB7ER5.mjs +0 -492
  49. package/dist/chunk-XQ3YBULV.mjs +0 -132
  50. package/dist/internal-2coHaaos.d.mts +0 -26
  51. package/dist/internal-2coHaaos.d.ts +0 -26
  52. package/dist/timescale.d.mts +0 -9
  53. package/dist/timescale.d.ts +0 -9
  54. package/dist/timescale.js +0 -4228
  55. package/dist/timescale.mjs +0 -138
@@ -1,19 +1,1108 @@
1
1
  import {
2
- getHyperliquidWhaleCoverageSeriesRows,
3
- getHyperliquidWhaleFlowAggregate,
4
- getHyperliquidWhaleFlowSeriesRows,
5
- getHyperliquidWhaleWalletCoverage,
6
- hasHyperliquidWhaleBackfillCoverage,
7
- rebuildHyperliquidWhaleCoverageRows,
8
- rebuildHyperliquidWhaleFlowRows,
9
- upsertHyperliquidWhaleCoverageRows,
10
- upsertHyperliquidWhaleFlowRows,
11
- upsertHyperliquidWhaleTradeEvents,
12
- upsertHyperliquidWhaleWalletCoverage
13
- } from "../chunk-NWXFWTWU.mjs";
2
+ ensureHyperliquidWhaleSchema,
3
+ getPool,
4
+ getSafeBulkInsertRows,
5
+ prepareMarketContextSchemaForRead,
6
+ queryMarketContext
7
+ } from "../chunk-ZXU7OYFU.mjs";
8
+
9
+ // src/timescale/hyperliquidWhales/commands.ts
10
+ import {
11
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION
12
+ } from "@tradejs/types";
13
+ async function upsertHyperliquidWhaleTradeEvents(rows) {
14
+ if (!rows.length) return;
15
+ await ensureHyperliquidWhaleSchema();
16
+ const cols = [
17
+ "symbol",
18
+ "ts",
19
+ "tid",
20
+ "price",
21
+ "size",
22
+ "notional_usd",
23
+ "buyer_address",
24
+ "seller_address",
25
+ "buyer_tracked",
26
+ "seller_tracked",
27
+ "buyer_start_position",
28
+ "buyer_end_position",
29
+ "buyer_position_action",
30
+ "buyer_closed_pnl",
31
+ "buyer_liquidation",
32
+ "seller_start_position",
33
+ "seller_end_position",
34
+ "seller_position_action",
35
+ "seller_closed_pnl",
36
+ "seller_liquidation",
37
+ "universe_fingerprint",
38
+ "whale_registry_fingerprint",
39
+ "source"
40
+ ];
41
+ const maxRows = getSafeBulkInsertRows(cols.length);
42
+ if (rows.length > maxRows) {
43
+ for (let index = 0; index < rows.length; index += maxRows) {
44
+ await upsertHyperliquidWhaleTradeEvents(
45
+ rows.slice(index, index + maxRows)
46
+ );
47
+ }
48
+ return;
49
+ }
50
+ const valuesSql = rows.map(
51
+ (_, rowIndex) => `(${cols.map((__, colIndex) => `$${rowIndex * cols.length + colIndex + 1}`).join(",")})`
52
+ ).join(",");
53
+ const flat = rows.flatMap((row) => [
54
+ row.symbol,
55
+ row.ts,
56
+ row.tid,
57
+ row.price,
58
+ row.size,
59
+ row.notionalUsd,
60
+ row.buyerAddress ?? null,
61
+ row.sellerAddress ?? null,
62
+ row.buyerTracked,
63
+ row.sellerTracked,
64
+ row.buyerStartPosition ?? null,
65
+ row.buyerEndPosition ?? null,
66
+ row.buyerPositionAction ?? null,
67
+ row.buyerClosedPnl ?? null,
68
+ row.buyerLiquidation ?? null,
69
+ row.sellerStartPosition ?? null,
70
+ row.sellerEndPosition ?? null,
71
+ row.sellerPositionAction ?? null,
72
+ row.sellerClosedPnl ?? null,
73
+ row.sellerLiquidation ?? null,
74
+ row.universeFingerprint,
75
+ row.whaleRegistryFingerprint,
76
+ row.source ?? null
77
+ ]);
78
+ await getPool().query(
79
+ `
80
+ INSERT INTO hyperliquid_whale_trade_events (${cols.join(",")})
81
+ VALUES ${valuesSql}
82
+ ON CONFLICT (
83
+ universe_fingerprint,
84
+ whale_registry_fingerprint,
85
+ symbol,
86
+ ts,
87
+ tid
88
+ ) DO UPDATE SET
89
+ buyer_address = COALESCE(
90
+ hyperliquid_whale_trade_events.buyer_address,
91
+ EXCLUDED.buyer_address
92
+ ),
93
+ seller_address = COALESCE(
94
+ hyperliquid_whale_trade_events.seller_address,
95
+ EXCLUDED.seller_address
96
+ ),
97
+ buyer_tracked = hyperliquid_whale_trade_events.buyer_tracked OR EXCLUDED.buyer_tracked,
98
+ seller_tracked = hyperliquid_whale_trade_events.seller_tracked OR EXCLUDED.seller_tracked,
99
+ buyer_start_position = COALESCE(
100
+ hyperliquid_whale_trade_events.buyer_start_position,
101
+ EXCLUDED.buyer_start_position
102
+ ),
103
+ buyer_end_position = COALESCE(
104
+ hyperliquid_whale_trade_events.buyer_end_position,
105
+ EXCLUDED.buyer_end_position
106
+ ),
107
+ buyer_position_action = COALESCE(
108
+ hyperliquid_whale_trade_events.buyer_position_action,
109
+ EXCLUDED.buyer_position_action
110
+ ),
111
+ buyer_closed_pnl = COALESCE(
112
+ hyperliquid_whale_trade_events.buyer_closed_pnl,
113
+ EXCLUDED.buyer_closed_pnl
114
+ ),
115
+ buyer_liquidation = COALESCE(
116
+ hyperliquid_whale_trade_events.buyer_liquidation,
117
+ EXCLUDED.buyer_liquidation
118
+ ),
119
+ seller_start_position = COALESCE(
120
+ hyperliquid_whale_trade_events.seller_start_position,
121
+ EXCLUDED.seller_start_position
122
+ ),
123
+ seller_end_position = COALESCE(
124
+ hyperliquid_whale_trade_events.seller_end_position,
125
+ EXCLUDED.seller_end_position
126
+ ),
127
+ seller_position_action = COALESCE(
128
+ hyperliquid_whale_trade_events.seller_position_action,
129
+ EXCLUDED.seller_position_action
130
+ ),
131
+ seller_closed_pnl = COALESCE(
132
+ hyperliquid_whale_trade_events.seller_closed_pnl,
133
+ EXCLUDED.seller_closed_pnl
134
+ ),
135
+ seller_liquidation = COALESCE(
136
+ hyperliquid_whale_trade_events.seller_liquidation,
137
+ EXCLUDED.seller_liquidation
138
+ ),
139
+ source = EXCLUDED.source,
140
+ ingested_at = now()
141
+ `,
142
+ flat
143
+ );
144
+ }
145
+ async function upsertHyperliquidWhaleFlowRows(rows) {
146
+ if (!rows.length) return;
147
+ await ensureHyperliquidWhaleSchema();
148
+ const cols = [
149
+ "symbol",
150
+ "interval",
151
+ "ts",
152
+ "trades",
153
+ "whale_sides",
154
+ "unique_whales",
155
+ "whale_addresses",
156
+ "buy_notional_usd",
157
+ "sell_notional_usd",
158
+ "net_notional_usd",
159
+ "buy_share_pct",
160
+ "position_aware_whale_sides",
161
+ "long_entry_whale_addresses",
162
+ "short_entry_whale_addresses",
163
+ "long_exit_whale_addresses",
164
+ "short_exit_whale_addresses",
165
+ "long_entry_notional_usd",
166
+ "short_entry_notional_usd",
167
+ "long_exit_notional_usd",
168
+ "short_exit_notional_usd",
169
+ "entry_net_notional_usd",
170
+ "entry_long_share_pct",
171
+ "universe_fingerprint",
172
+ "whale_registry_fingerprint",
173
+ "source"
174
+ ];
175
+ const maxRows = getSafeBulkInsertRows(cols.length);
176
+ if (rows.length > maxRows) {
177
+ for (let index = 0; index < rows.length; index += maxRows) {
178
+ await upsertHyperliquidWhaleFlowRows(rows.slice(index, index + maxRows));
179
+ }
180
+ return;
181
+ }
182
+ const valuesSql = rows.map(
183
+ (_, rowIndex) => `(${cols.map((__, colIndex) => `$${rowIndex * cols.length + colIndex + 1}`).join(",")})`
184
+ ).join(",");
185
+ const flat = rows.flatMap((row) => [
186
+ row.symbol,
187
+ row.interval,
188
+ row.ts,
189
+ row.trades,
190
+ row.whaleSides,
191
+ row.uniqueWhales,
192
+ row.whaleAddresses ?? [],
193
+ row.buyNotionalUsd,
194
+ row.sellNotionalUsd,
195
+ row.netNotionalUsd,
196
+ row.buySharePct ?? null,
197
+ row.positionAwareWhaleSides,
198
+ row.longEntryWhaleAddresses ?? [],
199
+ row.shortEntryWhaleAddresses ?? [],
200
+ row.longExitWhaleAddresses ?? [],
201
+ row.shortExitWhaleAddresses ?? [],
202
+ row.longEntryNotionalUsd,
203
+ row.shortEntryNotionalUsd,
204
+ row.longExitNotionalUsd,
205
+ row.shortExitNotionalUsd,
206
+ row.entryNetNotionalUsd,
207
+ row.entryLongSharePct ?? null,
208
+ row.universeFingerprint,
209
+ row.whaleRegistryFingerprint,
210
+ row.source ?? null
211
+ ]);
212
+ await getPool().query(
213
+ `
214
+ INSERT INTO hyperliquid_whale_flow (${cols.join(",")})
215
+ VALUES ${valuesSql}
216
+ ON CONFLICT (
217
+ universe_fingerprint,
218
+ whale_registry_fingerprint,
219
+ symbol,
220
+ interval,
221
+ ts
222
+ ) DO UPDATE SET
223
+ trades = EXCLUDED.trades,
224
+ whale_sides = EXCLUDED.whale_sides,
225
+ unique_whales = EXCLUDED.unique_whales,
226
+ whale_addresses = EXCLUDED.whale_addresses,
227
+ buy_notional_usd = EXCLUDED.buy_notional_usd,
228
+ sell_notional_usd = EXCLUDED.sell_notional_usd,
229
+ net_notional_usd = EXCLUDED.net_notional_usd,
230
+ buy_share_pct = EXCLUDED.buy_share_pct,
231
+ position_aware_whale_sides = EXCLUDED.position_aware_whale_sides,
232
+ long_entry_whale_addresses = EXCLUDED.long_entry_whale_addresses,
233
+ short_entry_whale_addresses = EXCLUDED.short_entry_whale_addresses,
234
+ long_exit_whale_addresses = EXCLUDED.long_exit_whale_addresses,
235
+ short_exit_whale_addresses = EXCLUDED.short_exit_whale_addresses,
236
+ long_entry_notional_usd = EXCLUDED.long_entry_notional_usd,
237
+ short_entry_notional_usd = EXCLUDED.short_entry_notional_usd,
238
+ long_exit_notional_usd = EXCLUDED.long_exit_notional_usd,
239
+ short_exit_notional_usd = EXCLUDED.short_exit_notional_usd,
240
+ entry_net_notional_usd = EXCLUDED.entry_net_notional_usd,
241
+ entry_long_share_pct = EXCLUDED.entry_long_share_pct,
242
+ source = EXCLUDED.source,
243
+ ingested_at = now()
244
+ `,
245
+ flat
246
+ );
247
+ }
248
+ async function rebuildHyperliquidWhaleFlowRows(params) {
249
+ await ensureHyperliquidWhaleSchema();
250
+ const client = await getPool().connect();
251
+ try {
252
+ await client.query("BEGIN");
253
+ const result = await client.query(
254
+ `
255
+ WITH source_events AS (
256
+ SELECT *
257
+ FROM hyperliquid_whale_trade_events
258
+ WHERE universe_fingerprint = $1
259
+ AND whale_registry_fingerprint = $2
260
+ AND ts >= to_timestamp($3/1000.0)
261
+ AND ts < to_timestamp($4/1000.0)
262
+ ), metrics AS (
263
+ SELECT
264
+ symbol,
265
+ date_trunc('minute', ts) AS bucket_ts,
266
+ COUNT(*)::int AS trades,
267
+ SUM(buyer_tracked::int + seller_tracked::int)::int AS whale_sides,
268
+ SUM(CASE WHEN buyer_tracked THEN notional_usd ELSE 0 END) AS buy_notional_usd,
269
+ SUM(CASE WHEN seller_tracked THEN notional_usd ELSE 0 END) AS sell_notional_usd
270
+ FROM source_events
271
+ GROUP BY symbol, date_trunc('minute', ts)
272
+ ), position_legs AS (
273
+ SELECT
274
+ symbol,
275
+ ts,
276
+ price,
277
+ buyer_address AS whale_address,
278
+ buyer_start_position AS start_position,
279
+ buyer_end_position AS end_position
280
+ FROM source_events
281
+ WHERE buyer_tracked
282
+ AND buyer_address IS NOT NULL
283
+ AND buyer_start_position IS NOT NULL
284
+ AND buyer_end_position IS NOT NULL
285
+ UNION ALL
286
+ SELECT
287
+ symbol,
288
+ ts,
289
+ price,
290
+ seller_address AS whale_address,
291
+ seller_start_position AS start_position,
292
+ seller_end_position AS end_position
293
+ FROM source_events
294
+ WHERE seller_tracked
295
+ AND seller_address IS NOT NULL
296
+ AND seller_start_position IS NOT NULL
297
+ AND seller_end_position IS NOT NULL
298
+ ), classified_legs AS (
299
+ SELECT
300
+ *,
301
+ GREATEST(
302
+ GREATEST(end_position, 0) - GREATEST(start_position, 0),
303
+ 0
304
+ ) AS long_entry_size,
305
+ GREATEST(
306
+ GREATEST(-end_position, 0) - GREATEST(-start_position, 0),
307
+ 0
308
+ ) AS short_entry_size,
309
+ GREATEST(
310
+ GREATEST(start_position, 0) - GREATEST(end_position, 0),
311
+ 0
312
+ ) AS long_exit_size,
313
+ GREATEST(
314
+ GREATEST(-start_position, 0) - GREATEST(-end_position, 0),
315
+ 0
316
+ ) AS short_exit_size
317
+ FROM position_legs
318
+ ), position_metrics AS (
319
+ SELECT
320
+ symbol,
321
+ date_trunc('minute', ts) AS bucket_ts,
322
+ COUNT(*)::int AS position_aware_whale_sides,
323
+ SUM(long_entry_size * price) AS long_entry_notional_usd,
324
+ SUM(short_entry_size * price) AS short_entry_notional_usd,
325
+ SUM(long_exit_size * price) AS long_exit_notional_usd,
326
+ SUM(short_exit_size * price) AS short_exit_notional_usd,
327
+ COALESCE(
328
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
329
+ FILTER (WHERE long_entry_size > 0),
330
+ '{}'
331
+ ) AS long_entry_whale_addresses,
332
+ COALESCE(
333
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
334
+ FILTER (WHERE short_entry_size > 0),
335
+ '{}'
336
+ ) AS short_entry_whale_addresses,
337
+ COALESCE(
338
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
339
+ FILTER (WHERE long_exit_size > 0),
340
+ '{}'
341
+ ) AS long_exit_whale_addresses,
342
+ COALESCE(
343
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
344
+ FILTER (WHERE short_exit_size > 0),
345
+ '{}'
346
+ ) AS short_exit_whale_addresses
347
+ FROM classified_legs
348
+ GROUP BY symbol, date_trunc('minute', ts)
349
+ ), addresses AS (
350
+ SELECT
351
+ symbol,
352
+ date_trunc('minute', ts) AS bucket_ts,
353
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address) AS whale_addresses
354
+ FROM source_events
355
+ CROSS JOIN LATERAL UNNEST(ARRAY[
356
+ CASE WHEN buyer_tracked THEN buyer_address END,
357
+ CASE WHEN seller_tracked THEN seller_address END
358
+ ]) AS expanded(whale_address)
359
+ WHERE whale_address IS NOT NULL
360
+ GROUP BY symbol, date_trunc('minute', ts)
361
+ )
362
+ INSERT INTO hyperliquid_whale_flow (
363
+ symbol,
364
+ interval,
365
+ ts,
366
+ trades,
367
+ whale_sides,
368
+ unique_whales,
369
+ whale_addresses,
370
+ buy_notional_usd,
371
+ sell_notional_usd,
372
+ net_notional_usd,
373
+ buy_share_pct,
374
+ position_aware_whale_sides,
375
+ long_entry_whale_addresses,
376
+ short_entry_whale_addresses,
377
+ long_exit_whale_addresses,
378
+ short_exit_whale_addresses,
379
+ long_entry_notional_usd,
380
+ short_entry_notional_usd,
381
+ long_exit_notional_usd,
382
+ short_exit_notional_usd,
383
+ entry_net_notional_usd,
384
+ entry_long_share_pct,
385
+ universe_fingerprint,
386
+ whale_registry_fingerprint,
387
+ source
388
+ )
389
+ SELECT
390
+ metrics.symbol,
391
+ '1m',
392
+ metrics.bucket_ts,
393
+ metrics.trades,
394
+ metrics.whale_sides,
395
+ COALESCE(CARDINALITY(addresses.whale_addresses), 0),
396
+ COALESCE(addresses.whale_addresses, '{}'),
397
+ metrics.buy_notional_usd,
398
+ metrics.sell_notional_usd,
399
+ metrics.buy_notional_usd - metrics.sell_notional_usd,
400
+ CASE
401
+ WHEN metrics.buy_notional_usd + metrics.sell_notional_usd > 0
402
+ THEN metrics.buy_notional_usd /
403
+ (metrics.buy_notional_usd + metrics.sell_notional_usd)
404
+ ELSE NULL
405
+ END,
406
+ COALESCE(position_metrics.position_aware_whale_sides, 0),
407
+ COALESCE(position_metrics.long_entry_whale_addresses, '{}'),
408
+ COALESCE(position_metrics.short_entry_whale_addresses, '{}'),
409
+ COALESCE(position_metrics.long_exit_whale_addresses, '{}'),
410
+ COALESCE(position_metrics.short_exit_whale_addresses, '{}'),
411
+ COALESCE(position_metrics.long_entry_notional_usd, 0),
412
+ COALESCE(position_metrics.short_entry_notional_usd, 0),
413
+ COALESCE(position_metrics.long_exit_notional_usd, 0),
414
+ COALESCE(position_metrics.short_exit_notional_usd, 0),
415
+ COALESCE(position_metrics.long_entry_notional_usd, 0) -
416
+ COALESCE(position_metrics.short_entry_notional_usd, 0),
417
+ CASE
418
+ WHEN COALESCE(position_metrics.long_entry_notional_usd, 0) +
419
+ COALESCE(position_metrics.short_entry_notional_usd, 0) > 0
420
+ THEN COALESCE(position_metrics.long_entry_notional_usd, 0) /
421
+ (
422
+ COALESCE(position_metrics.long_entry_notional_usd, 0) +
423
+ COALESCE(position_metrics.short_entry_notional_usd, 0)
424
+ )
425
+ ELSE NULL
426
+ END,
427
+ $1,
428
+ $2,
429
+ CASE
430
+ WHEN COALESCE(position_metrics.position_aware_whale_sides, 0) > 0
431
+ THEN 'hyperliquid_user_fills'
432
+ ELSE 'hyperliquid_trades'
433
+ END
434
+ FROM metrics
435
+ LEFT JOIN addresses USING (symbol, bucket_ts)
436
+ LEFT JOIN position_metrics USING (symbol, bucket_ts)
437
+ ON CONFLICT (
438
+ universe_fingerprint,
439
+ whale_registry_fingerprint,
440
+ symbol,
441
+ interval,
442
+ ts
443
+ ) DO UPDATE SET
444
+ trades = EXCLUDED.trades,
445
+ whale_sides = EXCLUDED.whale_sides,
446
+ unique_whales = EXCLUDED.unique_whales,
447
+ whale_addresses = EXCLUDED.whale_addresses,
448
+ buy_notional_usd = EXCLUDED.buy_notional_usd,
449
+ sell_notional_usd = EXCLUDED.sell_notional_usd,
450
+ net_notional_usd = EXCLUDED.net_notional_usd,
451
+ buy_share_pct = EXCLUDED.buy_share_pct,
452
+ position_aware_whale_sides = EXCLUDED.position_aware_whale_sides,
453
+ long_entry_whale_addresses = EXCLUDED.long_entry_whale_addresses,
454
+ short_entry_whale_addresses = EXCLUDED.short_entry_whale_addresses,
455
+ long_exit_whale_addresses = EXCLUDED.long_exit_whale_addresses,
456
+ short_exit_whale_addresses = EXCLUDED.short_exit_whale_addresses,
457
+ long_entry_notional_usd = EXCLUDED.long_entry_notional_usd,
458
+ short_entry_notional_usd = EXCLUDED.short_entry_notional_usd,
459
+ long_exit_notional_usd = EXCLUDED.long_exit_notional_usd,
460
+ short_exit_notional_usd = EXCLUDED.short_exit_notional_usd,
461
+ entry_net_notional_usd = EXCLUDED.entry_net_notional_usd,
462
+ entry_long_share_pct = EXCLUDED.entry_long_share_pct,
463
+ source = EXCLUDED.source,
464
+ ingested_at = now()
465
+ RETURNING 1
466
+ `,
467
+ [
468
+ params.universeFingerprint,
469
+ params.whaleRegistryFingerprint,
470
+ params.fromMs,
471
+ params.toMs
472
+ ]
473
+ );
474
+ if (params.deleteEventsBeforeMs != null) {
475
+ await client.query(
476
+ `
477
+ DELETE FROM hyperliquid_whale_trade_events
478
+ WHERE universe_fingerprint = $1
479
+ AND whale_registry_fingerprint = $2
480
+ AND ts < to_timestamp($3/1000.0)
481
+ `,
482
+ [
483
+ params.universeFingerprint,
484
+ params.whaleRegistryFingerprint,
485
+ params.deleteEventsBeforeMs
486
+ ]
487
+ );
488
+ }
489
+ await client.query("COMMIT");
490
+ return result.rowCount ?? 0;
491
+ } catch (error) {
492
+ await client.query("ROLLBACK");
493
+ throw error;
494
+ } finally {
495
+ client.release();
496
+ }
497
+ }
498
+ async function upsertHyperliquidWhaleWalletCoverage(params) {
499
+ await ensureHyperliquidWhaleSchema();
500
+ await getPool().query(
501
+ `
502
+ INSERT INTO hyperliquid_whale_wallet_coverage (
503
+ universe_fingerprint,
504
+ whale_registry_fingerprint,
505
+ address,
506
+ requested_from_ts,
507
+ requested_to_ts,
508
+ covered_from_ts,
509
+ covered_to_ts,
510
+ status,
511
+ fills_count,
512
+ error,
513
+ data_model_version
514
+ ) VALUES (
515
+ $1,
516
+ $2,
517
+ $3,
518
+ to_timestamp($4/1000.0),
519
+ to_timestamp($5/1000.0),
520
+ CASE WHEN $6::double precision IS NULL THEN NULL ELSE to_timestamp($6/1000.0) END,
521
+ CASE WHEN $7::double precision IS NULL THEN NULL ELSE to_timestamp($7/1000.0) END,
522
+ $8,
523
+ $9,
524
+ $10,
525
+ $11
526
+ )
527
+ ON CONFLICT (
528
+ universe_fingerprint,
529
+ whale_registry_fingerprint,
530
+ address,
531
+ requested_from_ts,
532
+ requested_to_ts
533
+ ) DO UPDATE SET
534
+ covered_from_ts = EXCLUDED.covered_from_ts,
535
+ covered_to_ts = EXCLUDED.covered_to_ts,
536
+ status = EXCLUDED.status,
537
+ fills_count = EXCLUDED.fills_count,
538
+ error = EXCLUDED.error,
539
+ data_model_version = EXCLUDED.data_model_version,
540
+ checked_at = now()
541
+ `,
542
+ [
543
+ params.universeFingerprint,
544
+ params.whaleRegistryFingerprint,
545
+ params.address.toLowerCase(),
546
+ params.fromMs,
547
+ params.toMs,
548
+ params.coveredFromMs,
549
+ params.coveredToMs,
550
+ params.status,
551
+ params.fillsCount,
552
+ params.error ?? null,
553
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION
554
+ ]
555
+ );
556
+ }
557
+ async function rebuildHyperliquidWhaleCoverageRows(params) {
558
+ await ensureHyperliquidWhaleSchema();
559
+ if (params.toMs <= params.fromMs) return 0;
560
+ const minuteMs = 6e4;
561
+ const defaultChunkMinutes = 7 * 24 * 60;
562
+ const chunkMinutes = Number.isFinite(params.chunkMinutes) && Number(params.chunkMinutes) > 0 ? Math.floor(Number(params.chunkMinutes)) : defaultChunkMinutes;
563
+ const chunkMs = chunkMinutes * minuteMs;
564
+ const totalBuckets = Math.ceil((params.toMs - params.fromMs) / minuteMs);
565
+ const totalChunks = Math.ceil((params.toMs - params.fromMs) / chunkMs);
566
+ let completedBuckets = 0;
567
+ let rows = 0;
568
+ for (let chunkIndex = 0, chunkFromMs = params.fromMs; chunkFromMs < params.toMs; chunkIndex += 1, chunkFromMs += chunkMs) {
569
+ const chunkToMs = Math.min(params.toMs, chunkFromMs + chunkMs);
570
+ const result = await getPool().query(
571
+ `
572
+ WITH normalized_ranges AS (
573
+ SELECT
574
+ address,
575
+ GREATEST(
576
+ to_timestamp($3/1000.0),
577
+ date_trunc('minute', covered_from_ts) +
578
+ CASE
579
+ WHEN covered_from_ts = date_trunc('minute', covered_from_ts)
580
+ THEN interval '0 minutes'
581
+ ELSE interval '1 minute'
582
+ END
583
+ ) AS range_start,
584
+ LEAST(
585
+ to_timestamp($4/1000.0),
586
+ date_trunc('minute', covered_to_ts)
587
+ ) AS range_end
588
+ FROM hyperliquid_whale_wallet_coverage
589
+ WHERE universe_fingerprint = $1
590
+ AND whale_registry_fingerprint = $2
591
+ AND data_model_version = $6
592
+ AND status IN ('complete', 'truncated')
593
+ AND covered_from_ts < to_timestamp($4/1000.0)
594
+ AND covered_to_ts > to_timestamp($3/1000.0)
595
+ ), eligible_ranges AS (
596
+ SELECT *
597
+ FROM normalized_ranges
598
+ WHERE range_start < range_end
599
+ ), ordered_ranges AS (
600
+ SELECT
601
+ *,
602
+ MAX(range_end) OVER (
603
+ PARTITION BY address
604
+ ORDER BY range_start, range_end
605
+ ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
606
+ ) AS previous_max_end
607
+ FROM eligible_ranges
608
+ ), marked_ranges AS (
609
+ SELECT
610
+ *,
611
+ SUM(
612
+ CASE
613
+ WHEN previous_max_end IS NULL OR range_start > previous_max_end
614
+ THEN 1
615
+ ELSE 0
616
+ END
617
+ ) OVER (
618
+ PARTITION BY address
619
+ ORDER BY range_start, range_end
620
+ ) AS range_group
621
+ FROM ordered_ranges
622
+ ), merged_ranges AS (
623
+ SELECT
624
+ address,
625
+ MIN(range_start) AS range_start,
626
+ MAX(range_end) AS range_end
627
+ FROM marked_ranges
628
+ GROUP BY address, range_group
629
+ ), deltas AS (
630
+ SELECT range_start AS ts, 1 AS delta
631
+ FROM merged_ranges
632
+ UNION ALL
633
+ SELECT range_end AS ts, -1 AS delta
634
+ FROM merged_ranges
635
+ ), bucket_deltas AS (
636
+ SELECT ts, SUM(delta)::int AS delta
637
+ FROM deltas
638
+ GROUP BY ts
639
+ ), buckets AS (
640
+ SELECT generate_series(
641
+ to_timestamp($3/1000.0),
642
+ to_timestamp($4/1000.0) - interval '1 minute',
643
+ interval '1 minute'
644
+ ) AS ts
645
+ ), coverage AS (
646
+ SELECT
647
+ buckets.ts,
648
+ SUM(COALESCE(bucket_deltas.delta, 0)) OVER (
649
+ ORDER BY buckets.ts
650
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
651
+ )::int AS covered_whales
652
+ FROM buckets
653
+ LEFT JOIN bucket_deltas USING (ts)
654
+ )
655
+ INSERT INTO hyperliquid_whale_coverage_1m (
656
+ ts,
657
+ covered_whales,
658
+ expected_whales,
659
+ coverage_pct,
660
+ universe_fingerprint,
661
+ whale_registry_fingerprint,
662
+ source,
663
+ data_model_version
664
+ )
665
+ SELECT
666
+ ts,
667
+ covered_whales,
668
+ $5,
669
+ CASE WHEN $5 > 0 THEN covered_whales::double precision / $5 ELSE 0 END,
670
+ $1,
671
+ $2,
672
+ 'hyperliquid_user_fills',
673
+ $6
674
+ FROM coverage
675
+ ON CONFLICT (
676
+ universe_fingerprint,
677
+ whale_registry_fingerprint,
678
+ ts
679
+ ) DO UPDATE SET
680
+ covered_whales = EXCLUDED.covered_whales,
681
+ expected_whales = EXCLUDED.expected_whales,
682
+ coverage_pct = EXCLUDED.coverage_pct,
683
+ source = EXCLUDED.source,
684
+ data_model_version = EXCLUDED.data_model_version,
685
+ ingested_at = now()
686
+ `,
687
+ [
688
+ params.universeFingerprint,
689
+ params.whaleRegistryFingerprint,
690
+ chunkFromMs,
691
+ chunkToMs,
692
+ params.expectedWhales,
693
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION
694
+ ]
695
+ );
696
+ const chunkBuckets = Math.ceil((chunkToMs - chunkFromMs) / minuteMs);
697
+ completedBuckets = Math.min(totalBuckets, completedBuckets + chunkBuckets);
698
+ rows += result.rowCount ?? 0;
699
+ params.onProgress?.({
700
+ chunkIndex: chunkIndex + 1,
701
+ totalChunks,
702
+ completedBuckets,
703
+ totalBuckets,
704
+ rows
705
+ });
706
+ }
707
+ return rows;
708
+ }
709
+ async function upsertHyperliquidWhaleCoverageRows(rows) {
710
+ if (!rows.length) return;
711
+ await ensureHyperliquidWhaleSchema();
712
+ const cols = [
713
+ "ts",
714
+ "covered_whales",
715
+ "expected_whales",
716
+ "coverage_pct",
717
+ "universe_fingerprint",
718
+ "whale_registry_fingerprint",
719
+ "source",
720
+ "data_model_version"
721
+ ];
722
+ const maxRows = getSafeBulkInsertRows(cols.length);
723
+ if (rows.length > maxRows) {
724
+ for (let index = 0; index < rows.length; index += maxRows) {
725
+ await upsertHyperliquidWhaleCoverageRows(
726
+ rows.slice(index, index + maxRows)
727
+ );
728
+ }
729
+ return;
730
+ }
731
+ const values = [];
732
+ const tuples = rows.map((row, rowIndex) => {
733
+ const offset = rowIndex * cols.length;
734
+ values.push(
735
+ row.ts,
736
+ row.coveredWhales,
737
+ row.expectedWhales,
738
+ row.coveragePct,
739
+ row.universeFingerprint,
740
+ row.whaleRegistryFingerprint,
741
+ row.source ?? null,
742
+ row.dataModelVersion ?? HYPERLIQUID_WHALE_DATA_MODEL_VERSION
743
+ );
744
+ return `(${cols.map((_, colIndex) => `$${offset + colIndex + 1}`).join(",")})`;
745
+ });
746
+ await getPool().query(
747
+ `
748
+ INSERT INTO hyperliquid_whale_coverage_1m (${cols.join(",")})
749
+ VALUES ${tuples.join(",")}
750
+ ON CONFLICT (
751
+ universe_fingerprint,
752
+ whale_registry_fingerprint,
753
+ ts
754
+ ) DO UPDATE SET
755
+ covered_whales = EXCLUDED.covered_whales,
756
+ expected_whales = EXCLUDED.expected_whales,
757
+ coverage_pct = EXCLUDED.coverage_pct,
758
+ source = EXCLUDED.source,
759
+ data_model_version = EXCLUDED.data_model_version,
760
+ ingested_at = now()
761
+ `,
762
+ values
763
+ );
764
+ }
765
+
766
+ // src/timescale/hyperliquidWhales/queries.ts
14
767
  import {
15
- ensureHyperliquidWhaleSchema
16
- } from "../chunk-I2J6YDBD.mjs";
768
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION as HYPERLIQUID_WHALE_DATA_MODEL_VERSION2
769
+ } from "@tradejs/types";
770
+ var HYPERLIQUID_CONTEXT_INTERVAL_MS = {
771
+ "1m": 6e4,
772
+ "5m": 5 * 6e4,
773
+ "15m": 15 * 6e4,
774
+ "1h": 60 * 6e4
775
+ };
776
+ async function getHyperliquidWhaleWalletCoverage(params) {
777
+ await ensureHyperliquidWhaleSchema();
778
+ const result = await getPool().query(
779
+ `
780
+ SELECT
781
+ status,
782
+ covered_from_ts,
783
+ covered_to_ts,
784
+ fills_count,
785
+ error,
786
+ checked_at
787
+ FROM hyperliquid_whale_wallet_coverage
788
+ WHERE universe_fingerprint = $1
789
+ AND whale_registry_fingerprint = $2
790
+ AND address = $3
791
+ AND data_model_version = $6
792
+ AND (
793
+ (
794
+ covered_from_ts <= to_timestamp($4/1000.0)
795
+ AND covered_to_ts >= to_timestamp($5/1000.0)
796
+ )
797
+ OR (
798
+ requested_from_ts = to_timestamp($4/1000.0)
799
+ AND requested_to_ts = to_timestamp($5/1000.0)
800
+ )
801
+ )
802
+ ORDER BY checked_at DESC
803
+ LIMIT 1
804
+ `,
805
+ [
806
+ params.universeFingerprint,
807
+ params.whaleRegistryFingerprint,
808
+ params.address.toLowerCase(),
809
+ params.fromMs,
810
+ params.toMs,
811
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION2
812
+ ]
813
+ );
814
+ const row = result.rows[0];
815
+ if (!row) return null;
816
+ return {
817
+ status: row.covered_from_ts != null && row.covered_to_ts != null && new Date(row.covered_from_ts).getTime() <= params.fromMs && new Date(row.covered_to_ts).getTime() >= params.toMs ? "complete" : String(row.status),
818
+ coveredFromMs: row.covered_from_ts == null ? null : new Date(row.covered_from_ts).getTime(),
819
+ coveredToMs: row.covered_to_ts == null ? null : new Date(row.covered_to_ts).getTime(),
820
+ fillsCount: Number(row.fills_count) || 0,
821
+ error: row.error == null ? null : String(row.error),
822
+ checkedAt: new Date(row.checked_at)
823
+ };
824
+ }
825
+ async function getHyperliquidWhaleCoverageSeriesRows(params) {
826
+ await prepareMarketContextSchemaForRead("hyperliquidWhales");
827
+ const result = await queryMarketContext(
828
+ `
829
+ SELECT
830
+ ts,
831
+ covered_whales,
832
+ expected_whales,
833
+ coverage_pct
834
+ FROM hyperliquid_whale_coverage_1m
835
+ WHERE universe_fingerprint = $1
836
+ AND whale_registry_fingerprint = $2
837
+ AND data_model_version = $3
838
+ AND ts >= to_timestamp($4/1000.0)
839
+ AND ts < to_timestamp($5/1000.0)
840
+ ORDER BY ts
841
+ `,
842
+ [
843
+ params.universeFingerprint,
844
+ params.whaleRegistryFingerprint,
845
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION2,
846
+ params.fromMs,
847
+ params.toMs
848
+ ],
849
+ params
850
+ );
851
+ return result.rows.map((row) => ({
852
+ ts: new Date(row.ts),
853
+ coveredWhales: Number(row.covered_whales) || 0,
854
+ expectedWhales: Number(row.expected_whales) || 0,
855
+ coveragePct: Number(row.coverage_pct) || 0
856
+ }));
857
+ }
858
+ async function getHyperliquidWhaleFlowSeriesRows(params) {
859
+ await prepareMarketContextSchemaForRead("hyperliquidWhales");
860
+ const result = await queryMarketContext(
861
+ `
862
+ SELECT
863
+ ts,
864
+ trades,
865
+ whale_sides,
866
+ whale_addresses,
867
+ buy_notional_usd,
868
+ sell_notional_usd,
869
+ position_aware_whale_sides,
870
+ long_entry_whale_addresses,
871
+ short_entry_whale_addresses,
872
+ long_exit_whale_addresses,
873
+ short_exit_whale_addresses,
874
+ long_entry_notional_usd,
875
+ short_entry_notional_usd,
876
+ long_exit_notional_usd,
877
+ short_exit_notional_usd
878
+ FROM hyperliquid_whale_flow
879
+ WHERE symbol = $1
880
+ AND interval = '1m'
881
+ AND universe_fingerprint = $2
882
+ AND whale_registry_fingerprint = $3
883
+ AND ts >= to_timestamp($4/1000.0)
884
+ AND ts < to_timestamp($5/1000.0)
885
+ ORDER BY ts
886
+ `,
887
+ [
888
+ params.symbol,
889
+ params.universeFingerprint,
890
+ params.whaleRegistryFingerprint,
891
+ params.fromMs,
892
+ params.toMs
893
+ ],
894
+ params
895
+ );
896
+ return result.rows.map((row) => ({
897
+ ts: new Date(row.ts),
898
+ trades: Number(row.trades) || 0,
899
+ whaleSides: Number(row.whale_sides) || 0,
900
+ whaleAddresses: Array.isArray(row.whale_addresses) ? row.whale_addresses.map(String) : [],
901
+ buyNotionalUsd: Number(row.buy_notional_usd) || 0,
902
+ sellNotionalUsd: Number(row.sell_notional_usd) || 0,
903
+ positionAwareWhaleSides: Number(row.position_aware_whale_sides) || 0,
904
+ longEntryWhaleAddresses: Array.isArray(row.long_entry_whale_addresses) ? row.long_entry_whale_addresses.map(String) : [],
905
+ shortEntryWhaleAddresses: Array.isArray(row.short_entry_whale_addresses) ? row.short_entry_whale_addresses.map(String) : [],
906
+ longExitWhaleAddresses: Array.isArray(row.long_exit_whale_addresses) ? row.long_exit_whale_addresses.map(String) : [],
907
+ shortExitWhaleAddresses: Array.isArray(row.short_exit_whale_addresses) ? row.short_exit_whale_addresses.map(String) : [],
908
+ longEntryNotionalUsd: Number(row.long_entry_notional_usd) || 0,
909
+ shortEntryNotionalUsd: Number(row.short_entry_notional_usd) || 0,
910
+ longExitNotionalUsd: Number(row.long_exit_notional_usd) || 0,
911
+ shortExitNotionalUsd: Number(row.short_exit_notional_usd) || 0
912
+ }));
913
+ }
914
+ async function getHyperliquidWhaleFlowAggregate(params) {
915
+ await prepareMarketContextSchemaForRead("hyperliquidWhales");
916
+ const intervalMs = HYPERLIQUID_CONTEXT_INTERVAL_MS[params.interval];
917
+ const expectedBuckets = Math.ceil(intervalMs / 6e4);
918
+ const res = await queryMarketContext(
919
+ `
920
+ WITH coverage_rows AS (
921
+ SELECT *
922
+ FROM hyperliquid_whale_coverage_1m
923
+ WHERE universe_fingerprint = $2
924
+ AND whale_registry_fingerprint = $3
925
+ AND data_model_version = $6
926
+ AND ts >= to_timestamp(
927
+ ($4::double precision - $5::double precision) / 1000.0
928
+ )
929
+ AND ts < to_timestamp($4/1000.0)
930
+ ), coverage_summary AS (
931
+ SELECT
932
+ COUNT(*)::int AS coverage_buckets,
933
+ MAX(ts) AS coverage_as_of_ts,
934
+ MIN(covered_whales)::int AS covered_whales,
935
+ MAX(expected_whales)::int AS expected_whales,
936
+ MIN(coverage_pct) AS coverage_pct
937
+ FROM coverage_rows
938
+ ), window_rows AS (
939
+ SELECT *
940
+ FROM hyperliquid_whale_flow
941
+ WHERE symbol = $1
942
+ AND interval = '1m'
943
+ AND universe_fingerprint = $2
944
+ AND whale_registry_fingerprint = $3
945
+ AND ts >= to_timestamp(
946
+ ($4::double precision - $5::double precision) / 1000.0
947
+ )
948
+ AND ts < to_timestamp($4/1000.0)
949
+ ), unique_addresses AS (
950
+ SELECT COUNT(DISTINCT address)::int AS unique_whales
951
+ FROM window_rows
952
+ CROSS JOIN LATERAL UNNEST(whale_addresses) AS expanded(address)
953
+ ), directional_counts AS (
954
+ SELECT
955
+ (
956
+ SELECT COUNT(DISTINCT address)::int
957
+ FROM window_rows
958
+ CROSS JOIN LATERAL UNNEST(long_entry_whale_addresses) AS expanded(address)
959
+ ) AS long_entry_whales,
960
+ (
961
+ SELECT COUNT(DISTINCT address)::int
962
+ FROM window_rows
963
+ CROSS JOIN LATERAL UNNEST(short_entry_whale_addresses) AS expanded(address)
964
+ ) AS short_entry_whales,
965
+ (
966
+ SELECT COUNT(DISTINCT address)::int
967
+ FROM window_rows
968
+ CROSS JOIN LATERAL UNNEST(long_exit_whale_addresses) AS expanded(address)
969
+ ) AS long_exit_whales,
970
+ (
971
+ SELECT COUNT(DISTINCT address)::int
972
+ FROM window_rows
973
+ CROSS JOIN LATERAL UNNEST(short_exit_whale_addresses) AS expanded(address)
974
+ ) AS short_exit_whales
975
+ )
976
+ SELECT
977
+ $1::text AS symbol,
978
+ coverage_summary.coverage_as_of_ts AS as_of_ts,
979
+ coverage_summary.coverage_buckets,
980
+ coverage_summary.covered_whales,
981
+ coverage_summary.expected_whales,
982
+ coverage_summary.coverage_pct,
983
+ COALESCE((SELECT SUM(trades) FROM window_rows), 0)::int AS trades,
984
+ COALESCE((SELECT SUM(whale_sides) FROM window_rows), 0)::int AS whale_sides,
985
+ COALESCE((SELECT unique_whales FROM unique_addresses), 0)::int AS unique_whales,
986
+ COALESCE((SELECT SUM(buy_notional_usd) FROM window_rows), 0) AS buy_notional_usd,
987
+ COALESCE((SELECT SUM(sell_notional_usd) FROM window_rows), 0) AS sell_notional_usd,
988
+ COALESCE((SELECT SUM(net_notional_usd) FROM window_rows), 0) AS net_notional_usd,
989
+ CASE
990
+ WHEN COALESCE((SELECT SUM(buy_notional_usd + sell_notional_usd) FROM window_rows), 0) > 0
991
+ THEN (SELECT SUM(buy_notional_usd) FROM window_rows) /
992
+ (SELECT SUM(buy_notional_usd + sell_notional_usd) FROM window_rows)
993
+ ELSE NULL
994
+ END AS buy_share_pct,
995
+ COALESCE((SELECT SUM(position_aware_whale_sides) FROM window_rows), 0)::int
996
+ AS position_aware_whale_sides,
997
+ CASE
998
+ WHEN COALESCE((SELECT SUM(whale_sides) FROM window_rows), 0) > 0
999
+ THEN COALESCE((SELECT SUM(position_aware_whale_sides) FROM window_rows), 0)::double precision /
1000
+ (SELECT SUM(whale_sides) FROM window_rows)
1001
+ ELSE 0
1002
+ END AS position_aware_pct,
1003
+ COALESCE((SELECT long_entry_whales FROM directional_counts), 0)::int AS long_entry_whales,
1004
+ COALESCE((SELECT short_entry_whales FROM directional_counts), 0)::int AS short_entry_whales,
1005
+ COALESCE((SELECT long_exit_whales FROM directional_counts), 0)::int AS long_exit_whales,
1006
+ COALESCE((SELECT short_exit_whales FROM directional_counts), 0)::int AS short_exit_whales,
1007
+ COALESCE((SELECT SUM(long_entry_notional_usd) FROM window_rows), 0)
1008
+ AS long_entry_notional_usd,
1009
+ COALESCE((SELECT SUM(short_entry_notional_usd) FROM window_rows), 0)
1010
+ AS short_entry_notional_usd,
1011
+ COALESCE((SELECT SUM(long_exit_notional_usd) FROM window_rows), 0)
1012
+ AS long_exit_notional_usd,
1013
+ COALESCE((SELECT SUM(short_exit_notional_usd) FROM window_rows), 0)
1014
+ AS short_exit_notional_usd,
1015
+ COALESCE((SELECT SUM(entry_net_notional_usd) FROM window_rows), 0)
1016
+ AS entry_net_notional_usd,
1017
+ CASE
1018
+ WHEN COALESCE((SELECT SUM(long_entry_notional_usd + short_entry_notional_usd) FROM window_rows), 0) > 0
1019
+ THEN (SELECT SUM(long_entry_notional_usd) FROM window_rows) /
1020
+ (SELECT SUM(long_entry_notional_usd + short_entry_notional_usd) FROM window_rows)
1021
+ ELSE NULL
1022
+ END AS entry_long_share_pct,
1023
+ (SELECT MAX(source) FROM window_rows) AS source
1024
+ FROM coverage_summary
1025
+ `,
1026
+ [
1027
+ params.symbol,
1028
+ params.universeFingerprint,
1029
+ params.whaleRegistryFingerprint,
1030
+ params.decisionTimeMs,
1031
+ intervalMs,
1032
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION2
1033
+ ],
1034
+ params
1035
+ );
1036
+ const row = res.rows[0];
1037
+ if (!row?.as_of_ts || Number(row.coverage_buckets) !== expectedBuckets || Number(row.covered_whales) <= 0) {
1038
+ return null;
1039
+ }
1040
+ const asOfTs = new Date(row.as_of_ts);
1041
+ const ageMs = params.decisionTimeMs - (asOfTs.getTime() + 6e4);
1042
+ return {
1043
+ symbol: params.symbol,
1044
+ interval: params.interval,
1045
+ asOfTs,
1046
+ windowEndTs: new Date(params.decisionTimeMs),
1047
+ trades: Number(row.trades) || 0,
1048
+ whaleSides: Number(row.whale_sides) || 0,
1049
+ uniqueWhales: Number(row.unique_whales) || 0,
1050
+ coveredWhales: Number(row.covered_whales) || 0,
1051
+ expectedWhales: Number(row.expected_whales) || 0,
1052
+ coveragePct: Number(row.coverage_pct) || 0,
1053
+ buyNotionalUsd: Number(row.buy_notional_usd) || 0,
1054
+ sellNotionalUsd: Number(row.sell_notional_usd) || 0,
1055
+ netNotionalUsd: Number(row.net_notional_usd) || 0,
1056
+ buySharePct: row.buy_share_pct == null ? null : Number(row.buy_share_pct) || 0,
1057
+ positionAwareWhaleSides: Number(row.position_aware_whale_sides) || 0,
1058
+ positionAwarePct: Number(row.position_aware_pct) || 0,
1059
+ longEntryWhales: Number(row.long_entry_whales) || 0,
1060
+ shortEntryWhales: Number(row.short_entry_whales) || 0,
1061
+ longExitWhales: Number(row.long_exit_whales) || 0,
1062
+ shortExitWhales: Number(row.short_exit_whales) || 0,
1063
+ longEntryNotionalUsd: Number(row.long_entry_notional_usd) || 0,
1064
+ shortEntryNotionalUsd: Number(row.short_entry_notional_usd) || 0,
1065
+ longExitNotionalUsd: Number(row.long_exit_notional_usd) || 0,
1066
+ shortExitNotionalUsd: Number(row.short_exit_notional_usd) || 0,
1067
+ entryNetNotionalUsd: Number(row.entry_net_notional_usd) || 0,
1068
+ entryLongSharePct: row.entry_long_share_pct == null ? null : Number(row.entry_long_share_pct) || 0,
1069
+ universeFingerprint: params.universeFingerprint,
1070
+ whaleRegistryFingerprint: params.whaleRegistryFingerprint,
1071
+ source: row.source == null ? null : String(row.source),
1072
+ ageMs,
1073
+ stale: ageMs < 0 || (params.maxAgeMs != null && Number.isFinite(params.maxAgeMs) ? ageMs > params.maxAgeMs : false)
1074
+ };
1075
+ }
1076
+ async function hasHyperliquidWhaleBackfillCoverage(params) {
1077
+ await ensureHyperliquidWhaleSchema();
1078
+ const expectedBuckets = Math.max(
1079
+ 0,
1080
+ Math.ceil((params.toMs - params.fromMs) / 6e4)
1081
+ );
1082
+ const result = await getPool().query(
1083
+ `
1084
+ SELECT
1085
+ COUNT(*)::int AS buckets,
1086
+ COUNT(*) FILTER (
1087
+ WHERE covered_whales = expected_whales
1088
+ )::int AS complete_buckets
1089
+ FROM hyperliquid_whale_coverage_1m
1090
+ WHERE universe_fingerprint = $1
1091
+ AND whale_registry_fingerprint = $2
1092
+ AND data_model_version = $5
1093
+ AND ts >= to_timestamp($3/1000.0)
1094
+ AND ts < to_timestamp($4/1000.0)
1095
+ `,
1096
+ [
1097
+ params.universeFingerprint,
1098
+ params.whaleRegistryFingerprint,
1099
+ params.fromMs,
1100
+ params.toMs,
1101
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION2
1102
+ ]
1103
+ );
1104
+ return Number(result.rows[0]?.buckets) === expectedBuckets && Number(result.rows[0]?.complete_buckets) === expectedBuckets;
1105
+ }
17
1106
  export {
18
1107
  ensureHyperliquidWhaleSchema,
19
1108
  getHyperliquidWhaleCoverageSeriesRows,