@tradejs/infra 2.0.21 → 3.0.1

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