@tradejs/infra 2.0.18 → 2.0.19

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 (53) hide show
  1. package/dist/ai.js +3 -54
  2. package/dist/ai.mjs +1 -54
  3. package/dist/chunk-2CZREG43.mjs +112 -0
  4. package/dist/chunk-DFMKDB2R.mjs +1285 -0
  5. package/dist/chunk-I2J6YDBD.mjs +910 -0
  6. package/dist/chunk-NWXFWTWU.mjs +1114 -0
  7. package/dist/chunk-SZQB7ER5.mjs +492 -0
  8. package/dist/chunk-YVIHTUV5.mjs +286 -0
  9. package/dist/internal-2coHaaos.d.mts +26 -0
  10. package/dist/internal-2coHaaos.d.ts +26 -0
  11. package/dist/runtimeDeployments.d.mts +10 -0
  12. package/dist/runtimeDeployments.d.ts +10 -0
  13. package/dist/runtimeDeployments.js +447 -0
  14. package/dist/runtimeDeployments.mjs +81 -0
  15. package/dist/runtimeStrategyConfigs.d.mts +28 -0
  16. package/dist/runtimeStrategyConfigs.d.ts +28 -0
  17. package/dist/runtimeStrategyConfigs.js +425 -0
  18. package/dist/runtimeStrategyConfigs.mjs +89 -0
  19. package/dist/timescale/candles.d.mts +38 -0
  20. package/dist/timescale/candles.d.ts +38 -0
  21. package/dist/timescale/candles.js +408 -0
  22. package/dist/timescale/candles.mjs +21 -0
  23. package/dist/timescale/client.d.mts +4 -0
  24. package/dist/timescale/client.d.ts +4 -0
  25. package/dist/timescale/client.js +109 -0
  26. package/dist/timescale/client.mjs +12 -0
  27. package/dist/timescale/derivatives.d.mts +90 -0
  28. package/dist/timescale/derivatives.d.ts +90 -0
  29. package/dist/timescale/derivatives.js +1270 -0
  30. package/dist/timescale/derivatives.mjs +26 -0
  31. package/dist/timescale/hyperliquidWhales.d.mts +149 -0
  32. package/dist/timescale/hyperliquidWhales.d.ts +149 -0
  33. package/dist/timescale/hyperliquidWhales.js +1893 -0
  34. package/dist/timescale/hyperliquidWhales.mjs +30 -0
  35. package/dist/timescale/marketContext.d.mts +188 -0
  36. package/dist/timescale/marketContext.d.ts +188 -0
  37. package/dist/timescale/marketContext.js +2091 -0
  38. package/dist/timescale/marketContext.mjs +60 -0
  39. package/dist/timescale/spread.d.mts +11 -0
  40. package/dist/timescale/spread.d.ts +11 -0
  41. package/dist/timescale/spread.js +215 -0
  42. package/dist/timescale/spread.mjs +11 -0
  43. package/dist/timescale.d.mts +9 -476
  44. package/dist/timescale.d.ts +9 -476
  45. package/dist/timescale.js +2121 -2112
  46. package/dist/timescale.mjs +73 -4070
  47. package/dist/tradingAccounts.d.mts +2 -8
  48. package/dist/tradingAccounts.d.ts +2 -8
  49. package/dist/tradingAccounts.js +0 -71
  50. package/dist/tradingAccounts.mjs +0 -65
  51. package/dist/values-BrvcmnfM.d.mts +6 -0
  52. package/dist/values-BrvcmnfM.d.ts +6 -0
  53. package/package.json +43 -2
@@ -0,0 +1,1893 @@
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/timescale/hyperliquidWhales.ts
21
+ var hyperliquidWhales_exports = {};
22
+ __export(hyperliquidWhales_exports, {
23
+ ensureHyperliquidWhaleSchema: () => ensureHyperliquidWhaleSchema,
24
+ getHyperliquidWhaleCoverageSeriesRows: () => getHyperliquidWhaleCoverageSeriesRows,
25
+ getHyperliquidWhaleFlowAggregate: () => getHyperliquidWhaleFlowAggregate,
26
+ getHyperliquidWhaleFlowSeriesRows: () => getHyperliquidWhaleFlowSeriesRows,
27
+ getHyperliquidWhaleWalletCoverage: () => getHyperliquidWhaleWalletCoverage,
28
+ hasHyperliquidWhaleBackfillCoverage: () => hasHyperliquidWhaleBackfillCoverage,
29
+ rebuildHyperliquidWhaleCoverageRows: () => rebuildHyperliquidWhaleCoverageRows,
30
+ rebuildHyperliquidWhaleFlowRows: () => rebuildHyperliquidWhaleFlowRows,
31
+ upsertHyperliquidWhaleCoverageRows: () => upsertHyperliquidWhaleCoverageRows,
32
+ upsertHyperliquidWhaleFlowRows: () => upsertHyperliquidWhaleFlowRows,
33
+ upsertHyperliquidWhaleTradeEvents: () => upsertHyperliquidWhaleTradeEvents,
34
+ upsertHyperliquidWhaleWalletCoverage: () => upsertHyperliquidWhaleWalletCoverage
35
+ });
36
+ module.exports = __toCommonJS(hyperliquidWhales_exports);
37
+ var import_types = require("@tradejs/types");
38
+
39
+ // src/timescale/pool.ts
40
+ var import_pg = require("pg");
41
+ var getPool = () => {
42
+ if (!global.__pgPool__) {
43
+ const max = Number(process.env.PG_POOL_MAX ?? 10);
44
+ const connectionTimeoutMillis = Number(
45
+ process.env.PG_CONNECTION_TIMEOUT_MS ?? 3e4
46
+ );
47
+ global.__pgPool__ = new import_pg.Pool({
48
+ host: process.env.PG_HOST || "127.0.0.1",
49
+ port: Number(process.env.PG_PORT ?? 5432),
50
+ user: process.env.PG_USER || "app",
51
+ password: String(process.env.PG_PASSWORD ?? "app"),
52
+ database: process.env.PG_DATABASE || process.env.PG_DB || "app",
53
+ max: Number.isFinite(max) && max > 0 ? Math.floor(max) : 10,
54
+ idleTimeoutMillis: 3e4,
55
+ connectionTimeoutMillis: Number.isFinite(connectionTimeoutMillis) && connectionTimeoutMillis > 0 ? Math.floor(connectionTimeoutMillis) : 3e4
56
+ });
57
+ }
58
+ return global.__pgPool__;
59
+ };
60
+
61
+ // src/timescale/query.ts
62
+ var resolveQueryTimeoutMs = (override) => {
63
+ if (Number.isFinite(override) && Number(override) > 0) {
64
+ return Math.floor(Number(override));
65
+ }
66
+ const configured = Number(process.env.MARKET_CONTEXT_SQL_TIMEOUT_MS);
67
+ return Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : 3e4;
68
+ };
69
+ var createQueryError = (name, message) => {
70
+ const error = new Error(message);
71
+ error.name = name;
72
+ return error;
73
+ };
74
+ var queryMarketContext = async (text, values, options = {}) => {
75
+ const client = await getPool().connect();
76
+ const timeoutMs = resolveQueryTimeoutMs(options.timeoutMs);
77
+ let released = false;
78
+ let rejectCancellation;
79
+ const release = (error) => {
80
+ if (released) return;
81
+ released = true;
82
+ client.release(error);
83
+ };
84
+ const cancellation = new Promise((_resolve, reject) => {
85
+ rejectCancellation = reject;
86
+ });
87
+ const cancel = (error) => {
88
+ release(error);
89
+ rejectCancellation?.(error);
90
+ };
91
+ const onAbort = () => cancel(
92
+ createQueryError("AbortError", "Timescale market-context query aborted")
93
+ );
94
+ const timer = setTimeout(
95
+ () => cancel(
96
+ createQueryError(
97
+ "TimescaleQueryTimeoutError",
98
+ `Timescale market-context query exceeded ${timeoutMs}ms`
99
+ )
100
+ ),
101
+ timeoutMs
102
+ );
103
+ timer.unref?.();
104
+ options.signal?.addEventListener("abort", onAbort, { once: true });
105
+ try {
106
+ if (options.signal?.aborted) {
107
+ const error = createQueryError(
108
+ "AbortError",
109
+ "Timescale market-context query aborted"
110
+ );
111
+ release(error);
112
+ throw error;
113
+ }
114
+ return await Promise.race([client.query(text, values), cancellation]);
115
+ } finally {
116
+ clearTimeout(timer);
117
+ options.signal?.removeEventListener("abort", onAbort);
118
+ release();
119
+ }
120
+ };
121
+
122
+ // src/timescale/values.ts
123
+ var PG_SAFE_MAX_BIND_PARAMS = 3e4;
124
+ var getSafeBulkInsertRows = (columnsCount) => Math.max(1, Math.floor(PG_SAFE_MAX_BIND_PARAMS / columnsCount));
125
+
126
+ // src/timescale/internal.ts
127
+ var derivativesSchemaReady = false;
128
+ var binanceMarketSchemaReady = false;
129
+ var hyperliquidWhaleSchemaReady = false;
130
+ var derivativesSchemaReadyPromise = null;
131
+ var binanceMarketSchemaReadyPromise = null;
132
+ var hyperliquidWhaleSchemaReadyPromise = null;
133
+ var marketContextSchemaMode = "ensure";
134
+ var verifiedMarketContextSchemas = /* @__PURE__ */ new Set();
135
+ var DERIVATIVES_SCHEMA_LOCK_KEY = 610001;
136
+ var BINANCE_MARKET_SCHEMA_LOCK_KEY = 610003;
137
+ var HYPERLIQUID_WHALE_SCHEMA_LOCK_KEY = 610004;
138
+ var withSchemaLock = async (lockKey, work) => {
139
+ const pool = getPool();
140
+ await pool.query("SELECT pg_advisory_lock($1)", [lockKey]);
141
+ try {
142
+ await work();
143
+ } finally {
144
+ await pool.query("SELECT pg_advisory_unlock($1)", [lockKey]);
145
+ }
146
+ };
147
+ var ensureDerivativesSchema = async () => {
148
+ if (derivativesSchemaReady) return;
149
+ if (derivativesSchemaReadyPromise) {
150
+ await derivativesSchemaReadyPromise;
151
+ return;
152
+ }
153
+ const pool = getPool();
154
+ derivativesSchemaReadyPromise = withSchemaLock(
155
+ DERIVATIVES_SCHEMA_LOCK_KEY,
156
+ async () => {
157
+ if (derivativesSchemaReady) return;
158
+ await pool.query("CREATE EXTENSION IF NOT EXISTS timescaledb");
159
+ await pool.query(`
160
+ CREATE TABLE IF NOT EXISTS derivatives_market (
161
+ symbol text NOT NULL,
162
+ interval text NOT NULL,
163
+ ts timestamptz NOT NULL,
164
+ open_interest double precision,
165
+ funding_rate double precision,
166
+ liq_long double precision,
167
+ liq_short double precision,
168
+ liq_total double precision,
169
+ source text,
170
+ ingested_at timestamptz NOT NULL DEFAULT now(),
171
+ PRIMARY KEY (symbol, interval, ts)
172
+ )
173
+ `);
174
+ await pool.query(`
175
+ SELECT create_hypertable(
176
+ 'derivatives_market',
177
+ 'ts',
178
+ if_not_exists => TRUE,
179
+ chunk_time_interval => interval '14 days'
180
+ )
181
+ `);
182
+ await pool.query(`
183
+ CREATE INDEX IF NOT EXISTS derivatives_market_symbol_tf_ts_idx
184
+ ON derivatives_market (symbol, interval, ts DESC)
185
+ `);
186
+ await pool.query(`
187
+ CREATE TABLE IF NOT EXISTS derivatives_backfill_coverage (
188
+ source text NOT NULL,
189
+ symbol text NOT NULL,
190
+ interval text NOT NULL,
191
+ from_ts timestamptz NOT NULL,
192
+ to_ts timestamptz NOT NULL,
193
+ rows_count integer NOT NULL DEFAULT 0,
194
+ checked_at timestamptz NOT NULL DEFAULT now(),
195
+ PRIMARY KEY (source, symbol, interval, from_ts, to_ts)
196
+ )
197
+ `);
198
+ await pool.query(`
199
+ CREATE INDEX IF NOT EXISTS derivatives_backfill_coverage_lookup_idx
200
+ ON derivatives_backfill_coverage (source, symbol, interval, from_ts, to_ts)
201
+ `);
202
+ await pool.query(`
203
+ CREATE TABLE IF NOT EXISTS derivatives_metric_coverage (
204
+ source text NOT NULL,
205
+ metric text NOT NULL,
206
+ symbol text NOT NULL,
207
+ interval text NOT NULL,
208
+ from_ts timestamptz NOT NULL,
209
+ to_ts timestamptz NOT NULL,
210
+ event_rows_count integer NOT NULL DEFAULT 0,
211
+ zero_rows_count integer NOT NULL DEFAULT 0,
212
+ checked_at timestamptz NOT NULL DEFAULT now(),
213
+ PRIMARY KEY (source, metric, symbol, interval, from_ts, to_ts)
214
+ )
215
+ `);
216
+ await pool.query(`
217
+ CREATE INDEX IF NOT EXISTS derivatives_metric_coverage_lookup_idx
218
+ ON derivatives_metric_coverage (
219
+ source,
220
+ metric,
221
+ symbol,
222
+ interval,
223
+ from_ts,
224
+ to_ts
225
+ )
226
+ `);
227
+ derivativesSchemaReady = true;
228
+ }
229
+ ).finally(() => {
230
+ derivativesSchemaReadyPromise = null;
231
+ });
232
+ await derivativesSchemaReadyPromise;
233
+ };
234
+ var ensureBinanceMarketSchema = async () => {
235
+ if (binanceMarketSchemaReady) return;
236
+ if (binanceMarketSchemaReadyPromise) {
237
+ await binanceMarketSchemaReadyPromise;
238
+ return;
239
+ }
240
+ const pool = getPool();
241
+ binanceMarketSchemaReadyPromise = withSchemaLock(
242
+ BINANCE_MARKET_SCHEMA_LOCK_KEY,
243
+ async () => {
244
+ if (binanceMarketSchemaReady) return;
245
+ await pool.query("CREATE EXTENSION IF NOT EXISTS timescaledb");
246
+ await pool.query(`
247
+ CREATE TABLE IF NOT EXISTS market_trade_flow (
248
+ symbol text NOT NULL,
249
+ interval text NOT NULL,
250
+ ts timestamptz NOT NULL,
251
+ trades integer NOT NULL,
252
+ buy_base_volume double precision,
253
+ sell_base_volume double precision,
254
+ buy_quote_volume double precision,
255
+ sell_quote_volume double precision,
256
+ net_base_delta double precision,
257
+ net_quote_delta double precision,
258
+ buy_pressure_pct double precision,
259
+ source text,
260
+ ingested_at timestamptz NOT NULL DEFAULT now(),
261
+ PRIMARY KEY (symbol, interval, ts)
262
+ )
263
+ `);
264
+ await pool.query(`
265
+ SELECT create_hypertable(
266
+ 'market_trade_flow',
267
+ 'ts',
268
+ if_not_exists => TRUE,
269
+ chunk_time_interval => interval '7 days'
270
+ )
271
+ `);
272
+ await pool.query(`
273
+ CREATE INDEX IF NOT EXISTS market_trade_flow_symbol_tf_ts_idx
274
+ ON market_trade_flow (symbol, interval, ts DESC)
275
+ `);
276
+ await pool.query(`
277
+ CREATE TABLE IF NOT EXISTS market_breadth (
278
+ universe text NOT NULL,
279
+ interval text NOT NULL,
280
+ ts timestamptz NOT NULL,
281
+ symbols_count integer NOT NULL,
282
+ advancers integer NOT NULL,
283
+ decliners integer NOT NULL,
284
+ unchanged integer NOT NULL,
285
+ advance_decline_ratio double precision,
286
+ pct_above_ma20 double precision,
287
+ pct_above_ma50 double precision,
288
+ equal_weighted_return double precision,
289
+ volume_weighted_return double precision,
290
+ dispersion double precision,
291
+ btc_return_1h double precision,
292
+ btc_return_4h double precision,
293
+ btc_return_24h double precision,
294
+ alt_basket_return_1h double precision,
295
+ alt_basket_return_4h double precision,
296
+ alt_basket_return_24h double precision,
297
+ btc_vs_alt_return_1h double precision,
298
+ btc_vs_alt_return_4h double precision,
299
+ btc_vs_alt_return_24h double precision,
300
+ btc_turnover_share_1h double precision,
301
+ btc_turnover_share_24h double precision,
302
+ btc_turnover_share_change_24h double precision,
303
+ alt_vol_to_btc_vol_24h double precision,
304
+ alt_dispersion_24h double precision,
305
+ btc_alt_regime text,
306
+ source text,
307
+ ingested_at timestamptz NOT NULL DEFAULT now(),
308
+ PRIMARY KEY (universe, interval, ts)
309
+ )
310
+ `);
311
+ await pool.query(`
312
+ ALTER TABLE market_breadth
313
+ ADD COLUMN IF NOT EXISTS btc_return_1h double precision,
314
+ ADD COLUMN IF NOT EXISTS btc_return_4h double precision,
315
+ ADD COLUMN IF NOT EXISTS btc_return_24h double precision,
316
+ ADD COLUMN IF NOT EXISTS alt_basket_return_1h double precision,
317
+ ADD COLUMN IF NOT EXISTS alt_basket_return_4h double precision,
318
+ ADD COLUMN IF NOT EXISTS alt_basket_return_24h double precision,
319
+ ADD COLUMN IF NOT EXISTS btc_vs_alt_return_1h double precision,
320
+ ADD COLUMN IF NOT EXISTS btc_vs_alt_return_4h double precision,
321
+ ADD COLUMN IF NOT EXISTS btc_vs_alt_return_24h double precision,
322
+ ADD COLUMN IF NOT EXISTS btc_turnover_share_1h double precision,
323
+ ADD COLUMN IF NOT EXISTS btc_turnover_share_24h double precision,
324
+ ADD COLUMN IF NOT EXISTS btc_turnover_share_change_24h double precision,
325
+ ADD COLUMN IF NOT EXISTS alt_vol_to_btc_vol_24h double precision,
326
+ ADD COLUMN IF NOT EXISTS alt_dispersion_24h double precision,
327
+ ADD COLUMN IF NOT EXISTS btc_alt_regime text
328
+ `);
329
+ await pool.query(`
330
+ SELECT create_hypertable(
331
+ 'market_breadth',
332
+ 'ts',
333
+ if_not_exists => TRUE,
334
+ chunk_time_interval => interval '14 days'
335
+ )
336
+ `);
337
+ await pool.query(`
338
+ CREATE INDEX IF NOT EXISTS market_breadth_universe_tf_ts_idx
339
+ ON market_breadth (universe, interval, ts DESC)
340
+ `);
341
+ await pool.query(`
342
+ CREATE TABLE IF NOT EXISTS market_global_context (
343
+ source text NOT NULL,
344
+ ts timestamptz NOT NULL,
345
+ updated_at_ts timestamptz,
346
+ active_cryptocurrencies integer,
347
+ active_exchanges integer,
348
+ active_market_pairs integer,
349
+ markets integer,
350
+ total_market_cap_usd double precision,
351
+ total_volume_usd double precision,
352
+ total_volume_reported_usd double precision,
353
+ btc_dominance_pct double precision,
354
+ eth_dominance_pct double precision,
355
+ alt_market_cap_usd double precision,
356
+ alt_volume_usd double precision,
357
+ alt_volume_reported_usd double precision,
358
+ btc_to_alt_market_cap_ratio double precision,
359
+ market_cap_change_pct_24h_usd double precision,
360
+ ingested_at timestamptz NOT NULL DEFAULT now(),
361
+ PRIMARY KEY (source, ts)
362
+ )
363
+ `);
364
+ await pool.query(`
365
+ SELECT create_hypertable(
366
+ 'market_global_context',
367
+ 'ts',
368
+ if_not_exists => TRUE,
369
+ chunk_time_interval => interval '30 days'
370
+ )
371
+ `);
372
+ await pool.query(`
373
+ CREATE INDEX IF NOT EXISTS market_global_context_source_ts_idx
374
+ ON market_global_context (source, ts DESC)
375
+ `);
376
+ await pool.query(`
377
+ ALTER TABLE market_global_context
378
+ ADD COLUMN IF NOT EXISTS active_exchanges integer,
379
+ ADD COLUMN IF NOT EXISTS active_market_pairs integer,
380
+ ADD COLUMN IF NOT EXISTS total_volume_reported_usd double precision,
381
+ ADD COLUMN IF NOT EXISTS alt_volume_usd double precision,
382
+ ADD COLUMN IF NOT EXISTS alt_volume_reported_usd double precision
383
+ `);
384
+ await pool.query(`
385
+ CREATE TABLE IF NOT EXISTS market_reference_asset_context (
386
+ source text NOT NULL,
387
+ symbol text NOT NULL,
388
+ cmc_id integer NOT NULL,
389
+ interval text NOT NULL,
390
+ ts timestamptz NOT NULL,
391
+ open_usd double precision,
392
+ high_usd double precision,
393
+ low_usd double precision,
394
+ close_usd double precision,
395
+ volume_usd double precision,
396
+ market_cap_usd double precision,
397
+ ingested_at timestamptz NOT NULL DEFAULT now(),
398
+ PRIMARY KEY (source, symbol, interval, ts)
399
+ )
400
+ `);
401
+ await pool.query(`
402
+ SELECT create_hypertable(
403
+ 'market_reference_asset_context',
404
+ 'ts',
405
+ if_not_exists => TRUE,
406
+ chunk_time_interval => interval '30 days'
407
+ )
408
+ `);
409
+ await pool.query(`
410
+ CREATE INDEX IF NOT EXISTS market_reference_asset_context_lookup_idx
411
+ ON market_reference_asset_context (source, symbol, interval, ts DESC)
412
+ `);
413
+ await pool.query(`
414
+ CREATE TABLE IF NOT EXISTS market_cmc_exchange_liquidity_context (
415
+ source text NOT NULL,
416
+ interval text NOT NULL,
417
+ ts timestamptz NOT NULL,
418
+ exchanges_count integer NOT NULL,
419
+ total_volume_usd double precision,
420
+ binance_volume_usd double precision,
421
+ binance_volume_share double precision,
422
+ top_exchange_volume_share double precision,
423
+ liquidity_regime text,
424
+ ingested_at timestamptz NOT NULL DEFAULT now(),
425
+ PRIMARY KEY (source, interval, ts)
426
+ )
427
+ `);
428
+ await pool.query(`
429
+ SELECT create_hypertable(
430
+ 'market_cmc_exchange_liquidity_context',
431
+ 'ts',
432
+ if_not_exists => TRUE,
433
+ chunk_time_interval => interval '30 days'
434
+ )
435
+ `);
436
+ await pool.query(`
437
+ CREATE INDEX IF NOT EXISTS market_cmc_exchange_liquidity_context_lookup_idx
438
+ ON market_cmc_exchange_liquidity_context (source, interval, ts DESC)
439
+ `);
440
+ await pool.query(`
441
+ CREATE TABLE IF NOT EXISTS market_cmc_fear_greed_context (
442
+ source text NOT NULL,
443
+ interval text NOT NULL,
444
+ ts timestamptz NOT NULL,
445
+ value integer NOT NULL,
446
+ classification text NOT NULL,
447
+ sentiment_regime text NOT NULL,
448
+ ingested_at timestamptz NOT NULL DEFAULT now(),
449
+ PRIMARY KEY (source, interval, ts)
450
+ )
451
+ `);
452
+ await pool.query(`
453
+ SELECT create_hypertable(
454
+ 'market_cmc_fear_greed_context',
455
+ 'ts',
456
+ if_not_exists => TRUE,
457
+ chunk_time_interval => interval '30 days'
458
+ )
459
+ `);
460
+ await pool.query(`
461
+ CREATE INDEX IF NOT EXISTS market_cmc_fear_greed_context_lookup_idx
462
+ ON market_cmc_fear_greed_context (source, interval, ts DESC)
463
+ `);
464
+ await pool.query(`
465
+ CREATE TABLE IF NOT EXISTS market_cmc_index_context (
466
+ source text NOT NULL,
467
+ index_slug text NOT NULL,
468
+ interval text NOT NULL,
469
+ ts timestamptz NOT NULL,
470
+ value double precision NOT NULL,
471
+ constituents_count integer,
472
+ top_constituent_symbol text,
473
+ top_constituent_weight_pct double precision,
474
+ constituents jsonb,
475
+ ingested_at timestamptz NOT NULL DEFAULT now(),
476
+ PRIMARY KEY (source, index_slug, interval, ts)
477
+ )
478
+ `);
479
+ await pool.query(`
480
+ SELECT create_hypertable(
481
+ 'market_cmc_index_context',
482
+ 'ts',
483
+ if_not_exists => TRUE,
484
+ chunk_time_interval => interval '30 days'
485
+ )
486
+ `);
487
+ await pool.query(`
488
+ CREATE INDEX IF NOT EXISTS market_cmc_index_context_lookup_idx
489
+ ON market_cmc_index_context (source, index_slug, interval, ts DESC)
490
+ `);
491
+ await pool.query(`
492
+ CREATE TABLE IF NOT EXISTS market_context_backfill_coverage (
493
+ source text NOT NULL,
494
+ scope text NOT NULL,
495
+ interval text NOT NULL,
496
+ from_ts timestamptz NOT NULL,
497
+ to_ts timestamptz NOT NULL,
498
+ rows_count integer NOT NULL DEFAULT 0,
499
+ checked_at timestamptz NOT NULL DEFAULT now(),
500
+ PRIMARY KEY (source, scope, interval, from_ts, to_ts)
501
+ )
502
+ `);
503
+ await pool.query(`
504
+ CREATE INDEX IF NOT EXISTS market_context_backfill_coverage_lookup_idx
505
+ ON market_context_backfill_coverage (source, scope, interval, from_ts, to_ts)
506
+ `);
507
+ binanceMarketSchemaReady = true;
508
+ }
509
+ ).finally(() => {
510
+ binanceMarketSchemaReadyPromise = null;
511
+ });
512
+ await binanceMarketSchemaReadyPromise;
513
+ };
514
+ var ensureHyperliquidWhaleSchema = async () => {
515
+ if (hyperliquidWhaleSchemaReady) return;
516
+ if (hyperliquidWhaleSchemaReadyPromise) {
517
+ await hyperliquidWhaleSchemaReadyPromise;
518
+ return;
519
+ }
520
+ hyperliquidWhaleSchemaReadyPromise = withSchemaLock(
521
+ HYPERLIQUID_WHALE_SCHEMA_LOCK_KEY,
522
+ async () => {
523
+ if (hyperliquidWhaleSchemaReady) return;
524
+ const pool = getPool();
525
+ await pool.query("CREATE EXTENSION IF NOT EXISTS timescaledb");
526
+ await pool.query(`
527
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_trade_events (
528
+ symbol text NOT NULL,
529
+ ts timestamptz NOT NULL,
530
+ tid text NOT NULL,
531
+ price double precision NOT NULL,
532
+ size double precision NOT NULL,
533
+ notional_usd double precision NOT NULL,
534
+ buyer_address text,
535
+ seller_address text,
536
+ buyer_tracked boolean NOT NULL,
537
+ seller_tracked boolean NOT NULL,
538
+ buyer_start_position double precision,
539
+ buyer_end_position double precision,
540
+ buyer_position_action text,
541
+ buyer_closed_pnl double precision,
542
+ buyer_liquidation boolean,
543
+ seller_start_position double precision,
544
+ seller_end_position double precision,
545
+ seller_position_action text,
546
+ seller_closed_pnl double precision,
547
+ seller_liquidation boolean,
548
+ universe_fingerprint text NOT NULL,
549
+ whale_registry_fingerprint text NOT NULL,
550
+ source text,
551
+ ingested_at timestamptz NOT NULL DEFAULT now(),
552
+ PRIMARY KEY (
553
+ universe_fingerprint,
554
+ whale_registry_fingerprint,
555
+ symbol,
556
+ ts,
557
+ tid
558
+ )
559
+ )
560
+ `);
561
+ await pool.query(`
562
+ ALTER TABLE hyperliquid_whale_trade_events
563
+ ADD COLUMN IF NOT EXISTS buyer_start_position double precision,
564
+ ADD COLUMN IF NOT EXISTS buyer_end_position double precision,
565
+ ADD COLUMN IF NOT EXISTS buyer_position_action text,
566
+ ADD COLUMN IF NOT EXISTS buyer_closed_pnl double precision,
567
+ ADD COLUMN IF NOT EXISTS buyer_liquidation boolean,
568
+ ADD COLUMN IF NOT EXISTS seller_start_position double precision,
569
+ ADD COLUMN IF NOT EXISTS seller_end_position double precision,
570
+ ADD COLUMN IF NOT EXISTS seller_position_action text,
571
+ ADD COLUMN IF NOT EXISTS seller_closed_pnl double precision,
572
+ ADD COLUMN IF NOT EXISTS seller_liquidation boolean
573
+ `);
574
+ await pool.query(`
575
+ SELECT create_hypertable(
576
+ 'hyperliquid_whale_trade_events',
577
+ 'ts',
578
+ if_not_exists => TRUE,
579
+ chunk_time_interval => interval '1 day'
580
+ )
581
+ `);
582
+ await pool.query(`
583
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_events_lookup_idx
584
+ ON hyperliquid_whale_trade_events (
585
+ universe_fingerprint,
586
+ whale_registry_fingerprint,
587
+ symbol,
588
+ ts DESC
589
+ )
590
+ `);
591
+ await pool.query(`
592
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_flow (
593
+ symbol text NOT NULL,
594
+ interval text NOT NULL,
595
+ ts timestamptz NOT NULL,
596
+ trades integer NOT NULL,
597
+ whale_sides integer NOT NULL,
598
+ unique_whales integer NOT NULL,
599
+ whale_addresses text[] NOT NULL DEFAULT '{}',
600
+ buy_notional_usd double precision NOT NULL,
601
+ sell_notional_usd double precision NOT NULL,
602
+ net_notional_usd double precision NOT NULL,
603
+ buy_share_pct double precision,
604
+ position_aware_whale_sides integer NOT NULL DEFAULT 0,
605
+ long_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
606
+ short_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
607
+ long_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
608
+ short_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
609
+ long_entry_notional_usd double precision NOT NULL DEFAULT 0,
610
+ short_entry_notional_usd double precision NOT NULL DEFAULT 0,
611
+ long_exit_notional_usd double precision NOT NULL DEFAULT 0,
612
+ short_exit_notional_usd double precision NOT NULL DEFAULT 0,
613
+ entry_net_notional_usd double precision NOT NULL DEFAULT 0,
614
+ entry_long_share_pct double precision,
615
+ universe_fingerprint text NOT NULL,
616
+ whale_registry_fingerprint text NOT NULL,
617
+ source text,
618
+ ingested_at timestamptz NOT NULL DEFAULT now(),
619
+ PRIMARY KEY (
620
+ universe_fingerprint,
621
+ whale_registry_fingerprint,
622
+ symbol,
623
+ interval,
624
+ ts
625
+ )
626
+ )
627
+ `);
628
+ await pool.query(`
629
+ ALTER TABLE hyperliquid_whale_flow
630
+ ADD COLUMN IF NOT EXISTS position_aware_whale_sides integer NOT NULL DEFAULT 0,
631
+ ADD COLUMN IF NOT EXISTS long_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
632
+ ADD COLUMN IF NOT EXISTS short_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
633
+ ADD COLUMN IF NOT EXISTS long_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
634
+ ADD COLUMN IF NOT EXISTS short_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
635
+ ADD COLUMN IF NOT EXISTS long_entry_notional_usd double precision NOT NULL DEFAULT 0,
636
+ ADD COLUMN IF NOT EXISTS short_entry_notional_usd double precision NOT NULL DEFAULT 0,
637
+ ADD COLUMN IF NOT EXISTS long_exit_notional_usd double precision NOT NULL DEFAULT 0,
638
+ ADD COLUMN IF NOT EXISTS short_exit_notional_usd double precision NOT NULL DEFAULT 0,
639
+ ADD COLUMN IF NOT EXISTS entry_net_notional_usd double precision NOT NULL DEFAULT 0,
640
+ ADD COLUMN IF NOT EXISTS entry_long_share_pct double precision
641
+ `);
642
+ await pool.query(`
643
+ SELECT create_hypertable(
644
+ 'hyperliquid_whale_flow',
645
+ 'ts',
646
+ if_not_exists => TRUE,
647
+ chunk_time_interval => interval '7 days'
648
+ )
649
+ `);
650
+ await pool.query(`
651
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_flow_lookup_idx
652
+ ON hyperliquid_whale_flow (
653
+ universe_fingerprint,
654
+ whale_registry_fingerprint,
655
+ symbol,
656
+ interval,
657
+ ts DESC
658
+ )
659
+ `);
660
+ await pool.query(`
661
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_wallet_coverage (
662
+ universe_fingerprint text NOT NULL,
663
+ whale_registry_fingerprint text NOT NULL,
664
+ address text NOT NULL,
665
+ requested_from_ts timestamptz NOT NULL,
666
+ requested_to_ts timestamptz NOT NULL,
667
+ covered_from_ts timestamptz,
668
+ covered_to_ts timestamptz,
669
+ status text NOT NULL CHECK (status IN ('complete', 'truncated', 'failed')),
670
+ fills_count integer NOT NULL DEFAULT 0,
671
+ error text,
672
+ data_model_version integer NOT NULL DEFAULT 2,
673
+ checked_at timestamptz NOT NULL DEFAULT now(),
674
+ PRIMARY KEY (
675
+ universe_fingerprint,
676
+ whale_registry_fingerprint,
677
+ address,
678
+ requested_from_ts,
679
+ requested_to_ts
680
+ )
681
+ )
682
+ `);
683
+ await pool.query(`
684
+ ALTER TABLE hyperliquid_whale_wallet_coverage
685
+ ADD COLUMN IF NOT EXISTS data_model_version integer NOT NULL DEFAULT 2
686
+ `);
687
+ await pool.query(`
688
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_wallet_coverage_lookup_idx
689
+ ON hyperliquid_whale_wallet_coverage (
690
+ universe_fingerprint,
691
+ whale_registry_fingerprint,
692
+ address,
693
+ requested_from_ts,
694
+ requested_to_ts
695
+ )
696
+ `);
697
+ await pool.query(`
698
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_coverage_1m (
699
+ ts timestamptz NOT NULL,
700
+ covered_whales integer NOT NULL,
701
+ expected_whales integer NOT NULL,
702
+ coverage_pct double precision NOT NULL,
703
+ universe_fingerprint text NOT NULL,
704
+ whale_registry_fingerprint text NOT NULL,
705
+ source text,
706
+ data_model_version integer NOT NULL DEFAULT 2,
707
+ ingested_at timestamptz NOT NULL DEFAULT now(),
708
+ PRIMARY KEY (
709
+ universe_fingerprint,
710
+ whale_registry_fingerprint,
711
+ ts
712
+ )
713
+ )
714
+ `);
715
+ await pool.query(`
716
+ ALTER TABLE hyperliquid_whale_coverage_1m
717
+ ADD COLUMN IF NOT EXISTS data_model_version integer NOT NULL DEFAULT 2
718
+ `);
719
+ await pool.query(`
720
+ SELECT create_hypertable(
721
+ 'hyperliquid_whale_coverage_1m',
722
+ 'ts',
723
+ if_not_exists => TRUE,
724
+ chunk_time_interval => interval '7 days'
725
+ )
726
+ `);
727
+ await pool.query(`
728
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_coverage_1m_lookup_idx
729
+ ON hyperliquid_whale_coverage_1m (
730
+ universe_fingerprint,
731
+ whale_registry_fingerprint,
732
+ ts DESC
733
+ )
734
+ `);
735
+ hyperliquidWhaleSchemaReady = true;
736
+ }
737
+ ).finally(() => {
738
+ hyperliquidWhaleSchemaReadyPromise = null;
739
+ });
740
+ await hyperliquidWhaleSchemaReadyPromise;
741
+ };
742
+ var ensureCoinMarketCapContextSchema = async () => ensureBinanceMarketSchema();
743
+ var ensureMarketContextSchema = async (source) => {
744
+ switch (source) {
745
+ case "binance":
746
+ return ensureBinanceMarketSchema();
747
+ case "coinmarketcap":
748
+ return ensureCoinMarketCapContextSchema();
749
+ case "derivatives":
750
+ return ensureDerivativesSchema();
751
+ case "hyperliquidWhales":
752
+ return ensureHyperliquidWhaleSchema();
753
+ }
754
+ };
755
+ var MARKET_CONTEXT_SCHEMA_TABLES = {
756
+ binance: ["market_trade_flow", "market_breadth"],
757
+ coinmarketcap: [
758
+ "market_global_context",
759
+ "market_reference_asset_context",
760
+ "market_cmc_exchange_liquidity_context",
761
+ "market_cmc_fear_greed_context",
762
+ "market_cmc_index_context"
763
+ ],
764
+ derivatives: ["derivatives_market"],
765
+ hyperliquidWhales: [
766
+ "hyperliquid_whale_flow",
767
+ "hyperliquid_whale_coverage_1m"
768
+ ]
769
+ };
770
+ var verifyMarketContextSchema = async (source) => {
771
+ if (verifiedMarketContextSchemas.has(source)) return;
772
+ const tables = MARKET_CONTEXT_SCHEMA_TABLES[source];
773
+ const result = await queryMarketContext(
774
+ `
775
+ SELECT table_name AS "tableName"
776
+ FROM unnest($1::text[]) AS requested(table_name)
777
+ WHERE to_regclass(requested.table_name) IS NULL
778
+ `,
779
+ [tables]
780
+ );
781
+ if (result.rows.length) {
782
+ throw new Error(
783
+ `Timescale ${source} schema is not prepared; missing: ${result.rows.map((row) => row.tableName).filter(Boolean).join(", ")}`
784
+ );
785
+ }
786
+ verifiedMarketContextSchemas.add(source);
787
+ };
788
+ var prepareMarketContextSchemaForRead = async (source) => marketContextSchemaMode === "verify" ? verifyMarketContextSchema(source) : ensureMarketContextSchema(source);
789
+
790
+ // src/timescale/hyperliquidWhales.ts
791
+ var HYPERLIQUID_CONTEXT_INTERVAL_MS = {
792
+ "1m": 6e4,
793
+ "5m": 5 * 6e4,
794
+ "15m": 15 * 6e4,
795
+ "1h": 60 * 6e4
796
+ };
797
+ async function upsertHyperliquidWhaleTradeEvents(rows) {
798
+ if (!rows.length) return;
799
+ await ensureHyperliquidWhaleSchema();
800
+ const cols = [
801
+ "symbol",
802
+ "ts",
803
+ "tid",
804
+ "price",
805
+ "size",
806
+ "notional_usd",
807
+ "buyer_address",
808
+ "seller_address",
809
+ "buyer_tracked",
810
+ "seller_tracked",
811
+ "buyer_start_position",
812
+ "buyer_end_position",
813
+ "buyer_position_action",
814
+ "buyer_closed_pnl",
815
+ "buyer_liquidation",
816
+ "seller_start_position",
817
+ "seller_end_position",
818
+ "seller_position_action",
819
+ "seller_closed_pnl",
820
+ "seller_liquidation",
821
+ "universe_fingerprint",
822
+ "whale_registry_fingerprint",
823
+ "source"
824
+ ];
825
+ const maxRows = getSafeBulkInsertRows(cols.length);
826
+ if (rows.length > maxRows) {
827
+ for (let index = 0; index < rows.length; index += maxRows) {
828
+ await upsertHyperliquidWhaleTradeEvents(
829
+ rows.slice(index, index + maxRows)
830
+ );
831
+ }
832
+ return;
833
+ }
834
+ const valuesSql = rows.map(
835
+ (_, rowIndex) => `(${cols.map((__, colIndex) => `$${rowIndex * cols.length + colIndex + 1}`).join(",")})`
836
+ ).join(",");
837
+ const flat = rows.flatMap((row) => [
838
+ row.symbol,
839
+ row.ts,
840
+ row.tid,
841
+ row.price,
842
+ row.size,
843
+ row.notionalUsd,
844
+ row.buyerAddress ?? null,
845
+ row.sellerAddress ?? null,
846
+ row.buyerTracked,
847
+ row.sellerTracked,
848
+ row.buyerStartPosition ?? null,
849
+ row.buyerEndPosition ?? null,
850
+ row.buyerPositionAction ?? null,
851
+ row.buyerClosedPnl ?? null,
852
+ row.buyerLiquidation ?? null,
853
+ row.sellerStartPosition ?? null,
854
+ row.sellerEndPosition ?? null,
855
+ row.sellerPositionAction ?? null,
856
+ row.sellerClosedPnl ?? null,
857
+ row.sellerLiquidation ?? null,
858
+ row.universeFingerprint,
859
+ row.whaleRegistryFingerprint,
860
+ row.source ?? null
861
+ ]);
862
+ await getPool().query(
863
+ `
864
+ INSERT INTO hyperliquid_whale_trade_events (${cols.join(",")})
865
+ VALUES ${valuesSql}
866
+ ON CONFLICT (
867
+ universe_fingerprint,
868
+ whale_registry_fingerprint,
869
+ symbol,
870
+ ts,
871
+ tid
872
+ ) DO UPDATE SET
873
+ buyer_address = COALESCE(
874
+ hyperliquid_whale_trade_events.buyer_address,
875
+ EXCLUDED.buyer_address
876
+ ),
877
+ seller_address = COALESCE(
878
+ hyperliquid_whale_trade_events.seller_address,
879
+ EXCLUDED.seller_address
880
+ ),
881
+ buyer_tracked = hyperliquid_whale_trade_events.buyer_tracked OR EXCLUDED.buyer_tracked,
882
+ seller_tracked = hyperliquid_whale_trade_events.seller_tracked OR EXCLUDED.seller_tracked,
883
+ buyer_start_position = COALESCE(
884
+ hyperliquid_whale_trade_events.buyer_start_position,
885
+ EXCLUDED.buyer_start_position
886
+ ),
887
+ buyer_end_position = COALESCE(
888
+ hyperliquid_whale_trade_events.buyer_end_position,
889
+ EXCLUDED.buyer_end_position
890
+ ),
891
+ buyer_position_action = COALESCE(
892
+ hyperliquid_whale_trade_events.buyer_position_action,
893
+ EXCLUDED.buyer_position_action
894
+ ),
895
+ buyer_closed_pnl = COALESCE(
896
+ hyperliquid_whale_trade_events.buyer_closed_pnl,
897
+ EXCLUDED.buyer_closed_pnl
898
+ ),
899
+ buyer_liquidation = COALESCE(
900
+ hyperliquid_whale_trade_events.buyer_liquidation,
901
+ EXCLUDED.buyer_liquidation
902
+ ),
903
+ seller_start_position = COALESCE(
904
+ hyperliquid_whale_trade_events.seller_start_position,
905
+ EXCLUDED.seller_start_position
906
+ ),
907
+ seller_end_position = COALESCE(
908
+ hyperliquid_whale_trade_events.seller_end_position,
909
+ EXCLUDED.seller_end_position
910
+ ),
911
+ seller_position_action = COALESCE(
912
+ hyperliquid_whale_trade_events.seller_position_action,
913
+ EXCLUDED.seller_position_action
914
+ ),
915
+ seller_closed_pnl = COALESCE(
916
+ hyperliquid_whale_trade_events.seller_closed_pnl,
917
+ EXCLUDED.seller_closed_pnl
918
+ ),
919
+ seller_liquidation = COALESCE(
920
+ hyperliquid_whale_trade_events.seller_liquidation,
921
+ EXCLUDED.seller_liquidation
922
+ ),
923
+ source = EXCLUDED.source,
924
+ ingested_at = now()
925
+ `,
926
+ flat
927
+ );
928
+ }
929
+ async function upsertHyperliquidWhaleFlowRows(rows) {
930
+ if (!rows.length) return;
931
+ await ensureHyperliquidWhaleSchema();
932
+ const cols = [
933
+ "symbol",
934
+ "interval",
935
+ "ts",
936
+ "trades",
937
+ "whale_sides",
938
+ "unique_whales",
939
+ "whale_addresses",
940
+ "buy_notional_usd",
941
+ "sell_notional_usd",
942
+ "net_notional_usd",
943
+ "buy_share_pct",
944
+ "position_aware_whale_sides",
945
+ "long_entry_whale_addresses",
946
+ "short_entry_whale_addresses",
947
+ "long_exit_whale_addresses",
948
+ "short_exit_whale_addresses",
949
+ "long_entry_notional_usd",
950
+ "short_entry_notional_usd",
951
+ "long_exit_notional_usd",
952
+ "short_exit_notional_usd",
953
+ "entry_net_notional_usd",
954
+ "entry_long_share_pct",
955
+ "universe_fingerprint",
956
+ "whale_registry_fingerprint",
957
+ "source"
958
+ ];
959
+ const maxRows = getSafeBulkInsertRows(cols.length);
960
+ if (rows.length > maxRows) {
961
+ for (let index = 0; index < rows.length; index += maxRows) {
962
+ await upsertHyperliquidWhaleFlowRows(rows.slice(index, index + maxRows));
963
+ }
964
+ return;
965
+ }
966
+ const valuesSql = rows.map(
967
+ (_, rowIndex) => `(${cols.map((__, colIndex) => `$${rowIndex * cols.length + colIndex + 1}`).join(",")})`
968
+ ).join(",");
969
+ const flat = rows.flatMap((row) => [
970
+ row.symbol,
971
+ row.interval,
972
+ row.ts,
973
+ row.trades,
974
+ row.whaleSides,
975
+ row.uniqueWhales,
976
+ row.whaleAddresses ?? [],
977
+ row.buyNotionalUsd,
978
+ row.sellNotionalUsd,
979
+ row.netNotionalUsd,
980
+ row.buySharePct ?? null,
981
+ row.positionAwareWhaleSides,
982
+ row.longEntryWhaleAddresses ?? [],
983
+ row.shortEntryWhaleAddresses ?? [],
984
+ row.longExitWhaleAddresses ?? [],
985
+ row.shortExitWhaleAddresses ?? [],
986
+ row.longEntryNotionalUsd,
987
+ row.shortEntryNotionalUsd,
988
+ row.longExitNotionalUsd,
989
+ row.shortExitNotionalUsd,
990
+ row.entryNetNotionalUsd,
991
+ row.entryLongSharePct ?? null,
992
+ row.universeFingerprint,
993
+ row.whaleRegistryFingerprint,
994
+ row.source ?? null
995
+ ]);
996
+ await getPool().query(
997
+ `
998
+ INSERT INTO hyperliquid_whale_flow (${cols.join(",")})
999
+ VALUES ${valuesSql}
1000
+ ON CONFLICT (
1001
+ universe_fingerprint,
1002
+ whale_registry_fingerprint,
1003
+ symbol,
1004
+ interval,
1005
+ ts
1006
+ ) DO UPDATE SET
1007
+ trades = EXCLUDED.trades,
1008
+ whale_sides = EXCLUDED.whale_sides,
1009
+ unique_whales = EXCLUDED.unique_whales,
1010
+ whale_addresses = EXCLUDED.whale_addresses,
1011
+ buy_notional_usd = EXCLUDED.buy_notional_usd,
1012
+ sell_notional_usd = EXCLUDED.sell_notional_usd,
1013
+ net_notional_usd = EXCLUDED.net_notional_usd,
1014
+ buy_share_pct = EXCLUDED.buy_share_pct,
1015
+ position_aware_whale_sides = EXCLUDED.position_aware_whale_sides,
1016
+ long_entry_whale_addresses = EXCLUDED.long_entry_whale_addresses,
1017
+ short_entry_whale_addresses = EXCLUDED.short_entry_whale_addresses,
1018
+ long_exit_whale_addresses = EXCLUDED.long_exit_whale_addresses,
1019
+ short_exit_whale_addresses = EXCLUDED.short_exit_whale_addresses,
1020
+ long_entry_notional_usd = EXCLUDED.long_entry_notional_usd,
1021
+ short_entry_notional_usd = EXCLUDED.short_entry_notional_usd,
1022
+ long_exit_notional_usd = EXCLUDED.long_exit_notional_usd,
1023
+ short_exit_notional_usd = EXCLUDED.short_exit_notional_usd,
1024
+ entry_net_notional_usd = EXCLUDED.entry_net_notional_usd,
1025
+ entry_long_share_pct = EXCLUDED.entry_long_share_pct,
1026
+ source = EXCLUDED.source,
1027
+ ingested_at = now()
1028
+ `,
1029
+ flat
1030
+ );
1031
+ }
1032
+ async function rebuildHyperliquidWhaleFlowRows(params) {
1033
+ await ensureHyperliquidWhaleSchema();
1034
+ const client = await getPool().connect();
1035
+ try {
1036
+ await client.query("BEGIN");
1037
+ const result = await client.query(
1038
+ `
1039
+ WITH source_events AS (
1040
+ SELECT *
1041
+ FROM hyperliquid_whale_trade_events
1042
+ WHERE universe_fingerprint = $1
1043
+ AND whale_registry_fingerprint = $2
1044
+ AND ts >= to_timestamp($3/1000.0)
1045
+ AND ts < to_timestamp($4/1000.0)
1046
+ ), metrics AS (
1047
+ SELECT
1048
+ symbol,
1049
+ date_trunc('minute', ts) AS bucket_ts,
1050
+ COUNT(*)::int AS trades,
1051
+ SUM(buyer_tracked::int + seller_tracked::int)::int AS whale_sides,
1052
+ SUM(CASE WHEN buyer_tracked THEN notional_usd ELSE 0 END) AS buy_notional_usd,
1053
+ SUM(CASE WHEN seller_tracked THEN notional_usd ELSE 0 END) AS sell_notional_usd
1054
+ FROM source_events
1055
+ GROUP BY symbol, date_trunc('minute', ts)
1056
+ ), position_legs AS (
1057
+ SELECT
1058
+ symbol,
1059
+ ts,
1060
+ price,
1061
+ buyer_address AS whale_address,
1062
+ buyer_start_position AS start_position,
1063
+ buyer_end_position AS end_position
1064
+ FROM source_events
1065
+ WHERE buyer_tracked
1066
+ AND buyer_address IS NOT NULL
1067
+ AND buyer_start_position IS NOT NULL
1068
+ AND buyer_end_position IS NOT NULL
1069
+ UNION ALL
1070
+ SELECT
1071
+ symbol,
1072
+ ts,
1073
+ price,
1074
+ seller_address AS whale_address,
1075
+ seller_start_position AS start_position,
1076
+ seller_end_position AS end_position
1077
+ FROM source_events
1078
+ WHERE seller_tracked
1079
+ AND seller_address IS NOT NULL
1080
+ AND seller_start_position IS NOT NULL
1081
+ AND seller_end_position IS NOT NULL
1082
+ ), classified_legs AS (
1083
+ SELECT
1084
+ *,
1085
+ GREATEST(
1086
+ GREATEST(end_position, 0) - GREATEST(start_position, 0),
1087
+ 0
1088
+ ) AS long_entry_size,
1089
+ GREATEST(
1090
+ GREATEST(-end_position, 0) - GREATEST(-start_position, 0),
1091
+ 0
1092
+ ) AS short_entry_size,
1093
+ GREATEST(
1094
+ GREATEST(start_position, 0) - GREATEST(end_position, 0),
1095
+ 0
1096
+ ) AS long_exit_size,
1097
+ GREATEST(
1098
+ GREATEST(-start_position, 0) - GREATEST(-end_position, 0),
1099
+ 0
1100
+ ) AS short_exit_size
1101
+ FROM position_legs
1102
+ ), position_metrics AS (
1103
+ SELECT
1104
+ symbol,
1105
+ date_trunc('minute', ts) AS bucket_ts,
1106
+ COUNT(*)::int AS position_aware_whale_sides,
1107
+ SUM(long_entry_size * price) AS long_entry_notional_usd,
1108
+ SUM(short_entry_size * price) AS short_entry_notional_usd,
1109
+ SUM(long_exit_size * price) AS long_exit_notional_usd,
1110
+ SUM(short_exit_size * price) AS short_exit_notional_usd,
1111
+ COALESCE(
1112
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
1113
+ FILTER (WHERE long_entry_size > 0),
1114
+ '{}'
1115
+ ) AS long_entry_whale_addresses,
1116
+ COALESCE(
1117
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
1118
+ FILTER (WHERE short_entry_size > 0),
1119
+ '{}'
1120
+ ) AS short_entry_whale_addresses,
1121
+ COALESCE(
1122
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
1123
+ FILTER (WHERE long_exit_size > 0),
1124
+ '{}'
1125
+ ) AS long_exit_whale_addresses,
1126
+ COALESCE(
1127
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
1128
+ FILTER (WHERE short_exit_size > 0),
1129
+ '{}'
1130
+ ) AS short_exit_whale_addresses
1131
+ FROM classified_legs
1132
+ GROUP BY symbol, date_trunc('minute', ts)
1133
+ ), addresses AS (
1134
+ SELECT
1135
+ symbol,
1136
+ date_trunc('minute', ts) AS bucket_ts,
1137
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address) AS whale_addresses
1138
+ FROM source_events
1139
+ CROSS JOIN LATERAL UNNEST(ARRAY[
1140
+ CASE WHEN buyer_tracked THEN buyer_address END,
1141
+ CASE WHEN seller_tracked THEN seller_address END
1142
+ ]) AS expanded(whale_address)
1143
+ WHERE whale_address IS NOT NULL
1144
+ GROUP BY symbol, date_trunc('minute', ts)
1145
+ )
1146
+ INSERT INTO hyperliquid_whale_flow (
1147
+ symbol,
1148
+ interval,
1149
+ ts,
1150
+ trades,
1151
+ whale_sides,
1152
+ unique_whales,
1153
+ whale_addresses,
1154
+ buy_notional_usd,
1155
+ sell_notional_usd,
1156
+ net_notional_usd,
1157
+ buy_share_pct,
1158
+ position_aware_whale_sides,
1159
+ long_entry_whale_addresses,
1160
+ short_entry_whale_addresses,
1161
+ long_exit_whale_addresses,
1162
+ short_exit_whale_addresses,
1163
+ long_entry_notional_usd,
1164
+ short_entry_notional_usd,
1165
+ long_exit_notional_usd,
1166
+ short_exit_notional_usd,
1167
+ entry_net_notional_usd,
1168
+ entry_long_share_pct,
1169
+ universe_fingerprint,
1170
+ whale_registry_fingerprint,
1171
+ source
1172
+ )
1173
+ SELECT
1174
+ metrics.symbol,
1175
+ '1m',
1176
+ metrics.bucket_ts,
1177
+ metrics.trades,
1178
+ metrics.whale_sides,
1179
+ COALESCE(CARDINALITY(addresses.whale_addresses), 0),
1180
+ COALESCE(addresses.whale_addresses, '{}'),
1181
+ metrics.buy_notional_usd,
1182
+ metrics.sell_notional_usd,
1183
+ metrics.buy_notional_usd - metrics.sell_notional_usd,
1184
+ CASE
1185
+ WHEN metrics.buy_notional_usd + metrics.sell_notional_usd > 0
1186
+ THEN metrics.buy_notional_usd /
1187
+ (metrics.buy_notional_usd + metrics.sell_notional_usd)
1188
+ ELSE NULL
1189
+ END,
1190
+ COALESCE(position_metrics.position_aware_whale_sides, 0),
1191
+ COALESCE(position_metrics.long_entry_whale_addresses, '{}'),
1192
+ COALESCE(position_metrics.short_entry_whale_addresses, '{}'),
1193
+ COALESCE(position_metrics.long_exit_whale_addresses, '{}'),
1194
+ COALESCE(position_metrics.short_exit_whale_addresses, '{}'),
1195
+ COALESCE(position_metrics.long_entry_notional_usd, 0),
1196
+ COALESCE(position_metrics.short_entry_notional_usd, 0),
1197
+ COALESCE(position_metrics.long_exit_notional_usd, 0),
1198
+ COALESCE(position_metrics.short_exit_notional_usd, 0),
1199
+ COALESCE(position_metrics.long_entry_notional_usd, 0) -
1200
+ COALESCE(position_metrics.short_entry_notional_usd, 0),
1201
+ CASE
1202
+ WHEN COALESCE(position_metrics.long_entry_notional_usd, 0) +
1203
+ COALESCE(position_metrics.short_entry_notional_usd, 0) > 0
1204
+ THEN COALESCE(position_metrics.long_entry_notional_usd, 0) /
1205
+ (
1206
+ COALESCE(position_metrics.long_entry_notional_usd, 0) +
1207
+ COALESCE(position_metrics.short_entry_notional_usd, 0)
1208
+ )
1209
+ ELSE NULL
1210
+ END,
1211
+ $1,
1212
+ $2,
1213
+ CASE
1214
+ WHEN COALESCE(position_metrics.position_aware_whale_sides, 0) > 0
1215
+ THEN 'hyperliquid_user_fills'
1216
+ ELSE 'hyperliquid_trades'
1217
+ END
1218
+ FROM metrics
1219
+ LEFT JOIN addresses USING (symbol, bucket_ts)
1220
+ LEFT JOIN position_metrics USING (symbol, bucket_ts)
1221
+ ON CONFLICT (
1222
+ universe_fingerprint,
1223
+ whale_registry_fingerprint,
1224
+ symbol,
1225
+ interval,
1226
+ ts
1227
+ ) DO UPDATE SET
1228
+ trades = EXCLUDED.trades,
1229
+ whale_sides = EXCLUDED.whale_sides,
1230
+ unique_whales = EXCLUDED.unique_whales,
1231
+ whale_addresses = EXCLUDED.whale_addresses,
1232
+ buy_notional_usd = EXCLUDED.buy_notional_usd,
1233
+ sell_notional_usd = EXCLUDED.sell_notional_usd,
1234
+ net_notional_usd = EXCLUDED.net_notional_usd,
1235
+ buy_share_pct = EXCLUDED.buy_share_pct,
1236
+ position_aware_whale_sides = EXCLUDED.position_aware_whale_sides,
1237
+ long_entry_whale_addresses = EXCLUDED.long_entry_whale_addresses,
1238
+ short_entry_whale_addresses = EXCLUDED.short_entry_whale_addresses,
1239
+ long_exit_whale_addresses = EXCLUDED.long_exit_whale_addresses,
1240
+ short_exit_whale_addresses = EXCLUDED.short_exit_whale_addresses,
1241
+ long_entry_notional_usd = EXCLUDED.long_entry_notional_usd,
1242
+ short_entry_notional_usd = EXCLUDED.short_entry_notional_usd,
1243
+ long_exit_notional_usd = EXCLUDED.long_exit_notional_usd,
1244
+ short_exit_notional_usd = EXCLUDED.short_exit_notional_usd,
1245
+ entry_net_notional_usd = EXCLUDED.entry_net_notional_usd,
1246
+ entry_long_share_pct = EXCLUDED.entry_long_share_pct,
1247
+ source = EXCLUDED.source,
1248
+ ingested_at = now()
1249
+ RETURNING 1
1250
+ `,
1251
+ [
1252
+ params.universeFingerprint,
1253
+ params.whaleRegistryFingerprint,
1254
+ params.fromMs,
1255
+ params.toMs
1256
+ ]
1257
+ );
1258
+ if (params.deleteEventsBeforeMs != null) {
1259
+ await client.query(
1260
+ `
1261
+ DELETE FROM hyperliquid_whale_trade_events
1262
+ WHERE universe_fingerprint = $1
1263
+ AND whale_registry_fingerprint = $2
1264
+ AND ts < to_timestamp($3/1000.0)
1265
+ `,
1266
+ [
1267
+ params.universeFingerprint,
1268
+ params.whaleRegistryFingerprint,
1269
+ params.deleteEventsBeforeMs
1270
+ ]
1271
+ );
1272
+ }
1273
+ await client.query("COMMIT");
1274
+ return result.rowCount ?? 0;
1275
+ } catch (error) {
1276
+ await client.query("ROLLBACK");
1277
+ throw error;
1278
+ } finally {
1279
+ client.release();
1280
+ }
1281
+ }
1282
+ async function getHyperliquidWhaleWalletCoverage(params) {
1283
+ await ensureHyperliquidWhaleSchema();
1284
+ const result = await getPool().query(
1285
+ `
1286
+ SELECT
1287
+ status,
1288
+ covered_from_ts,
1289
+ covered_to_ts,
1290
+ fills_count,
1291
+ error,
1292
+ checked_at
1293
+ FROM hyperliquid_whale_wallet_coverage
1294
+ WHERE universe_fingerprint = $1
1295
+ AND whale_registry_fingerprint = $2
1296
+ AND address = $3
1297
+ AND data_model_version = $6
1298
+ AND (
1299
+ (
1300
+ covered_from_ts <= to_timestamp($4/1000.0)
1301
+ AND covered_to_ts >= to_timestamp($5/1000.0)
1302
+ )
1303
+ OR (
1304
+ requested_from_ts = to_timestamp($4/1000.0)
1305
+ AND requested_to_ts = to_timestamp($5/1000.0)
1306
+ )
1307
+ )
1308
+ ORDER BY checked_at DESC
1309
+ LIMIT 1
1310
+ `,
1311
+ [
1312
+ params.universeFingerprint,
1313
+ params.whaleRegistryFingerprint,
1314
+ params.address.toLowerCase(),
1315
+ params.fromMs,
1316
+ params.toMs,
1317
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
1318
+ ]
1319
+ );
1320
+ const row = result.rows[0];
1321
+ if (!row) return null;
1322
+ return {
1323
+ 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),
1324
+ coveredFromMs: row.covered_from_ts == null ? null : new Date(row.covered_from_ts).getTime(),
1325
+ coveredToMs: row.covered_to_ts == null ? null : new Date(row.covered_to_ts).getTime(),
1326
+ fillsCount: Number(row.fills_count) || 0,
1327
+ error: row.error == null ? null : String(row.error),
1328
+ checkedAt: new Date(row.checked_at)
1329
+ };
1330
+ }
1331
+ async function upsertHyperliquidWhaleWalletCoverage(params) {
1332
+ await ensureHyperliquidWhaleSchema();
1333
+ await getPool().query(
1334
+ `
1335
+ INSERT INTO hyperliquid_whale_wallet_coverage (
1336
+ universe_fingerprint,
1337
+ whale_registry_fingerprint,
1338
+ address,
1339
+ requested_from_ts,
1340
+ requested_to_ts,
1341
+ covered_from_ts,
1342
+ covered_to_ts,
1343
+ status,
1344
+ fills_count,
1345
+ error,
1346
+ data_model_version
1347
+ ) VALUES (
1348
+ $1,
1349
+ $2,
1350
+ $3,
1351
+ to_timestamp($4/1000.0),
1352
+ to_timestamp($5/1000.0),
1353
+ CASE WHEN $6::double precision IS NULL THEN NULL ELSE to_timestamp($6/1000.0) END,
1354
+ CASE WHEN $7::double precision IS NULL THEN NULL ELSE to_timestamp($7/1000.0) END,
1355
+ $8,
1356
+ $9,
1357
+ $10,
1358
+ $11
1359
+ )
1360
+ ON CONFLICT (
1361
+ universe_fingerprint,
1362
+ whale_registry_fingerprint,
1363
+ address,
1364
+ requested_from_ts,
1365
+ requested_to_ts
1366
+ ) DO UPDATE SET
1367
+ covered_from_ts = EXCLUDED.covered_from_ts,
1368
+ covered_to_ts = EXCLUDED.covered_to_ts,
1369
+ status = EXCLUDED.status,
1370
+ fills_count = EXCLUDED.fills_count,
1371
+ error = EXCLUDED.error,
1372
+ data_model_version = EXCLUDED.data_model_version,
1373
+ checked_at = now()
1374
+ `,
1375
+ [
1376
+ params.universeFingerprint,
1377
+ params.whaleRegistryFingerprint,
1378
+ params.address.toLowerCase(),
1379
+ params.fromMs,
1380
+ params.toMs,
1381
+ params.coveredFromMs,
1382
+ params.coveredToMs,
1383
+ params.status,
1384
+ params.fillsCount,
1385
+ params.error ?? null,
1386
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
1387
+ ]
1388
+ );
1389
+ }
1390
+ async function rebuildHyperliquidWhaleCoverageRows(params) {
1391
+ await ensureHyperliquidWhaleSchema();
1392
+ if (params.toMs <= params.fromMs) return 0;
1393
+ const minuteMs = 6e4;
1394
+ const defaultChunkMinutes = 7 * 24 * 60;
1395
+ const chunkMinutes = Number.isFinite(params.chunkMinutes) && Number(params.chunkMinutes) > 0 ? Math.floor(Number(params.chunkMinutes)) : defaultChunkMinutes;
1396
+ const chunkMs = chunkMinutes * minuteMs;
1397
+ const totalBuckets = Math.ceil((params.toMs - params.fromMs) / minuteMs);
1398
+ const totalChunks = Math.ceil((params.toMs - params.fromMs) / chunkMs);
1399
+ let completedBuckets = 0;
1400
+ let rows = 0;
1401
+ for (let chunkIndex = 0, chunkFromMs = params.fromMs; chunkFromMs < params.toMs; chunkIndex += 1, chunkFromMs += chunkMs) {
1402
+ const chunkToMs = Math.min(params.toMs, chunkFromMs + chunkMs);
1403
+ const result = await getPool().query(
1404
+ `
1405
+ WITH normalized_ranges AS (
1406
+ SELECT
1407
+ address,
1408
+ GREATEST(
1409
+ to_timestamp($3/1000.0),
1410
+ date_trunc('minute', covered_from_ts) +
1411
+ CASE
1412
+ WHEN covered_from_ts = date_trunc('minute', covered_from_ts)
1413
+ THEN interval '0 minutes'
1414
+ ELSE interval '1 minute'
1415
+ END
1416
+ ) AS range_start,
1417
+ LEAST(
1418
+ to_timestamp($4/1000.0),
1419
+ date_trunc('minute', covered_to_ts)
1420
+ ) AS range_end
1421
+ FROM hyperliquid_whale_wallet_coverage
1422
+ WHERE universe_fingerprint = $1
1423
+ AND whale_registry_fingerprint = $2
1424
+ AND data_model_version = $6
1425
+ AND status IN ('complete', 'truncated')
1426
+ AND covered_from_ts < to_timestamp($4/1000.0)
1427
+ AND covered_to_ts > to_timestamp($3/1000.0)
1428
+ ), eligible_ranges AS (
1429
+ SELECT *
1430
+ FROM normalized_ranges
1431
+ WHERE range_start < range_end
1432
+ ), ordered_ranges AS (
1433
+ SELECT
1434
+ *,
1435
+ MAX(range_end) OVER (
1436
+ PARTITION BY address
1437
+ ORDER BY range_start, range_end
1438
+ ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
1439
+ ) AS previous_max_end
1440
+ FROM eligible_ranges
1441
+ ), marked_ranges AS (
1442
+ SELECT
1443
+ *,
1444
+ SUM(
1445
+ CASE
1446
+ WHEN previous_max_end IS NULL OR range_start > previous_max_end
1447
+ THEN 1
1448
+ ELSE 0
1449
+ END
1450
+ ) OVER (
1451
+ PARTITION BY address
1452
+ ORDER BY range_start, range_end
1453
+ ) AS range_group
1454
+ FROM ordered_ranges
1455
+ ), merged_ranges AS (
1456
+ SELECT
1457
+ address,
1458
+ MIN(range_start) AS range_start,
1459
+ MAX(range_end) AS range_end
1460
+ FROM marked_ranges
1461
+ GROUP BY address, range_group
1462
+ ), deltas AS (
1463
+ SELECT range_start AS ts, 1 AS delta
1464
+ FROM merged_ranges
1465
+ UNION ALL
1466
+ SELECT range_end AS ts, -1 AS delta
1467
+ FROM merged_ranges
1468
+ ), bucket_deltas AS (
1469
+ SELECT ts, SUM(delta)::int AS delta
1470
+ FROM deltas
1471
+ GROUP BY ts
1472
+ ), buckets AS (
1473
+ SELECT generate_series(
1474
+ to_timestamp($3/1000.0),
1475
+ to_timestamp($4/1000.0) - interval '1 minute',
1476
+ interval '1 minute'
1477
+ ) AS ts
1478
+ ), coverage AS (
1479
+ SELECT
1480
+ buckets.ts,
1481
+ SUM(COALESCE(bucket_deltas.delta, 0)) OVER (
1482
+ ORDER BY buckets.ts
1483
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
1484
+ )::int AS covered_whales
1485
+ FROM buckets
1486
+ LEFT JOIN bucket_deltas USING (ts)
1487
+ )
1488
+ INSERT INTO hyperliquid_whale_coverage_1m (
1489
+ ts,
1490
+ covered_whales,
1491
+ expected_whales,
1492
+ coverage_pct,
1493
+ universe_fingerprint,
1494
+ whale_registry_fingerprint,
1495
+ source,
1496
+ data_model_version
1497
+ )
1498
+ SELECT
1499
+ ts,
1500
+ covered_whales,
1501
+ $5,
1502
+ CASE WHEN $5 > 0 THEN covered_whales::double precision / $5 ELSE 0 END,
1503
+ $1,
1504
+ $2,
1505
+ 'hyperliquid_user_fills',
1506
+ $6
1507
+ FROM coverage
1508
+ ON CONFLICT (
1509
+ universe_fingerprint,
1510
+ whale_registry_fingerprint,
1511
+ ts
1512
+ ) DO UPDATE SET
1513
+ covered_whales = EXCLUDED.covered_whales,
1514
+ expected_whales = EXCLUDED.expected_whales,
1515
+ coverage_pct = EXCLUDED.coverage_pct,
1516
+ source = EXCLUDED.source,
1517
+ data_model_version = EXCLUDED.data_model_version,
1518
+ ingested_at = now()
1519
+ `,
1520
+ [
1521
+ params.universeFingerprint,
1522
+ params.whaleRegistryFingerprint,
1523
+ chunkFromMs,
1524
+ chunkToMs,
1525
+ params.expectedWhales,
1526
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
1527
+ ]
1528
+ );
1529
+ const chunkBuckets = Math.ceil((chunkToMs - chunkFromMs) / minuteMs);
1530
+ completedBuckets = Math.min(totalBuckets, completedBuckets + chunkBuckets);
1531
+ rows += result.rowCount ?? 0;
1532
+ params.onProgress?.({
1533
+ chunkIndex: chunkIndex + 1,
1534
+ totalChunks,
1535
+ completedBuckets,
1536
+ totalBuckets,
1537
+ rows
1538
+ });
1539
+ }
1540
+ return rows;
1541
+ }
1542
+ async function upsertHyperliquidWhaleCoverageRows(rows) {
1543
+ if (!rows.length) return;
1544
+ await ensureHyperliquidWhaleSchema();
1545
+ const cols = [
1546
+ "ts",
1547
+ "covered_whales",
1548
+ "expected_whales",
1549
+ "coverage_pct",
1550
+ "universe_fingerprint",
1551
+ "whale_registry_fingerprint",
1552
+ "source",
1553
+ "data_model_version"
1554
+ ];
1555
+ const maxRows = getSafeBulkInsertRows(cols.length);
1556
+ if (rows.length > maxRows) {
1557
+ for (let index = 0; index < rows.length; index += maxRows) {
1558
+ await upsertHyperliquidWhaleCoverageRows(
1559
+ rows.slice(index, index + maxRows)
1560
+ );
1561
+ }
1562
+ return;
1563
+ }
1564
+ const values = [];
1565
+ const tuples = rows.map((row, rowIndex) => {
1566
+ const offset = rowIndex * cols.length;
1567
+ values.push(
1568
+ row.ts,
1569
+ row.coveredWhales,
1570
+ row.expectedWhales,
1571
+ row.coveragePct,
1572
+ row.universeFingerprint,
1573
+ row.whaleRegistryFingerprint,
1574
+ row.source ?? null,
1575
+ row.dataModelVersion ?? import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
1576
+ );
1577
+ return `(${cols.map((_, colIndex) => `$${offset + colIndex + 1}`).join(",")})`;
1578
+ });
1579
+ await getPool().query(
1580
+ `
1581
+ INSERT INTO hyperliquid_whale_coverage_1m (${cols.join(",")})
1582
+ VALUES ${tuples.join(",")}
1583
+ ON CONFLICT (
1584
+ universe_fingerprint,
1585
+ whale_registry_fingerprint,
1586
+ ts
1587
+ ) DO UPDATE SET
1588
+ covered_whales = EXCLUDED.covered_whales,
1589
+ expected_whales = EXCLUDED.expected_whales,
1590
+ coverage_pct = EXCLUDED.coverage_pct,
1591
+ source = EXCLUDED.source,
1592
+ data_model_version = EXCLUDED.data_model_version,
1593
+ ingested_at = now()
1594
+ `,
1595
+ values
1596
+ );
1597
+ }
1598
+ async function getHyperliquidWhaleCoverageSeriesRows(params) {
1599
+ await prepareMarketContextSchemaForRead("hyperliquidWhales");
1600
+ const result = await queryMarketContext(
1601
+ `
1602
+ SELECT
1603
+ ts,
1604
+ covered_whales,
1605
+ expected_whales,
1606
+ coverage_pct
1607
+ FROM hyperliquid_whale_coverage_1m
1608
+ WHERE universe_fingerprint = $1
1609
+ AND whale_registry_fingerprint = $2
1610
+ AND data_model_version = $3
1611
+ AND ts >= to_timestamp($4/1000.0)
1612
+ AND ts < to_timestamp($5/1000.0)
1613
+ ORDER BY ts
1614
+ `,
1615
+ [
1616
+ params.universeFingerprint,
1617
+ params.whaleRegistryFingerprint,
1618
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION,
1619
+ params.fromMs,
1620
+ params.toMs
1621
+ ],
1622
+ params
1623
+ );
1624
+ return result.rows.map((row) => ({
1625
+ ts: new Date(row.ts),
1626
+ coveredWhales: Number(row.covered_whales) || 0,
1627
+ expectedWhales: Number(row.expected_whales) || 0,
1628
+ coveragePct: Number(row.coverage_pct) || 0
1629
+ }));
1630
+ }
1631
+ async function getHyperliquidWhaleFlowSeriesRows(params) {
1632
+ await prepareMarketContextSchemaForRead("hyperliquidWhales");
1633
+ const result = await queryMarketContext(
1634
+ `
1635
+ SELECT
1636
+ ts,
1637
+ trades,
1638
+ whale_sides,
1639
+ whale_addresses,
1640
+ buy_notional_usd,
1641
+ sell_notional_usd,
1642
+ position_aware_whale_sides,
1643
+ long_entry_whale_addresses,
1644
+ short_entry_whale_addresses,
1645
+ long_exit_whale_addresses,
1646
+ short_exit_whale_addresses,
1647
+ long_entry_notional_usd,
1648
+ short_entry_notional_usd,
1649
+ long_exit_notional_usd,
1650
+ short_exit_notional_usd
1651
+ FROM hyperliquid_whale_flow
1652
+ WHERE symbol = $1
1653
+ AND interval = '1m'
1654
+ AND universe_fingerprint = $2
1655
+ AND whale_registry_fingerprint = $3
1656
+ AND ts >= to_timestamp($4/1000.0)
1657
+ AND ts < to_timestamp($5/1000.0)
1658
+ ORDER BY ts
1659
+ `,
1660
+ [
1661
+ params.symbol,
1662
+ params.universeFingerprint,
1663
+ params.whaleRegistryFingerprint,
1664
+ params.fromMs,
1665
+ params.toMs
1666
+ ],
1667
+ params
1668
+ );
1669
+ return result.rows.map((row) => ({
1670
+ ts: new Date(row.ts),
1671
+ trades: Number(row.trades) || 0,
1672
+ whaleSides: Number(row.whale_sides) || 0,
1673
+ whaleAddresses: Array.isArray(row.whale_addresses) ? row.whale_addresses.map(String) : [],
1674
+ buyNotionalUsd: Number(row.buy_notional_usd) || 0,
1675
+ sellNotionalUsd: Number(row.sell_notional_usd) || 0,
1676
+ positionAwareWhaleSides: Number(row.position_aware_whale_sides) || 0,
1677
+ longEntryWhaleAddresses: Array.isArray(row.long_entry_whale_addresses) ? row.long_entry_whale_addresses.map(String) : [],
1678
+ shortEntryWhaleAddresses: Array.isArray(row.short_entry_whale_addresses) ? row.short_entry_whale_addresses.map(String) : [],
1679
+ longExitWhaleAddresses: Array.isArray(row.long_exit_whale_addresses) ? row.long_exit_whale_addresses.map(String) : [],
1680
+ shortExitWhaleAddresses: Array.isArray(row.short_exit_whale_addresses) ? row.short_exit_whale_addresses.map(String) : [],
1681
+ longEntryNotionalUsd: Number(row.long_entry_notional_usd) || 0,
1682
+ shortEntryNotionalUsd: Number(row.short_entry_notional_usd) || 0,
1683
+ longExitNotionalUsd: Number(row.long_exit_notional_usd) || 0,
1684
+ shortExitNotionalUsd: Number(row.short_exit_notional_usd) || 0
1685
+ }));
1686
+ }
1687
+ async function getHyperliquidWhaleFlowAggregate(params) {
1688
+ await prepareMarketContextSchemaForRead("hyperliquidWhales");
1689
+ const intervalMs = HYPERLIQUID_CONTEXT_INTERVAL_MS[params.interval];
1690
+ const expectedBuckets = Math.ceil(intervalMs / 6e4);
1691
+ const res = await queryMarketContext(
1692
+ `
1693
+ WITH coverage_rows AS (
1694
+ SELECT *
1695
+ FROM hyperliquid_whale_coverage_1m
1696
+ WHERE universe_fingerprint = $2
1697
+ AND whale_registry_fingerprint = $3
1698
+ AND data_model_version = $6
1699
+ AND ts >= to_timestamp(
1700
+ ($4::double precision - $5::double precision) / 1000.0
1701
+ )
1702
+ AND ts < to_timestamp($4/1000.0)
1703
+ ), coverage_summary AS (
1704
+ SELECT
1705
+ COUNT(*)::int AS coverage_buckets,
1706
+ MAX(ts) AS coverage_as_of_ts,
1707
+ MIN(covered_whales)::int AS covered_whales,
1708
+ MAX(expected_whales)::int AS expected_whales,
1709
+ MIN(coverage_pct) AS coverage_pct
1710
+ FROM coverage_rows
1711
+ ), window_rows AS (
1712
+ SELECT *
1713
+ FROM hyperliquid_whale_flow
1714
+ WHERE symbol = $1
1715
+ AND interval = '1m'
1716
+ AND universe_fingerprint = $2
1717
+ AND whale_registry_fingerprint = $3
1718
+ AND ts >= to_timestamp(
1719
+ ($4::double precision - $5::double precision) / 1000.0
1720
+ )
1721
+ AND ts < to_timestamp($4/1000.0)
1722
+ ), unique_addresses AS (
1723
+ SELECT COUNT(DISTINCT address)::int AS unique_whales
1724
+ FROM window_rows
1725
+ CROSS JOIN LATERAL UNNEST(whale_addresses) AS expanded(address)
1726
+ ), directional_counts AS (
1727
+ SELECT
1728
+ (
1729
+ SELECT COUNT(DISTINCT address)::int
1730
+ FROM window_rows
1731
+ CROSS JOIN LATERAL UNNEST(long_entry_whale_addresses) AS expanded(address)
1732
+ ) AS long_entry_whales,
1733
+ (
1734
+ SELECT COUNT(DISTINCT address)::int
1735
+ FROM window_rows
1736
+ CROSS JOIN LATERAL UNNEST(short_entry_whale_addresses) AS expanded(address)
1737
+ ) AS short_entry_whales,
1738
+ (
1739
+ SELECT COUNT(DISTINCT address)::int
1740
+ FROM window_rows
1741
+ CROSS JOIN LATERAL UNNEST(long_exit_whale_addresses) AS expanded(address)
1742
+ ) AS long_exit_whales,
1743
+ (
1744
+ SELECT COUNT(DISTINCT address)::int
1745
+ FROM window_rows
1746
+ CROSS JOIN LATERAL UNNEST(short_exit_whale_addresses) AS expanded(address)
1747
+ ) AS short_exit_whales
1748
+ )
1749
+ SELECT
1750
+ $1::text AS symbol,
1751
+ coverage_summary.coverage_as_of_ts AS as_of_ts,
1752
+ coverage_summary.coverage_buckets,
1753
+ coverage_summary.covered_whales,
1754
+ coverage_summary.expected_whales,
1755
+ coverage_summary.coverage_pct,
1756
+ COALESCE((SELECT SUM(trades) FROM window_rows), 0)::int AS trades,
1757
+ COALESCE((SELECT SUM(whale_sides) FROM window_rows), 0)::int AS whale_sides,
1758
+ COALESCE((SELECT unique_whales FROM unique_addresses), 0)::int AS unique_whales,
1759
+ COALESCE((SELECT SUM(buy_notional_usd) FROM window_rows), 0) AS buy_notional_usd,
1760
+ COALESCE((SELECT SUM(sell_notional_usd) FROM window_rows), 0) AS sell_notional_usd,
1761
+ COALESCE((SELECT SUM(net_notional_usd) FROM window_rows), 0) AS net_notional_usd,
1762
+ CASE
1763
+ WHEN COALESCE((SELECT SUM(buy_notional_usd + sell_notional_usd) FROM window_rows), 0) > 0
1764
+ THEN (SELECT SUM(buy_notional_usd) FROM window_rows) /
1765
+ (SELECT SUM(buy_notional_usd + sell_notional_usd) FROM window_rows)
1766
+ ELSE NULL
1767
+ END AS buy_share_pct,
1768
+ COALESCE((SELECT SUM(position_aware_whale_sides) FROM window_rows), 0)::int
1769
+ AS position_aware_whale_sides,
1770
+ CASE
1771
+ WHEN COALESCE((SELECT SUM(whale_sides) FROM window_rows), 0) > 0
1772
+ THEN COALESCE((SELECT SUM(position_aware_whale_sides) FROM window_rows), 0)::double precision /
1773
+ (SELECT SUM(whale_sides) FROM window_rows)
1774
+ ELSE 0
1775
+ END AS position_aware_pct,
1776
+ COALESCE((SELECT long_entry_whales FROM directional_counts), 0)::int AS long_entry_whales,
1777
+ COALESCE((SELECT short_entry_whales FROM directional_counts), 0)::int AS short_entry_whales,
1778
+ COALESCE((SELECT long_exit_whales FROM directional_counts), 0)::int AS long_exit_whales,
1779
+ COALESCE((SELECT short_exit_whales FROM directional_counts), 0)::int AS short_exit_whales,
1780
+ COALESCE((SELECT SUM(long_entry_notional_usd) FROM window_rows), 0)
1781
+ AS long_entry_notional_usd,
1782
+ COALESCE((SELECT SUM(short_entry_notional_usd) FROM window_rows), 0)
1783
+ AS short_entry_notional_usd,
1784
+ COALESCE((SELECT SUM(long_exit_notional_usd) FROM window_rows), 0)
1785
+ AS long_exit_notional_usd,
1786
+ COALESCE((SELECT SUM(short_exit_notional_usd) FROM window_rows), 0)
1787
+ AS short_exit_notional_usd,
1788
+ COALESCE((SELECT SUM(entry_net_notional_usd) FROM window_rows), 0)
1789
+ AS entry_net_notional_usd,
1790
+ CASE
1791
+ WHEN COALESCE((SELECT SUM(long_entry_notional_usd + short_entry_notional_usd) FROM window_rows), 0) > 0
1792
+ THEN (SELECT SUM(long_entry_notional_usd) FROM window_rows) /
1793
+ (SELECT SUM(long_entry_notional_usd + short_entry_notional_usd) FROM window_rows)
1794
+ ELSE NULL
1795
+ END AS entry_long_share_pct,
1796
+ (SELECT MAX(source) FROM window_rows) AS source
1797
+ FROM coverage_summary
1798
+ `,
1799
+ [
1800
+ params.symbol,
1801
+ params.universeFingerprint,
1802
+ params.whaleRegistryFingerprint,
1803
+ params.decisionTimeMs,
1804
+ intervalMs,
1805
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
1806
+ ],
1807
+ params
1808
+ );
1809
+ const row = res.rows[0];
1810
+ if (!row?.as_of_ts || Number(row.coverage_buckets) !== expectedBuckets || Number(row.covered_whales) <= 0) {
1811
+ return null;
1812
+ }
1813
+ const asOfTs = new Date(row.as_of_ts);
1814
+ const ageMs = params.decisionTimeMs - (asOfTs.getTime() + 6e4);
1815
+ return {
1816
+ symbol: params.symbol,
1817
+ interval: params.interval,
1818
+ asOfTs,
1819
+ windowEndTs: new Date(params.decisionTimeMs),
1820
+ trades: Number(row.trades) || 0,
1821
+ whaleSides: Number(row.whale_sides) || 0,
1822
+ uniqueWhales: Number(row.unique_whales) || 0,
1823
+ coveredWhales: Number(row.covered_whales) || 0,
1824
+ expectedWhales: Number(row.expected_whales) || 0,
1825
+ coveragePct: Number(row.coverage_pct) || 0,
1826
+ buyNotionalUsd: Number(row.buy_notional_usd) || 0,
1827
+ sellNotionalUsd: Number(row.sell_notional_usd) || 0,
1828
+ netNotionalUsd: Number(row.net_notional_usd) || 0,
1829
+ buySharePct: row.buy_share_pct == null ? null : Number(row.buy_share_pct) || 0,
1830
+ positionAwareWhaleSides: Number(row.position_aware_whale_sides) || 0,
1831
+ positionAwarePct: Number(row.position_aware_pct) || 0,
1832
+ longEntryWhales: Number(row.long_entry_whales) || 0,
1833
+ shortEntryWhales: Number(row.short_entry_whales) || 0,
1834
+ longExitWhales: Number(row.long_exit_whales) || 0,
1835
+ shortExitWhales: Number(row.short_exit_whales) || 0,
1836
+ longEntryNotionalUsd: Number(row.long_entry_notional_usd) || 0,
1837
+ shortEntryNotionalUsd: Number(row.short_entry_notional_usd) || 0,
1838
+ longExitNotionalUsd: Number(row.long_exit_notional_usd) || 0,
1839
+ shortExitNotionalUsd: Number(row.short_exit_notional_usd) || 0,
1840
+ entryNetNotionalUsd: Number(row.entry_net_notional_usd) || 0,
1841
+ entryLongSharePct: row.entry_long_share_pct == null ? null : Number(row.entry_long_share_pct) || 0,
1842
+ universeFingerprint: params.universeFingerprint,
1843
+ whaleRegistryFingerprint: params.whaleRegistryFingerprint,
1844
+ source: row.source == null ? null : String(row.source),
1845
+ ageMs,
1846
+ stale: ageMs < 0 || (params.maxAgeMs != null && Number.isFinite(params.maxAgeMs) ? ageMs > params.maxAgeMs : false)
1847
+ };
1848
+ }
1849
+ async function hasHyperliquidWhaleBackfillCoverage(params) {
1850
+ await ensureHyperliquidWhaleSchema();
1851
+ const expectedBuckets = Math.max(
1852
+ 0,
1853
+ Math.ceil((params.toMs - params.fromMs) / 6e4)
1854
+ );
1855
+ const result = await getPool().query(
1856
+ `
1857
+ SELECT
1858
+ COUNT(*)::int AS buckets,
1859
+ COUNT(*) FILTER (
1860
+ WHERE covered_whales = expected_whales
1861
+ )::int AS complete_buckets
1862
+ FROM hyperliquid_whale_coverage_1m
1863
+ WHERE universe_fingerprint = $1
1864
+ AND whale_registry_fingerprint = $2
1865
+ AND data_model_version = $5
1866
+ AND ts >= to_timestamp($3/1000.0)
1867
+ AND ts < to_timestamp($4/1000.0)
1868
+ `,
1869
+ [
1870
+ params.universeFingerprint,
1871
+ params.whaleRegistryFingerprint,
1872
+ params.fromMs,
1873
+ params.toMs,
1874
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
1875
+ ]
1876
+ );
1877
+ return Number(result.rows[0]?.buckets) === expectedBuckets && Number(result.rows[0]?.complete_buckets) === expectedBuckets;
1878
+ }
1879
+ // Annotate the CommonJS export names for ESM import in node:
1880
+ 0 && (module.exports = {
1881
+ ensureHyperliquidWhaleSchema,
1882
+ getHyperliquidWhaleCoverageSeriesRows,
1883
+ getHyperliquidWhaleFlowAggregate,
1884
+ getHyperliquidWhaleFlowSeriesRows,
1885
+ getHyperliquidWhaleWalletCoverage,
1886
+ hasHyperliquidWhaleBackfillCoverage,
1887
+ rebuildHyperliquidWhaleCoverageRows,
1888
+ rebuildHyperliquidWhaleFlowRows,
1889
+ upsertHyperliquidWhaleCoverageRows,
1890
+ upsertHyperliquidWhaleFlowRows,
1891
+ upsertHyperliquidWhaleTradeEvents,
1892
+ upsertHyperliquidWhaleWalletCoverage
1893
+ });