@tradejs/infra 2.0.17 → 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,1270 @@
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/derivatives.ts
21
+ var derivatives_exports = {};
22
+ __export(derivatives_exports, {
23
+ applyDerivativesMetricCoverage: () => applyDerivativesMetricCoverage,
24
+ ensureDerivativesSchema: () => ensureDerivativesSchema,
25
+ getDerivativesBackfillCoverage: () => getDerivativesBackfillCoverage,
26
+ getDerivativesDataEdgesForSymbols: () => getDerivativesDataEdgesForSymbols,
27
+ getDerivativesMetricCoverage: () => getDerivativesMetricCoverage,
28
+ getDerivativesRangeForSymbols: () => getDerivativesRangeForSymbols,
29
+ getDerivativesSummary: () => getDerivativesSummary,
30
+ getDerivativesWindow: () => getDerivativesWindow,
31
+ upsertDerivatives: () => upsertDerivatives,
32
+ upsertDerivativesBackfillCoverage: () => upsertDerivativesBackfillCoverage
33
+ });
34
+ module.exports = __toCommonJS(derivatives_exports);
35
+
36
+ // src/timescale/pool.ts
37
+ var import_pg = require("pg");
38
+ var getPool = () => {
39
+ if (!global.__pgPool__) {
40
+ const max = Number(process.env.PG_POOL_MAX ?? 10);
41
+ const connectionTimeoutMillis = Number(
42
+ process.env.PG_CONNECTION_TIMEOUT_MS ?? 3e4
43
+ );
44
+ global.__pgPool__ = new import_pg.Pool({
45
+ host: process.env.PG_HOST || "127.0.0.1",
46
+ port: Number(process.env.PG_PORT ?? 5432),
47
+ user: process.env.PG_USER || "app",
48
+ password: String(process.env.PG_PASSWORD ?? "app"),
49
+ database: process.env.PG_DATABASE || process.env.PG_DB || "app",
50
+ max: Number.isFinite(max) && max > 0 ? Math.floor(max) : 10,
51
+ idleTimeoutMillis: 3e4,
52
+ connectionTimeoutMillis: Number.isFinite(connectionTimeoutMillis) && connectionTimeoutMillis > 0 ? Math.floor(connectionTimeoutMillis) : 3e4
53
+ });
54
+ }
55
+ return global.__pgPool__;
56
+ };
57
+
58
+ // src/timescale/query.ts
59
+ var resolveQueryTimeoutMs = (override) => {
60
+ if (Number.isFinite(override) && Number(override) > 0) {
61
+ return Math.floor(Number(override));
62
+ }
63
+ const configured = Number(process.env.MARKET_CONTEXT_SQL_TIMEOUT_MS);
64
+ return Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : 3e4;
65
+ };
66
+ var createQueryError = (name, message) => {
67
+ const error = new Error(message);
68
+ error.name = name;
69
+ return error;
70
+ };
71
+ var queryMarketContext = async (text, values, options = {}) => {
72
+ const client = await getPool().connect();
73
+ const timeoutMs = resolveQueryTimeoutMs(options.timeoutMs);
74
+ let released = false;
75
+ let rejectCancellation;
76
+ const release = (error) => {
77
+ if (released) return;
78
+ released = true;
79
+ client.release(error);
80
+ };
81
+ const cancellation = new Promise((_resolve, reject) => {
82
+ rejectCancellation = reject;
83
+ });
84
+ const cancel = (error) => {
85
+ release(error);
86
+ rejectCancellation?.(error);
87
+ };
88
+ const onAbort = () => cancel(
89
+ createQueryError("AbortError", "Timescale market-context query aborted")
90
+ );
91
+ const timer = setTimeout(
92
+ () => cancel(
93
+ createQueryError(
94
+ "TimescaleQueryTimeoutError",
95
+ `Timescale market-context query exceeded ${timeoutMs}ms`
96
+ )
97
+ ),
98
+ timeoutMs
99
+ );
100
+ timer.unref?.();
101
+ options.signal?.addEventListener("abort", onAbort, { once: true });
102
+ try {
103
+ if (options.signal?.aborted) {
104
+ const error = createQueryError(
105
+ "AbortError",
106
+ "Timescale market-context query aborted"
107
+ );
108
+ release(error);
109
+ throw error;
110
+ }
111
+ return await Promise.race([client.query(text, values), cancellation]);
112
+ } finally {
113
+ clearTimeout(timer);
114
+ options.signal?.removeEventListener("abort", onAbort);
115
+ release();
116
+ }
117
+ };
118
+
119
+ // src/timescale/values.ts
120
+ var normalizeCandleSymbol = (symbol) => String(symbol || "").trim().toUpperCase();
121
+
122
+ // src/timescale/internal.ts
123
+ var derivativesSchemaReady = false;
124
+ var binanceMarketSchemaReady = false;
125
+ var hyperliquidWhaleSchemaReady = false;
126
+ var derivativesSchemaReadyPromise = null;
127
+ var binanceMarketSchemaReadyPromise = null;
128
+ var hyperliquidWhaleSchemaReadyPromise = null;
129
+ var marketContextSchemaMode = "ensure";
130
+ var verifiedMarketContextSchemas = /* @__PURE__ */ new Set();
131
+ var DERIVATIVES_SCHEMA_LOCK_KEY = 610001;
132
+ var BINANCE_MARKET_SCHEMA_LOCK_KEY = 610003;
133
+ var HYPERLIQUID_WHALE_SCHEMA_LOCK_KEY = 610004;
134
+ var withSchemaLock = async (lockKey, work) => {
135
+ const pool = getPool();
136
+ await pool.query("SELECT pg_advisory_lock($1)", [lockKey]);
137
+ try {
138
+ await work();
139
+ } finally {
140
+ await pool.query("SELECT pg_advisory_unlock($1)", [lockKey]);
141
+ }
142
+ };
143
+ var ensureDerivativesSchema = async () => {
144
+ if (derivativesSchemaReady) return;
145
+ if (derivativesSchemaReadyPromise) {
146
+ await derivativesSchemaReadyPromise;
147
+ return;
148
+ }
149
+ const pool = getPool();
150
+ derivativesSchemaReadyPromise = withSchemaLock(
151
+ DERIVATIVES_SCHEMA_LOCK_KEY,
152
+ async () => {
153
+ if (derivativesSchemaReady) return;
154
+ await pool.query("CREATE EXTENSION IF NOT EXISTS timescaledb");
155
+ await pool.query(`
156
+ CREATE TABLE IF NOT EXISTS derivatives_market (
157
+ symbol text NOT NULL,
158
+ interval text NOT NULL,
159
+ ts timestamptz NOT NULL,
160
+ open_interest double precision,
161
+ funding_rate double precision,
162
+ liq_long double precision,
163
+ liq_short double precision,
164
+ liq_total double precision,
165
+ source text,
166
+ ingested_at timestamptz NOT NULL DEFAULT now(),
167
+ PRIMARY KEY (symbol, interval, ts)
168
+ )
169
+ `);
170
+ await pool.query(`
171
+ SELECT create_hypertable(
172
+ 'derivatives_market',
173
+ 'ts',
174
+ if_not_exists => TRUE,
175
+ chunk_time_interval => interval '14 days'
176
+ )
177
+ `);
178
+ await pool.query(`
179
+ CREATE INDEX IF NOT EXISTS derivatives_market_symbol_tf_ts_idx
180
+ ON derivatives_market (symbol, interval, ts DESC)
181
+ `);
182
+ await pool.query(`
183
+ CREATE TABLE IF NOT EXISTS derivatives_backfill_coverage (
184
+ source text NOT NULL,
185
+ symbol text NOT NULL,
186
+ interval text NOT NULL,
187
+ from_ts timestamptz NOT NULL,
188
+ to_ts timestamptz NOT NULL,
189
+ rows_count integer NOT NULL DEFAULT 0,
190
+ checked_at timestamptz NOT NULL DEFAULT now(),
191
+ PRIMARY KEY (source, symbol, interval, from_ts, to_ts)
192
+ )
193
+ `);
194
+ await pool.query(`
195
+ CREATE INDEX IF NOT EXISTS derivatives_backfill_coverage_lookup_idx
196
+ ON derivatives_backfill_coverage (source, symbol, interval, from_ts, to_ts)
197
+ `);
198
+ await pool.query(`
199
+ CREATE TABLE IF NOT EXISTS derivatives_metric_coverage (
200
+ source text NOT NULL,
201
+ metric text NOT NULL,
202
+ symbol text NOT NULL,
203
+ interval text NOT NULL,
204
+ from_ts timestamptz NOT NULL,
205
+ to_ts timestamptz NOT NULL,
206
+ event_rows_count integer NOT NULL DEFAULT 0,
207
+ zero_rows_count integer NOT NULL DEFAULT 0,
208
+ checked_at timestamptz NOT NULL DEFAULT now(),
209
+ PRIMARY KEY (source, metric, symbol, interval, from_ts, to_ts)
210
+ )
211
+ `);
212
+ await pool.query(`
213
+ CREATE INDEX IF NOT EXISTS derivatives_metric_coverage_lookup_idx
214
+ ON derivatives_metric_coverage (
215
+ source,
216
+ metric,
217
+ symbol,
218
+ interval,
219
+ from_ts,
220
+ to_ts
221
+ )
222
+ `);
223
+ derivativesSchemaReady = true;
224
+ }
225
+ ).finally(() => {
226
+ derivativesSchemaReadyPromise = null;
227
+ });
228
+ await derivativesSchemaReadyPromise;
229
+ };
230
+ var ensureBinanceMarketSchema = async () => {
231
+ if (binanceMarketSchemaReady) return;
232
+ if (binanceMarketSchemaReadyPromise) {
233
+ await binanceMarketSchemaReadyPromise;
234
+ return;
235
+ }
236
+ const pool = getPool();
237
+ binanceMarketSchemaReadyPromise = withSchemaLock(
238
+ BINANCE_MARKET_SCHEMA_LOCK_KEY,
239
+ async () => {
240
+ if (binanceMarketSchemaReady) return;
241
+ await pool.query("CREATE EXTENSION IF NOT EXISTS timescaledb");
242
+ await pool.query(`
243
+ CREATE TABLE IF NOT EXISTS market_trade_flow (
244
+ symbol text NOT NULL,
245
+ interval text NOT NULL,
246
+ ts timestamptz NOT NULL,
247
+ trades integer NOT NULL,
248
+ buy_base_volume double precision,
249
+ sell_base_volume double precision,
250
+ buy_quote_volume double precision,
251
+ sell_quote_volume double precision,
252
+ net_base_delta double precision,
253
+ net_quote_delta double precision,
254
+ buy_pressure_pct double precision,
255
+ source text,
256
+ ingested_at timestamptz NOT NULL DEFAULT now(),
257
+ PRIMARY KEY (symbol, interval, ts)
258
+ )
259
+ `);
260
+ await pool.query(`
261
+ SELECT create_hypertable(
262
+ 'market_trade_flow',
263
+ 'ts',
264
+ if_not_exists => TRUE,
265
+ chunk_time_interval => interval '7 days'
266
+ )
267
+ `);
268
+ await pool.query(`
269
+ CREATE INDEX IF NOT EXISTS market_trade_flow_symbol_tf_ts_idx
270
+ ON market_trade_flow (symbol, interval, ts DESC)
271
+ `);
272
+ await pool.query(`
273
+ CREATE TABLE IF NOT EXISTS market_breadth (
274
+ universe text NOT NULL,
275
+ interval text NOT NULL,
276
+ ts timestamptz NOT NULL,
277
+ symbols_count integer NOT NULL,
278
+ advancers integer NOT NULL,
279
+ decliners integer NOT NULL,
280
+ unchanged integer NOT NULL,
281
+ advance_decline_ratio double precision,
282
+ pct_above_ma20 double precision,
283
+ pct_above_ma50 double precision,
284
+ equal_weighted_return double precision,
285
+ volume_weighted_return double precision,
286
+ dispersion double precision,
287
+ btc_return_1h double precision,
288
+ btc_return_4h double precision,
289
+ btc_return_24h double precision,
290
+ alt_basket_return_1h double precision,
291
+ alt_basket_return_4h double precision,
292
+ alt_basket_return_24h double precision,
293
+ btc_vs_alt_return_1h double precision,
294
+ btc_vs_alt_return_4h double precision,
295
+ btc_vs_alt_return_24h double precision,
296
+ btc_turnover_share_1h double precision,
297
+ btc_turnover_share_24h double precision,
298
+ btc_turnover_share_change_24h double precision,
299
+ alt_vol_to_btc_vol_24h double precision,
300
+ alt_dispersion_24h double precision,
301
+ btc_alt_regime text,
302
+ source text,
303
+ ingested_at timestamptz NOT NULL DEFAULT now(),
304
+ PRIMARY KEY (universe, interval, ts)
305
+ )
306
+ `);
307
+ await pool.query(`
308
+ ALTER TABLE market_breadth
309
+ ADD COLUMN IF NOT EXISTS btc_return_1h double precision,
310
+ ADD COLUMN IF NOT EXISTS btc_return_4h double precision,
311
+ ADD COLUMN IF NOT EXISTS btc_return_24h double precision,
312
+ ADD COLUMN IF NOT EXISTS alt_basket_return_1h double precision,
313
+ ADD COLUMN IF NOT EXISTS alt_basket_return_4h double precision,
314
+ ADD COLUMN IF NOT EXISTS alt_basket_return_24h double precision,
315
+ ADD COLUMN IF NOT EXISTS btc_vs_alt_return_1h double precision,
316
+ ADD COLUMN IF NOT EXISTS btc_vs_alt_return_4h double precision,
317
+ ADD COLUMN IF NOT EXISTS btc_vs_alt_return_24h double precision,
318
+ ADD COLUMN IF NOT EXISTS btc_turnover_share_1h double precision,
319
+ ADD COLUMN IF NOT EXISTS btc_turnover_share_24h double precision,
320
+ ADD COLUMN IF NOT EXISTS btc_turnover_share_change_24h double precision,
321
+ ADD COLUMN IF NOT EXISTS alt_vol_to_btc_vol_24h double precision,
322
+ ADD COLUMN IF NOT EXISTS alt_dispersion_24h double precision,
323
+ ADD COLUMN IF NOT EXISTS btc_alt_regime text
324
+ `);
325
+ await pool.query(`
326
+ SELECT create_hypertable(
327
+ 'market_breadth',
328
+ 'ts',
329
+ if_not_exists => TRUE,
330
+ chunk_time_interval => interval '14 days'
331
+ )
332
+ `);
333
+ await pool.query(`
334
+ CREATE INDEX IF NOT EXISTS market_breadth_universe_tf_ts_idx
335
+ ON market_breadth (universe, interval, ts DESC)
336
+ `);
337
+ await pool.query(`
338
+ CREATE TABLE IF NOT EXISTS market_global_context (
339
+ source text NOT NULL,
340
+ ts timestamptz NOT NULL,
341
+ updated_at_ts timestamptz,
342
+ active_cryptocurrencies integer,
343
+ active_exchanges integer,
344
+ active_market_pairs integer,
345
+ markets integer,
346
+ total_market_cap_usd double precision,
347
+ total_volume_usd double precision,
348
+ total_volume_reported_usd double precision,
349
+ btc_dominance_pct double precision,
350
+ eth_dominance_pct double precision,
351
+ alt_market_cap_usd double precision,
352
+ alt_volume_usd double precision,
353
+ alt_volume_reported_usd double precision,
354
+ btc_to_alt_market_cap_ratio double precision,
355
+ market_cap_change_pct_24h_usd double precision,
356
+ ingested_at timestamptz NOT NULL DEFAULT now(),
357
+ PRIMARY KEY (source, ts)
358
+ )
359
+ `);
360
+ await pool.query(`
361
+ SELECT create_hypertable(
362
+ 'market_global_context',
363
+ 'ts',
364
+ if_not_exists => TRUE,
365
+ chunk_time_interval => interval '30 days'
366
+ )
367
+ `);
368
+ await pool.query(`
369
+ CREATE INDEX IF NOT EXISTS market_global_context_source_ts_idx
370
+ ON market_global_context (source, ts DESC)
371
+ `);
372
+ await pool.query(`
373
+ ALTER TABLE market_global_context
374
+ ADD COLUMN IF NOT EXISTS active_exchanges integer,
375
+ ADD COLUMN IF NOT EXISTS active_market_pairs integer,
376
+ ADD COLUMN IF NOT EXISTS total_volume_reported_usd double precision,
377
+ ADD COLUMN IF NOT EXISTS alt_volume_usd double precision,
378
+ ADD COLUMN IF NOT EXISTS alt_volume_reported_usd double precision
379
+ `);
380
+ await pool.query(`
381
+ CREATE TABLE IF NOT EXISTS market_reference_asset_context (
382
+ source text NOT NULL,
383
+ symbol text NOT NULL,
384
+ cmc_id integer NOT NULL,
385
+ interval text NOT NULL,
386
+ ts timestamptz NOT NULL,
387
+ open_usd double precision,
388
+ high_usd double precision,
389
+ low_usd double precision,
390
+ close_usd double precision,
391
+ volume_usd double precision,
392
+ market_cap_usd double precision,
393
+ ingested_at timestamptz NOT NULL DEFAULT now(),
394
+ PRIMARY KEY (source, symbol, interval, ts)
395
+ )
396
+ `);
397
+ await pool.query(`
398
+ SELECT create_hypertable(
399
+ 'market_reference_asset_context',
400
+ 'ts',
401
+ if_not_exists => TRUE,
402
+ chunk_time_interval => interval '30 days'
403
+ )
404
+ `);
405
+ await pool.query(`
406
+ CREATE INDEX IF NOT EXISTS market_reference_asset_context_lookup_idx
407
+ ON market_reference_asset_context (source, symbol, interval, ts DESC)
408
+ `);
409
+ await pool.query(`
410
+ CREATE TABLE IF NOT EXISTS market_cmc_exchange_liquidity_context (
411
+ source text NOT NULL,
412
+ interval text NOT NULL,
413
+ ts timestamptz NOT NULL,
414
+ exchanges_count integer NOT NULL,
415
+ total_volume_usd double precision,
416
+ binance_volume_usd double precision,
417
+ binance_volume_share double precision,
418
+ top_exchange_volume_share double precision,
419
+ liquidity_regime text,
420
+ ingested_at timestamptz NOT NULL DEFAULT now(),
421
+ PRIMARY KEY (source, interval, ts)
422
+ )
423
+ `);
424
+ await pool.query(`
425
+ SELECT create_hypertable(
426
+ 'market_cmc_exchange_liquidity_context',
427
+ 'ts',
428
+ if_not_exists => TRUE,
429
+ chunk_time_interval => interval '30 days'
430
+ )
431
+ `);
432
+ await pool.query(`
433
+ CREATE INDEX IF NOT EXISTS market_cmc_exchange_liquidity_context_lookup_idx
434
+ ON market_cmc_exchange_liquidity_context (source, interval, ts DESC)
435
+ `);
436
+ await pool.query(`
437
+ CREATE TABLE IF NOT EXISTS market_cmc_fear_greed_context (
438
+ source text NOT NULL,
439
+ interval text NOT NULL,
440
+ ts timestamptz NOT NULL,
441
+ value integer NOT NULL,
442
+ classification text NOT NULL,
443
+ sentiment_regime text NOT NULL,
444
+ ingested_at timestamptz NOT NULL DEFAULT now(),
445
+ PRIMARY KEY (source, interval, ts)
446
+ )
447
+ `);
448
+ await pool.query(`
449
+ SELECT create_hypertable(
450
+ 'market_cmc_fear_greed_context',
451
+ 'ts',
452
+ if_not_exists => TRUE,
453
+ chunk_time_interval => interval '30 days'
454
+ )
455
+ `);
456
+ await pool.query(`
457
+ CREATE INDEX IF NOT EXISTS market_cmc_fear_greed_context_lookup_idx
458
+ ON market_cmc_fear_greed_context (source, interval, ts DESC)
459
+ `);
460
+ await pool.query(`
461
+ CREATE TABLE IF NOT EXISTS market_cmc_index_context (
462
+ source text NOT NULL,
463
+ index_slug text NOT NULL,
464
+ interval text NOT NULL,
465
+ ts timestamptz NOT NULL,
466
+ value double precision NOT NULL,
467
+ constituents_count integer,
468
+ top_constituent_symbol text,
469
+ top_constituent_weight_pct double precision,
470
+ constituents jsonb,
471
+ ingested_at timestamptz NOT NULL DEFAULT now(),
472
+ PRIMARY KEY (source, index_slug, interval, ts)
473
+ )
474
+ `);
475
+ await pool.query(`
476
+ SELECT create_hypertable(
477
+ 'market_cmc_index_context',
478
+ 'ts',
479
+ if_not_exists => TRUE,
480
+ chunk_time_interval => interval '30 days'
481
+ )
482
+ `);
483
+ await pool.query(`
484
+ CREATE INDEX IF NOT EXISTS market_cmc_index_context_lookup_idx
485
+ ON market_cmc_index_context (source, index_slug, interval, ts DESC)
486
+ `);
487
+ await pool.query(`
488
+ CREATE TABLE IF NOT EXISTS market_context_backfill_coverage (
489
+ source text NOT NULL,
490
+ scope text NOT NULL,
491
+ interval text NOT NULL,
492
+ from_ts timestamptz NOT NULL,
493
+ to_ts timestamptz NOT NULL,
494
+ rows_count integer NOT NULL DEFAULT 0,
495
+ checked_at timestamptz NOT NULL DEFAULT now(),
496
+ PRIMARY KEY (source, scope, interval, from_ts, to_ts)
497
+ )
498
+ `);
499
+ await pool.query(`
500
+ CREATE INDEX IF NOT EXISTS market_context_backfill_coverage_lookup_idx
501
+ ON market_context_backfill_coverage (source, scope, interval, from_ts, to_ts)
502
+ `);
503
+ binanceMarketSchemaReady = true;
504
+ }
505
+ ).finally(() => {
506
+ binanceMarketSchemaReadyPromise = null;
507
+ });
508
+ await binanceMarketSchemaReadyPromise;
509
+ };
510
+ var ensureHyperliquidWhaleSchema = async () => {
511
+ if (hyperliquidWhaleSchemaReady) return;
512
+ if (hyperliquidWhaleSchemaReadyPromise) {
513
+ await hyperliquidWhaleSchemaReadyPromise;
514
+ return;
515
+ }
516
+ hyperliquidWhaleSchemaReadyPromise = withSchemaLock(
517
+ HYPERLIQUID_WHALE_SCHEMA_LOCK_KEY,
518
+ async () => {
519
+ if (hyperliquidWhaleSchemaReady) return;
520
+ const pool = getPool();
521
+ await pool.query("CREATE EXTENSION IF NOT EXISTS timescaledb");
522
+ await pool.query(`
523
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_trade_events (
524
+ symbol text NOT NULL,
525
+ ts timestamptz NOT NULL,
526
+ tid text NOT NULL,
527
+ price double precision NOT NULL,
528
+ size double precision NOT NULL,
529
+ notional_usd double precision NOT NULL,
530
+ buyer_address text,
531
+ seller_address text,
532
+ buyer_tracked boolean NOT NULL,
533
+ seller_tracked boolean NOT NULL,
534
+ buyer_start_position double precision,
535
+ buyer_end_position double precision,
536
+ buyer_position_action text,
537
+ buyer_closed_pnl double precision,
538
+ buyer_liquidation boolean,
539
+ seller_start_position double precision,
540
+ seller_end_position double precision,
541
+ seller_position_action text,
542
+ seller_closed_pnl double precision,
543
+ seller_liquidation boolean,
544
+ universe_fingerprint text NOT NULL,
545
+ whale_registry_fingerprint text NOT NULL,
546
+ source text,
547
+ ingested_at timestamptz NOT NULL DEFAULT now(),
548
+ PRIMARY KEY (
549
+ universe_fingerprint,
550
+ whale_registry_fingerprint,
551
+ symbol,
552
+ ts,
553
+ tid
554
+ )
555
+ )
556
+ `);
557
+ await pool.query(`
558
+ ALTER TABLE hyperliquid_whale_trade_events
559
+ ADD COLUMN IF NOT EXISTS buyer_start_position double precision,
560
+ ADD COLUMN IF NOT EXISTS buyer_end_position double precision,
561
+ ADD COLUMN IF NOT EXISTS buyer_position_action text,
562
+ ADD COLUMN IF NOT EXISTS buyer_closed_pnl double precision,
563
+ ADD COLUMN IF NOT EXISTS buyer_liquidation boolean,
564
+ ADD COLUMN IF NOT EXISTS seller_start_position double precision,
565
+ ADD COLUMN IF NOT EXISTS seller_end_position double precision,
566
+ ADD COLUMN IF NOT EXISTS seller_position_action text,
567
+ ADD COLUMN IF NOT EXISTS seller_closed_pnl double precision,
568
+ ADD COLUMN IF NOT EXISTS seller_liquidation boolean
569
+ `);
570
+ await pool.query(`
571
+ SELECT create_hypertable(
572
+ 'hyperliquid_whale_trade_events',
573
+ 'ts',
574
+ if_not_exists => TRUE,
575
+ chunk_time_interval => interval '1 day'
576
+ )
577
+ `);
578
+ await pool.query(`
579
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_events_lookup_idx
580
+ ON hyperliquid_whale_trade_events (
581
+ universe_fingerprint,
582
+ whale_registry_fingerprint,
583
+ symbol,
584
+ ts DESC
585
+ )
586
+ `);
587
+ await pool.query(`
588
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_flow (
589
+ symbol text NOT NULL,
590
+ interval text NOT NULL,
591
+ ts timestamptz NOT NULL,
592
+ trades integer NOT NULL,
593
+ whale_sides integer NOT NULL,
594
+ unique_whales integer NOT NULL,
595
+ whale_addresses text[] NOT NULL DEFAULT '{}',
596
+ buy_notional_usd double precision NOT NULL,
597
+ sell_notional_usd double precision NOT NULL,
598
+ net_notional_usd double precision NOT NULL,
599
+ buy_share_pct double precision,
600
+ position_aware_whale_sides integer NOT NULL DEFAULT 0,
601
+ long_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
602
+ short_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
603
+ long_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
604
+ short_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
605
+ long_entry_notional_usd double precision NOT NULL DEFAULT 0,
606
+ short_entry_notional_usd double precision NOT NULL DEFAULT 0,
607
+ long_exit_notional_usd double precision NOT NULL DEFAULT 0,
608
+ short_exit_notional_usd double precision NOT NULL DEFAULT 0,
609
+ entry_net_notional_usd double precision NOT NULL DEFAULT 0,
610
+ entry_long_share_pct double precision,
611
+ universe_fingerprint text NOT NULL,
612
+ whale_registry_fingerprint text NOT NULL,
613
+ source text,
614
+ ingested_at timestamptz NOT NULL DEFAULT now(),
615
+ PRIMARY KEY (
616
+ universe_fingerprint,
617
+ whale_registry_fingerprint,
618
+ symbol,
619
+ interval,
620
+ ts
621
+ )
622
+ )
623
+ `);
624
+ await pool.query(`
625
+ ALTER TABLE hyperliquid_whale_flow
626
+ ADD COLUMN IF NOT EXISTS position_aware_whale_sides integer NOT NULL DEFAULT 0,
627
+ ADD COLUMN IF NOT EXISTS long_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
628
+ ADD COLUMN IF NOT EXISTS short_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
629
+ ADD COLUMN IF NOT EXISTS long_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
630
+ ADD COLUMN IF NOT EXISTS short_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
631
+ ADD COLUMN IF NOT EXISTS long_entry_notional_usd double precision NOT NULL DEFAULT 0,
632
+ ADD COLUMN IF NOT EXISTS short_entry_notional_usd double precision NOT NULL DEFAULT 0,
633
+ ADD COLUMN IF NOT EXISTS long_exit_notional_usd double precision NOT NULL DEFAULT 0,
634
+ ADD COLUMN IF NOT EXISTS short_exit_notional_usd double precision NOT NULL DEFAULT 0,
635
+ ADD COLUMN IF NOT EXISTS entry_net_notional_usd double precision NOT NULL DEFAULT 0,
636
+ ADD COLUMN IF NOT EXISTS entry_long_share_pct double precision
637
+ `);
638
+ await pool.query(`
639
+ SELECT create_hypertable(
640
+ 'hyperliquid_whale_flow',
641
+ 'ts',
642
+ if_not_exists => TRUE,
643
+ chunk_time_interval => interval '7 days'
644
+ )
645
+ `);
646
+ await pool.query(`
647
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_flow_lookup_idx
648
+ ON hyperliquid_whale_flow (
649
+ universe_fingerprint,
650
+ whale_registry_fingerprint,
651
+ symbol,
652
+ interval,
653
+ ts DESC
654
+ )
655
+ `);
656
+ await pool.query(`
657
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_wallet_coverage (
658
+ universe_fingerprint text NOT NULL,
659
+ whale_registry_fingerprint text NOT NULL,
660
+ address text NOT NULL,
661
+ requested_from_ts timestamptz NOT NULL,
662
+ requested_to_ts timestamptz NOT NULL,
663
+ covered_from_ts timestamptz,
664
+ covered_to_ts timestamptz,
665
+ status text NOT NULL CHECK (status IN ('complete', 'truncated', 'failed')),
666
+ fills_count integer NOT NULL DEFAULT 0,
667
+ error text,
668
+ data_model_version integer NOT NULL DEFAULT 2,
669
+ checked_at timestamptz NOT NULL DEFAULT now(),
670
+ PRIMARY KEY (
671
+ universe_fingerprint,
672
+ whale_registry_fingerprint,
673
+ address,
674
+ requested_from_ts,
675
+ requested_to_ts
676
+ )
677
+ )
678
+ `);
679
+ await pool.query(`
680
+ ALTER TABLE hyperliquid_whale_wallet_coverage
681
+ ADD COLUMN IF NOT EXISTS data_model_version integer NOT NULL DEFAULT 2
682
+ `);
683
+ await pool.query(`
684
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_wallet_coverage_lookup_idx
685
+ ON hyperliquid_whale_wallet_coverage (
686
+ universe_fingerprint,
687
+ whale_registry_fingerprint,
688
+ address,
689
+ requested_from_ts,
690
+ requested_to_ts
691
+ )
692
+ `);
693
+ await pool.query(`
694
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_coverage_1m (
695
+ ts timestamptz NOT NULL,
696
+ covered_whales integer NOT NULL,
697
+ expected_whales integer NOT NULL,
698
+ coverage_pct double precision NOT NULL,
699
+ universe_fingerprint text NOT NULL,
700
+ whale_registry_fingerprint text NOT NULL,
701
+ source text,
702
+ data_model_version integer NOT NULL DEFAULT 2,
703
+ ingested_at timestamptz NOT NULL DEFAULT now(),
704
+ PRIMARY KEY (
705
+ universe_fingerprint,
706
+ whale_registry_fingerprint,
707
+ ts
708
+ )
709
+ )
710
+ `);
711
+ await pool.query(`
712
+ ALTER TABLE hyperliquid_whale_coverage_1m
713
+ ADD COLUMN IF NOT EXISTS data_model_version integer NOT NULL DEFAULT 2
714
+ `);
715
+ await pool.query(`
716
+ SELECT create_hypertable(
717
+ 'hyperliquid_whale_coverage_1m',
718
+ 'ts',
719
+ if_not_exists => TRUE,
720
+ chunk_time_interval => interval '7 days'
721
+ )
722
+ `);
723
+ await pool.query(`
724
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_coverage_1m_lookup_idx
725
+ ON hyperliquid_whale_coverage_1m (
726
+ universe_fingerprint,
727
+ whale_registry_fingerprint,
728
+ ts DESC
729
+ )
730
+ `);
731
+ hyperliquidWhaleSchemaReady = true;
732
+ }
733
+ ).finally(() => {
734
+ hyperliquidWhaleSchemaReadyPromise = null;
735
+ });
736
+ await hyperliquidWhaleSchemaReadyPromise;
737
+ };
738
+ var ensureCoinMarketCapContextSchema = async () => ensureBinanceMarketSchema();
739
+ var ensureMarketContextSchema = async (source) => {
740
+ switch (source) {
741
+ case "binance":
742
+ return ensureBinanceMarketSchema();
743
+ case "coinmarketcap":
744
+ return ensureCoinMarketCapContextSchema();
745
+ case "derivatives":
746
+ return ensureDerivativesSchema();
747
+ case "hyperliquidWhales":
748
+ return ensureHyperliquidWhaleSchema();
749
+ }
750
+ };
751
+ var MARKET_CONTEXT_SCHEMA_TABLES = {
752
+ binance: ["market_trade_flow", "market_breadth"],
753
+ coinmarketcap: [
754
+ "market_global_context",
755
+ "market_reference_asset_context",
756
+ "market_cmc_exchange_liquidity_context",
757
+ "market_cmc_fear_greed_context",
758
+ "market_cmc_index_context"
759
+ ],
760
+ derivatives: ["derivatives_market"],
761
+ hyperliquidWhales: [
762
+ "hyperliquid_whale_flow",
763
+ "hyperliquid_whale_coverage_1m"
764
+ ]
765
+ };
766
+ var verifyMarketContextSchema = async (source) => {
767
+ if (verifiedMarketContextSchemas.has(source)) return;
768
+ const tables = MARKET_CONTEXT_SCHEMA_TABLES[source];
769
+ const result = await queryMarketContext(
770
+ `
771
+ SELECT table_name AS "tableName"
772
+ FROM unnest($1::text[]) AS requested(table_name)
773
+ WHERE to_regclass(requested.table_name) IS NULL
774
+ `,
775
+ [tables]
776
+ );
777
+ if (result.rows.length) {
778
+ throw new Error(
779
+ `Timescale ${source} schema is not prepared; missing: ${result.rows.map((row) => row.tableName).filter(Boolean).join(", ")}`
780
+ );
781
+ }
782
+ verifiedMarketContextSchemas.add(source);
783
+ };
784
+ var prepareMarketContextSchemaForRead = async (source) => marketContextSchemaMode === "verify" ? verifyMarketContextSchema(source) : ensureMarketContextSchema(source);
785
+
786
+ // src/timescale/derivatives.ts
787
+ async function upsertDerivatives(rows) {
788
+ if (!rows.length) return;
789
+ await ensureDerivativesSchema();
790
+ const pool = getPool();
791
+ const cols = [
792
+ "symbol",
793
+ "interval",
794
+ "ts",
795
+ "open_interest",
796
+ "funding_rate",
797
+ "liq_long",
798
+ "liq_short",
799
+ "liq_total",
800
+ "source"
801
+ ];
802
+ const maxRows = Math.floor(65535 / cols.length);
803
+ if (rows.length > maxRows) {
804
+ for (let i = 0; i < rows.length; i += maxRows) {
805
+ await upsertDerivatives(rows.slice(i, i + maxRows));
806
+ }
807
+ return;
808
+ }
809
+ const valuesSql = rows.map(
810
+ (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
811
+ ).join(",");
812
+ const flat = rows.flatMap((row) => [
813
+ row.symbol,
814
+ row.interval,
815
+ row.ts,
816
+ row.openInterest ?? null,
817
+ row.fundingRate ?? null,
818
+ row.liqLong ?? null,
819
+ row.liqShort ?? null,
820
+ row.liqTotal ?? null,
821
+ row.source ?? null
822
+ ]);
823
+ const sql = `
824
+ INSERT INTO derivatives_market (${cols.join(",")})
825
+ VALUES ${valuesSql}
826
+ ON CONFLICT (symbol, interval, ts) DO UPDATE SET
827
+ open_interest = COALESCE(EXCLUDED.open_interest, derivatives_market.open_interest),
828
+ funding_rate = COALESCE(EXCLUDED.funding_rate, derivatives_market.funding_rate),
829
+ liq_long = COALESCE(EXCLUDED.liq_long, derivatives_market.liq_long),
830
+ liq_short = COALESCE(EXCLUDED.liq_short, derivatives_market.liq_short),
831
+ liq_total = COALESCE(EXCLUDED.liq_total, derivatives_market.liq_total),
832
+ source = COALESCE(EXCLUDED.source, derivatives_market.source),
833
+ ingested_at = now()
834
+ `;
835
+ await pool.query(sql, flat);
836
+ }
837
+ async function getDerivativesRangeForSymbols(symbols, interval, startMs, endMs) {
838
+ if (!symbols.length)
839
+ return [];
840
+ await ensureDerivativesSchema();
841
+ const pool = getPool();
842
+ const sql = `
843
+ SELECT symbol, interval, ts, open_interest, funding_rate, liq_long, liq_short, liq_total
844
+ FROM derivatives_market
845
+ WHERE symbol = ANY($1)
846
+ AND interval = $2
847
+ AND ts >= to_timestamp($3/1000.0)
848
+ AND ts <= to_timestamp($4/1000.0)
849
+ ORDER BY symbol ASC, ts ASC
850
+ `;
851
+ const res = await pool.query(sql, [symbols, interval, startMs, endMs]);
852
+ return res.rows;
853
+ }
854
+ async function getDerivativesDataEdgesForSymbols(symbols, interval) {
855
+ const normalizedSymbols = [
856
+ ...new Set(
857
+ symbols.map(
858
+ (symbol) => String(symbol || "").trim().toUpperCase()
859
+ ).filter(Boolean)
860
+ )
861
+ ];
862
+ const edges = /* @__PURE__ */ new Map();
863
+ if (!normalizedSymbols.length) return edges;
864
+ await ensureDerivativesSchema();
865
+ const pool = getPool();
866
+ const sql = `
867
+ SELECT
868
+ symbol,
869
+ extract(epoch from MIN(ts))*1000 AS min,
870
+ extract(epoch from MAX(ts))*1000 AS max
871
+ FROM derivatives_market
872
+ WHERE symbol = ANY($1)
873
+ AND interval = $2
874
+ GROUP BY symbol
875
+ `;
876
+ const res = await pool.query(sql, [normalizedSymbols, interval]);
877
+ for (const row of res.rows) {
878
+ const min = Number(row.min);
879
+ const max = Number(row.max);
880
+ edges.set(String(row.symbol).toUpperCase(), {
881
+ min: Number.isFinite(min) ? min : void 0,
882
+ max: Number.isFinite(max) ? max : void 0
883
+ });
884
+ }
885
+ return edges;
886
+ }
887
+ async function getDerivativesBackfillCoverage(params) {
888
+ const normalizedSource = String(params.source || "").trim().toLowerCase();
889
+ const normalizedSymbols = [
890
+ ...new Set(
891
+ params.symbols.map(
892
+ (symbol) => String(symbol || "").trim().toUpperCase()
893
+ ).filter(Boolean)
894
+ )
895
+ ];
896
+ if (!normalizedSource || !normalizedSymbols.length) {
897
+ return [];
898
+ }
899
+ await ensureDerivativesSchema();
900
+ const pool = getPool();
901
+ const res = await pool.query(
902
+ `
903
+ SELECT
904
+ symbol,
905
+ interval,
906
+ extract(epoch from from_ts)*1000 AS from_ms,
907
+ extract(epoch from to_ts)*1000 AS to_ms,
908
+ rows_count
909
+ FROM derivatives_backfill_coverage
910
+ WHERE source = $1
911
+ AND symbol = ANY($2)
912
+ AND interval = $3
913
+ AND from_ts <= to_timestamp($5/1000.0)
914
+ AND to_ts >= to_timestamp($4/1000.0)
915
+ `,
916
+ [
917
+ normalizedSource,
918
+ normalizedSymbols,
919
+ params.interval,
920
+ params.fromMs,
921
+ params.toMs
922
+ ]
923
+ );
924
+ return res.rows.map((row) => ({
925
+ symbol: String(row.symbol).toUpperCase(),
926
+ interval: row.interval,
927
+ fromMs: Number(row.from_ms),
928
+ toMs: Number(row.to_ms),
929
+ rowsCount: Number(row.rows_count ?? 0)
930
+ }));
931
+ }
932
+ async function upsertDerivativesBackfillCoverage(rows) {
933
+ if (!rows.length) return;
934
+ await ensureDerivativesSchema();
935
+ const pool = getPool();
936
+ const cols = [
937
+ "source",
938
+ "symbol",
939
+ "interval",
940
+ "from_ts",
941
+ "to_ts",
942
+ "rows_count"
943
+ ];
944
+ const valuesSql = rows.map(
945
+ (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
946
+ ).join(",");
947
+ const flat = rows.flatMap((row) => [
948
+ String(row.source || "").trim().toLowerCase(),
949
+ String(row.symbol || "").trim().toUpperCase(),
950
+ row.interval,
951
+ new Date(row.fromMs),
952
+ new Date(row.toMs),
953
+ Math.max(0, Math.trunc(row.rowsCount))
954
+ ]);
955
+ await pool.query(
956
+ `
957
+ INSERT INTO derivatives_backfill_coverage (${cols.join(",")})
958
+ VALUES ${valuesSql}
959
+ ON CONFLICT (source, symbol, interval, from_ts, to_ts) DO UPDATE SET
960
+ rows_count = EXCLUDED.rows_count,
961
+ checked_at = now()
962
+ `,
963
+ flat
964
+ );
965
+ }
966
+ async function getDerivativesMetricCoverage(params) {
967
+ const normalizedSource = String(params.source || "").trim().toLowerCase();
968
+ const normalizedSymbols = [
969
+ ...new Set(
970
+ params.symbols.map(
971
+ (symbol) => String(symbol || "").trim().toUpperCase()
972
+ ).filter(Boolean)
973
+ )
974
+ ];
975
+ if (!normalizedSource || !normalizedSymbols.length) {
976
+ return [];
977
+ }
978
+ await ensureDerivativesSchema();
979
+ const pool = getPool();
980
+ const res = await pool.query(
981
+ `
982
+ SELECT
983
+ symbol,
984
+ interval,
985
+ extract(epoch from from_ts)*1000 AS from_ms,
986
+ extract(epoch from to_ts)*1000 AS to_ms,
987
+ event_rows_count,
988
+ zero_rows_count
989
+ FROM derivatives_metric_coverage
990
+ WHERE source = $1
991
+ AND metric = $2
992
+ AND symbol = ANY($3)
993
+ AND interval = $4
994
+ AND from_ts <= to_timestamp($6/1000.0)
995
+ AND to_ts >= to_timestamp($5/1000.0)
996
+ `,
997
+ [
998
+ normalizedSource,
999
+ params.metric,
1000
+ normalizedSymbols,
1001
+ params.interval,
1002
+ params.fromMs,
1003
+ params.toMs
1004
+ ]
1005
+ );
1006
+ return res.rows.map((row) => ({
1007
+ symbol: String(row.symbol).toUpperCase(),
1008
+ interval: row.interval,
1009
+ fromMs: Number(row.from_ms),
1010
+ toMs: Number(row.to_ms),
1011
+ eventRowsCount: Number(row.event_rows_count ?? 0),
1012
+ zeroRowsCount: Number(row.zero_rows_count ?? 0)
1013
+ }));
1014
+ }
1015
+ async function applyDerivativesMetricCoverage(rows) {
1016
+ if (!rows.length)
1017
+ return [];
1018
+ await ensureDerivativesSchema();
1019
+ const pool = getPool();
1020
+ const client = await pool.connect();
1021
+ const results = [];
1022
+ try {
1023
+ await client.query("BEGIN");
1024
+ for (const row of rows) {
1025
+ const source = String(row.source || "").trim().toLowerCase();
1026
+ const symbol = String(row.symbol || "").trim().toUpperCase();
1027
+ const fromMs = Math.trunc(row.fromMs);
1028
+ const toMs = Math.trunc(row.toMs);
1029
+ if (!source || !symbol || fromMs > toMs) continue;
1030
+ await client.query(
1031
+ `
1032
+ UPDATE derivatives_market
1033
+ SET
1034
+ liq_long = 0,
1035
+ liq_short = 0,
1036
+ liq_total = 0,
1037
+ ingested_at = now()
1038
+ WHERE symbol = $1
1039
+ AND interval = $2
1040
+ AND ts >= to_timestamp($3/1000.0)
1041
+ AND ts <= to_timestamp($4/1000.0)
1042
+ AND liq_long IS NULL
1043
+ AND liq_short IS NULL
1044
+ AND liq_total IS NULL
1045
+ `,
1046
+ [symbol, row.interval, fromMs, toMs]
1047
+ );
1048
+ const zeroCountResult = await client.query(
1049
+ `
1050
+ SELECT COUNT(*)::integer AS count
1051
+ FROM derivatives_market
1052
+ WHERE symbol = $1
1053
+ AND interval = $2
1054
+ AND ts >= to_timestamp($3/1000.0)
1055
+ AND ts <= to_timestamp($4/1000.0)
1056
+ AND liq_long = 0
1057
+ AND liq_short = 0
1058
+ AND liq_total = 0
1059
+ `,
1060
+ [symbol, row.interval, fromMs, toMs]
1061
+ );
1062
+ const zeroRowsCount = Math.max(
1063
+ 0,
1064
+ Number(zeroCountResult.rows[0]?.count ?? 0)
1065
+ );
1066
+ await client.query(
1067
+ `
1068
+ INSERT INTO derivatives_metric_coverage (
1069
+ source,
1070
+ metric,
1071
+ symbol,
1072
+ interval,
1073
+ from_ts,
1074
+ to_ts,
1075
+ event_rows_count,
1076
+ zero_rows_count
1077
+ )
1078
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
1079
+ ON CONFLICT (source, metric, symbol, interval, from_ts, to_ts)
1080
+ DO UPDATE SET
1081
+ event_rows_count = EXCLUDED.event_rows_count,
1082
+ zero_rows_count = EXCLUDED.zero_rows_count,
1083
+ checked_at = now()
1084
+ `,
1085
+ [
1086
+ source,
1087
+ row.metric,
1088
+ symbol,
1089
+ row.interval,
1090
+ new Date(fromMs),
1091
+ new Date(toMs),
1092
+ Math.max(0, Math.trunc(row.eventRowsCount)),
1093
+ zeroRowsCount
1094
+ ]
1095
+ );
1096
+ results.push({ symbol, zeroRowsCount });
1097
+ }
1098
+ await client.query("COMMIT");
1099
+ return results;
1100
+ } catch (error) {
1101
+ await client.query("ROLLBACK");
1102
+ throw error;
1103
+ } finally {
1104
+ client.release();
1105
+ }
1106
+ }
1107
+ async function getDerivativesWindow(params) {
1108
+ const { symbol, intervals, endMs, lookbackMs } = params;
1109
+ const normalizedSymbol = String(symbol || "").trim().toUpperCase();
1110
+ const normalizedIntervals = [...new Set(intervals)].filter(Boolean);
1111
+ if (!normalizedSymbol || !normalizedIntervals.length) {
1112
+ return {};
1113
+ }
1114
+ await prepareMarketContextSchemaForRead("derivatives");
1115
+ const startMs = endMs - Math.max(0, lookbackMs);
1116
+ const sql = `
1117
+ SELECT symbol, interval, ts, open_interest, funding_rate, liq_long, liq_short, liq_total, source
1118
+ FROM derivatives_market
1119
+ WHERE symbol = $1
1120
+ AND interval = ANY($2)
1121
+ AND ts >= to_timestamp($3/1000.0)
1122
+ AND ts <= to_timestamp($4/1000.0)
1123
+ ORDER BY interval ASC, ts ASC
1124
+ `;
1125
+ const res = await queryMarketContext(
1126
+ sql,
1127
+ [normalizedSymbol, normalizedIntervals, startMs, endMs],
1128
+ params
1129
+ );
1130
+ const rowsByInterval = {};
1131
+ for (const row of res.rows) {
1132
+ const interval = row.interval;
1133
+ rowsByInterval[interval] ??= [];
1134
+ rowsByInterval[interval]?.push({
1135
+ symbol: row.symbol,
1136
+ interval,
1137
+ ts: row.ts,
1138
+ openInterest: row.open_interest,
1139
+ fundingRate: row.funding_rate,
1140
+ liqLong: row.liq_long,
1141
+ liqShort: row.liq_short,
1142
+ liqTotal: row.liq_total,
1143
+ source: row.source
1144
+ });
1145
+ }
1146
+ return rowsByInterval;
1147
+ }
1148
+ async function getDerivativesSummary(hours = 24, limit = 500, symbols) {
1149
+ await ensureDerivativesSchema();
1150
+ const pool = getPool();
1151
+ const cappedHours = Math.max(1, Math.min(24 * 90, hours));
1152
+ const cappedLimit = Math.max(10, Math.min(1e3, limit));
1153
+ const normalizedSymbols = Array.isArray(symbols) ? [...new Set(symbols.map(normalizeCandleSymbol).filter(Boolean))] : [];
1154
+ const symbolsFilterSql = normalizedSymbols.length ? "AND symbol = ANY($3)" : "";
1155
+ const summaryQ = await pool.query(
1156
+ `
1157
+ WITH filtered AS (
1158
+ SELECT
1159
+ symbol,
1160
+ interval,
1161
+ ts,
1162
+ open_interest,
1163
+ funding_rate,
1164
+ liq_long,
1165
+ liq_short,
1166
+ liq_total
1167
+ FROM derivatives_market
1168
+ WHERE ts >= now() - ($1 || ' hours')::interval
1169
+ ${symbolsFilterSql}
1170
+ ),
1171
+ latest AS (
1172
+ SELECT DISTINCT ON (symbol, interval)
1173
+ symbol,
1174
+ interval,
1175
+ ts AS last_ts,
1176
+ open_interest AS latest_open_interest,
1177
+ funding_rate AS latest_funding_rate
1178
+ FROM filtered
1179
+ ORDER BY symbol ASC, interval ASC, ts DESC
1180
+ ),
1181
+ first AS (
1182
+ SELECT DISTINCT ON (symbol, interval)
1183
+ symbol,
1184
+ interval,
1185
+ ts AS first_ts,
1186
+ open_interest AS first_open_interest,
1187
+ funding_rate AS first_funding_rate
1188
+ FROM filtered
1189
+ ORDER BY symbol ASC, interval ASC, ts ASC
1190
+ ),
1191
+ aggregated AS (
1192
+ SELECT
1193
+ symbol,
1194
+ interval,
1195
+ COUNT(*)::int AS points,
1196
+ SUM(COALESCE(liq_long, 0)) AS sum_liq_long,
1197
+ SUM(COALESCE(liq_short, 0)) AS sum_liq_short,
1198
+ SUM(COALESCE(liq_total, 0)) AS sum_liq_total
1199
+ FROM filtered
1200
+ GROUP BY symbol, interval
1201
+ )
1202
+ SELECT
1203
+ aggregated.symbol,
1204
+ aggregated.interval,
1205
+ aggregated.points,
1206
+ latest.last_ts,
1207
+ first.first_ts,
1208
+ latest.latest_open_interest,
1209
+ first.first_open_interest,
1210
+ latest.latest_funding_rate,
1211
+ first.first_funding_rate,
1212
+ aggregated.sum_liq_long,
1213
+ aggregated.sum_liq_short,
1214
+ aggregated.sum_liq_total
1215
+ FROM aggregated
1216
+ JOIN latest
1217
+ ON latest.symbol = aggregated.symbol
1218
+ AND latest.interval = aggregated.interval
1219
+ JOIN first
1220
+ ON first.symbol = aggregated.symbol
1221
+ AND first.interval = aggregated.interval
1222
+ ORDER BY aggregated.sum_liq_total DESC, aggregated.symbol ASC
1223
+ LIMIT $2
1224
+ `,
1225
+ normalizedSymbols.length ? [String(cappedHours), cappedLimit, normalizedSymbols] : [String(cappedHours), cappedLimit]
1226
+ );
1227
+ const items = summaryQ.rows.map((row) => {
1228
+ const latestOpenInterest = row.latest_open_interest == null ? null : Number(row.latest_open_interest);
1229
+ const firstOpenInterest = row.first_open_interest == null ? null : Number(row.first_open_interest);
1230
+ const latestFundingRate = row.latest_funding_rate == null ? null : Number(row.latest_funding_rate);
1231
+ const firstFundingRate = row.first_funding_rate == null ? null : Number(row.first_funding_rate);
1232
+ const oiChange = latestOpenInterest != null && firstOpenInterest != null ? latestOpenInterest - firstOpenInterest : null;
1233
+ const oiChangePct = oiChange != null && firstOpenInterest != null && Number.isFinite(firstOpenInterest) && Math.abs(firstOpenInterest) > 0 ? oiChange / Math.abs(firstOpenInterest) * 100 : null;
1234
+ const fundingChange = latestFundingRate != null && firstFundingRate != null ? latestFundingRate - firstFundingRate : null;
1235
+ return {
1236
+ symbol: row.symbol,
1237
+ interval: row.interval,
1238
+ points: Number(row.points || 0),
1239
+ last_ts: row.last_ts,
1240
+ first_ts: row.first_ts,
1241
+ latest_open_interest: latestOpenInterest,
1242
+ first_open_interest: firstOpenInterest,
1243
+ oi_change: oiChange,
1244
+ oi_change_pct: oiChangePct,
1245
+ latest_funding_rate: latestFundingRate,
1246
+ first_funding_rate: firstFundingRate,
1247
+ funding_change: fundingChange,
1248
+ sum_liq_long: row.sum_liq_long == null ? null : Number(row.sum_liq_long),
1249
+ sum_liq_short: row.sum_liq_short == null ? null : Number(row.sum_liq_short),
1250
+ sum_liq_total: row.sum_liq_total == null ? null : Number(row.sum_liq_total)
1251
+ };
1252
+ });
1253
+ return {
1254
+ hours: cappedHours,
1255
+ items
1256
+ };
1257
+ }
1258
+ // Annotate the CommonJS export names for ESM import in node:
1259
+ 0 && (module.exports = {
1260
+ applyDerivativesMetricCoverage,
1261
+ ensureDerivativesSchema,
1262
+ getDerivativesBackfillCoverage,
1263
+ getDerivativesDataEdgesForSymbols,
1264
+ getDerivativesMetricCoverage,
1265
+ getDerivativesRangeForSymbols,
1266
+ getDerivativesSummary,
1267
+ getDerivativesWindow,
1268
+ upsertDerivatives,
1269
+ upsertDerivativesBackfillCoverage
1270
+ });