@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.
@@ -37,6 +37,12 @@ var derivativesSchemaReadyPromise = null;
37
37
  var spreadSchemaReadyPromise = null;
38
38
  var binanceMarketSchemaReadyPromise = null;
39
39
  var hyperliquidWhaleSchemaReadyPromise = null;
40
+ var marketContextSchemaMode = "ensure";
41
+ var verifiedMarketContextSchemas = /* @__PURE__ */ new Set();
42
+ var configureTimescaleMarketContextSchemaMode = (mode) => {
43
+ marketContextSchemaMode = mode;
44
+ verifiedMarketContextSchemas.clear();
45
+ };
40
46
  var closeTimescalePool = async () => {
41
47
  const pool = global.__pgPool__;
42
48
  if (!pool) {
@@ -53,6 +59,7 @@ var closeTimescalePool = async () => {
53
59
  spreadSchemaReadyPromise = null;
54
60
  binanceMarketSchemaReadyPromise = null;
55
61
  hyperliquidWhaleSchemaReadyPromise = null;
62
+ verifiedMarketContextSchemas.clear();
56
63
  await pool.end();
57
64
  };
58
65
  var CANDLES_SCHEMA_LOCK_KEY = 61e4;
@@ -61,6 +68,70 @@ var SPREAD_SCHEMA_LOCK_KEY = 610002;
61
68
  var BINANCE_MARKET_SCHEMA_LOCK_KEY = 610003;
62
69
  var HYPERLIQUID_WHALE_SCHEMA_LOCK_KEY = 610004;
63
70
  var PG_SAFE_MAX_BIND_PARAMS = 3e4;
71
+ var resolveMarketContextQueryTimeoutMs = (override) => {
72
+ if (Number.isFinite(override) && Number(override) > 0) {
73
+ return Math.floor(Number(override));
74
+ }
75
+ const configured = Number(process.env.MARKET_CONTEXT_SQL_TIMEOUT_MS);
76
+ return Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : 3e4;
77
+ };
78
+ var createMarketContextQueryError = (name, message) => {
79
+ const error = new Error(message);
80
+ error.name = name;
81
+ return error;
82
+ };
83
+ var queryMarketContext = async (text, values, options = {}) => {
84
+ const pool = getPool();
85
+ const client = await pool.connect();
86
+ const timeoutMs = resolveMarketContextQueryTimeoutMs(options.timeoutMs);
87
+ let released = false;
88
+ let rejectCancellation;
89
+ const release = (error) => {
90
+ if (released) return;
91
+ released = true;
92
+ client.release(error);
93
+ };
94
+ const cancellation = new Promise((_resolve, reject) => {
95
+ rejectCancellation = reject;
96
+ });
97
+ const cancel = (error) => {
98
+ release(error);
99
+ rejectCancellation?.(error);
100
+ };
101
+ const onAbort = () => cancel(
102
+ createMarketContextQueryError(
103
+ "AbortError",
104
+ "Timescale market-context query aborted"
105
+ )
106
+ );
107
+ const timer = setTimeout(
108
+ () => cancel(
109
+ createMarketContextQueryError(
110
+ "TimescaleQueryTimeoutError",
111
+ `Timescale market-context query exceeded ${timeoutMs}ms`
112
+ )
113
+ ),
114
+ timeoutMs
115
+ );
116
+ timer.unref?.();
117
+ options.signal?.addEventListener("abort", onAbort, { once: true });
118
+ try {
119
+ if (options.signal?.aborted) {
120
+ const error = createMarketContextQueryError(
121
+ "AbortError",
122
+ "Timescale market-context query aborted"
123
+ );
124
+ release(error);
125
+ throw error;
126
+ }
127
+ const query = client.query(text, values);
128
+ return await Promise.race([query, cancellation]);
129
+ } finally {
130
+ clearTimeout(timer);
131
+ options.signal?.removeEventListener("abort", onAbort);
132
+ release();
133
+ }
134
+ };
64
135
  var normalizeCandleProvider = (provider) => String(provider || "").trim().toLowerCase();
65
136
  var normalizeCandleSymbol = (symbol) => String(symbol || "").trim().toUpperCase();
66
137
  var getSafeBulkInsertRows = (columnsCount) => Math.max(1, Math.floor(PG_SAFE_MAX_BIND_PARAMS / columnsCount));
@@ -865,6 +936,58 @@ var ensureHyperliquidWhaleSchema = async () => {
865
936
  });
866
937
  await hyperliquidWhaleSchemaReadyPromise;
867
938
  };
939
+ var ensureCoinMarketCapContextSchema = async () => ensureBinanceMarketSchema();
940
+ var ensureMarketContextSchema = async (source) => {
941
+ switch (source) {
942
+ case "binance":
943
+ return ensureBinanceMarketSchema();
944
+ case "coinmarketcap":
945
+ return ensureCoinMarketCapContextSchema();
946
+ case "derivatives":
947
+ return ensureDerivativesSchema();
948
+ case "hyperliquidWhales":
949
+ return ensureHyperliquidWhaleSchema();
950
+ }
951
+ };
952
+ var MARKET_CONTEXT_SCHEMA_TABLES = {
953
+ binance: ["market_trade_flow", "market_breadth"],
954
+ coinmarketcap: [
955
+ "market_global_context",
956
+ "market_reference_asset_context",
957
+ "market_cmc_exchange_liquidity_context",
958
+ "market_cmc_fear_greed_context",
959
+ "market_cmc_index_context"
960
+ ],
961
+ derivatives: ["derivatives_market"],
962
+ hyperliquidWhales: [
963
+ "hyperliquid_whale_flow",
964
+ "hyperliquid_whale_coverage_1m"
965
+ ]
966
+ };
967
+ var verifyMarketContextSchema = async (source) => {
968
+ if (verifiedMarketContextSchemas.has(source)) return;
969
+ const tables = MARKET_CONTEXT_SCHEMA_TABLES[source];
970
+ const result = await queryMarketContext(
971
+ `
972
+ SELECT table_name AS "tableName"
973
+ FROM unnest($1::text[]) AS requested(table_name)
974
+ WHERE to_regclass(requested.table_name) IS NULL
975
+ `,
976
+ [tables]
977
+ );
978
+ if (result.rows.length) {
979
+ throw new Error(
980
+ `Timescale ${source} schema is not prepared; missing: ${result.rows.map((row) => row.tableName).filter(Boolean).join(", ")}`
981
+ );
982
+ }
983
+ verifiedMarketContextSchemas.add(source);
984
+ };
985
+ var prepareMarketContextSchemaForRead = async (source) => marketContextSchemaMode === "verify" ? verifyMarketContextSchema(source) : ensureMarketContextSchema(source);
986
+ var ensureMarketContextSchemas = async (sources) => {
987
+ for (const source of new Set(sources)) {
988
+ await ensureMarketContextSchema(source);
989
+ }
990
+ };
868
991
  async function upsertDerivatives(rows) {
869
992
  if (!rows.length) return;
870
993
  await ensureDerivativesSchema();
@@ -1192,9 +1315,8 @@ async function getDerivativesWindow(params) {
1192
1315
  if (!normalizedSymbol || !normalizedIntervals.length) {
1193
1316
  return {};
1194
1317
  }
1195
- await ensureDerivativesSchema();
1318
+ await prepareMarketContextSchemaForRead("derivatives");
1196
1319
  const startMs = endMs - Math.max(0, lookbackMs);
1197
- const pool = getPool();
1198
1320
  const sql = `
1199
1321
  SELECT symbol, interval, ts, open_interest, funding_rate, liq_long, liq_short, liq_total, source
1200
1322
  FROM derivatives_market
@@ -1204,12 +1326,11 @@ async function getDerivativesWindow(params) {
1204
1326
  AND ts <= to_timestamp($4/1000.0)
1205
1327
  ORDER BY interval ASC, ts ASC
1206
1328
  `;
1207
- const res = await pool.query(sql, [
1208
- normalizedSymbol,
1209
- normalizedIntervals,
1210
- startMs,
1211
- endMs
1212
- ]);
1329
+ const res = await queryMarketContext(
1330
+ sql,
1331
+ [normalizedSymbol, normalizedIntervals, startMs, endMs],
1332
+ params
1333
+ );
1213
1334
  const rowsByInterval = {};
1214
1335
  for (const row of res.rows) {
1215
1336
  const interval = row.interval;
@@ -2416,9 +2537,8 @@ var toMarketFeatureAge = (rowTs, atMs) => {
2416
2537
  return Number.isFinite(ageMs) ? ageMs : null;
2417
2538
  };
2418
2539
  async function getLatestMarketTradeFlow(params) {
2419
- await ensureBinanceMarketSchema();
2420
- const pool = getPool();
2421
- const res = await pool.query(
2540
+ await prepareMarketContextSchemaForRead("binance");
2541
+ const res = await queryMarketContext(
2422
2542
  `
2423
2543
  SELECT
2424
2544
  symbol,
@@ -2440,7 +2560,8 @@ async function getLatestMarketTradeFlow(params) {
2440
2560
  ORDER BY ts DESC
2441
2561
  LIMIT 1
2442
2562
  `,
2443
- [params.symbol.toUpperCase(), params.interval, params.atMs]
2563
+ [params.symbol.toUpperCase(), params.interval, params.atMs],
2564
+ params
2444
2565
  );
2445
2566
  const row = res.rows[0];
2446
2567
  if (!row) return null;
@@ -2568,71 +2689,154 @@ async function upsertHyperliquidWhaleWalletCoverage(params) {
2568
2689
  async function rebuildHyperliquidWhaleCoverageRows(params) {
2569
2690
  await ensureHyperliquidWhaleSchema();
2570
2691
  if (params.toMs <= params.fromMs) return 0;
2571
- const result = await getPool().query(
2572
- `
2573
- WITH buckets AS (
2574
- SELECT generate_series(
2575
- to_timestamp($3/1000.0),
2576
- to_timestamp($4/1000.0) - interval '1 minute',
2577
- interval '1 minute'
2578
- ) AS ts
2579
- ), coverage AS (
2692
+ const minuteMs = 6e4;
2693
+ const defaultChunkMinutes = 7 * 24 * 60;
2694
+ const chunkMinutes = Number.isFinite(params.chunkMinutes) && Number(params.chunkMinutes) > 0 ? Math.floor(Number(params.chunkMinutes)) : defaultChunkMinutes;
2695
+ const chunkMs = chunkMinutes * minuteMs;
2696
+ const totalBuckets = Math.ceil((params.toMs - params.fromMs) / minuteMs);
2697
+ const totalChunks = Math.ceil((params.toMs - params.fromMs) / chunkMs);
2698
+ let completedBuckets = 0;
2699
+ let rows = 0;
2700
+ for (let chunkIndex = 0, chunkFromMs = params.fromMs; chunkFromMs < params.toMs; chunkIndex += 1, chunkFromMs += chunkMs) {
2701
+ const chunkToMs = Math.min(params.toMs, chunkFromMs + chunkMs);
2702
+ const result = await getPool().query(
2703
+ `
2704
+ WITH normalized_ranges AS (
2705
+ SELECT
2706
+ address,
2707
+ GREATEST(
2708
+ to_timestamp($3/1000.0),
2709
+ date_trunc('minute', covered_from_ts) +
2710
+ CASE
2711
+ WHEN covered_from_ts = date_trunc('minute', covered_from_ts)
2712
+ THEN interval '0 minutes'
2713
+ ELSE interval '1 minute'
2714
+ END
2715
+ ) AS range_start,
2716
+ LEAST(
2717
+ to_timestamp($4/1000.0),
2718
+ date_trunc('minute', covered_to_ts)
2719
+ ) AS range_end
2720
+ FROM hyperliquid_whale_wallet_coverage
2721
+ WHERE universe_fingerprint = $1
2722
+ AND whale_registry_fingerprint = $2
2723
+ AND data_model_version = $6
2724
+ AND status IN ('complete', 'truncated')
2725
+ AND covered_from_ts < to_timestamp($4/1000.0)
2726
+ AND covered_to_ts > to_timestamp($3/1000.0)
2727
+ ), eligible_ranges AS (
2728
+ SELECT *
2729
+ FROM normalized_ranges
2730
+ WHERE range_start < range_end
2731
+ ), ordered_ranges AS (
2732
+ SELECT
2733
+ *,
2734
+ MAX(range_end) OVER (
2735
+ PARTITION BY address
2736
+ ORDER BY range_start, range_end
2737
+ ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
2738
+ ) AS previous_max_end
2739
+ FROM eligible_ranges
2740
+ ), marked_ranges AS (
2741
+ SELECT
2742
+ *,
2743
+ SUM(
2744
+ CASE
2745
+ WHEN previous_max_end IS NULL OR range_start > previous_max_end
2746
+ THEN 1
2747
+ ELSE 0
2748
+ END
2749
+ ) OVER (
2750
+ PARTITION BY address
2751
+ ORDER BY range_start, range_end
2752
+ ) AS range_group
2753
+ FROM ordered_ranges
2754
+ ), merged_ranges AS (
2755
+ SELECT
2756
+ address,
2757
+ MIN(range_start) AS range_start,
2758
+ MAX(range_end) AS range_end
2759
+ FROM marked_ranges
2760
+ GROUP BY address, range_group
2761
+ ), deltas AS (
2762
+ SELECT range_start AS ts, 1 AS delta
2763
+ FROM merged_ranges
2764
+ UNION ALL
2765
+ SELECT range_end AS ts, -1 AS delta
2766
+ FROM merged_ranges
2767
+ ), bucket_deltas AS (
2768
+ SELECT ts, SUM(delta)::int AS delta
2769
+ FROM deltas
2770
+ GROUP BY ts
2771
+ ), buckets AS (
2772
+ SELECT generate_series(
2773
+ to_timestamp($3/1000.0),
2774
+ to_timestamp($4/1000.0) - interval '1 minute',
2775
+ interval '1 minute'
2776
+ ) AS ts
2777
+ ), coverage AS (
2778
+ SELECT
2779
+ buckets.ts,
2780
+ SUM(COALESCE(bucket_deltas.delta, 0)) OVER (
2781
+ ORDER BY buckets.ts
2782
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
2783
+ )::int AS covered_whales
2784
+ FROM buckets
2785
+ LEFT JOIN bucket_deltas USING (ts)
2786
+ )
2787
+ INSERT INTO hyperliquid_whale_coverage_1m (
2788
+ ts,
2789
+ covered_whales,
2790
+ expected_whales,
2791
+ coverage_pct,
2792
+ universe_fingerprint,
2793
+ whale_registry_fingerprint,
2794
+ source,
2795
+ data_model_version
2796
+ )
2580
2797
  SELECT
2581
- buckets.ts,
2582
- COUNT(DISTINCT wallets.address)::int AS covered_whales
2583
- FROM buckets
2584
- LEFT JOIN hyperliquid_whale_wallet_coverage wallets
2585
- ON wallets.universe_fingerprint = $1
2586
- AND wallets.whale_registry_fingerprint = $2
2587
- AND wallets.data_model_version = $6
2588
- AND wallets.status IN ('complete', 'truncated')
2589
- AND wallets.covered_from_ts <= buckets.ts
2590
- AND wallets.covered_to_ts >= buckets.ts + interval '1 minute'
2591
- GROUP BY buckets.ts
2592
- )
2593
- INSERT INTO hyperliquid_whale_coverage_1m (
2594
- ts,
2595
- covered_whales,
2596
- expected_whales,
2597
- coverage_pct,
2598
- universe_fingerprint,
2599
- whale_registry_fingerprint,
2600
- source
2601
- ,data_model_version
2602
- )
2603
- SELECT
2604
- ts,
2605
- covered_whales,
2606
- $5,
2607
- CASE WHEN $5 > 0 THEN covered_whales::double precision / $5 ELSE 0 END,
2608
- $1,
2609
- $2,
2610
- 'hyperliquid_user_fills',
2611
- $6
2612
- FROM coverage
2613
- ON CONFLICT (
2614
- universe_fingerprint,
2615
- whale_registry_fingerprint,
2616
- ts
2617
- ) DO UPDATE SET
2618
- covered_whales = EXCLUDED.covered_whales,
2619
- expected_whales = EXCLUDED.expected_whales,
2620
- coverage_pct = EXCLUDED.coverage_pct,
2621
- source = EXCLUDED.source,
2622
- data_model_version = EXCLUDED.data_model_version,
2623
- ingested_at = now()
2624
- RETURNING 1
2625
- `,
2626
- [
2627
- params.universeFingerprint,
2628
- params.whaleRegistryFingerprint,
2629
- params.fromMs,
2630
- params.toMs,
2631
- params.expectedWhales,
2632
- HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2633
- ]
2634
- );
2635
- return result.rowCount ?? 0;
2798
+ ts,
2799
+ covered_whales,
2800
+ $5,
2801
+ CASE WHEN $5 > 0 THEN covered_whales::double precision / $5 ELSE 0 END,
2802
+ $1,
2803
+ $2,
2804
+ 'hyperliquid_user_fills',
2805
+ $6
2806
+ FROM coverage
2807
+ ON CONFLICT (
2808
+ universe_fingerprint,
2809
+ whale_registry_fingerprint,
2810
+ ts
2811
+ ) DO UPDATE SET
2812
+ covered_whales = EXCLUDED.covered_whales,
2813
+ expected_whales = EXCLUDED.expected_whales,
2814
+ coverage_pct = EXCLUDED.coverage_pct,
2815
+ source = EXCLUDED.source,
2816
+ data_model_version = EXCLUDED.data_model_version,
2817
+ ingested_at = now()
2818
+ `,
2819
+ [
2820
+ params.universeFingerprint,
2821
+ params.whaleRegistryFingerprint,
2822
+ chunkFromMs,
2823
+ chunkToMs,
2824
+ params.expectedWhales,
2825
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2826
+ ]
2827
+ );
2828
+ const chunkBuckets = Math.ceil((chunkToMs - chunkFromMs) / minuteMs);
2829
+ completedBuckets = Math.min(totalBuckets, completedBuckets + chunkBuckets);
2830
+ rows += result.rowCount ?? 0;
2831
+ params.onProgress?.({
2832
+ chunkIndex: chunkIndex + 1,
2833
+ totalChunks,
2834
+ completedBuckets,
2835
+ totalBuckets,
2836
+ rows
2837
+ });
2838
+ }
2839
+ return rows;
2636
2840
  }
2637
2841
  async function upsertHyperliquidWhaleCoverageRows(rows) {
2638
2842
  if (!rows.length) return;
@@ -2691,8 +2895,8 @@ async function upsertHyperliquidWhaleCoverageRows(rows) {
2691
2895
  );
2692
2896
  }
2693
2897
  async function getHyperliquidWhaleCoverageSeriesRows(params) {
2694
- await ensureHyperliquidWhaleSchema();
2695
- const result = await getPool().query(
2898
+ await prepareMarketContextSchemaForRead("hyperliquidWhales");
2899
+ const result = await queryMarketContext(
2696
2900
  `
2697
2901
  SELECT
2698
2902
  ts,
@@ -2713,7 +2917,8 @@ async function getHyperliquidWhaleCoverageSeriesRows(params) {
2713
2917
  HYPERLIQUID_WHALE_DATA_MODEL_VERSION,
2714
2918
  params.fromMs,
2715
2919
  params.toMs
2716
- ]
2920
+ ],
2921
+ params
2717
2922
  );
2718
2923
  return result.rows.map((row) => ({
2719
2924
  ts: new Date(row.ts),
@@ -2723,8 +2928,8 @@ async function getHyperliquidWhaleCoverageSeriesRows(params) {
2723
2928
  }));
2724
2929
  }
2725
2930
  async function getHyperliquidWhaleFlowSeriesRows(params) {
2726
- await ensureHyperliquidWhaleSchema();
2727
- const result = await getPool().query(
2931
+ await prepareMarketContextSchemaForRead("hyperliquidWhales");
2932
+ const result = await queryMarketContext(
2728
2933
  `
2729
2934
  SELECT
2730
2935
  ts,
@@ -2757,7 +2962,8 @@ async function getHyperliquidWhaleFlowSeriesRows(params) {
2757
2962
  params.whaleRegistryFingerprint,
2758
2963
  params.fromMs,
2759
2964
  params.toMs
2760
- ]
2965
+ ],
2966
+ params
2761
2967
  );
2762
2968
  return result.rows.map((row) => ({
2763
2969
  ts: new Date(row.ts),
@@ -2778,10 +2984,10 @@ async function getHyperliquidWhaleFlowSeriesRows(params) {
2778
2984
  }));
2779
2985
  }
2780
2986
  async function getHyperliquidWhaleFlowAggregate(params) {
2781
- await ensureHyperliquidWhaleSchema();
2987
+ await prepareMarketContextSchemaForRead("hyperliquidWhales");
2782
2988
  const intervalMs = HYPERLIQUID_CONTEXT_INTERVAL_MS[params.interval];
2783
2989
  const expectedBuckets = Math.ceil(intervalMs / 6e4);
2784
- const res = await getPool().query(
2990
+ const res = await queryMarketContext(
2785
2991
  `
2786
2992
  WITH coverage_rows AS (
2787
2993
  SELECT *
@@ -2896,7 +3102,8 @@ async function getHyperliquidWhaleFlowAggregate(params) {
2896
3102
  params.decisionTimeMs,
2897
3103
  intervalMs,
2898
3104
  HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2899
- ]
3105
+ ],
3106
+ params
2900
3107
  );
2901
3108
  const row = res.rows[0];
2902
3109
  if (!row?.as_of_ts || Number(row.coverage_buckets) !== expectedBuckets || Number(row.covered_whales) <= 0) {
@@ -2969,9 +3176,8 @@ async function hasHyperliquidWhaleBackfillCoverage(params) {
2969
3176
  return Number(result.rows[0]?.buckets) === expectedBuckets && Number(result.rows[0]?.complete_buckets) === expectedBuckets;
2970
3177
  }
2971
3178
  async function getLatestMarketBreadth(params) {
2972
- await ensureBinanceMarketSchema();
2973
- const pool = getPool();
2974
- const res = await pool.query(
3179
+ await prepareMarketContextSchemaForRead("binance");
3180
+ const res = await queryMarketContext(
2975
3181
  `
2976
3182
  SELECT
2977
3183
  universe,
@@ -3010,7 +3216,8 @@ async function getLatestMarketBreadth(params) {
3010
3216
  ORDER BY ts DESC
3011
3217
  LIMIT 1
3012
3218
  `,
3013
- [params.universe, params.interval, params.atMs]
3219
+ [params.universe, params.interval, params.atMs],
3220
+ params
3014
3221
  );
3015
3222
  const row = res.rows[0];
3016
3223
  if (!row) return null;
@@ -3022,10 +3229,9 @@ async function getLatestMarketBreadth(params) {
3022
3229
  };
3023
3230
  }
3024
3231
  async function getLatestMarketGlobalContext(params) {
3025
- await ensureBinanceMarketSchema();
3026
- const pool = getPool();
3232
+ await prepareMarketContextSchemaForRead("coinmarketcap");
3027
3233
  const source = params.source ?? "coinmarketcap_global";
3028
- const res = await pool.query(
3234
+ const res = await queryMarketContext(
3029
3235
  `
3030
3236
  SELECT
3031
3237
  source,
@@ -3054,11 +3260,12 @@ async function getLatestMarketGlobalContext(params) {
3054
3260
  ORDER BY ts DESC
3055
3261
  LIMIT 1
3056
3262
  `,
3057
- [source, params.atMs]
3263
+ [source, params.atMs],
3264
+ params
3058
3265
  );
3059
3266
  const row = res.rows[0];
3060
3267
  if (!row) return null;
3061
- const previousRes = await pool.query(
3268
+ const previousRes = await queryMarketContext(
3062
3269
  `
3063
3270
  SELECT
3064
3271
  btc_dominance_pct AS "btcDominancePct",
@@ -3071,7 +3278,8 @@ async function getLatestMarketGlobalContext(params) {
3071
3278
  ORDER BY ts DESC
3072
3279
  LIMIT 1
3073
3280
  `,
3074
- [source, row.ts]
3281
+ [source, row.ts],
3282
+ params
3075
3283
  );
3076
3284
  const previousDominance = previousRes.rows[0]?.btcDominancePct == null ? null : Number(previousRes.rows[0].btcDominancePct);
3077
3285
  const previousEthDominance = previousRes.rows[0]?.ethDominancePct == null ? null : Number(previousRes.rows[0].ethDominancePct);
@@ -3164,9 +3372,8 @@ async function getLatestMarketReferenceAssetContexts(params) {
3164
3372
  ];
3165
3373
  const rows = /* @__PURE__ */ new Map();
3166
3374
  if (!symbols.length) return rows;
3167
- await ensureBinanceMarketSchema();
3168
- const pool = getPool();
3169
- const res = await pool.query(
3375
+ await prepareMarketContextSchemaForRead("coinmarketcap");
3376
+ const res = await queryMarketContext(
3170
3377
  `
3171
3378
  SELECT DISTINCT ON (symbol)
3172
3379
  source,
@@ -3191,7 +3398,8 @@ async function getLatestMarketReferenceAssetContexts(params) {
3191
3398
  END <= to_timestamp($4/1000.0)
3192
3399
  ORDER BY symbol ASC, ts DESC
3193
3400
  `,
3194
- [source, symbols, interval, params.atMs]
3401
+ [source, symbols, interval, params.atMs],
3402
+ params
3195
3403
  );
3196
3404
  for (const row of res.rows) {
3197
3405
  const ageMs = toMarketFeatureAge(row.ts, params.atMs);
@@ -3204,11 +3412,10 @@ async function getLatestMarketReferenceAssetContexts(params) {
3204
3412
  return rows;
3205
3413
  }
3206
3414
  async function getLatestMarketCmcExchangeLiquidityContext(params) {
3207
- await ensureBinanceMarketSchema();
3208
- const pool = getPool();
3415
+ await prepareMarketContextSchemaForRead("coinmarketcap");
3209
3416
  const source = params.source ?? "coinmarketcap_exchange_liquidity";
3210
3417
  const interval = params.interval ?? "1d";
3211
- const res = await pool.query(
3418
+ const res = await queryMarketContext(
3212
3419
  `
3213
3420
  SELECT
3214
3421
  source,
@@ -3231,11 +3438,12 @@ async function getLatestMarketCmcExchangeLiquidityContext(params) {
3231
3438
  ORDER BY ts DESC
3232
3439
  LIMIT 1
3233
3440
  `,
3234
- [source, interval, params.atMs]
3441
+ [source, interval, params.atMs],
3442
+ params
3235
3443
  );
3236
3444
  const row = res.rows[0];
3237
3445
  if (!row) return null;
3238
- const previousRes = await pool.query(
3446
+ const previousRes = await queryMarketContext(
3239
3447
  `
3240
3448
  SELECT total_volume_usd AS "totalVolumeUsd"
3241
3449
  FROM market_cmc_exchange_liquidity_context
@@ -3245,7 +3453,8 @@ async function getLatestMarketCmcExchangeLiquidityContext(params) {
3245
3453
  ORDER BY ts DESC
3246
3454
  LIMIT 1
3247
3455
  `,
3248
- [source, interval, row.ts]
3456
+ [source, interval, row.ts],
3457
+ params
3249
3458
  );
3250
3459
  const currentTotal = row.totalVolumeUsd == null ? null : Number(row.totalVolumeUsd);
3251
3460
  const previousTotal = previousRes.rows[0]?.totalVolumeUsd == null ? null : Number(previousRes.rows[0].totalVolumeUsd);
@@ -3269,9 +3478,8 @@ async function getLatestMarketCmcIndexContexts(params) {
3269
3478
  ];
3270
3479
  const rows = /* @__PURE__ */ new Map();
3271
3480
  if (!indexSlugs.length) return rows;
3272
- await ensureBinanceMarketSchema();
3273
- const pool = getPool();
3274
- const res = await pool.query(
3481
+ await prepareMarketContextSchemaForRead("coinmarketcap");
3482
+ const res = await queryMarketContext(
3275
3483
  `
3276
3484
  SELECT DISTINCT ON (index_slug)
3277
3485
  source,
@@ -3294,10 +3502,11 @@ async function getLatestMarketCmcIndexContexts(params) {
3294
3502
  END <= to_timestamp($4/1000.0)
3295
3503
  ORDER BY index_slug ASC, ts DESC
3296
3504
  `,
3297
- [source, indexSlugs, interval, params.atMs]
3505
+ [source, indexSlugs, interval, params.atMs],
3506
+ params
3298
3507
  );
3299
3508
  for (const row of res.rows) {
3300
- const previousRes = await pool.query(
3509
+ const previousRes = await queryMarketContext(
3301
3510
  `
3302
3511
  SELECT value
3303
3512
  FROM market_cmc_index_context
@@ -3308,7 +3517,8 @@ async function getLatestMarketCmcIndexContexts(params) {
3308
3517
  ORDER BY ts DESC
3309
3518
  LIMIT 1
3310
3519
  `,
3311
- [source, row.indexSlug, interval, row.ts]
3520
+ [source, row.indexSlug, interval, row.ts],
3521
+ params
3312
3522
  );
3313
3523
  const currentValue = row.value == null ? null : Number(row.value);
3314
3524
  const previousValue = previousRes.rows[0]?.value == null ? null : Number(previousRes.rows[0].value);
@@ -3323,11 +3533,10 @@ async function getLatestMarketCmcIndexContexts(params) {
3323
3533
  return rows;
3324
3534
  }
3325
3535
  async function getLatestMarketCmcFearGreedContext(params) {
3326
- await ensureBinanceMarketSchema();
3327
- const pool = getPool();
3536
+ await prepareMarketContextSchemaForRead("coinmarketcap");
3328
3537
  const source = params.source ?? "coinmarketcap_fear_greed";
3329
3538
  const interval = params.interval ?? "1d";
3330
- const res = await pool.query(
3539
+ const res = await queryMarketContext(
3331
3540
  `
3332
3541
  SELECT
3333
3542
  source,
@@ -3347,11 +3556,12 @@ async function getLatestMarketCmcFearGreedContext(params) {
3347
3556
  ORDER BY ts DESC
3348
3557
  LIMIT 1
3349
3558
  `,
3350
- [source, interval, params.atMs]
3559
+ [source, interval, params.atMs],
3560
+ params
3351
3561
  );
3352
3562
  const row = res.rows[0];
3353
3563
  if (!row) return null;
3354
- const previousRes = await pool.query(
3564
+ const previousRes = await queryMarketContext(
3355
3565
  `
3356
3566
  SELECT
3357
3567
  value::int AS value,
@@ -3363,9 +3573,10 @@ async function getLatestMarketCmcFearGreedContext(params) {
3363
3573
  ORDER BY ts DESC
3364
3574
  LIMIT 1
3365
3575
  `,
3366
- [source, interval, row.ts]
3576
+ [source, interval, row.ts],
3577
+ params
3367
3578
  );
3368
- const previous7dRes = await pool.query(
3579
+ const previous7dRes = await queryMarketContext(
3369
3580
  `
3370
3581
  SELECT value::int AS value
3371
3582
  FROM market_cmc_fear_greed_context
@@ -3375,7 +3586,8 @@ async function getLatestMarketCmcFearGreedContext(params) {
3375
3586
  ORDER BY ts DESC
3376
3587
  LIMIT 1
3377
3588
  `,
3378
- [source, interval, row.ts]
3589
+ [source, interval, row.ts],
3590
+ params
3379
3591
  );
3380
3592
  const previousValue = previousRes.rows[0]?.value == null ? null : Number(previousRes.rows[0].value);
3381
3593
  const previous7dValue = previous7dRes.rows[0]?.value == null ? null : Number(previous7dRes.rows[0].value);
@@ -3861,7 +4073,13 @@ export {
3861
4073
  applyDerivativesMetricCoverage,
3862
4074
  cleanupDeprecatedMarketContext,
3863
4075
  closeTimescalePool,
4076
+ configureTimescaleMarketContextSchemaMode,
3864
4077
  deleteCandles,
4078
+ ensureBinanceMarketSchema,
4079
+ ensureCoinMarketCapContextSchema,
4080
+ ensureDerivativesSchema,
4081
+ ensureHyperliquidWhaleSchema,
4082
+ ensureMarketContextSchemas,
3865
4083
  findContinuityGap,
3866
4084
  getCandlesRange,
3867
4085
  getDataEdges,