@tradejs/infra 2.0.21 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/{chunk-YVIHTUV5.mjs → chunk-OLB6NQRG.mjs} +1 -1
  2. package/dist/{chunk-I2J6YDBD.mjs → chunk-ZXU7OYFU.mjs} +65 -38
  3. package/dist/internal-BuHqoCCV.d.mts +9 -0
  4. package/dist/internal-q48Torp4.d.ts +9 -0
  5. package/dist/ml.mjs +3 -3
  6. package/dist/query-tkHVD4Xo.d.mts +12 -0
  7. package/dist/query-tkHVD4Xo.d.ts +12 -0
  8. package/dist/timescale/candles.js +6 -4
  9. package/dist/timescale/candles.mjs +2 -2
  10. package/dist/timescale/client.d.mts +3 -2
  11. package/dist/timescale/client.d.ts +3 -2
  12. package/dist/timescale/client.js +40 -15
  13. package/dist/timescale/client.mjs +2 -2
  14. package/dist/timescale/derivatives.d.mts +4 -2
  15. package/dist/timescale/derivatives.d.ts +4 -2
  16. package/dist/timescale/derivatives.js +20 -12
  17. package/dist/timescale/derivatives.mjs +479 -13
  18. package/dist/timescale/hyperliquidWhales.d.mts +29 -25
  19. package/dist/timescale/hyperliquidWhales.d.ts +29 -25
  20. package/dist/timescale/hyperliquidWhales.js +84 -71
  21. package/dist/timescale/hyperliquidWhales.mjs +1103 -14
  22. package/dist/timescale/marketContext.d.mts +15 -11
  23. package/dist/timescale/marketContext.d.ts +15 -11
  24. package/dist/timescale/marketContext.js +67 -57
  25. package/dist/timescale/marketContext.mjs +1259 -28
  26. package/dist/timescale/spread.js +6 -4
  27. package/dist/timescale/spread.mjs +105 -5
  28. package/dist/userSettings.js +3 -316
  29. package/dist/userSettings.mjs +3 -21
  30. package/package.json +2 -22
  31. package/dist/aiEndpoints.d.mts +0 -10
  32. package/dist/aiEndpoints.d.ts +0 -10
  33. package/dist/aiEndpoints.js +0 -159
  34. package/dist/aiEndpoints.mjs +0 -12
  35. package/dist/aiLanguages.d.mts +0 -11
  36. package/dist/aiLanguages.d.ts +0 -11
  37. package/dist/aiLanguages.js +0 -71
  38. package/dist/aiLanguages.mjs +0 -12
  39. package/dist/aiModels.d.mts +0 -12
  40. package/dist/aiModels.d.ts +0 -12
  41. package/dist/aiModels.js +0 -272
  42. package/dist/aiModels.mjs +0 -17
  43. package/dist/chunk-2CZREG43.mjs +0 -112
  44. package/dist/chunk-CCC7DX2T.mjs +0 -44
  45. package/dist/chunk-DFMKDB2R.mjs +0 -1285
  46. package/dist/chunk-DTCLZIBM.mjs +0 -163
  47. package/dist/chunk-NWXFWTWU.mjs +0 -1114
  48. package/dist/chunk-SZQB7ER5.mjs +0 -492
  49. package/dist/chunk-XQ3YBULV.mjs +0 -132
  50. package/dist/internal-2coHaaos.d.mts +0 -26
  51. package/dist/internal-2coHaaos.d.ts +0 -26
  52. package/dist/timescale.d.mts +0 -9
  53. package/dist/timescale.d.ts +0 -9
  54. package/dist/timescale.js +0 -4228
  55. package/dist/timescale.mjs +0 -138
@@ -1,1285 +0,0 @@
1
- import {
2
- ensureBinanceMarketSchema,
3
- getPool,
4
- getSafeBulkInsertRows,
5
- prepareMarketContextSchemaForRead,
6
- queryMarketContext,
7
- toMarketFeatureAge
8
- } from "./chunk-I2J6YDBD.mjs";
9
-
10
- // src/timescale/marketContext.ts
11
- async function upsertMarketTradeFlowRows(rows) {
12
- if (!rows.length) return;
13
- await ensureBinanceMarketSchema();
14
- const pool = getPool();
15
- const cols = [
16
- "symbol",
17
- "interval",
18
- "ts",
19
- "trades",
20
- "buy_base_volume",
21
- "sell_base_volume",
22
- "buy_quote_volume",
23
- "sell_quote_volume",
24
- "net_base_delta",
25
- "net_quote_delta",
26
- "buy_pressure_pct",
27
- "source"
28
- ];
29
- const maxRows = getSafeBulkInsertRows(cols.length);
30
- if (rows.length > maxRows) {
31
- for (let i = 0; i < rows.length; i += maxRows) {
32
- await upsertMarketTradeFlowRows(rows.slice(i, i + maxRows));
33
- }
34
- return;
35
- }
36
- const valuesSql = rows.map(
37
- (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
38
- ).join(",");
39
- const flat = rows.flatMap((row) => [
40
- row.symbol,
41
- row.interval,
42
- row.ts,
43
- row.trades,
44
- row.buyBaseVolume ?? null,
45
- row.sellBaseVolume ?? null,
46
- row.buyQuoteVolume ?? null,
47
- row.sellQuoteVolume ?? null,
48
- row.netBaseDelta ?? null,
49
- row.netQuoteDelta ?? null,
50
- row.buyPressurePct ?? null,
51
- row.source ?? null
52
- ]);
53
- await pool.query(
54
- `
55
- INSERT INTO market_trade_flow (${cols.join(",")})
56
- VALUES ${valuesSql}
57
- ON CONFLICT (symbol, interval, ts) DO UPDATE SET
58
- trades = EXCLUDED.trades,
59
- buy_base_volume = COALESCE(EXCLUDED.buy_base_volume, market_trade_flow.buy_base_volume),
60
- sell_base_volume = COALESCE(EXCLUDED.sell_base_volume, market_trade_flow.sell_base_volume),
61
- buy_quote_volume = COALESCE(EXCLUDED.buy_quote_volume, market_trade_flow.buy_quote_volume),
62
- sell_quote_volume = COALESCE(EXCLUDED.sell_quote_volume, market_trade_flow.sell_quote_volume),
63
- net_base_delta = COALESCE(EXCLUDED.net_base_delta, market_trade_flow.net_base_delta),
64
- net_quote_delta = COALESCE(EXCLUDED.net_quote_delta, market_trade_flow.net_quote_delta),
65
- buy_pressure_pct = COALESCE(EXCLUDED.buy_pressure_pct, market_trade_flow.buy_pressure_pct),
66
- source = COALESCE(EXCLUDED.source, market_trade_flow.source),
67
- ingested_at = now()
68
- `,
69
- flat
70
- );
71
- }
72
- async function upsertMarketBreadthRows(rows) {
73
- if (!rows.length) return;
74
- await ensureBinanceMarketSchema();
75
- const pool = getPool();
76
- const cols = [
77
- "universe",
78
- "interval",
79
- "ts",
80
- "symbols_count",
81
- "advancers",
82
- "decliners",
83
- "unchanged",
84
- "advance_decline_ratio",
85
- "pct_above_ma20",
86
- "pct_above_ma50",
87
- "equal_weighted_return",
88
- "volume_weighted_return",
89
- "dispersion",
90
- "btc_return_1h",
91
- "btc_return_4h",
92
- "btc_return_24h",
93
- "alt_basket_return_1h",
94
- "alt_basket_return_4h",
95
- "alt_basket_return_24h",
96
- "btc_vs_alt_return_1h",
97
- "btc_vs_alt_return_4h",
98
- "btc_vs_alt_return_24h",
99
- "btc_turnover_share_1h",
100
- "btc_turnover_share_24h",
101
- "btc_turnover_share_change_24h",
102
- "alt_vol_to_btc_vol_24h",
103
- "alt_dispersion_24h",
104
- "btc_alt_regime",
105
- "source"
106
- ];
107
- const maxRows = getSafeBulkInsertRows(cols.length);
108
- if (rows.length > maxRows) {
109
- for (let i = 0; i < rows.length; i += maxRows) {
110
- await upsertMarketBreadthRows(rows.slice(i, i + maxRows));
111
- }
112
- return;
113
- }
114
- const valuesSql = rows.map(
115
- (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
116
- ).join(",");
117
- const flat = rows.flatMap((row) => [
118
- row.universe,
119
- row.interval,
120
- row.ts,
121
- row.symbolsCount,
122
- row.advancers,
123
- row.decliners,
124
- row.unchanged,
125
- row.advanceDeclineRatio ?? null,
126
- row.pctAboveMa20 ?? null,
127
- row.pctAboveMa50 ?? null,
128
- row.equalWeightedReturn ?? null,
129
- row.volumeWeightedReturn ?? null,
130
- row.dispersion ?? null,
131
- row.btcReturn1h ?? null,
132
- row.btcReturn4h ?? null,
133
- row.btcReturn24h ?? null,
134
- row.altBasketReturn1h ?? null,
135
- row.altBasketReturn4h ?? null,
136
- row.altBasketReturn24h ?? null,
137
- row.btcVsAltReturn1h ?? null,
138
- row.btcVsAltReturn4h ?? null,
139
- row.btcVsAltReturn24h ?? null,
140
- row.btcTurnoverShare1h ?? null,
141
- row.btcTurnoverShare24h ?? null,
142
- row.btcTurnoverShareChange24h ?? null,
143
- row.altVolToBtcVol24h ?? null,
144
- row.altDispersion24h ?? null,
145
- row.btcAltRegime ?? null,
146
- row.source ?? null
147
- ]);
148
- await pool.query(
149
- `
150
- INSERT INTO market_breadth (${cols.join(",")})
151
- VALUES ${valuesSql}
152
- ON CONFLICT (universe, interval, ts) DO UPDATE SET
153
- symbols_count = EXCLUDED.symbols_count,
154
- advancers = EXCLUDED.advancers,
155
- decliners = EXCLUDED.decliners,
156
- unchanged = EXCLUDED.unchanged,
157
- advance_decline_ratio = COALESCE(EXCLUDED.advance_decline_ratio, market_breadth.advance_decline_ratio),
158
- pct_above_ma20 = COALESCE(EXCLUDED.pct_above_ma20, market_breadth.pct_above_ma20),
159
- pct_above_ma50 = COALESCE(EXCLUDED.pct_above_ma50, market_breadth.pct_above_ma50),
160
- equal_weighted_return = COALESCE(EXCLUDED.equal_weighted_return, market_breadth.equal_weighted_return),
161
- volume_weighted_return = COALESCE(EXCLUDED.volume_weighted_return, market_breadth.volume_weighted_return),
162
- dispersion = COALESCE(EXCLUDED.dispersion, market_breadth.dispersion),
163
- btc_return_1h = COALESCE(EXCLUDED.btc_return_1h, market_breadth.btc_return_1h),
164
- btc_return_4h = COALESCE(EXCLUDED.btc_return_4h, market_breadth.btc_return_4h),
165
- btc_return_24h = COALESCE(EXCLUDED.btc_return_24h, market_breadth.btc_return_24h),
166
- alt_basket_return_1h = COALESCE(EXCLUDED.alt_basket_return_1h, market_breadth.alt_basket_return_1h),
167
- alt_basket_return_4h = COALESCE(EXCLUDED.alt_basket_return_4h, market_breadth.alt_basket_return_4h),
168
- alt_basket_return_24h = COALESCE(EXCLUDED.alt_basket_return_24h, market_breadth.alt_basket_return_24h),
169
- btc_vs_alt_return_1h = COALESCE(EXCLUDED.btc_vs_alt_return_1h, market_breadth.btc_vs_alt_return_1h),
170
- btc_vs_alt_return_4h = COALESCE(EXCLUDED.btc_vs_alt_return_4h, market_breadth.btc_vs_alt_return_4h),
171
- btc_vs_alt_return_24h = COALESCE(EXCLUDED.btc_vs_alt_return_24h, market_breadth.btc_vs_alt_return_24h),
172
- btc_turnover_share_1h = COALESCE(EXCLUDED.btc_turnover_share_1h, market_breadth.btc_turnover_share_1h),
173
- btc_turnover_share_24h = COALESCE(EXCLUDED.btc_turnover_share_24h, market_breadth.btc_turnover_share_24h),
174
- btc_turnover_share_change_24h = COALESCE(EXCLUDED.btc_turnover_share_change_24h, market_breadth.btc_turnover_share_change_24h),
175
- alt_vol_to_btc_vol_24h = COALESCE(EXCLUDED.alt_vol_to_btc_vol_24h, market_breadth.alt_vol_to_btc_vol_24h),
176
- alt_dispersion_24h = COALESCE(EXCLUDED.alt_dispersion_24h, market_breadth.alt_dispersion_24h),
177
- btc_alt_regime = COALESCE(EXCLUDED.btc_alt_regime, market_breadth.btc_alt_regime),
178
- source = COALESCE(EXCLUDED.source, market_breadth.source),
179
- ingested_at = now()
180
- `,
181
- flat
182
- );
183
- }
184
- async function upsertMarketGlobalContextRows(rows) {
185
- if (!rows.length) return;
186
- await ensureBinanceMarketSchema();
187
- const pool = getPool();
188
- const cols = [
189
- "source",
190
- "ts",
191
- "updated_at_ts",
192
- "active_cryptocurrencies",
193
- "active_exchanges",
194
- "active_market_pairs",
195
- "markets",
196
- "total_market_cap_usd",
197
- "total_volume_usd",
198
- "total_volume_reported_usd",
199
- "btc_dominance_pct",
200
- "eth_dominance_pct",
201
- "alt_market_cap_usd",
202
- "alt_volume_usd",
203
- "alt_volume_reported_usd",
204
- "btc_to_alt_market_cap_ratio",
205
- "market_cap_change_pct_24h_usd"
206
- ];
207
- const maxRows = getSafeBulkInsertRows(cols.length);
208
- if (rows.length > maxRows) {
209
- for (let i = 0; i < rows.length; i += maxRows) {
210
- await upsertMarketGlobalContextRows(rows.slice(i, i + maxRows));
211
- }
212
- return;
213
- }
214
- const valuesSql = rows.map(
215
- (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
216
- ).join(",");
217
- const flat = rows.flatMap((row) => [
218
- row.source,
219
- row.ts,
220
- row.updatedAt ?? null,
221
- row.activeCryptocurrencies ?? null,
222
- row.activeExchanges ?? null,
223
- row.activeMarketPairs ?? null,
224
- row.markets ?? null,
225
- row.totalMarketCapUsd ?? null,
226
- row.totalVolumeUsd ?? null,
227
- row.totalVolumeReportedUsd ?? null,
228
- row.btcDominancePct ?? null,
229
- row.ethDominancePct ?? null,
230
- row.altMarketCapUsd ?? null,
231
- row.altVolumeUsd ?? null,
232
- row.altVolumeReportedUsd ?? null,
233
- row.btcToAltMarketCapRatio ?? null,
234
- row.marketCapChangePct24hUsd ?? null
235
- ]);
236
- await pool.query(
237
- `
238
- INSERT INTO market_global_context (${cols.join(",")})
239
- VALUES ${valuesSql}
240
- ON CONFLICT (source, ts) DO UPDATE SET
241
- updated_at_ts = COALESCE(EXCLUDED.updated_at_ts, market_global_context.updated_at_ts),
242
- active_cryptocurrencies = COALESCE(EXCLUDED.active_cryptocurrencies, market_global_context.active_cryptocurrencies),
243
- active_exchanges = COALESCE(EXCLUDED.active_exchanges, market_global_context.active_exchanges),
244
- active_market_pairs = COALESCE(EXCLUDED.active_market_pairs, market_global_context.active_market_pairs),
245
- markets = COALESCE(EXCLUDED.markets, market_global_context.markets),
246
- total_market_cap_usd = COALESCE(EXCLUDED.total_market_cap_usd, market_global_context.total_market_cap_usd),
247
- total_volume_usd = COALESCE(EXCLUDED.total_volume_usd, market_global_context.total_volume_usd),
248
- total_volume_reported_usd = COALESCE(EXCLUDED.total_volume_reported_usd, market_global_context.total_volume_reported_usd),
249
- btc_dominance_pct = COALESCE(EXCLUDED.btc_dominance_pct, market_global_context.btc_dominance_pct),
250
- eth_dominance_pct = COALESCE(EXCLUDED.eth_dominance_pct, market_global_context.eth_dominance_pct),
251
- alt_market_cap_usd = COALESCE(EXCLUDED.alt_market_cap_usd, market_global_context.alt_market_cap_usd),
252
- alt_volume_usd = COALESCE(EXCLUDED.alt_volume_usd, market_global_context.alt_volume_usd),
253
- alt_volume_reported_usd = COALESCE(EXCLUDED.alt_volume_reported_usd, market_global_context.alt_volume_reported_usd),
254
- btc_to_alt_market_cap_ratio = COALESCE(EXCLUDED.btc_to_alt_market_cap_ratio, market_global_context.btc_to_alt_market_cap_ratio),
255
- market_cap_change_pct_24h_usd = COALESCE(EXCLUDED.market_cap_change_pct_24h_usd, market_global_context.market_cap_change_pct_24h_usd),
256
- ingested_at = now()
257
- `,
258
- flat
259
- );
260
- }
261
- async function upsertMarketReferenceAssetContextRows(rows) {
262
- if (!rows.length) return;
263
- await ensureBinanceMarketSchema();
264
- const pool = getPool();
265
- const cols = [
266
- "source",
267
- "symbol",
268
- "cmc_id",
269
- "interval",
270
- "ts",
271
- "open_usd",
272
- "high_usd",
273
- "low_usd",
274
- "close_usd",
275
- "volume_usd",
276
- "market_cap_usd"
277
- ];
278
- const maxRows = getSafeBulkInsertRows(cols.length);
279
- if (rows.length > maxRows) {
280
- for (let i = 0; i < rows.length; i += maxRows) {
281
- await upsertMarketReferenceAssetContextRows(rows.slice(i, i + maxRows));
282
- }
283
- return;
284
- }
285
- const valuesSql = rows.map(
286
- (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
287
- ).join(",");
288
- const flat = rows.flatMap((row) => [
289
- row.source,
290
- row.symbol.trim().toUpperCase(),
291
- Math.trunc(row.cmcId),
292
- row.interval,
293
- row.ts,
294
- row.openUsd ?? null,
295
- row.highUsd ?? null,
296
- row.lowUsd ?? null,
297
- row.closeUsd ?? null,
298
- row.volumeUsd ?? null,
299
- row.marketCapUsd ?? null
300
- ]);
301
- await pool.query(
302
- `
303
- INSERT INTO market_reference_asset_context (${cols.join(",")})
304
- VALUES ${valuesSql}
305
- ON CONFLICT (source, symbol, interval, ts) DO UPDATE SET
306
- cmc_id = EXCLUDED.cmc_id,
307
- open_usd = COALESCE(EXCLUDED.open_usd, market_reference_asset_context.open_usd),
308
- high_usd = COALESCE(EXCLUDED.high_usd, market_reference_asset_context.high_usd),
309
- low_usd = COALESCE(EXCLUDED.low_usd, market_reference_asset_context.low_usd),
310
- close_usd = COALESCE(EXCLUDED.close_usd, market_reference_asset_context.close_usd),
311
- volume_usd = COALESCE(EXCLUDED.volume_usd, market_reference_asset_context.volume_usd),
312
- market_cap_usd = COALESCE(EXCLUDED.market_cap_usd, market_reference_asset_context.market_cap_usd),
313
- ingested_at = now()
314
- `,
315
- flat
316
- );
317
- }
318
- async function upsertMarketCmcExchangeLiquidityContextRows(rows) {
319
- if (!rows.length) return;
320
- await ensureBinanceMarketSchema();
321
- const pool = getPool();
322
- const cols = [
323
- "source",
324
- "interval",
325
- "ts",
326
- "exchanges_count",
327
- "total_volume_usd",
328
- "binance_volume_usd",
329
- "binance_volume_share",
330
- "top_exchange_volume_share",
331
- "liquidity_regime"
332
- ];
333
- const maxRows = getSafeBulkInsertRows(cols.length);
334
- if (rows.length > maxRows) {
335
- for (let i = 0; i < rows.length; i += maxRows) {
336
- await upsertMarketCmcExchangeLiquidityContextRows(
337
- rows.slice(i, i + maxRows)
338
- );
339
- }
340
- return;
341
- }
342
- const valuesSql = rows.map(
343
- (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
344
- ).join(",");
345
- const flat = rows.flatMap((row) => [
346
- row.source,
347
- row.interval,
348
- row.ts,
349
- Math.trunc(row.exchangesCount),
350
- row.totalVolumeUsd ?? null,
351
- row.binanceVolumeUsd ?? null,
352
- row.binanceVolumeShare ?? null,
353
- row.topExchangeVolumeShare ?? null,
354
- row.liquidityRegime ?? null
355
- ]);
356
- await pool.query(
357
- `
358
- INSERT INTO market_cmc_exchange_liquidity_context (${cols.join(",")})
359
- VALUES ${valuesSql}
360
- ON CONFLICT (source, interval, ts) DO UPDATE SET
361
- exchanges_count = EXCLUDED.exchanges_count,
362
- total_volume_usd = COALESCE(EXCLUDED.total_volume_usd, market_cmc_exchange_liquidity_context.total_volume_usd),
363
- binance_volume_usd = COALESCE(EXCLUDED.binance_volume_usd, market_cmc_exchange_liquidity_context.binance_volume_usd),
364
- binance_volume_share = COALESCE(EXCLUDED.binance_volume_share, market_cmc_exchange_liquidity_context.binance_volume_share),
365
- top_exchange_volume_share = COALESCE(EXCLUDED.top_exchange_volume_share, market_cmc_exchange_liquidity_context.top_exchange_volume_share),
366
- liquidity_regime = COALESCE(EXCLUDED.liquidity_regime, market_cmc_exchange_liquidity_context.liquidity_regime),
367
- ingested_at = now()
368
- `,
369
- flat
370
- );
371
- }
372
- async function upsertMarketCmcFearGreedContextRows(rows) {
373
- if (!rows.length) return;
374
- await ensureBinanceMarketSchema();
375
- const pool = getPool();
376
- const cols = [
377
- "source",
378
- "interval",
379
- "ts",
380
- "value",
381
- "classification",
382
- "sentiment_regime"
383
- ];
384
- const maxRows = getSafeBulkInsertRows(cols.length);
385
- if (rows.length > maxRows) {
386
- for (let i = 0; i < rows.length; i += maxRows) {
387
- await upsertMarketCmcFearGreedContextRows(rows.slice(i, i + maxRows));
388
- }
389
- return;
390
- }
391
- const valuesSql = rows.map(
392
- (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
393
- ).join(",");
394
- const flat = rows.flatMap((row) => [
395
- row.source,
396
- row.interval,
397
- row.ts,
398
- Math.trunc(row.value),
399
- row.classification,
400
- row.sentimentRegime
401
- ]);
402
- await pool.query(
403
- `
404
- INSERT INTO market_cmc_fear_greed_context (${cols.join(",")})
405
- VALUES ${valuesSql}
406
- ON CONFLICT (source, interval, ts) DO UPDATE SET
407
- value = EXCLUDED.value,
408
- classification = EXCLUDED.classification,
409
- sentiment_regime = EXCLUDED.sentiment_regime,
410
- ingested_at = now()
411
- `,
412
- flat
413
- );
414
- }
415
- async function upsertMarketCmcIndexContextRows(rows) {
416
- if (!rows.length) return;
417
- await ensureBinanceMarketSchema();
418
- const pool = getPool();
419
- const cols = [
420
- "source",
421
- "index_slug",
422
- "interval",
423
- "ts",
424
- "value",
425
- "constituents_count",
426
- "top_constituent_symbol",
427
- "top_constituent_weight_pct",
428
- "constituents"
429
- ];
430
- const maxRows = getSafeBulkInsertRows(cols.length);
431
- if (rows.length > maxRows) {
432
- for (let i = 0; i < rows.length; i += maxRows) {
433
- await upsertMarketCmcIndexContextRows(rows.slice(i, i + maxRows));
434
- }
435
- return;
436
- }
437
- const valuesSql = rows.map(
438
- (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
439
- ).join(",");
440
- const flat = rows.flatMap((row) => [
441
- row.source,
442
- row.indexSlug,
443
- row.interval,
444
- row.ts,
445
- row.value,
446
- row.constituentsCount ?? null,
447
- row.topConstituentSymbol ?? null,
448
- row.topConstituentWeightPct ?? null,
449
- row.constituents ? JSON.stringify(row.constituents) : null
450
- ]);
451
- await pool.query(
452
- `
453
- INSERT INTO market_cmc_index_context (${cols.join(",")})
454
- VALUES ${valuesSql}
455
- ON CONFLICT (source, index_slug, interval, ts) DO UPDATE SET
456
- value = EXCLUDED.value,
457
- constituents_count = COALESCE(EXCLUDED.constituents_count, market_cmc_index_context.constituents_count),
458
- top_constituent_symbol = COALESCE(EXCLUDED.top_constituent_symbol, market_cmc_index_context.top_constituent_symbol),
459
- top_constituent_weight_pct = COALESCE(EXCLUDED.top_constituent_weight_pct, market_cmc_index_context.top_constituent_weight_pct),
460
- constituents = COALESCE(EXCLUDED.constituents, market_cmc_index_context.constituents),
461
- ingested_at = now()
462
- `,
463
- flat
464
- );
465
- }
466
- async function getMarketContextBackfillCoverage(params) {
467
- const source = String(params.source || "").trim().toLowerCase();
468
- const scopes = [
469
- ...new Set(
470
- params.scopes.map(
471
- (scope) => String(scope || "").trim().toLowerCase()
472
- ).filter(Boolean)
473
- )
474
- ];
475
- const interval = String(params.interval || "").trim().toLowerCase();
476
- if (!source || !scopes.length || !interval) return [];
477
- await ensureBinanceMarketSchema();
478
- const pool = getPool();
479
- const res = await pool.query(
480
- `
481
- SELECT
482
- source,
483
- scope,
484
- interval,
485
- extract(epoch from from_ts)*1000 AS from_ms,
486
- extract(epoch from to_ts)*1000 AS to_ms,
487
- rows_count,
488
- extract(epoch from checked_at)*1000 AS checked_at_ms
489
- FROM market_context_backfill_coverage
490
- WHERE source = $1
491
- AND scope = ANY($2)
492
- AND interval = $3
493
- AND from_ts >= to_timestamp($4/1000.0)
494
- AND to_ts <= to_timestamp($5/1000.0)
495
- `,
496
- [source, scopes, interval, params.fromMs, params.toMs]
497
- );
498
- return res.rows.map((row) => {
499
- const checkedAtMs = Number(row.checked_at_ms);
500
- return {
501
- source: String(row.source).toLowerCase(),
502
- scope: String(row.scope).toLowerCase(),
503
- interval: String(row.interval).toLowerCase(),
504
- fromMs: Number(row.from_ms),
505
- toMs: Number(row.to_ms),
506
- rowsCount: Number(row.rows_count ?? 0),
507
- ...Number.isFinite(checkedAtMs) ? { checkedAtMs } : {}
508
- };
509
- });
510
- }
511
- async function upsertMarketContextBackfillCoverage(rows) {
512
- const normalizedRows = rows.map((row) => ({
513
- source: String(row.source || "").trim().toLowerCase(),
514
- scope: String(row.scope || "").trim().toLowerCase(),
515
- interval: String(row.interval || "").trim().toLowerCase(),
516
- fromMs: Math.trunc(row.fromMs),
517
- toMs: Math.trunc(row.toMs),
518
- rowsCount: Math.max(0, Math.trunc(row.rowsCount))
519
- })).filter(
520
- (row) => row.source && row.scope && row.interval && Number.isFinite(row.fromMs) && Number.isFinite(row.toMs) && row.toMs >= row.fromMs
521
- );
522
- if (!normalizedRows.length) return;
523
- await ensureBinanceMarketSchema();
524
- const pool = getPool();
525
- const cols = [
526
- "source",
527
- "scope",
528
- "interval",
529
- "from_ts",
530
- "to_ts",
531
- "rows_count"
532
- ];
533
- const valuesSql = normalizedRows.map(
534
- (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
535
- ).join(",");
536
- const flat = normalizedRows.flatMap((row) => [
537
- row.source,
538
- row.scope,
539
- row.interval,
540
- new Date(row.fromMs),
541
- new Date(row.toMs),
542
- row.rowsCount
543
- ]);
544
- await pool.query(
545
- `
546
- INSERT INTO market_context_backfill_coverage (${cols.join(",")})
547
- VALUES ${valuesSql}
548
- ON CONFLICT (source, scope, interval, from_ts, to_ts) DO UPDATE SET
549
- rows_count = EXCLUDED.rows_count,
550
- checked_at = now()
551
- `,
552
- flat
553
- );
554
- }
555
- async function getLatestMarketTradeFlow(params) {
556
- await prepareMarketContextSchemaForRead("binance");
557
- const res = await queryMarketContext(
558
- `
559
- SELECT
560
- symbol,
561
- interval,
562
- ts,
563
- trades::int AS trades,
564
- buy_base_volume AS "buyBaseVolume",
565
- sell_base_volume AS "sellBaseVolume",
566
- buy_quote_volume AS "buyQuoteVolume",
567
- sell_quote_volume AS "sellQuoteVolume",
568
- net_base_delta AS "netBaseDelta",
569
- net_quote_delta AS "netQuoteDelta",
570
- buy_pressure_pct AS "buyPressurePct",
571
- source
572
- FROM market_trade_flow
573
- WHERE symbol = $1
574
- AND interval = $2
575
- AND ts <= to_timestamp($3/1000.0)
576
- ORDER BY ts DESC
577
- LIMIT 1
578
- `,
579
- [params.symbol.toUpperCase(), params.interval, params.atMs],
580
- params
581
- );
582
- const row = res.rows[0];
583
- if (!row) return null;
584
- const ageMs = toMarketFeatureAge(row.ts, params.atMs);
585
- return {
586
- ...row,
587
- ageMs,
588
- stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
589
- };
590
- }
591
- async function getLatestMarketBreadth(params) {
592
- await prepareMarketContextSchemaForRead("binance");
593
- const res = await queryMarketContext(
594
- `
595
- SELECT
596
- universe,
597
- interval,
598
- ts,
599
- symbols_count::int AS "symbolsCount",
600
- advancers::int AS advancers,
601
- decliners::int AS decliners,
602
- unchanged::int AS unchanged,
603
- advance_decline_ratio AS "advanceDeclineRatio",
604
- pct_above_ma20 AS "pctAboveMa20",
605
- pct_above_ma50 AS "pctAboveMa50",
606
- equal_weighted_return AS "equalWeightedReturn",
607
- volume_weighted_return AS "volumeWeightedReturn",
608
- dispersion,
609
- btc_return_1h AS "btcReturn1h",
610
- btc_return_4h AS "btcReturn4h",
611
- btc_return_24h AS "btcReturn24h",
612
- alt_basket_return_1h AS "altBasketReturn1h",
613
- alt_basket_return_4h AS "altBasketReturn4h",
614
- alt_basket_return_24h AS "altBasketReturn24h",
615
- btc_vs_alt_return_1h AS "btcVsAltReturn1h",
616
- btc_vs_alt_return_4h AS "btcVsAltReturn4h",
617
- btc_vs_alt_return_24h AS "btcVsAltReturn24h",
618
- btc_turnover_share_1h AS "btcTurnoverShare1h",
619
- btc_turnover_share_24h AS "btcTurnoverShare24h",
620
- btc_turnover_share_change_24h AS "btcTurnoverShareChange24h",
621
- alt_vol_to_btc_vol_24h AS "altVolToBtcVol24h",
622
- alt_dispersion_24h AS "altDispersion24h",
623
- btc_alt_regime AS "btcAltRegime",
624
- source
625
- FROM market_breadth
626
- WHERE universe = $1
627
- AND interval = $2
628
- AND ts <= to_timestamp($3/1000.0)
629
- ORDER BY ts DESC
630
- LIMIT 1
631
- `,
632
- [params.universe, params.interval, params.atMs],
633
- params
634
- );
635
- const row = res.rows[0];
636
- if (!row) return null;
637
- const ageMs = toMarketFeatureAge(row.ts, params.atMs);
638
- return {
639
- ...row,
640
- ageMs,
641
- stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
642
- };
643
- }
644
- async function getLatestMarketGlobalContext(params) {
645
- await prepareMarketContextSchemaForRead("coinmarketcap");
646
- const source = params.source ?? "coinmarketcap_global";
647
- const res = await queryMarketContext(
648
- `
649
- SELECT
650
- source,
651
- ts,
652
- updated_at_ts AS "updatedAt",
653
- active_cryptocurrencies::int AS "activeCryptocurrencies",
654
- active_exchanges::int AS "activeExchanges",
655
- active_market_pairs::int AS "activeMarketPairs",
656
- markets::int AS markets,
657
- total_market_cap_usd AS "totalMarketCapUsd",
658
- total_volume_usd AS "totalVolumeUsd",
659
- total_volume_reported_usd AS "totalVolumeReportedUsd",
660
- btc_dominance_pct AS "btcDominancePct",
661
- eth_dominance_pct AS "ethDominancePct",
662
- alt_market_cap_usd AS "altMarketCapUsd",
663
- alt_volume_usd AS "altVolumeUsd",
664
- alt_volume_reported_usd AS "altVolumeReportedUsd",
665
- btc_to_alt_market_cap_ratio AS "btcToAltMarketCapRatio",
666
- market_cap_change_pct_24h_usd AS "marketCapChangePct24hUsd"
667
- FROM market_global_context
668
- WHERE source = $1
669
- AND ts + CASE
670
- WHEN source = 'coinmarketcap_global' THEN interval '1 day'
671
- ELSE interval '0 seconds'
672
- END <= to_timestamp($2/1000.0)
673
- ORDER BY ts DESC
674
- LIMIT 1
675
- `,
676
- [source, params.atMs],
677
- params
678
- );
679
- const row = res.rows[0];
680
- if (!row) return null;
681
- const previousRes = await queryMarketContext(
682
- `
683
- SELECT
684
- btc_dominance_pct AS "btcDominancePct",
685
- eth_dominance_pct AS "ethDominancePct",
686
- alt_market_cap_usd AS "altMarketCapUsd",
687
- alt_volume_usd AS "altVolumeUsd"
688
- FROM market_global_context
689
- WHERE source = $1
690
- AND ts <= $2::timestamptz - interval '24 hours'
691
- ORDER BY ts DESC
692
- LIMIT 1
693
- `,
694
- [source, row.ts],
695
- params
696
- );
697
- const previousDominance = previousRes.rows[0]?.btcDominancePct == null ? null : Number(previousRes.rows[0].btcDominancePct);
698
- const previousEthDominance = previousRes.rows[0]?.ethDominancePct == null ? null : Number(previousRes.rows[0].ethDominancePct);
699
- const previousAltMarketCap = previousRes.rows[0]?.altMarketCapUsd == null ? null : Number(previousRes.rows[0].altMarketCapUsd);
700
- const previousAltVolume = previousRes.rows[0]?.altVolumeUsd == null ? null : Number(previousRes.rows[0].altVolumeUsd);
701
- const currentDominance = row.btcDominancePct == null ? null : Number(row.btcDominancePct);
702
- const currentEthDominance = row.ethDominancePct == null ? null : Number(row.ethDominancePct);
703
- const currentAltMarketCap = row.altMarketCapUsd == null ? null : Number(row.altMarketCapUsd);
704
- const currentAltVolume = row.altVolumeUsd == null ? null : Number(row.altVolumeUsd);
705
- const ageMs = toMarketFeatureAge(row.ts, params.atMs);
706
- return {
707
- ...row,
708
- ageMs,
709
- stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs,
710
- btcDominanceChange24hPct: currentDominance != null && previousDominance != null ? currentDominance - previousDominance : null,
711
- ethDominanceChange24hPct: currentEthDominance != null && previousEthDominance != null ? currentEthDominance - previousEthDominance : null,
712
- altMarketCapChange24hPct: currentAltMarketCap != null && previousAltMarketCap != null && previousAltMarketCap > 0 ? (currentAltMarketCap - previousAltMarketCap) / previousAltMarketCap : null,
713
- altVolumeChange24hPct: currentAltVolume != null && previousAltVolume != null && previousAltVolume > 0 ? (currentAltVolume - previousAltVolume) / previousAltVolume : null
714
- };
715
- }
716
- async function getMarketGlobalContextCoverage(params) {
717
- await ensureBinanceMarketSchema();
718
- const pool = getPool();
719
- const res = await pool.query(
720
- `
721
- SELECT
722
- extract(epoch from MIN(ts))*1000 AS first_ms,
723
- extract(epoch from MAX(ts))*1000 AS last_ms,
724
- COUNT(*)::int AS rows
725
- FROM market_global_context
726
- WHERE source = $1
727
- AND ts >= to_timestamp($2/1000.0)
728
- AND ts <= to_timestamp($3/1000.0)
729
- `,
730
- [params.source, params.startMs, params.endMs]
731
- );
732
- const row = res.rows[0];
733
- const rows = Number(row?.rows ?? 0);
734
- const firstMs = Number(row?.first_ms);
735
- const lastMs = Number(row?.last_ms);
736
- if (!rows || !Number.isFinite(firstMs) || !Number.isFinite(lastMs)) {
737
- return null;
738
- }
739
- return { firstMs, lastMs, rows };
740
- }
741
- async function getMarketReferenceAssetContextCoverage(params) {
742
- const symbols = [
743
- ...new Set(
744
- params.symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean)
745
- )
746
- ];
747
- const coverage = /* @__PURE__ */ new Map();
748
- if (!symbols.length) return coverage;
749
- await ensureBinanceMarketSchema();
750
- const pool = getPool();
751
- const res = await pool.query(
752
- `
753
- SELECT
754
- symbol,
755
- extract(epoch from MIN(ts))*1000 AS first_ms,
756
- extract(epoch from MAX(ts))*1000 AS last_ms,
757
- COUNT(*)::int AS rows
758
- FROM market_reference_asset_context
759
- WHERE source = $1
760
- AND symbol = ANY($2)
761
- AND interval = $3
762
- AND ts >= to_timestamp($4/1000.0)
763
- AND ts <= to_timestamp($5/1000.0)
764
- GROUP BY symbol
765
- `,
766
- [params.source, symbols, params.interval, params.startMs, params.endMs]
767
- );
768
- for (const row of res.rows) {
769
- const firstMs = Number(row.first_ms);
770
- const lastMs = Number(row.last_ms);
771
- const rows = Number(row.rows);
772
- if (Number.isFinite(firstMs) && Number.isFinite(lastMs) && rows > 0) {
773
- coverage.set(row.symbol.toUpperCase(), { firstMs, lastMs, rows });
774
- }
775
- }
776
- return coverage;
777
- }
778
- async function getLatestMarketReferenceAssetContexts(params) {
779
- const source = params.source ?? "coinmarketcap_reference_asset";
780
- const interval = params.interval ?? "1d";
781
- const symbols = [
782
- ...new Set(
783
- params.symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean)
784
- )
785
- ];
786
- const rows = /* @__PURE__ */ new Map();
787
- if (!symbols.length) return rows;
788
- await prepareMarketContextSchemaForRead("coinmarketcap");
789
- const res = await queryMarketContext(
790
- `
791
- SELECT DISTINCT ON (symbol)
792
- source,
793
- symbol,
794
- cmc_id AS "cmcId",
795
- interval,
796
- ts,
797
- open_usd AS "openUsd",
798
- high_usd AS "highUsd",
799
- low_usd AS "lowUsd",
800
- close_usd AS "closeUsd",
801
- volume_usd AS "volumeUsd",
802
- market_cap_usd AS "marketCapUsd"
803
- FROM market_reference_asset_context
804
- WHERE source = $1
805
- AND symbol = ANY($2)
806
- AND interval = $3
807
- AND ts + CASE interval
808
- WHEN '1d' THEN interval '1 day'
809
- WHEN '1h' THEN interval '1 hour'
810
- ELSE interval '0 seconds'
811
- END <= to_timestamp($4/1000.0)
812
- ORDER BY symbol ASC, ts DESC
813
- `,
814
- [source, symbols, interval, params.atMs],
815
- params
816
- );
817
- for (const row of res.rows) {
818
- const ageMs = toMarketFeatureAge(row.ts, params.atMs);
819
- rows.set(row.symbol.toUpperCase(), {
820
- ...row,
821
- ageMs,
822
- stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
823
- });
824
- }
825
- return rows;
826
- }
827
- async function getLatestMarketCmcExchangeLiquidityContext(params) {
828
- await prepareMarketContextSchemaForRead("coinmarketcap");
829
- const source = params.source ?? "coinmarketcap_exchange_liquidity";
830
- const interval = params.interval ?? "1d";
831
- const res = await queryMarketContext(
832
- `
833
- SELECT
834
- source,
835
- interval,
836
- ts,
837
- exchanges_count::int AS "exchangesCount",
838
- total_volume_usd AS "totalVolumeUsd",
839
- binance_volume_usd AS "binanceVolumeUsd",
840
- binance_volume_share AS "binanceVolumeShare",
841
- top_exchange_volume_share AS "topExchangeVolumeShare",
842
- liquidity_regime AS "liquidityRegime"
843
- FROM market_cmc_exchange_liquidity_context
844
- WHERE source = $1
845
- AND interval = $2
846
- AND ts + CASE interval
847
- WHEN '1d' THEN interval '1 day'
848
- WHEN '1h' THEN interval '1 hour'
849
- ELSE interval '0 seconds'
850
- END <= to_timestamp($3/1000.0)
851
- ORDER BY ts DESC
852
- LIMIT 1
853
- `,
854
- [source, interval, params.atMs],
855
- params
856
- );
857
- const row = res.rows[0];
858
- if (!row) return null;
859
- const previousRes = await queryMarketContext(
860
- `
861
- SELECT total_volume_usd AS "totalVolumeUsd"
862
- FROM market_cmc_exchange_liquidity_context
863
- WHERE source = $1
864
- AND interval = $2
865
- AND ts <= $3::timestamptz - interval '24 hours'
866
- ORDER BY ts DESC
867
- LIMIT 1
868
- `,
869
- [source, interval, row.ts],
870
- params
871
- );
872
- const currentTotal = row.totalVolumeUsd == null ? null : Number(row.totalVolumeUsd);
873
- const previousTotal = previousRes.rows[0]?.totalVolumeUsd == null ? null : Number(previousRes.rows[0].totalVolumeUsd);
874
- const ageMs = toMarketFeatureAge(row.ts, params.atMs);
875
- return {
876
- ...row,
877
- ageMs,
878
- stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs,
879
- totalVolumeChange24hPct: currentTotal != null && previousTotal != null && previousTotal > 0 ? (currentTotal - previousTotal) / previousTotal : null
880
- };
881
- }
882
- async function getLatestMarketCmcIndexContexts(params) {
883
- const source = params.source ?? "coinmarketcap_index";
884
- const interval = params.interval ?? "1d";
885
- const indexSlugs = [
886
- ...new Set(
887
- params.indexSlugs.map((slug) => slug.trim().toLowerCase()).filter(
888
- (slug) => ["cmc100", "cmc20"].includes(slug)
889
- )
890
- )
891
- ];
892
- const rows = /* @__PURE__ */ new Map();
893
- if (!indexSlugs.length) return rows;
894
- await prepareMarketContextSchemaForRead("coinmarketcap");
895
- const res = await queryMarketContext(
896
- `
897
- SELECT DISTINCT ON (index_slug)
898
- source,
899
- index_slug AS "indexSlug",
900
- interval,
901
- ts,
902
- value,
903
- constituents_count::int AS "constituentsCount",
904
- top_constituent_symbol AS "topConstituentSymbol",
905
- top_constituent_weight_pct AS "topConstituentWeightPct",
906
- constituents
907
- FROM market_cmc_index_context
908
- WHERE source = $1
909
- AND index_slug = ANY($2)
910
- AND interval = $3
911
- AND ts + CASE interval
912
- WHEN '1d' THEN interval '1 day'
913
- WHEN '1h' THEN interval '1 hour'
914
- ELSE interval '0 seconds'
915
- END <= to_timestamp($4/1000.0)
916
- ORDER BY index_slug ASC, ts DESC
917
- `,
918
- [source, indexSlugs, interval, params.atMs],
919
- params
920
- );
921
- for (const row of res.rows) {
922
- const previousRes = await queryMarketContext(
923
- `
924
- SELECT value
925
- FROM market_cmc_index_context
926
- WHERE source = $1
927
- AND index_slug = $2
928
- AND interval = $3
929
- AND ts <= $4::timestamptz - interval '24 hours'
930
- ORDER BY ts DESC
931
- LIMIT 1
932
- `,
933
- [source, row.indexSlug, interval, row.ts],
934
- params
935
- );
936
- const currentValue = row.value == null ? null : Number(row.value);
937
- const previousValue = previousRes.rows[0]?.value == null ? null : Number(previousRes.rows[0].value);
938
- const ageMs = toMarketFeatureAge(row.ts, params.atMs);
939
- rows.set(row.indexSlug, {
940
- ...row,
941
- ageMs,
942
- stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs,
943
- valueChange24hPct: currentValue != null && previousValue != null && previousValue > 0 ? (currentValue - previousValue) / previousValue : null
944
- });
945
- }
946
- return rows;
947
- }
948
- async function getLatestMarketCmcFearGreedContext(params) {
949
- await prepareMarketContextSchemaForRead("coinmarketcap");
950
- const source = params.source ?? "coinmarketcap_fear_greed";
951
- const interval = params.interval ?? "1d";
952
- const res = await queryMarketContext(
953
- `
954
- SELECT
955
- source,
956
- interval,
957
- ts,
958
- value::int AS value,
959
- classification,
960
- sentiment_regime AS "sentimentRegime"
961
- FROM market_cmc_fear_greed_context
962
- WHERE source = $1
963
- AND interval = $2
964
- AND ts + CASE interval
965
- WHEN '1d' THEN interval '1 day'
966
- WHEN '1h' THEN interval '1 hour'
967
- ELSE interval '0 seconds'
968
- END <= to_timestamp($3/1000.0)
969
- ORDER BY ts DESC
970
- LIMIT 1
971
- `,
972
- [source, interval, params.atMs],
973
- params
974
- );
975
- const row = res.rows[0];
976
- if (!row) return null;
977
- const previousRes = await queryMarketContext(
978
- `
979
- SELECT
980
- value::int AS value,
981
- '24h' AS bucket
982
- FROM market_cmc_fear_greed_context
983
- WHERE source = $1
984
- AND interval = $2
985
- AND ts <= $3::timestamptz - interval '24 hours'
986
- ORDER BY ts DESC
987
- LIMIT 1
988
- `,
989
- [source, interval, row.ts],
990
- params
991
- );
992
- const previous7dRes = await queryMarketContext(
993
- `
994
- SELECT value::int AS value
995
- FROM market_cmc_fear_greed_context
996
- WHERE source = $1
997
- AND interval = $2
998
- AND ts <= $3::timestamptz - interval '7 days'
999
- ORDER BY ts DESC
1000
- LIMIT 1
1001
- `,
1002
- [source, interval, row.ts],
1003
- params
1004
- );
1005
- const previousValue = previousRes.rows[0]?.value == null ? null : Number(previousRes.rows[0].value);
1006
- const previous7dValue = previous7dRes.rows[0]?.value == null ? null : Number(previous7dRes.rows[0].value);
1007
- const ageMs = toMarketFeatureAge(row.ts, params.atMs);
1008
- return {
1009
- ...row,
1010
- ageMs,
1011
- stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs,
1012
- valueChange24h: previousValue == null ? null : row.value - previousValue,
1013
- valueChange7d: previous7dValue == null ? null : row.value - previous7dValue
1014
- };
1015
- }
1016
- async function getMarketCmcFearGreedContextCoverage(params) {
1017
- await ensureBinanceMarketSchema();
1018
- const pool = getPool();
1019
- const res = await pool.query(
1020
- `
1021
- SELECT
1022
- extract(epoch from MIN(ts))*1000 AS first_ms,
1023
- extract(epoch from MAX(ts))*1000 AS last_ms,
1024
- COUNT(*)::int AS rows
1025
- FROM market_cmc_fear_greed_context
1026
- WHERE source = $1
1027
- AND interval = $2
1028
- AND ts >= to_timestamp($3/1000.0)
1029
- AND ts <= to_timestamp($4/1000.0)
1030
- `,
1031
- [params.source, params.interval, params.startMs, params.endMs]
1032
- );
1033
- const rows = Number(res.rows[0]?.rows ?? 0);
1034
- const firstMs = Number(res.rows[0]?.first_ms);
1035
- const lastMs = Number(res.rows[0]?.last_ms);
1036
- if (!rows || !Number.isFinite(firstMs) || !Number.isFinite(lastMs)) {
1037
- return null;
1038
- }
1039
- return { firstMs, lastMs, rows };
1040
- }
1041
- async function getMarketCmcExchangeLiquidityContextCoverage(params) {
1042
- await ensureBinanceMarketSchema();
1043
- const pool = getPool();
1044
- const res = await pool.query(
1045
- `
1046
- SELECT
1047
- extract(epoch from MIN(ts))*1000 AS first_ms,
1048
- extract(epoch from MAX(ts))*1000 AS last_ms,
1049
- COUNT(*)::int AS rows
1050
- FROM market_cmc_exchange_liquidity_context
1051
- WHERE source = $1
1052
- AND interval = $2
1053
- AND ts >= to_timestamp($3/1000.0)
1054
- AND ts <= to_timestamp($4/1000.0)
1055
- `,
1056
- [params.source, params.interval, params.startMs, params.endMs]
1057
- );
1058
- const rows = Number(res.rows[0]?.rows ?? 0);
1059
- const firstMs = Number(res.rows[0]?.first_ms);
1060
- const lastMs = Number(res.rows[0]?.last_ms);
1061
- if (!rows || !Number.isFinite(firstMs) || !Number.isFinite(lastMs)) {
1062
- return null;
1063
- }
1064
- return { firstMs, lastMs, rows };
1065
- }
1066
- async function getMarketCmcIndexContextCoverage(params) {
1067
- const indexSlugs = [
1068
- ...new Set(
1069
- params.indexSlugs.map((slug) => slug.trim().toLowerCase()).filter(
1070
- (slug) => ["cmc100", "cmc20"].includes(slug)
1071
- )
1072
- )
1073
- ];
1074
- const coverage = /* @__PURE__ */ new Map();
1075
- if (!indexSlugs.length) return coverage;
1076
- await ensureBinanceMarketSchema();
1077
- const pool = getPool();
1078
- const res = await pool.query(
1079
- `
1080
- SELECT
1081
- index_slug,
1082
- extract(epoch from MIN(ts))*1000 AS first_ms,
1083
- extract(epoch from MAX(ts))*1000 AS last_ms,
1084
- COUNT(*)::int AS rows
1085
- FROM market_cmc_index_context
1086
- WHERE source = $1
1087
- AND index_slug = ANY($2)
1088
- AND interval = $3
1089
- AND ts >= to_timestamp($4/1000.0)
1090
- AND ts <= to_timestamp($5/1000.0)
1091
- GROUP BY index_slug
1092
- `,
1093
- [params.source, indexSlugs, params.interval, params.startMs, params.endMs]
1094
- );
1095
- for (const row of res.rows) {
1096
- const indexSlug = row.index_slug;
1097
- const firstMs = Number(row.first_ms);
1098
- const lastMs = Number(row.last_ms);
1099
- const rows = Number(row.rows);
1100
- if (Number.isFinite(firstMs) && Number.isFinite(lastMs) && rows > 0) {
1101
- coverage.set(indexSlug, { firstMs, lastMs, rows });
1102
- }
1103
- }
1104
- return coverage;
1105
- }
1106
- async function getMarketTradeFlowCoverage(params) {
1107
- const symbols = [
1108
- ...new Set(params.symbols.map((item) => item.toUpperCase()))
1109
- ];
1110
- if (!symbols.length) return /* @__PURE__ */ new Map();
1111
- await ensureBinanceMarketSchema();
1112
- const pool = getPool();
1113
- const res = await pool.query(
1114
- `
1115
- SELECT
1116
- symbol,
1117
- MIN(ts) AS first_ts,
1118
- MAX(ts) AS last_ts,
1119
- COUNT(*)::int AS rows
1120
- FROM market_trade_flow
1121
- WHERE symbol = ANY($1)
1122
- AND interval = $2
1123
- AND ts >= to_timestamp($3/1000.0)
1124
- AND ts <= to_timestamp($4/1000.0)
1125
- GROUP BY symbol
1126
- `,
1127
- [symbols, params.interval, params.startMs, params.endMs]
1128
- );
1129
- return new Map(
1130
- res.rows.map((row) => [
1131
- String(row.symbol).toUpperCase(),
1132
- {
1133
- firstMs: new Date(row.first_ts).getTime(),
1134
- lastMs: new Date(row.last_ts).getTime(),
1135
- rows: Number(row.rows) || 0
1136
- }
1137
- ])
1138
- );
1139
- }
1140
- var getTableRowCountIfExists = async (tableName) => {
1141
- const pool = getPool();
1142
- const exists = await pool.query("SELECT to_regclass($1) AS name", [
1143
- tableName
1144
- ]);
1145
- if (!exists.rows[0]?.name) return null;
1146
- const count = await pool.query(
1147
- `SELECT COUNT(*)::int AS rows FROM ${tableName}`
1148
- );
1149
- return Number(count.rows[0]?.rows ?? 0);
1150
- };
1151
- async function cleanupDeprecatedMarketContext(params = {}) {
1152
- const apply = Boolean(params.apply);
1153
- const pool = getPool();
1154
- const items = [];
1155
- const cleanupRows = async ({
1156
- tableName,
1157
- whereSql,
1158
- name
1159
- }) => {
1160
- const tableRows = await getTableRowCountIfExists(tableName);
1161
- if (tableRows == null) return;
1162
- const count = await pool.query(
1163
- `
1164
- SELECT COUNT(*)::int AS rows
1165
- FROM ${tableName}
1166
- WHERE ${whereSql}
1167
- `
1168
- );
1169
- const rows = Number(count.rows[0]?.rows ?? 0);
1170
- if (rows <= 0) return;
1171
- if (apply) {
1172
- await pool.query(
1173
- `
1174
- DELETE FROM ${tableName}
1175
- WHERE ${whereSql}
1176
- `
1177
- );
1178
- }
1179
- items.push({
1180
- kind: "rows",
1181
- name,
1182
- rows,
1183
- action: "delete_rows",
1184
- applied: apply
1185
- });
1186
- };
1187
- for (const tableName of ["market_order_book_depth", "onchain_flow_context"]) {
1188
- const rows = await getTableRowCountIfExists(tableName);
1189
- if (rows == null) continue;
1190
- if (apply) {
1191
- await pool.query(`DROP TABLE IF EXISTS ${tableName}`);
1192
- }
1193
- items.push({
1194
- kind: "table",
1195
- name: tableName,
1196
- rows,
1197
- action: "drop_table",
1198
- applied: apply
1199
- });
1200
- }
1201
- await cleanupRows({
1202
- tableName: "market_global_context",
1203
- whereSql: "source = 'coingecko_global'",
1204
- name: "market_global_context/source=coingecko_global"
1205
- });
1206
- await cleanupRows({
1207
- tableName: "market_global_context",
1208
- whereSql: "source = 'coinmarketcap_global_hourly'",
1209
- name: "market_global_context/source=coinmarketcap_global_hourly"
1210
- });
1211
- await cleanupRows({
1212
- tableName: "market_reference_asset_context",
1213
- whereSql: "source = 'coinmarketcap_reference_asset' AND interval = '1h'",
1214
- name: "market_reference_asset_context/source=coinmarketcap_reference_asset/interval=1h"
1215
- });
1216
- await cleanupRows({
1217
- tableName: "market_cmc_breadth_context",
1218
- whereSql: "source = 'coinmarketcap_market_breadth'",
1219
- name: "market_cmc_breadth_context/source=coinmarketcap_market_breadth"
1220
- });
1221
- await cleanupRows({
1222
- tableName: "market_context_backfill_coverage",
1223
- whereSql: "(source IN ('coinmarketcap_global_hourly', 'coinmarketcap_market_breadth') OR (source = 'coinmarketcap_reference_asset' AND interval = '1h'))",
1224
- name: "market_context_backfill_coverage/deprecated_cmc_sources"
1225
- });
1226
- return items;
1227
- }
1228
- async function getMarketBreadthCoverage(params) {
1229
- await ensureBinanceMarketSchema();
1230
- const pool = getPool();
1231
- const res = await pool.query(
1232
- `
1233
- SELECT
1234
- MIN(ts) AS first_ts,
1235
- MAX(ts) AS last_ts,
1236
- COUNT(*)::int AS rows,
1237
- COUNT(*) FILTER (
1238
- WHERE btc_alt_regime IS NOT NULL
1239
- AND btc_return_24h IS NOT NULL
1240
- AND alt_basket_return_24h IS NOT NULL
1241
- )::int AS btc_alt_metrics_rows
1242
- FROM market_breadth
1243
- WHERE universe = $1
1244
- AND interval = $2
1245
- AND ts >= to_timestamp($3/1000.0)
1246
- AND ts <= to_timestamp($4/1000.0)
1247
- `,
1248
- [params.universe, params.interval, params.startMs, params.endMs]
1249
- );
1250
- const row = res.rows[0];
1251
- if (!row?.first_ts || !row?.last_ts) return null;
1252
- return {
1253
- firstMs: new Date(row.first_ts).getTime(),
1254
- lastMs: new Date(row.last_ts).getTime(),
1255
- rows: Number(row.rows) || 0,
1256
- btcAltMetricsRows: Number(row.btc_alt_metrics_rows) || 0
1257
- };
1258
- }
1259
-
1260
- export {
1261
- upsertMarketTradeFlowRows,
1262
- upsertMarketBreadthRows,
1263
- upsertMarketGlobalContextRows,
1264
- upsertMarketReferenceAssetContextRows,
1265
- upsertMarketCmcExchangeLiquidityContextRows,
1266
- upsertMarketCmcFearGreedContextRows,
1267
- upsertMarketCmcIndexContextRows,
1268
- getMarketContextBackfillCoverage,
1269
- upsertMarketContextBackfillCoverage,
1270
- getLatestMarketTradeFlow,
1271
- getLatestMarketBreadth,
1272
- getLatestMarketGlobalContext,
1273
- getMarketGlobalContextCoverage,
1274
- getMarketReferenceAssetContextCoverage,
1275
- getLatestMarketReferenceAssetContexts,
1276
- getLatestMarketCmcExchangeLiquidityContext,
1277
- getLatestMarketCmcIndexContexts,
1278
- getLatestMarketCmcFearGreedContext,
1279
- getMarketCmcFearGreedContextCoverage,
1280
- getMarketCmcExchangeLiquidityContextCoverage,
1281
- getMarketCmcIndexContextCoverage,
1282
- getMarketTradeFlowCoverage,
1283
- cleanupDeprecatedMarketContext,
1284
- getMarketBreadthCoverage
1285
- };