@tradejs/infra 3.1.27 → 3.1.28-beta.252

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.
@@ -4,8 +4,22 @@ import {
4
4
  normalizeCandleProvider,
5
5
  normalizeCandleSymbol
6
6
  } from "./chunk-SSL36P7G.mjs";
7
+ import {
8
+ logger
9
+ } from "./chunk-LNFUOXDW.mjs";
7
10
 
8
11
  // src/timescale/candles.ts
12
+ var CANDLE_UPSERT_MAX_ATTEMPTS = 3;
13
+ var CANDLE_DEADLOCK_RETRY_DELAY_MS = 25;
14
+ var isPostgresDeadlock = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "40P01";
15
+ var wait = (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs));
16
+ var normalizeAndSortCandleRows = (rows) => rows.map((row) => ({
17
+ ...row,
18
+ provider: normalizeCandleProvider(row.provider),
19
+ symbol: normalizeCandleSymbol(row.symbol)
20
+ })).sort(
21
+ (left, right) => left.provider.localeCompare(right.provider) || left.symbol.localeCompare(right.symbol) || left.interval - right.interval || left.ts.getTime() - right.ts.getTime()
22
+ );
9
23
  var toRows = (provider, symbol, interval, data) => {
10
24
  const normalizedProvider = normalizeCandleProvider(provider);
11
25
  if (!normalizedProvider) {
@@ -34,6 +48,7 @@ async function upsertCandles(rows) {
34
48
  if (!rows.length) return;
35
49
  await ensureCandlesSchema();
36
50
  const pool = getPool();
51
+ const normalizedRows = normalizeAndSortCandleRows(rows);
37
52
  const cols = [
38
53
  "provider",
39
54
  "symbol",
@@ -51,32 +66,28 @@ async function upsertCandles(rows) {
51
66
  "taker_sell_quote_volume"
52
67
  ];
53
68
  const maxRows = Math.floor(65535 / cols.length);
54
- if (rows.length > maxRows) {
55
- for (let i = 0; i < rows.length; i += maxRows) {
56
- await upsertCandles(rows.slice(i, i + maxRows));
57
- }
58
- return;
59
- }
60
- const valuesSql = rows.map(
61
- (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
62
- ).join(",");
63
- const flat = rows.flatMap((r) => [
64
- normalizeCandleProvider(r.provider),
65
- normalizeCandleSymbol(r.symbol),
66
- r.interval,
67
- r.ts,
68
- r.open,
69
- r.high,
70
- r.low,
71
- r.close,
72
- r.volume ?? null,
73
- r.turnover ?? null,
74
- r.takerBuyBaseVolume ?? null,
75
- r.takerBuyQuoteVolume ?? null,
76
- r.takerSellBaseVolume ?? null,
77
- r.takerSellQuoteVolume ?? null
78
- ]);
79
- const sql = `
69
+ for (let offset = 0; offset < normalizedRows.length; offset += maxRows) {
70
+ const chunk = normalizedRows.slice(offset, offset + maxRows);
71
+ const valuesSql = chunk.map(
72
+ (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
73
+ ).join(",");
74
+ const flat = chunk.flatMap((r) => [
75
+ r.provider,
76
+ r.symbol,
77
+ r.interval,
78
+ r.ts,
79
+ r.open,
80
+ r.high,
81
+ r.low,
82
+ r.close,
83
+ r.volume ?? null,
84
+ r.turnover ?? null,
85
+ r.takerBuyBaseVolume ?? null,
86
+ r.takerBuyQuoteVolume ?? null,
87
+ r.takerSellBaseVolume ?? null,
88
+ r.takerSellQuoteVolume ?? null
89
+ ]);
90
+ const sql = `
80
91
  INSERT INTO candles (${cols.join(",")})
81
92
  VALUES ${valuesSql}
82
93
  ON CONFLICT (provider, symbol, interval, ts) DO UPDATE SET
@@ -91,16 +102,29 @@ async function upsertCandles(rows) {
91
102
  taker_sell_base_volume = COALESCE(EXCLUDED.taker_sell_base_volume, candles.taker_sell_base_volume),
92
103
  taker_sell_quote_volume = COALESCE(EXCLUDED.taker_sell_quote_volume, candles.taker_sell_quote_volume)
93
104
  `;
94
- const client = await pool.connect();
95
- try {
96
- await client.query("BEGIN");
97
- await client.query(sql, flat);
98
- await client.query("COMMIT");
99
- } catch (e) {
100
- await client.query("ROLLBACK");
101
- throw e;
102
- } finally {
103
- client.release();
105
+ for (let attempt = 1; attempt <= CANDLE_UPSERT_MAX_ATTEMPTS; attempt += 1) {
106
+ const client = await pool.connect();
107
+ try {
108
+ await client.query("BEGIN");
109
+ await client.query(sql, flat);
110
+ await client.query("COMMIT");
111
+ break;
112
+ } catch (error) {
113
+ await client.query("ROLLBACK").catch(() => void 0);
114
+ if (!isPostgresDeadlock(error) || attempt === CANDLE_UPSERT_MAX_ATTEMPTS) {
115
+ throw error;
116
+ }
117
+ logger.warn(
118
+ "candle upsert deadlock (40P01); retrying transaction (attempt=%s/%s rows=%s)",
119
+ attempt + 1,
120
+ CANDLE_UPSERT_MAX_ATTEMPTS,
121
+ chunk.length
122
+ );
123
+ } finally {
124
+ client.release();
125
+ }
126
+ await wait(CANDLE_DEADLOCK_RETRY_DELAY_MS * 2 ** (attempt - 1));
127
+ }
104
128
  }
105
129
  }
106
130
  async function getCandlesRange(provider, symbol, interval, startMs, endMs) {
@@ -31,6 +31,45 @@ __export(candles_exports, {
31
31
  });
32
32
  module.exports = __toCommonJS(candles_exports);
33
33
 
34
+ // src/logger.ts
35
+ var import_winston = require("winston");
36
+ var baseFormat = import_winston.format.combine(
37
+ import_winston.format.timestamp({ format: "DD MMM HH:mm:ss" }),
38
+ import_winston.format.splat()
39
+ );
40
+ var logger = (0, import_winston.createLogger)({
41
+ format: baseFormat,
42
+ transports: [
43
+ new import_winston.transports.Console({
44
+ format: import_winston.format.combine(
45
+ import_winston.format.colorize({ all: true }),
46
+ import_winston.format.printf(
47
+ ({ level, timestamp, message }) => `${level}: ${timestamp}: ${message}`
48
+ )
49
+ )
50
+ }),
51
+ new import_winston.transports.File({
52
+ filename: "service.log",
53
+ format: import_winston.format.combine(
54
+ import_winston.format.uncolorize(),
55
+ import_winston.format.printf(
56
+ ({ level, timestamp, message }) => `${level}: ${timestamp}: ${message}`
57
+ )
58
+ )
59
+ }),
60
+ new import_winston.transports.File({
61
+ filename: "error.log",
62
+ format: import_winston.format.combine(
63
+ import_winston.format.uncolorize(),
64
+ import_winston.format.printf(
65
+ ({ level, timestamp, message }) => `${level}: ${timestamp}: ${message}`
66
+ )
67
+ ),
68
+ level: "error"
69
+ })
70
+ ]
71
+ });
72
+
34
73
  // src/timescale/pool.ts
35
74
  var import_pg = require("pg");
36
75
  var getPool = () => {
@@ -145,6 +184,17 @@ var ensureCandlesSchema = async () => {
145
184
  var marketContextSchemaMode = process.env.TRADEJS_TIMESCALE_READ_ONLY === "true" ? "verify" : "ensure";
146
185
 
147
186
  // src/timescale/candles.ts
187
+ var CANDLE_UPSERT_MAX_ATTEMPTS = 3;
188
+ var CANDLE_DEADLOCK_RETRY_DELAY_MS = 25;
189
+ var isPostgresDeadlock = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "40P01";
190
+ var wait = (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs));
191
+ var normalizeAndSortCandleRows = (rows) => rows.map((row) => ({
192
+ ...row,
193
+ provider: normalizeCandleProvider(row.provider),
194
+ symbol: normalizeCandleSymbol(row.symbol)
195
+ })).sort(
196
+ (left, right) => left.provider.localeCompare(right.provider) || left.symbol.localeCompare(right.symbol) || left.interval - right.interval || left.ts.getTime() - right.ts.getTime()
197
+ );
148
198
  var toRows = (provider, symbol, interval, data) => {
149
199
  const normalizedProvider = normalizeCandleProvider(provider);
150
200
  if (!normalizedProvider) {
@@ -173,6 +223,7 @@ async function upsertCandles(rows) {
173
223
  if (!rows.length) return;
174
224
  await ensureCandlesSchema();
175
225
  const pool = getPool();
226
+ const normalizedRows = normalizeAndSortCandleRows(rows);
176
227
  const cols = [
177
228
  "provider",
178
229
  "symbol",
@@ -190,32 +241,28 @@ async function upsertCandles(rows) {
190
241
  "taker_sell_quote_volume"
191
242
  ];
192
243
  const maxRows = Math.floor(65535 / cols.length);
193
- if (rows.length > maxRows) {
194
- for (let i = 0; i < rows.length; i += maxRows) {
195
- await upsertCandles(rows.slice(i, i + maxRows));
196
- }
197
- return;
198
- }
199
- const valuesSql = rows.map(
200
- (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
201
- ).join(",");
202
- const flat = rows.flatMap((r) => [
203
- normalizeCandleProvider(r.provider),
204
- normalizeCandleSymbol(r.symbol),
205
- r.interval,
206
- r.ts,
207
- r.open,
208
- r.high,
209
- r.low,
210
- r.close,
211
- r.volume ?? null,
212
- r.turnover ?? null,
213
- r.takerBuyBaseVolume ?? null,
214
- r.takerBuyQuoteVolume ?? null,
215
- r.takerSellBaseVolume ?? null,
216
- r.takerSellQuoteVolume ?? null
217
- ]);
218
- const sql = `
244
+ for (let offset = 0; offset < normalizedRows.length; offset += maxRows) {
245
+ const chunk = normalizedRows.slice(offset, offset + maxRows);
246
+ const valuesSql = chunk.map(
247
+ (_, i) => `(${cols.map((__, j) => `$${i * cols.length + j + 1}`).join(",")})`
248
+ ).join(",");
249
+ const flat = chunk.flatMap((r) => [
250
+ r.provider,
251
+ r.symbol,
252
+ r.interval,
253
+ r.ts,
254
+ r.open,
255
+ r.high,
256
+ r.low,
257
+ r.close,
258
+ r.volume ?? null,
259
+ r.turnover ?? null,
260
+ r.takerBuyBaseVolume ?? null,
261
+ r.takerBuyQuoteVolume ?? null,
262
+ r.takerSellBaseVolume ?? null,
263
+ r.takerSellQuoteVolume ?? null
264
+ ]);
265
+ const sql = `
219
266
  INSERT INTO candles (${cols.join(",")})
220
267
  VALUES ${valuesSql}
221
268
  ON CONFLICT (provider, symbol, interval, ts) DO UPDATE SET
@@ -230,16 +277,29 @@ async function upsertCandles(rows) {
230
277
  taker_sell_base_volume = COALESCE(EXCLUDED.taker_sell_base_volume, candles.taker_sell_base_volume),
231
278
  taker_sell_quote_volume = COALESCE(EXCLUDED.taker_sell_quote_volume, candles.taker_sell_quote_volume)
232
279
  `;
233
- const client = await pool.connect();
234
- try {
235
- await client.query("BEGIN");
236
- await client.query(sql, flat);
237
- await client.query("COMMIT");
238
- } catch (e) {
239
- await client.query("ROLLBACK");
240
- throw e;
241
- } finally {
242
- client.release();
280
+ for (let attempt = 1; attempt <= CANDLE_UPSERT_MAX_ATTEMPTS; attempt += 1) {
281
+ const client = await pool.connect();
282
+ try {
283
+ await client.query("BEGIN");
284
+ await client.query(sql, flat);
285
+ await client.query("COMMIT");
286
+ break;
287
+ } catch (error) {
288
+ await client.query("ROLLBACK").catch(() => void 0);
289
+ if (!isPostgresDeadlock(error) || attempt === CANDLE_UPSERT_MAX_ATTEMPTS) {
290
+ throw error;
291
+ }
292
+ logger.warn(
293
+ "candle upsert deadlock (40P01); retrying transaction (attempt=%s/%s rows=%s)",
294
+ attempt + 1,
295
+ CANDLE_UPSERT_MAX_ATTEMPTS,
296
+ chunk.length
297
+ );
298
+ } finally {
299
+ client.release();
300
+ }
301
+ await wait(CANDLE_DEADLOCK_RETRY_DELAY_MS * 2 ** (attempt - 1));
302
+ }
243
303
  }
244
304
  }
245
305
  async function getCandlesRange(provider, symbol, interval, startMs, endMs) {
@@ -7,8 +7,9 @@ import {
7
7
  toRows,
8
8
  upsertCandles,
9
9
  waitForDbReady
10
- } from "../chunk-CH5L3BE4.mjs";
10
+ } from "../chunk-C5XG6VVP.mjs";
11
11
  import "../chunk-SSL36P7G.mjs";
12
+ import "../chunk-LNFUOXDW.mjs";
12
13
  export {
13
14
  deleteCandles,
14
15
  findContinuityGap,
@@ -111,6 +111,45 @@ var closeTimescalePool = async () => {
111
111
  await closePool();
112
112
  };
113
113
 
114
+ // src/logger.ts
115
+ var import_winston = require("winston");
116
+ var baseFormat = import_winston.format.combine(
117
+ import_winston.format.timestamp({ format: "DD MMM HH:mm:ss" }),
118
+ import_winston.format.splat()
119
+ );
120
+ var logger = (0, import_winston.createLogger)({
121
+ format: baseFormat,
122
+ transports: [
123
+ new import_winston.transports.Console({
124
+ format: import_winston.format.combine(
125
+ import_winston.format.colorize({ all: true }),
126
+ import_winston.format.printf(
127
+ ({ level, timestamp, message }) => `${level}: ${timestamp}: ${message}`
128
+ )
129
+ )
130
+ }),
131
+ new import_winston.transports.File({
132
+ filename: "service.log",
133
+ format: import_winston.format.combine(
134
+ import_winston.format.uncolorize(),
135
+ import_winston.format.printf(
136
+ ({ level, timestamp, message }) => `${level}: ${timestamp}: ${message}`
137
+ )
138
+ )
139
+ }),
140
+ new import_winston.transports.File({
141
+ filename: "error.log",
142
+ format: import_winston.format.combine(
143
+ import_winston.format.uncolorize(),
144
+ import_winston.format.printf(
145
+ ({ level, timestamp, message }) => `${level}: ${timestamp}: ${message}`
146
+ )
147
+ ),
148
+ level: "error"
149
+ })
150
+ ]
151
+ });
152
+
114
153
  // src/timescale/candles.ts
115
154
  async function waitForDbReady(attempts = 20, delayMs = 1e3) {
116
155
  const pool = getPool();
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  waitForDbReady
3
- } from "../chunk-CH5L3BE4.mjs";
3
+ } from "../chunk-C5XG6VVP.mjs";
4
4
  import {
5
5
  closeTimescalePool,
6
6
  configureTimescaleMarketContextSchemaMode
7
7
  } from "../chunk-SSL36P7G.mjs";
8
+ import "../chunk-LNFUOXDW.mjs";
8
9
  export {
9
10
  closeTimescalePool,
10
11
  configureTimescaleMarketContextSchemaMode,
@@ -45,6 +45,14 @@ declare function getLatestMarketTradeFlow(params: {
45
45
  signal?: AbortSignal;
46
46
  timeoutMs?: number;
47
47
  }): Promise<MarketFeatureAsOf<MarketTradeFlowRow> | null>;
48
+ declare function getMarketTradeFlowRows(params: {
49
+ symbols: string[];
50
+ interval: MarketFeatureInterval;
51
+ fromMs: number;
52
+ toMs: number;
53
+ signal?: AbortSignal;
54
+ timeoutMs?: number;
55
+ }): Promise<MarketTradeFlowRow[]>;
48
56
  declare function getLatestMarketBreadth(params: {
49
57
  universe: string;
50
58
  interval: MarketFeatureInterval;
@@ -53,6 +61,14 @@ declare function getLatestMarketBreadth(params: {
53
61
  signal?: AbortSignal;
54
62
  timeoutMs?: number;
55
63
  }): Promise<MarketFeatureAsOf<MarketBreadthRow> | null>;
64
+ declare function getMarketBreadthRows(params: {
65
+ universes: string[];
66
+ interval: MarketFeatureInterval;
67
+ fromMs: number;
68
+ toMs: number;
69
+ signal?: AbortSignal;
70
+ timeoutMs?: number;
71
+ }): Promise<MarketBreadthRow[]>;
56
72
  declare function getLatestMarketGlobalContext(params: {
57
73
  source?: MarketGlobalContextRow['source'];
58
74
  atMs: number;
@@ -189,4 +205,4 @@ declare function getMarketBreadthCoverage(params: {
189
205
  btcAltMetricsRows: number;
190
206
  } | null>;
191
207
 
192
- export { type DeprecatedMarketContextCleanupItem, MarketFeatureAsOf, cleanupDeprecatedMarketContext, ensureBinanceMarketSchema, getLatestMarketBreadth, getLatestMarketCmcExchangeLiquidityContext, getLatestMarketCmcFearGreedContext, getLatestMarketCmcIndexContexts, getLatestMarketGlobalContext, getLatestMarketReferenceAssetContexts, getLatestMarketTradeFlow, getMarketBreadthCoverage, getMarketCmcExchangeLiquidityContextCoverage, getMarketCmcFearGreedContextCoverage, getMarketCmcIndexContextCoverage, getMarketContextBackfillCoverage, getMarketGlobalContextCoverage, getMarketReferenceAssetContextCoverage, getMarketTradeFlowCoverage, upsertMarketBreadthRows, upsertMarketCmcExchangeLiquidityContextRows, upsertMarketCmcFearGreedContextRows, upsertMarketCmcIndexContextRows, upsertMarketContextBackfillCoverage, upsertMarketGlobalContextRows, upsertMarketReferenceAssetContextRows, upsertMarketTradeFlowRows };
208
+ export { type DeprecatedMarketContextCleanupItem, MarketFeatureAsOf, cleanupDeprecatedMarketContext, ensureBinanceMarketSchema, getLatestMarketBreadth, getLatestMarketCmcExchangeLiquidityContext, getLatestMarketCmcFearGreedContext, getLatestMarketCmcIndexContexts, getLatestMarketGlobalContext, getLatestMarketReferenceAssetContexts, getLatestMarketTradeFlow, getMarketBreadthCoverage, getMarketBreadthRows, getMarketCmcExchangeLiquidityContextCoverage, getMarketCmcFearGreedContextCoverage, getMarketCmcIndexContextCoverage, getMarketContextBackfillCoverage, getMarketGlobalContextCoverage, getMarketReferenceAssetContextCoverage, getMarketTradeFlowCoverage, getMarketTradeFlowRows, upsertMarketBreadthRows, upsertMarketCmcExchangeLiquidityContextRows, upsertMarketCmcFearGreedContextRows, upsertMarketCmcIndexContextRows, upsertMarketContextBackfillCoverage, upsertMarketGlobalContextRows, upsertMarketReferenceAssetContextRows, upsertMarketTradeFlowRows };
@@ -45,6 +45,14 @@ declare function getLatestMarketTradeFlow(params: {
45
45
  signal?: AbortSignal;
46
46
  timeoutMs?: number;
47
47
  }): Promise<MarketFeatureAsOf<MarketTradeFlowRow> | null>;
48
+ declare function getMarketTradeFlowRows(params: {
49
+ symbols: string[];
50
+ interval: MarketFeatureInterval;
51
+ fromMs: number;
52
+ toMs: number;
53
+ signal?: AbortSignal;
54
+ timeoutMs?: number;
55
+ }): Promise<MarketTradeFlowRow[]>;
48
56
  declare function getLatestMarketBreadth(params: {
49
57
  universe: string;
50
58
  interval: MarketFeatureInterval;
@@ -53,6 +61,14 @@ declare function getLatestMarketBreadth(params: {
53
61
  signal?: AbortSignal;
54
62
  timeoutMs?: number;
55
63
  }): Promise<MarketFeatureAsOf<MarketBreadthRow> | null>;
64
+ declare function getMarketBreadthRows(params: {
65
+ universes: string[];
66
+ interval: MarketFeatureInterval;
67
+ fromMs: number;
68
+ toMs: number;
69
+ signal?: AbortSignal;
70
+ timeoutMs?: number;
71
+ }): Promise<MarketBreadthRow[]>;
56
72
  declare function getLatestMarketGlobalContext(params: {
57
73
  source?: MarketGlobalContextRow['source'];
58
74
  atMs: number;
@@ -189,4 +205,4 @@ declare function getMarketBreadthCoverage(params: {
189
205
  btcAltMetricsRows: number;
190
206
  } | null>;
191
207
 
192
- export { type DeprecatedMarketContextCleanupItem, MarketFeatureAsOf, cleanupDeprecatedMarketContext, ensureBinanceMarketSchema, getLatestMarketBreadth, getLatestMarketCmcExchangeLiquidityContext, getLatestMarketCmcFearGreedContext, getLatestMarketCmcIndexContexts, getLatestMarketGlobalContext, getLatestMarketReferenceAssetContexts, getLatestMarketTradeFlow, getMarketBreadthCoverage, getMarketCmcExchangeLiquidityContextCoverage, getMarketCmcFearGreedContextCoverage, getMarketCmcIndexContextCoverage, getMarketContextBackfillCoverage, getMarketGlobalContextCoverage, getMarketReferenceAssetContextCoverage, getMarketTradeFlowCoverage, upsertMarketBreadthRows, upsertMarketCmcExchangeLiquidityContextRows, upsertMarketCmcFearGreedContextRows, upsertMarketCmcIndexContextRows, upsertMarketContextBackfillCoverage, upsertMarketGlobalContextRows, upsertMarketReferenceAssetContextRows, upsertMarketTradeFlowRows };
208
+ export { type DeprecatedMarketContextCleanupItem, MarketFeatureAsOf, cleanupDeprecatedMarketContext, ensureBinanceMarketSchema, getLatestMarketBreadth, getLatestMarketCmcExchangeLiquidityContext, getLatestMarketCmcFearGreedContext, getLatestMarketCmcIndexContexts, getLatestMarketGlobalContext, getLatestMarketReferenceAssetContexts, getLatestMarketTradeFlow, getMarketBreadthCoverage, getMarketBreadthRows, getMarketCmcExchangeLiquidityContextCoverage, getMarketCmcFearGreedContextCoverage, getMarketCmcIndexContextCoverage, getMarketContextBackfillCoverage, getMarketGlobalContextCoverage, getMarketReferenceAssetContextCoverage, getMarketTradeFlowCoverage, getMarketTradeFlowRows, upsertMarketBreadthRows, upsertMarketCmcExchangeLiquidityContextRows, upsertMarketCmcFearGreedContextRows, upsertMarketCmcIndexContextRows, upsertMarketContextBackfillCoverage, upsertMarketGlobalContextRows, upsertMarketReferenceAssetContextRows, upsertMarketTradeFlowRows };
@@ -32,6 +32,7 @@ __export(marketContext_exports, {
32
32
  getLatestMarketReferenceAssetContexts: () => getLatestMarketReferenceAssetContexts,
33
33
  getLatestMarketTradeFlow: () => getLatestMarketTradeFlow,
34
34
  getMarketBreadthCoverage: () => getMarketBreadthCoverage,
35
+ getMarketBreadthRows: () => getMarketBreadthRows,
35
36
  getMarketCmcExchangeLiquidityContextCoverage: () => getMarketCmcExchangeLiquidityContextCoverage,
36
37
  getMarketCmcFearGreedContextCoverage: () => getMarketCmcFearGreedContextCoverage,
37
38
  getMarketCmcIndexContextCoverage: () => getMarketCmcIndexContextCoverage,
@@ -39,6 +40,7 @@ __export(marketContext_exports, {
39
40
  getMarketGlobalContextCoverage: () => getMarketGlobalContextCoverage,
40
41
  getMarketReferenceAssetContextCoverage: () => getMarketReferenceAssetContextCoverage,
41
42
  getMarketTradeFlowCoverage: () => getMarketTradeFlowCoverage,
43
+ getMarketTradeFlowRows: () => getMarketTradeFlowRows,
42
44
  upsertMarketBreadthRows: () => upsertMarketBreadthRows,
43
45
  upsertMarketCmcExchangeLiquidityContextRows: () => upsertMarketCmcExchangeLiquidityContextRows,
44
46
  upsertMarketCmcFearGreedContextRows: () => upsertMarketCmcFearGreedContextRows,
@@ -1401,6 +1403,39 @@ async function getLatestMarketTradeFlow(params) {
1401
1403
  stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
1402
1404
  };
1403
1405
  }
1406
+ async function getMarketTradeFlowRows(params) {
1407
+ const symbols = [
1408
+ ...new Set(params.symbols.map((symbol) => symbol.trim().toUpperCase()))
1409
+ ].filter(Boolean);
1410
+ if (!symbols.length || params.toMs < params.fromMs) return [];
1411
+ await prepareMarketContextSchemaForRead("binance");
1412
+ const res = await queryMarketContext(
1413
+ `
1414
+ SELECT
1415
+ symbol,
1416
+ interval,
1417
+ ts,
1418
+ trades::int AS trades,
1419
+ buy_base_volume AS "buyBaseVolume",
1420
+ sell_base_volume AS "sellBaseVolume",
1421
+ buy_quote_volume AS "buyQuoteVolume",
1422
+ sell_quote_volume AS "sellQuoteVolume",
1423
+ net_base_delta AS "netBaseDelta",
1424
+ net_quote_delta AS "netQuoteDelta",
1425
+ buy_pressure_pct AS "buyPressurePct",
1426
+ source
1427
+ FROM market_trade_flow
1428
+ WHERE symbol = ANY($1)
1429
+ AND interval = $2
1430
+ AND ts >= to_timestamp($3/1000.0)
1431
+ AND ts <= to_timestamp($4/1000.0)
1432
+ ORDER BY symbol, ts
1433
+ `,
1434
+ [symbols, params.interval, params.fromMs, params.toMs],
1435
+ params
1436
+ );
1437
+ return res.rows;
1438
+ }
1404
1439
  async function getLatestMarketBreadth(params) {
1405
1440
  await prepareMarketContextSchemaForRead("binance");
1406
1441
  const res = await queryMarketContext(
@@ -1454,6 +1489,56 @@ async function getLatestMarketBreadth(params) {
1454
1489
  stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
1455
1490
  };
1456
1491
  }
1492
+ async function getMarketBreadthRows(params) {
1493
+ const universes = [
1494
+ ...new Set(params.universes.map((universe) => universe.trim()))
1495
+ ].filter(Boolean);
1496
+ if (!universes.length || params.toMs < params.fromMs) return [];
1497
+ await prepareMarketContextSchemaForRead("binance");
1498
+ const res = await queryMarketContext(
1499
+ `
1500
+ SELECT
1501
+ universe,
1502
+ interval,
1503
+ ts,
1504
+ symbols_count::int AS "symbolsCount",
1505
+ advancers::int AS advancers,
1506
+ decliners::int AS decliners,
1507
+ unchanged::int AS unchanged,
1508
+ advance_decline_ratio AS "advanceDeclineRatio",
1509
+ pct_above_ma20 AS "pctAboveMa20",
1510
+ pct_above_ma50 AS "pctAboveMa50",
1511
+ equal_weighted_return AS "equalWeightedReturn",
1512
+ volume_weighted_return AS "volumeWeightedReturn",
1513
+ dispersion,
1514
+ btc_return_1h AS "btcReturn1h",
1515
+ btc_return_4h AS "btcReturn4h",
1516
+ btc_return_24h AS "btcReturn24h",
1517
+ alt_basket_return_1h AS "altBasketReturn1h",
1518
+ alt_basket_return_4h AS "altBasketReturn4h",
1519
+ alt_basket_return_24h AS "altBasketReturn24h",
1520
+ btc_vs_alt_return_1h AS "btcVsAltReturn1h",
1521
+ btc_vs_alt_return_4h AS "btcVsAltReturn4h",
1522
+ btc_vs_alt_return_24h AS "btcVsAltReturn24h",
1523
+ btc_turnover_share_1h AS "btcTurnoverShare1h",
1524
+ btc_turnover_share_24h AS "btcTurnoverShare24h",
1525
+ btc_turnover_share_change_24h AS "btcTurnoverShareChange24h",
1526
+ alt_vol_to_btc_vol_24h AS "altVolToBtcVol24h",
1527
+ alt_dispersion_24h AS "altDispersion24h",
1528
+ btc_alt_regime AS "btcAltRegime",
1529
+ source
1530
+ FROM market_breadth
1531
+ WHERE universe = ANY($1)
1532
+ AND interval = $2
1533
+ AND ts >= to_timestamp($3/1000.0)
1534
+ AND ts <= to_timestamp($4/1000.0)
1535
+ ORDER BY universe, ts
1536
+ `,
1537
+ [universes, params.interval, params.fromMs, params.toMs],
1538
+ params
1539
+ );
1540
+ return res.rows;
1541
+ }
1457
1542
  async function getLatestMarketGlobalContext(params) {
1458
1543
  await prepareMarketContextSchemaForRead("coinmarketcap");
1459
1544
  const source = params.source ?? "coinmarketcap_global";
@@ -2083,6 +2168,7 @@ async function getMarketBreadthCoverage(params) {
2083
2168
  getLatestMarketReferenceAssetContexts,
2084
2169
  getLatestMarketTradeFlow,
2085
2170
  getMarketBreadthCoverage,
2171
+ getMarketBreadthRows,
2086
2172
  getMarketCmcExchangeLiquidityContextCoverage,
2087
2173
  getMarketCmcFearGreedContextCoverage,
2088
2174
  getMarketCmcIndexContextCoverage,
@@ -2090,6 +2176,7 @@ async function getMarketBreadthCoverage(params) {
2090
2176
  getMarketGlobalContextCoverage,
2091
2177
  getMarketReferenceAssetContextCoverage,
2092
2178
  getMarketTradeFlowCoverage,
2179
+ getMarketTradeFlowRows,
2093
2180
  upsertMarketBreadthRows,
2094
2181
  upsertMarketCmcExchangeLiquidityContextRows,
2095
2182
  upsertMarketCmcFearGreedContextRows,
@@ -592,6 +592,39 @@ async function getLatestMarketTradeFlow(params) {
592
592
  stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
593
593
  };
594
594
  }
595
+ async function getMarketTradeFlowRows(params) {
596
+ const symbols = [
597
+ ...new Set(params.symbols.map((symbol) => symbol.trim().toUpperCase()))
598
+ ].filter(Boolean);
599
+ if (!symbols.length || params.toMs < params.fromMs) return [];
600
+ await prepareMarketContextSchemaForRead("binance");
601
+ const res = await queryMarketContext(
602
+ `
603
+ SELECT
604
+ symbol,
605
+ interval,
606
+ ts,
607
+ trades::int AS trades,
608
+ buy_base_volume AS "buyBaseVolume",
609
+ sell_base_volume AS "sellBaseVolume",
610
+ buy_quote_volume AS "buyQuoteVolume",
611
+ sell_quote_volume AS "sellQuoteVolume",
612
+ net_base_delta AS "netBaseDelta",
613
+ net_quote_delta AS "netQuoteDelta",
614
+ buy_pressure_pct AS "buyPressurePct",
615
+ source
616
+ FROM market_trade_flow
617
+ WHERE symbol = ANY($1)
618
+ AND interval = $2
619
+ AND ts >= to_timestamp($3/1000.0)
620
+ AND ts <= to_timestamp($4/1000.0)
621
+ ORDER BY symbol, ts
622
+ `,
623
+ [symbols, params.interval, params.fromMs, params.toMs],
624
+ params
625
+ );
626
+ return res.rows;
627
+ }
595
628
  async function getLatestMarketBreadth(params) {
596
629
  await prepareMarketContextSchemaForRead("binance");
597
630
  const res = await queryMarketContext(
@@ -645,6 +678,56 @@ async function getLatestMarketBreadth(params) {
645
678
  stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
646
679
  };
647
680
  }
681
+ async function getMarketBreadthRows(params) {
682
+ const universes = [
683
+ ...new Set(params.universes.map((universe) => universe.trim()))
684
+ ].filter(Boolean);
685
+ if (!universes.length || params.toMs < params.fromMs) return [];
686
+ await prepareMarketContextSchemaForRead("binance");
687
+ const res = await queryMarketContext(
688
+ `
689
+ SELECT
690
+ universe,
691
+ interval,
692
+ ts,
693
+ symbols_count::int AS "symbolsCount",
694
+ advancers::int AS advancers,
695
+ decliners::int AS decliners,
696
+ unchanged::int AS unchanged,
697
+ advance_decline_ratio AS "advanceDeclineRatio",
698
+ pct_above_ma20 AS "pctAboveMa20",
699
+ pct_above_ma50 AS "pctAboveMa50",
700
+ equal_weighted_return AS "equalWeightedReturn",
701
+ volume_weighted_return AS "volumeWeightedReturn",
702
+ dispersion,
703
+ btc_return_1h AS "btcReturn1h",
704
+ btc_return_4h AS "btcReturn4h",
705
+ btc_return_24h AS "btcReturn24h",
706
+ alt_basket_return_1h AS "altBasketReturn1h",
707
+ alt_basket_return_4h AS "altBasketReturn4h",
708
+ alt_basket_return_24h AS "altBasketReturn24h",
709
+ btc_vs_alt_return_1h AS "btcVsAltReturn1h",
710
+ btc_vs_alt_return_4h AS "btcVsAltReturn4h",
711
+ btc_vs_alt_return_24h AS "btcVsAltReturn24h",
712
+ btc_turnover_share_1h AS "btcTurnoverShare1h",
713
+ btc_turnover_share_24h AS "btcTurnoverShare24h",
714
+ btc_turnover_share_change_24h AS "btcTurnoverShareChange24h",
715
+ alt_vol_to_btc_vol_24h AS "altVolToBtcVol24h",
716
+ alt_dispersion_24h AS "altDispersion24h",
717
+ btc_alt_regime AS "btcAltRegime",
718
+ source
719
+ FROM market_breadth
720
+ WHERE universe = ANY($1)
721
+ AND interval = $2
722
+ AND ts >= to_timestamp($3/1000.0)
723
+ AND ts <= to_timestamp($4/1000.0)
724
+ ORDER BY universe, ts
725
+ `,
726
+ [universes, params.interval, params.fromMs, params.toMs],
727
+ params
728
+ );
729
+ return res.rows;
730
+ }
648
731
  async function getLatestMarketGlobalContext(params) {
649
732
  await prepareMarketContextSchemaForRead("coinmarketcap");
650
733
  const source = params.source ?? "coinmarketcap_global";
@@ -1273,6 +1356,7 @@ export {
1273
1356
  getLatestMarketReferenceAssetContexts,
1274
1357
  getLatestMarketTradeFlow,
1275
1358
  getMarketBreadthCoverage,
1359
+ getMarketBreadthRows,
1276
1360
  getMarketCmcExchangeLiquidityContextCoverage,
1277
1361
  getMarketCmcFearGreedContextCoverage,
1278
1362
  getMarketCmcIndexContextCoverage,
@@ -1280,6 +1364,7 @@ export {
1280
1364
  getMarketGlobalContextCoverage,
1281
1365
  getMarketReferenceAssetContextCoverage,
1282
1366
  getMarketTradeFlowCoverage,
1367
+ getMarketTradeFlowRows,
1283
1368
  upsertMarketBreadthRows,
1284
1369
  upsertMarketCmcExchangeLiquidityContextRows,
1285
1370
  upsertMarketCmcFearGreedContextRows,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/infra",
3
- "version": "3.1.27",
3
+ "version": "3.1.28-beta.252",
4
4
  "description": "MIT-licensed server infrastructure adapters for TradeJS: Redis, Timescale, ML, logging, and IO.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -130,7 +130,7 @@
130
130
  "dependencies": {
131
131
  "@grpc/grpc-js": "^1.14.4",
132
132
  "@grpc/proto-loader": "^0.8.1",
133
- "@tradejs/types": "^3.1.27",
133
+ "@tradejs/types": "^3.1.28-beta.252",
134
134
  "chalk": "4.1.2",
135
135
  "date-fns": "^3.6.0",
136
136
  "ioredis": "5.11.1",