@tradejs/infra 2.0.15 → 2.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/timescale.js CHANGED
@@ -23,7 +23,13 @@ __export(timescale_exports, {
23
23
  applyDerivativesMetricCoverage: () => applyDerivativesMetricCoverage,
24
24
  cleanupDeprecatedMarketContext: () => cleanupDeprecatedMarketContext,
25
25
  closeTimescalePool: () => closeTimescalePool,
26
+ configureTimescaleMarketContextSchemaMode: () => configureTimescaleMarketContextSchemaMode,
26
27
  deleteCandles: () => deleteCandles,
28
+ ensureBinanceMarketSchema: () => ensureBinanceMarketSchema,
29
+ ensureCoinMarketCapContextSchema: () => ensureCoinMarketCapContextSchema,
30
+ ensureDerivativesSchema: () => ensureDerivativesSchema,
31
+ ensureHyperliquidWhaleSchema: () => ensureHyperliquidWhaleSchema,
32
+ ensureMarketContextSchemas: () => ensureMarketContextSchemas,
27
33
  findContinuityGap: () => findContinuityGap,
28
34
  getCandlesRange: () => getCandlesRange,
29
35
  getDataEdges: () => getDataEdges,
@@ -114,6 +120,12 @@ var derivativesSchemaReadyPromise = null;
114
120
  var spreadSchemaReadyPromise = null;
115
121
  var binanceMarketSchemaReadyPromise = null;
116
122
  var hyperliquidWhaleSchemaReadyPromise = null;
123
+ var marketContextSchemaMode = "ensure";
124
+ var verifiedMarketContextSchemas = /* @__PURE__ */ new Set();
125
+ var configureTimescaleMarketContextSchemaMode = (mode) => {
126
+ marketContextSchemaMode = mode;
127
+ verifiedMarketContextSchemas.clear();
128
+ };
117
129
  var closeTimescalePool = async () => {
118
130
  const pool = global.__pgPool__;
119
131
  if (!pool) {
@@ -130,6 +142,7 @@ var closeTimescalePool = async () => {
130
142
  spreadSchemaReadyPromise = null;
131
143
  binanceMarketSchemaReadyPromise = null;
132
144
  hyperliquidWhaleSchemaReadyPromise = null;
145
+ verifiedMarketContextSchemas.clear();
133
146
  await pool.end();
134
147
  };
135
148
  var CANDLES_SCHEMA_LOCK_KEY = 61e4;
@@ -138,6 +151,70 @@ var SPREAD_SCHEMA_LOCK_KEY = 610002;
138
151
  var BINANCE_MARKET_SCHEMA_LOCK_KEY = 610003;
139
152
  var HYPERLIQUID_WHALE_SCHEMA_LOCK_KEY = 610004;
140
153
  var PG_SAFE_MAX_BIND_PARAMS = 3e4;
154
+ var resolveMarketContextQueryTimeoutMs = (override) => {
155
+ if (Number.isFinite(override) && Number(override) > 0) {
156
+ return Math.floor(Number(override));
157
+ }
158
+ const configured = Number(process.env.MARKET_CONTEXT_SQL_TIMEOUT_MS);
159
+ return Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : 3e4;
160
+ };
161
+ var createMarketContextQueryError = (name, message) => {
162
+ const error = new Error(message);
163
+ error.name = name;
164
+ return error;
165
+ };
166
+ var queryMarketContext = async (text, values, options = {}) => {
167
+ const pool = getPool();
168
+ const client = await pool.connect();
169
+ const timeoutMs = resolveMarketContextQueryTimeoutMs(options.timeoutMs);
170
+ let released = false;
171
+ let rejectCancellation;
172
+ const release = (error) => {
173
+ if (released) return;
174
+ released = true;
175
+ client.release(error);
176
+ };
177
+ const cancellation = new Promise((_resolve, reject) => {
178
+ rejectCancellation = reject;
179
+ });
180
+ const cancel = (error) => {
181
+ release(error);
182
+ rejectCancellation?.(error);
183
+ };
184
+ const onAbort = () => cancel(
185
+ createMarketContextQueryError(
186
+ "AbortError",
187
+ "Timescale market-context query aborted"
188
+ )
189
+ );
190
+ const timer = setTimeout(
191
+ () => cancel(
192
+ createMarketContextQueryError(
193
+ "TimescaleQueryTimeoutError",
194
+ `Timescale market-context query exceeded ${timeoutMs}ms`
195
+ )
196
+ ),
197
+ timeoutMs
198
+ );
199
+ timer.unref?.();
200
+ options.signal?.addEventListener("abort", onAbort, { once: true });
201
+ try {
202
+ if (options.signal?.aborted) {
203
+ const error = createMarketContextQueryError(
204
+ "AbortError",
205
+ "Timescale market-context query aborted"
206
+ );
207
+ release(error);
208
+ throw error;
209
+ }
210
+ const query = client.query(text, values);
211
+ return await Promise.race([query, cancellation]);
212
+ } finally {
213
+ clearTimeout(timer);
214
+ options.signal?.removeEventListener("abort", onAbort);
215
+ release();
216
+ }
217
+ };
141
218
  var normalizeCandleProvider = (provider) => String(provider || "").trim().toLowerCase();
142
219
  var normalizeCandleSymbol = (symbol) => String(symbol || "").trim().toUpperCase();
143
220
  var getSafeBulkInsertRows = (columnsCount) => Math.max(1, Math.floor(PG_SAFE_MAX_BIND_PARAMS / columnsCount));
@@ -942,6 +1019,58 @@ var ensureHyperliquidWhaleSchema = async () => {
942
1019
  });
943
1020
  await hyperliquidWhaleSchemaReadyPromise;
944
1021
  };
1022
+ var ensureCoinMarketCapContextSchema = async () => ensureBinanceMarketSchema();
1023
+ var ensureMarketContextSchema = async (source) => {
1024
+ switch (source) {
1025
+ case "binance":
1026
+ return ensureBinanceMarketSchema();
1027
+ case "coinmarketcap":
1028
+ return ensureCoinMarketCapContextSchema();
1029
+ case "derivatives":
1030
+ return ensureDerivativesSchema();
1031
+ case "hyperliquidWhales":
1032
+ return ensureHyperliquidWhaleSchema();
1033
+ }
1034
+ };
1035
+ var MARKET_CONTEXT_SCHEMA_TABLES = {
1036
+ binance: ["market_trade_flow", "market_breadth"],
1037
+ coinmarketcap: [
1038
+ "market_global_context",
1039
+ "market_reference_asset_context",
1040
+ "market_cmc_exchange_liquidity_context",
1041
+ "market_cmc_fear_greed_context",
1042
+ "market_cmc_index_context"
1043
+ ],
1044
+ derivatives: ["derivatives_market"],
1045
+ hyperliquidWhales: [
1046
+ "hyperliquid_whale_flow",
1047
+ "hyperliquid_whale_coverage_1m"
1048
+ ]
1049
+ };
1050
+ var verifyMarketContextSchema = async (source) => {
1051
+ if (verifiedMarketContextSchemas.has(source)) return;
1052
+ const tables = MARKET_CONTEXT_SCHEMA_TABLES[source];
1053
+ const result = await queryMarketContext(
1054
+ `
1055
+ SELECT table_name AS "tableName"
1056
+ FROM unnest($1::text[]) AS requested(table_name)
1057
+ WHERE to_regclass(requested.table_name) IS NULL
1058
+ `,
1059
+ [tables]
1060
+ );
1061
+ if (result.rows.length) {
1062
+ throw new Error(
1063
+ `Timescale ${source} schema is not prepared; missing: ${result.rows.map((row) => row.tableName).filter(Boolean).join(", ")}`
1064
+ );
1065
+ }
1066
+ verifiedMarketContextSchemas.add(source);
1067
+ };
1068
+ var prepareMarketContextSchemaForRead = async (source) => marketContextSchemaMode === "verify" ? verifyMarketContextSchema(source) : ensureMarketContextSchema(source);
1069
+ var ensureMarketContextSchemas = async (sources) => {
1070
+ for (const source of new Set(sources)) {
1071
+ await ensureMarketContextSchema(source);
1072
+ }
1073
+ };
945
1074
  async function upsertDerivatives(rows) {
946
1075
  if (!rows.length) return;
947
1076
  await ensureDerivativesSchema();
@@ -1269,9 +1398,8 @@ async function getDerivativesWindow(params) {
1269
1398
  if (!normalizedSymbol || !normalizedIntervals.length) {
1270
1399
  return {};
1271
1400
  }
1272
- await ensureDerivativesSchema();
1401
+ await prepareMarketContextSchemaForRead("derivatives");
1273
1402
  const startMs = endMs - Math.max(0, lookbackMs);
1274
- const pool = getPool();
1275
1403
  const sql = `
1276
1404
  SELECT symbol, interval, ts, open_interest, funding_rate, liq_long, liq_short, liq_total, source
1277
1405
  FROM derivatives_market
@@ -1281,12 +1409,11 @@ async function getDerivativesWindow(params) {
1281
1409
  AND ts <= to_timestamp($4/1000.0)
1282
1410
  ORDER BY interval ASC, ts ASC
1283
1411
  `;
1284
- const res = await pool.query(sql, [
1285
- normalizedSymbol,
1286
- normalizedIntervals,
1287
- startMs,
1288
- endMs
1289
- ]);
1412
+ const res = await queryMarketContext(
1413
+ sql,
1414
+ [normalizedSymbol, normalizedIntervals, startMs, endMs],
1415
+ params
1416
+ );
1290
1417
  const rowsByInterval = {};
1291
1418
  for (const row of res.rows) {
1292
1419
  const interval = row.interval;
@@ -2493,9 +2620,8 @@ var toMarketFeatureAge = (rowTs, atMs) => {
2493
2620
  return Number.isFinite(ageMs) ? ageMs : null;
2494
2621
  };
2495
2622
  async function getLatestMarketTradeFlow(params) {
2496
- await ensureBinanceMarketSchema();
2497
- const pool = getPool();
2498
- const res = await pool.query(
2623
+ await prepareMarketContextSchemaForRead("binance");
2624
+ const res = await queryMarketContext(
2499
2625
  `
2500
2626
  SELECT
2501
2627
  symbol,
@@ -2517,7 +2643,8 @@ async function getLatestMarketTradeFlow(params) {
2517
2643
  ORDER BY ts DESC
2518
2644
  LIMIT 1
2519
2645
  `,
2520
- [params.symbol.toUpperCase(), params.interval, params.atMs]
2646
+ [params.symbol.toUpperCase(), params.interval, params.atMs],
2647
+ params
2521
2648
  );
2522
2649
  const row = res.rows[0];
2523
2650
  if (!row) return null;
@@ -2645,71 +2772,154 @@ async function upsertHyperliquidWhaleWalletCoverage(params) {
2645
2772
  async function rebuildHyperliquidWhaleCoverageRows(params) {
2646
2773
  await ensureHyperliquidWhaleSchema();
2647
2774
  if (params.toMs <= params.fromMs) return 0;
2648
- const result = await getPool().query(
2649
- `
2650
- WITH buckets AS (
2651
- SELECT generate_series(
2652
- to_timestamp($3/1000.0),
2653
- to_timestamp($4/1000.0) - interval '1 minute',
2654
- interval '1 minute'
2655
- ) AS ts
2656
- ), coverage AS (
2775
+ const minuteMs = 6e4;
2776
+ const defaultChunkMinutes = 7 * 24 * 60;
2777
+ const chunkMinutes = Number.isFinite(params.chunkMinutes) && Number(params.chunkMinutes) > 0 ? Math.floor(Number(params.chunkMinutes)) : defaultChunkMinutes;
2778
+ const chunkMs = chunkMinutes * minuteMs;
2779
+ const totalBuckets = Math.ceil((params.toMs - params.fromMs) / minuteMs);
2780
+ const totalChunks = Math.ceil((params.toMs - params.fromMs) / chunkMs);
2781
+ let completedBuckets = 0;
2782
+ let rows = 0;
2783
+ for (let chunkIndex = 0, chunkFromMs = params.fromMs; chunkFromMs < params.toMs; chunkIndex += 1, chunkFromMs += chunkMs) {
2784
+ const chunkToMs = Math.min(params.toMs, chunkFromMs + chunkMs);
2785
+ const result = await getPool().query(
2786
+ `
2787
+ WITH normalized_ranges AS (
2788
+ SELECT
2789
+ address,
2790
+ GREATEST(
2791
+ to_timestamp($3/1000.0),
2792
+ date_trunc('minute', covered_from_ts) +
2793
+ CASE
2794
+ WHEN covered_from_ts = date_trunc('minute', covered_from_ts)
2795
+ THEN interval '0 minutes'
2796
+ ELSE interval '1 minute'
2797
+ END
2798
+ ) AS range_start,
2799
+ LEAST(
2800
+ to_timestamp($4/1000.0),
2801
+ date_trunc('minute', covered_to_ts)
2802
+ ) AS range_end
2803
+ FROM hyperliquid_whale_wallet_coverage
2804
+ WHERE universe_fingerprint = $1
2805
+ AND whale_registry_fingerprint = $2
2806
+ AND data_model_version = $6
2807
+ AND status IN ('complete', 'truncated')
2808
+ AND covered_from_ts < to_timestamp($4/1000.0)
2809
+ AND covered_to_ts > to_timestamp($3/1000.0)
2810
+ ), eligible_ranges AS (
2811
+ SELECT *
2812
+ FROM normalized_ranges
2813
+ WHERE range_start < range_end
2814
+ ), ordered_ranges AS (
2815
+ SELECT
2816
+ *,
2817
+ MAX(range_end) OVER (
2818
+ PARTITION BY address
2819
+ ORDER BY range_start, range_end
2820
+ ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
2821
+ ) AS previous_max_end
2822
+ FROM eligible_ranges
2823
+ ), marked_ranges AS (
2824
+ SELECT
2825
+ *,
2826
+ SUM(
2827
+ CASE
2828
+ WHEN previous_max_end IS NULL OR range_start > previous_max_end
2829
+ THEN 1
2830
+ ELSE 0
2831
+ END
2832
+ ) OVER (
2833
+ PARTITION BY address
2834
+ ORDER BY range_start, range_end
2835
+ ) AS range_group
2836
+ FROM ordered_ranges
2837
+ ), merged_ranges AS (
2838
+ SELECT
2839
+ address,
2840
+ MIN(range_start) AS range_start,
2841
+ MAX(range_end) AS range_end
2842
+ FROM marked_ranges
2843
+ GROUP BY address, range_group
2844
+ ), deltas AS (
2845
+ SELECT range_start AS ts, 1 AS delta
2846
+ FROM merged_ranges
2847
+ UNION ALL
2848
+ SELECT range_end AS ts, -1 AS delta
2849
+ FROM merged_ranges
2850
+ ), bucket_deltas AS (
2851
+ SELECT ts, SUM(delta)::int AS delta
2852
+ FROM deltas
2853
+ GROUP BY ts
2854
+ ), buckets AS (
2855
+ SELECT generate_series(
2856
+ to_timestamp($3/1000.0),
2857
+ to_timestamp($4/1000.0) - interval '1 minute',
2858
+ interval '1 minute'
2859
+ ) AS ts
2860
+ ), coverage AS (
2861
+ SELECT
2862
+ buckets.ts,
2863
+ SUM(COALESCE(bucket_deltas.delta, 0)) OVER (
2864
+ ORDER BY buckets.ts
2865
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
2866
+ )::int AS covered_whales
2867
+ FROM buckets
2868
+ LEFT JOIN bucket_deltas USING (ts)
2869
+ )
2870
+ INSERT INTO hyperliquid_whale_coverage_1m (
2871
+ ts,
2872
+ covered_whales,
2873
+ expected_whales,
2874
+ coverage_pct,
2875
+ universe_fingerprint,
2876
+ whale_registry_fingerprint,
2877
+ source,
2878
+ data_model_version
2879
+ )
2657
2880
  SELECT
2658
- buckets.ts,
2659
- COUNT(DISTINCT wallets.address)::int AS covered_whales
2660
- FROM buckets
2661
- LEFT JOIN hyperliquid_whale_wallet_coverage wallets
2662
- ON wallets.universe_fingerprint = $1
2663
- AND wallets.whale_registry_fingerprint = $2
2664
- AND wallets.data_model_version = $6
2665
- AND wallets.status IN ('complete', 'truncated')
2666
- AND wallets.covered_from_ts <= buckets.ts
2667
- AND wallets.covered_to_ts >= buckets.ts + interval '1 minute'
2668
- GROUP BY buckets.ts
2669
- )
2670
- INSERT INTO hyperliquid_whale_coverage_1m (
2671
- ts,
2672
- covered_whales,
2673
- expected_whales,
2674
- coverage_pct,
2675
- universe_fingerprint,
2676
- whale_registry_fingerprint,
2677
- source
2678
- ,data_model_version
2679
- )
2680
- SELECT
2681
- ts,
2682
- covered_whales,
2683
- $5,
2684
- CASE WHEN $5 > 0 THEN covered_whales::double precision / $5 ELSE 0 END,
2685
- $1,
2686
- $2,
2687
- 'hyperliquid_user_fills',
2688
- $6
2689
- FROM coverage
2690
- ON CONFLICT (
2691
- universe_fingerprint,
2692
- whale_registry_fingerprint,
2693
- ts
2694
- ) DO UPDATE SET
2695
- covered_whales = EXCLUDED.covered_whales,
2696
- expected_whales = EXCLUDED.expected_whales,
2697
- coverage_pct = EXCLUDED.coverage_pct,
2698
- source = EXCLUDED.source,
2699
- data_model_version = EXCLUDED.data_model_version,
2700
- ingested_at = now()
2701
- RETURNING 1
2702
- `,
2703
- [
2704
- params.universeFingerprint,
2705
- params.whaleRegistryFingerprint,
2706
- params.fromMs,
2707
- params.toMs,
2708
- params.expectedWhales,
2709
- import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2710
- ]
2711
- );
2712
- return result.rowCount ?? 0;
2881
+ ts,
2882
+ covered_whales,
2883
+ $5,
2884
+ CASE WHEN $5 > 0 THEN covered_whales::double precision / $5 ELSE 0 END,
2885
+ $1,
2886
+ $2,
2887
+ 'hyperliquid_user_fills',
2888
+ $6
2889
+ FROM coverage
2890
+ ON CONFLICT (
2891
+ universe_fingerprint,
2892
+ whale_registry_fingerprint,
2893
+ ts
2894
+ ) DO UPDATE SET
2895
+ covered_whales = EXCLUDED.covered_whales,
2896
+ expected_whales = EXCLUDED.expected_whales,
2897
+ coverage_pct = EXCLUDED.coverage_pct,
2898
+ source = EXCLUDED.source,
2899
+ data_model_version = EXCLUDED.data_model_version,
2900
+ ingested_at = now()
2901
+ `,
2902
+ [
2903
+ params.universeFingerprint,
2904
+ params.whaleRegistryFingerprint,
2905
+ chunkFromMs,
2906
+ chunkToMs,
2907
+ params.expectedWhales,
2908
+ import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2909
+ ]
2910
+ );
2911
+ const chunkBuckets = Math.ceil((chunkToMs - chunkFromMs) / minuteMs);
2912
+ completedBuckets = Math.min(totalBuckets, completedBuckets + chunkBuckets);
2913
+ rows += result.rowCount ?? 0;
2914
+ params.onProgress?.({
2915
+ chunkIndex: chunkIndex + 1,
2916
+ totalChunks,
2917
+ completedBuckets,
2918
+ totalBuckets,
2919
+ rows
2920
+ });
2921
+ }
2922
+ return rows;
2713
2923
  }
2714
2924
  async function upsertHyperliquidWhaleCoverageRows(rows) {
2715
2925
  if (!rows.length) return;
@@ -2768,8 +2978,8 @@ async function upsertHyperliquidWhaleCoverageRows(rows) {
2768
2978
  );
2769
2979
  }
2770
2980
  async function getHyperliquidWhaleCoverageSeriesRows(params) {
2771
- await ensureHyperliquidWhaleSchema();
2772
- const result = await getPool().query(
2981
+ await prepareMarketContextSchemaForRead("hyperliquidWhales");
2982
+ const result = await queryMarketContext(
2773
2983
  `
2774
2984
  SELECT
2775
2985
  ts,
@@ -2790,7 +3000,8 @@ async function getHyperliquidWhaleCoverageSeriesRows(params) {
2790
3000
  import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION,
2791
3001
  params.fromMs,
2792
3002
  params.toMs
2793
- ]
3003
+ ],
3004
+ params
2794
3005
  );
2795
3006
  return result.rows.map((row) => ({
2796
3007
  ts: new Date(row.ts),
@@ -2800,8 +3011,8 @@ async function getHyperliquidWhaleCoverageSeriesRows(params) {
2800
3011
  }));
2801
3012
  }
2802
3013
  async function getHyperliquidWhaleFlowSeriesRows(params) {
2803
- await ensureHyperliquidWhaleSchema();
2804
- const result = await getPool().query(
3014
+ await prepareMarketContextSchemaForRead("hyperliquidWhales");
3015
+ const result = await queryMarketContext(
2805
3016
  `
2806
3017
  SELECT
2807
3018
  ts,
@@ -2834,7 +3045,8 @@ async function getHyperliquidWhaleFlowSeriesRows(params) {
2834
3045
  params.whaleRegistryFingerprint,
2835
3046
  params.fromMs,
2836
3047
  params.toMs
2837
- ]
3048
+ ],
3049
+ params
2838
3050
  );
2839
3051
  return result.rows.map((row) => ({
2840
3052
  ts: new Date(row.ts),
@@ -2855,10 +3067,10 @@ async function getHyperliquidWhaleFlowSeriesRows(params) {
2855
3067
  }));
2856
3068
  }
2857
3069
  async function getHyperliquidWhaleFlowAggregate(params) {
2858
- await ensureHyperliquidWhaleSchema();
3070
+ await prepareMarketContextSchemaForRead("hyperliquidWhales");
2859
3071
  const intervalMs = HYPERLIQUID_CONTEXT_INTERVAL_MS[params.interval];
2860
3072
  const expectedBuckets = Math.ceil(intervalMs / 6e4);
2861
- const res = await getPool().query(
3073
+ const res = await queryMarketContext(
2862
3074
  `
2863
3075
  WITH coverage_rows AS (
2864
3076
  SELECT *
@@ -2973,7 +3185,8 @@ async function getHyperliquidWhaleFlowAggregate(params) {
2973
3185
  params.decisionTimeMs,
2974
3186
  intervalMs,
2975
3187
  import_types.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2976
- ]
3188
+ ],
3189
+ params
2977
3190
  );
2978
3191
  const row = res.rows[0];
2979
3192
  if (!row?.as_of_ts || Number(row.coverage_buckets) !== expectedBuckets || Number(row.covered_whales) <= 0) {
@@ -3046,9 +3259,8 @@ async function hasHyperliquidWhaleBackfillCoverage(params) {
3046
3259
  return Number(result.rows[0]?.buckets) === expectedBuckets && Number(result.rows[0]?.complete_buckets) === expectedBuckets;
3047
3260
  }
3048
3261
  async function getLatestMarketBreadth(params) {
3049
- await ensureBinanceMarketSchema();
3050
- const pool = getPool();
3051
- const res = await pool.query(
3262
+ await prepareMarketContextSchemaForRead("binance");
3263
+ const res = await queryMarketContext(
3052
3264
  `
3053
3265
  SELECT
3054
3266
  universe,
@@ -3087,7 +3299,8 @@ async function getLatestMarketBreadth(params) {
3087
3299
  ORDER BY ts DESC
3088
3300
  LIMIT 1
3089
3301
  `,
3090
- [params.universe, params.interval, params.atMs]
3302
+ [params.universe, params.interval, params.atMs],
3303
+ params
3091
3304
  );
3092
3305
  const row = res.rows[0];
3093
3306
  if (!row) return null;
@@ -3099,10 +3312,9 @@ async function getLatestMarketBreadth(params) {
3099
3312
  };
3100
3313
  }
3101
3314
  async function getLatestMarketGlobalContext(params) {
3102
- await ensureBinanceMarketSchema();
3103
- const pool = getPool();
3315
+ await prepareMarketContextSchemaForRead("coinmarketcap");
3104
3316
  const source = params.source ?? "coinmarketcap_global";
3105
- const res = await pool.query(
3317
+ const res = await queryMarketContext(
3106
3318
  `
3107
3319
  SELECT
3108
3320
  source,
@@ -3131,11 +3343,12 @@ async function getLatestMarketGlobalContext(params) {
3131
3343
  ORDER BY ts DESC
3132
3344
  LIMIT 1
3133
3345
  `,
3134
- [source, params.atMs]
3346
+ [source, params.atMs],
3347
+ params
3135
3348
  );
3136
3349
  const row = res.rows[0];
3137
3350
  if (!row) return null;
3138
- const previousRes = await pool.query(
3351
+ const previousRes = await queryMarketContext(
3139
3352
  `
3140
3353
  SELECT
3141
3354
  btc_dominance_pct AS "btcDominancePct",
@@ -3148,7 +3361,8 @@ async function getLatestMarketGlobalContext(params) {
3148
3361
  ORDER BY ts DESC
3149
3362
  LIMIT 1
3150
3363
  `,
3151
- [source, row.ts]
3364
+ [source, row.ts],
3365
+ params
3152
3366
  );
3153
3367
  const previousDominance = previousRes.rows[0]?.btcDominancePct == null ? null : Number(previousRes.rows[0].btcDominancePct);
3154
3368
  const previousEthDominance = previousRes.rows[0]?.ethDominancePct == null ? null : Number(previousRes.rows[0].ethDominancePct);
@@ -3241,9 +3455,8 @@ async function getLatestMarketReferenceAssetContexts(params) {
3241
3455
  ];
3242
3456
  const rows = /* @__PURE__ */ new Map();
3243
3457
  if (!symbols.length) return rows;
3244
- await ensureBinanceMarketSchema();
3245
- const pool = getPool();
3246
- const res = await pool.query(
3458
+ await prepareMarketContextSchemaForRead("coinmarketcap");
3459
+ const res = await queryMarketContext(
3247
3460
  `
3248
3461
  SELECT DISTINCT ON (symbol)
3249
3462
  source,
@@ -3268,7 +3481,8 @@ async function getLatestMarketReferenceAssetContexts(params) {
3268
3481
  END <= to_timestamp($4/1000.0)
3269
3482
  ORDER BY symbol ASC, ts DESC
3270
3483
  `,
3271
- [source, symbols, interval, params.atMs]
3484
+ [source, symbols, interval, params.atMs],
3485
+ params
3272
3486
  );
3273
3487
  for (const row of res.rows) {
3274
3488
  const ageMs = toMarketFeatureAge(row.ts, params.atMs);
@@ -3281,11 +3495,10 @@ async function getLatestMarketReferenceAssetContexts(params) {
3281
3495
  return rows;
3282
3496
  }
3283
3497
  async function getLatestMarketCmcExchangeLiquidityContext(params) {
3284
- await ensureBinanceMarketSchema();
3285
- const pool = getPool();
3498
+ await prepareMarketContextSchemaForRead("coinmarketcap");
3286
3499
  const source = params.source ?? "coinmarketcap_exchange_liquidity";
3287
3500
  const interval = params.interval ?? "1d";
3288
- const res = await pool.query(
3501
+ const res = await queryMarketContext(
3289
3502
  `
3290
3503
  SELECT
3291
3504
  source,
@@ -3308,11 +3521,12 @@ async function getLatestMarketCmcExchangeLiquidityContext(params) {
3308
3521
  ORDER BY ts DESC
3309
3522
  LIMIT 1
3310
3523
  `,
3311
- [source, interval, params.atMs]
3524
+ [source, interval, params.atMs],
3525
+ params
3312
3526
  );
3313
3527
  const row = res.rows[0];
3314
3528
  if (!row) return null;
3315
- const previousRes = await pool.query(
3529
+ const previousRes = await queryMarketContext(
3316
3530
  `
3317
3531
  SELECT total_volume_usd AS "totalVolumeUsd"
3318
3532
  FROM market_cmc_exchange_liquidity_context
@@ -3322,7 +3536,8 @@ async function getLatestMarketCmcExchangeLiquidityContext(params) {
3322
3536
  ORDER BY ts DESC
3323
3537
  LIMIT 1
3324
3538
  `,
3325
- [source, interval, row.ts]
3539
+ [source, interval, row.ts],
3540
+ params
3326
3541
  );
3327
3542
  const currentTotal = row.totalVolumeUsd == null ? null : Number(row.totalVolumeUsd);
3328
3543
  const previousTotal = previousRes.rows[0]?.totalVolumeUsd == null ? null : Number(previousRes.rows[0].totalVolumeUsd);
@@ -3346,9 +3561,8 @@ async function getLatestMarketCmcIndexContexts(params) {
3346
3561
  ];
3347
3562
  const rows = /* @__PURE__ */ new Map();
3348
3563
  if (!indexSlugs.length) return rows;
3349
- await ensureBinanceMarketSchema();
3350
- const pool = getPool();
3351
- const res = await pool.query(
3564
+ await prepareMarketContextSchemaForRead("coinmarketcap");
3565
+ const res = await queryMarketContext(
3352
3566
  `
3353
3567
  SELECT DISTINCT ON (index_slug)
3354
3568
  source,
@@ -3371,10 +3585,11 @@ async function getLatestMarketCmcIndexContexts(params) {
3371
3585
  END <= to_timestamp($4/1000.0)
3372
3586
  ORDER BY index_slug ASC, ts DESC
3373
3587
  `,
3374
- [source, indexSlugs, interval, params.atMs]
3588
+ [source, indexSlugs, interval, params.atMs],
3589
+ params
3375
3590
  );
3376
3591
  for (const row of res.rows) {
3377
- const previousRes = await pool.query(
3592
+ const previousRes = await queryMarketContext(
3378
3593
  `
3379
3594
  SELECT value
3380
3595
  FROM market_cmc_index_context
@@ -3385,7 +3600,8 @@ async function getLatestMarketCmcIndexContexts(params) {
3385
3600
  ORDER BY ts DESC
3386
3601
  LIMIT 1
3387
3602
  `,
3388
- [source, row.indexSlug, interval, row.ts]
3603
+ [source, row.indexSlug, interval, row.ts],
3604
+ params
3389
3605
  );
3390
3606
  const currentValue = row.value == null ? null : Number(row.value);
3391
3607
  const previousValue = previousRes.rows[0]?.value == null ? null : Number(previousRes.rows[0].value);
@@ -3400,11 +3616,10 @@ async function getLatestMarketCmcIndexContexts(params) {
3400
3616
  return rows;
3401
3617
  }
3402
3618
  async function getLatestMarketCmcFearGreedContext(params) {
3403
- await ensureBinanceMarketSchema();
3404
- const pool = getPool();
3619
+ await prepareMarketContextSchemaForRead("coinmarketcap");
3405
3620
  const source = params.source ?? "coinmarketcap_fear_greed";
3406
3621
  const interval = params.interval ?? "1d";
3407
- const res = await pool.query(
3622
+ const res = await queryMarketContext(
3408
3623
  `
3409
3624
  SELECT
3410
3625
  source,
@@ -3424,11 +3639,12 @@ async function getLatestMarketCmcFearGreedContext(params) {
3424
3639
  ORDER BY ts DESC
3425
3640
  LIMIT 1
3426
3641
  `,
3427
- [source, interval, params.atMs]
3642
+ [source, interval, params.atMs],
3643
+ params
3428
3644
  );
3429
3645
  const row = res.rows[0];
3430
3646
  if (!row) return null;
3431
- const previousRes = await pool.query(
3647
+ const previousRes = await queryMarketContext(
3432
3648
  `
3433
3649
  SELECT
3434
3650
  value::int AS value,
@@ -3440,9 +3656,10 @@ async function getLatestMarketCmcFearGreedContext(params) {
3440
3656
  ORDER BY ts DESC
3441
3657
  LIMIT 1
3442
3658
  `,
3443
- [source, interval, row.ts]
3659
+ [source, interval, row.ts],
3660
+ params
3444
3661
  );
3445
- const previous7dRes = await pool.query(
3662
+ const previous7dRes = await queryMarketContext(
3446
3663
  `
3447
3664
  SELECT value::int AS value
3448
3665
  FROM market_cmc_fear_greed_context
@@ -3452,7 +3669,8 @@ async function getLatestMarketCmcFearGreedContext(params) {
3452
3669
  ORDER BY ts DESC
3453
3670
  LIMIT 1
3454
3671
  `,
3455
- [source, interval, row.ts]
3672
+ [source, interval, row.ts],
3673
+ params
3456
3674
  );
3457
3675
  const previousValue = previousRes.rows[0]?.value == null ? null : Number(previousRes.rows[0].value);
3458
3676
  const previous7dValue = previous7dRes.rows[0]?.value == null ? null : Number(previous7dRes.rows[0].value);
@@ -3939,7 +4157,13 @@ async function findContinuityGap(provider, symbol, interval) {
3939
4157
  applyDerivativesMetricCoverage,
3940
4158
  cleanupDeprecatedMarketContext,
3941
4159
  closeTimescalePool,
4160
+ configureTimescaleMarketContextSchemaMode,
3942
4161
  deleteCandles,
4162
+ ensureBinanceMarketSchema,
4163
+ ensureCoinMarketCapContextSchema,
4164
+ ensureDerivativesSchema,
4165
+ ensureHyperliquidWhaleSchema,
4166
+ ensureMarketContextSchemas,
3943
4167
  findContinuityGap,
3944
4168
  getCandlesRange,
3945
4169
  getDataEdges,