@tradejs/cli 2.0.5 → 2.0.7

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.
@@ -46,6 +46,7 @@ var import_progress = __toESM(require("progress"));
46
46
  var import_chalk = __toESM(require("chalk"));
47
47
  var import_connectors = require("@tradejs/connectors");
48
48
  var import_connectors2 = require("@tradejs/node/connectors");
49
+ var import_strategies = require("@tradejs/node/strategies");
49
50
  var import_timescale = require("@tradejs/infra/timescale");
50
51
 
51
52
  // src/lib/binanceMarketData.ts
@@ -55,14 +56,6 @@ var MARKET_FEATURE_INTERVAL_MS = {
55
56
  "15m": 9e5,
56
57
  "1h": 36e5
57
58
  };
58
- var STABLE_QUOTE_SYMBOLS = /* @__PURE__ */ new Set([
59
- "USDCUSDT",
60
- "FDUSDUSDT",
61
- "TUSDUSDT",
62
- "BUSDUSDT",
63
- "USDPUSDT",
64
- "DAIUSDT"
65
- ]);
66
59
  var safeDivide = (num, den) => Number.isFinite(num) && Number.isFinite(den) && den !== 0 ? num / den : null;
67
60
  var mean = (values) => values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
68
61
  var standardDeviation = (values) => {
@@ -106,9 +99,6 @@ var classifyBtcAltRegime = ({
106
99
  if (btcVsAltReturn24h < -5e-3) return "alt_lead";
107
100
  return "neutral";
108
101
  };
109
- var selectBreadthUniverseFromTickers = (tickers, limit) => tickers.filter(
110
- (ticker) => ticker.symbol.endsWith("USDT") && ticker.symbol !== "BTCUSDT" && !STABLE_QUOTE_SYMBOLS.has(ticker.symbol) && Number.isFinite(ticker.turnover24h) && ticker.turnover24h > 0
111
- ).sort((a, b) => b.turnover24h - a.turnover24h).slice(0, Math.max(0, limit)).map((ticker) => ticker.symbol);
112
102
  var aggregateAggTradesToRows = ({
113
103
  symbol,
114
104
  interval,
@@ -590,10 +580,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
590
580
  process.env.BINANCE_MARKET_CONTEXT_TRADE_FLOW_SYMBOL_LIMIT,
591
581
  2
592
582
  );
593
- const breadthLimit = asInt(
594
- process.env.BINANCE_MARKET_CONTEXT_BREADTH_LIMIT,
595
- 30
596
- );
597
583
  const breadthChunkDays = asFloat(
598
584
  process.env.BINANCE_MARKET_CONTEXT_BREADTH_CHUNK_DAYS,
599
585
  30
@@ -716,88 +702,113 @@ var backfillBinanceMarketContext = async (params, enabled) => {
716
702
  }
717
703
  }
718
704
  if (includeBreadth) {
719
- const tickers = await connector.getTickers();
720
- const breadthSymbols = selectBreadthUniverseFromTickers(
721
- tickers,
722
- breadthLimit
723
- );
724
- const universe = `binance_top${breadthSymbols.length}_usdt`;
725
- const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
726
- universe,
727
- interval,
728
- startMs: breadthStartMs,
729
- endMs
730
- });
731
- const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
732
- if (!hasCoverage({
733
- coverage,
705
+ const breadthUniverses = (0, import_strategies.getBinanceBreadthUniverses)();
706
+ const breadthSymbols = breadthUniverses.at(-1)?.symbols ?? [];
707
+ const connectorInterval = marketIntervalToConnectorInterval(interval);
708
+ const chunks = buildBreadthBackfillChunks({
734
709
  startMs: breadthStartMs,
735
710
  endMs,
736
- intervalMs
737
- }) || !hasBtcAltMetrics) {
738
- const connectorInterval = marketIntervalToConnectorInterval(interval);
739
- const chunks = buildBreadthBackfillChunks({
740
- startMs: breadthStartMs,
741
- endMs,
742
- intervalMs,
743
- chunkDays: breadthChunkDays
744
- });
745
- const missingChunks = filterMissingBreadthBackfillChunks({
746
- chunks,
747
- coverage,
748
- intervalMs
749
- });
750
- const bar = new import_progress.default(
751
- "breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
752
- {
753
- total: Math.max(1, missingChunks.length * breadthSymbols.length),
754
- width: 24
755
- }
756
- );
757
- let candlesRead = 0;
758
- let skippedBreadthChunks = chunks.length - missingChunks.length;
759
- if (!missingChunks.length) {
760
- bar.tick(1, {
761
- candles: 0,
762
- skip: skippedBreadthChunks,
763
- chunk: "cached",
764
- symbol: universe
711
+ intervalMs,
712
+ chunkDays: breadthChunkDays
713
+ });
714
+ const plans = await Promise.all(
715
+ breadthUniverses.map(async (definition) => {
716
+ const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
717
+ universe: definition.universe,
718
+ interval,
719
+ startMs: breadthStartMs,
720
+ endMs
765
721
  });
766
- }
767
- for (let chunkIndex = 0; chunkIndex < missingChunks.length; chunkIndex += 1) {
768
- const chunk = missingChunks[chunkIndex];
769
- const originalChunkIndex = chunks.findIndex(
770
- (item) => item.startMs === chunk.startMs && item.endMs === chunk.endMs
771
- );
772
- const candlesBySymbol = {};
773
- for (const symbol of breadthSymbols) {
774
- const candles = await connector.kline({
775
- symbol,
776
- interval: connectorInterval,
777
- start: chunk.fetchStartMs,
778
- end: chunk.endMs,
779
- silent: true
780
- });
781
- candlesBySymbol[symbol] = candles;
782
- candlesRead += candles.length;
783
- bar.tick(1, {
784
- candles: candlesRead,
785
- skip: skippedBreadthChunks,
786
- chunk: `${originalChunkIndex + 1}/${chunks.length}`,
787
- symbol
788
- });
722
+ const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
723
+ const missingChunks2 = !hasCoverage({
724
+ coverage,
725
+ startMs: breadthStartMs,
726
+ endMs,
727
+ intervalMs
728
+ }) || !hasBtcAltMetrics ? !hasBtcAltMetrics ? chunks : filterMissingBreadthBackfillChunks({
729
+ chunks,
730
+ coverage,
731
+ intervalMs
732
+ }) : [];
733
+ if (!missingChunks2.length) {
734
+ console.log(
735
+ import_chalk.default.gray(`breadth cached: universe=${definition.universe}`)
736
+ );
789
737
  }
790
- const btcCandles = await connector.kline({
791
- symbol: "BTCUSDT",
738
+ return {
739
+ definition,
740
+ missingChunkKeys: new Set(
741
+ missingChunks2.map((chunk) => `${chunk.startMs}:${chunk.endMs}`)
742
+ )
743
+ };
744
+ })
745
+ );
746
+ const missingChunks = chunks.filter((chunk) => {
747
+ const key = `${chunk.startMs}:${chunk.endMs}`;
748
+ return plans.some((plan) => plan.missingChunkKeys.has(key));
749
+ });
750
+ const bar = new import_progress.default(
751
+ "breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
752
+ {
753
+ total: Math.max(1, missingChunks.length * breadthSymbols.length),
754
+ width: 24
755
+ }
756
+ );
757
+ let candlesRead = 0;
758
+ const skippedBreadthChunks = chunks.length - missingChunks.length;
759
+ if (!missingChunks.length) {
760
+ bar.tick(1, {
761
+ candles: 0,
762
+ skip: skippedBreadthChunks,
763
+ chunk: "cached",
764
+ symbol: "top5/top10/top30/top50/top100"
765
+ });
766
+ }
767
+ for (let chunkIndex = 0; chunkIndex < missingChunks.length; chunkIndex += 1) {
768
+ const chunk = missingChunks[chunkIndex];
769
+ const originalChunkIndex = chunks.findIndex(
770
+ (item) => item.startMs === chunk.startMs && item.endMs === chunk.endMs
771
+ );
772
+ const candlesBySymbol = {};
773
+ for (const symbol of breadthSymbols) {
774
+ const candles = await connector.kline({
775
+ symbol,
792
776
  interval: connectorInterval,
793
777
  start: chunk.fetchStartMs,
794
778
  end: chunk.endMs,
795
779
  silent: true
796
780
  });
781
+ candlesBySymbol[symbol] = candles;
782
+ candlesRead += candles.length;
783
+ bar.tick(1, {
784
+ candles: candlesRead,
785
+ skip: skippedBreadthChunks,
786
+ chunk: `${originalChunkIndex + 1}/${chunks.length}`,
787
+ symbol
788
+ });
789
+ }
790
+ const btcCandles = await connector.kline({
791
+ symbol: "BTCUSDT",
792
+ interval: connectorInterval,
793
+ start: chunk.fetchStartMs,
794
+ end: chunk.endMs,
795
+ silent: true
796
+ });
797
+ const chunkKey = `${chunk.startMs}:${chunk.endMs}`;
798
+ for (const plan of plans) {
799
+ if (!plan.missingChunkKeys.has(chunkKey)) {
800
+ continue;
801
+ }
802
+ const symbolSet = new Set(plan.definition.symbols);
803
+ const universeCandles = Object.fromEntries(
804
+ Object.entries(candlesBySymbol).filter(
805
+ ([symbol]) => symbolSet.has(symbol)
806
+ )
807
+ );
797
808
  const rows = buildMarketBreadthRows({
798
- universe,
809
+ universe: plan.definition.universe,
799
810
  interval,
800
- candlesBySymbol,
811
+ candlesBySymbol: universeCandles,
801
812
  btcCandles
802
813
  }).filter((row) => {
803
814
  const ts = row.ts.getTime();
@@ -806,8 +817,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
806
817
  await (0, import_timescale.upsertMarketBreadthRows)(rows);
807
818
  breadthRows += rows.length;
808
819
  }
809
- } else {
810
- console.log(import_chalk.default.gray(`breadth cached: universe=${universe}`));
811
820
  }
812
821
  }
813
822
  console.log(
@@ -106,14 +106,17 @@ var estimateBinanceMarketDataVolume = ({
106
106
  interval,
107
107
  includeAggTrades,
108
108
  includeBreadth,
109
- breadthLimit
109
+ breadthSizes
110
110
  }) => {
111
111
  const intervalMs = MARKET_FEATURE_INTERVAL_MS[interval];
112
112
  const bucketRowsPerSymbol = Math.ceil(days * 864e5 / intervalMs);
113
113
  const aggTradeBucketRows = includeAggTrades ? symbols.length * bucketRowsPerSymbol : 0;
114
- const breadthSymbols = includeBreadth ? Math.max(0, breadthLimit) : 0;
114
+ const normalizedBreadthSizes = breadthSizes.filter(
115
+ (size) => Number.isFinite(size) && size > 0
116
+ );
117
+ const breadthSymbols = includeBreadth ? Math.max(0, ...normalizedBreadthSizes) : 0;
115
118
  const breadthCandleRows = breadthSymbols * bucketRowsPerSymbol;
116
- const breadthRows = includeBreadth ? bucketRowsPerSymbol : 0;
119
+ const breadthRows = includeBreadth ? bucketRowsPerSymbol * normalizedBreadthSizes.length : 0;
117
120
  return {
118
121
  interval,
119
122
  days,
@@ -34,9 +34,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  ));
35
35
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
36
 
37
- // ../../node_modules/debug/node_modules/ms/index.js
37
+ // ../../node_modules/ms/index.js
38
38
  var require_ms = __commonJS({
39
- "../../node_modules/debug/node_modules/ms/index.js"(exports2, module2) {
39
+ "../../node_modules/ms/index.js"(exports2, module2) {
40
40
  "use strict";
41
41
  var s = 1e3;
42
42
  var m = s * 60;
@@ -151,9 +151,9 @@ var require_ms = __commonJS({
151
151
  }
152
152
  });
153
153
 
154
- // ../../node_modules/debug/src/common.js
154
+ // ../../node_modules/list-it/node_modules/debug/src/common.js
155
155
  var require_common = __commonJS({
156
- "../../node_modules/debug/src/common.js"(exports2, module2) {
156
+ "../../node_modules/list-it/node_modules/debug/src/common.js"(exports2, module2) {
157
157
  "use strict";
158
158
  function setup(env) {
159
159
  createDebug.debug = createDebug;
@@ -255,50 +255,64 @@ var require_common = __commonJS({
255
255
  createDebug.namespaces = namespaces;
256
256
  createDebug.names = [];
257
257
  createDebug.skips = [];
258
- let i;
259
- const split = (typeof namespaces === "string" ? namespaces : "").split(/[\s,]+/);
260
- const len = split.length;
261
- for (i = 0; i < len; i++) {
262
- if (!split[i]) {
263
- continue;
258
+ const split = (typeof namespaces === "string" ? namespaces : "").trim().replace(/\s+/g, ",").split(",").filter(Boolean);
259
+ for (const ns of split) {
260
+ if (ns[0] === "-") {
261
+ createDebug.skips.push(ns.slice(1));
262
+ } else {
263
+ createDebug.names.push(ns);
264
264
  }
265
- namespaces = split[i].replace(/\*/g, ".*?");
266
- if (namespaces[0] === "-") {
267
- createDebug.skips.push(new RegExp("^" + namespaces.slice(1) + "$"));
265
+ }
266
+ }
267
+ function matchesTemplate(search, template) {
268
+ let searchIndex = 0;
269
+ let templateIndex = 0;
270
+ let starIndex = -1;
271
+ let matchIndex = 0;
272
+ while (searchIndex < search.length) {
273
+ if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) {
274
+ if (template[templateIndex] === "*") {
275
+ starIndex = templateIndex;
276
+ matchIndex = searchIndex;
277
+ templateIndex++;
278
+ } else {
279
+ searchIndex++;
280
+ templateIndex++;
281
+ }
282
+ } else if (starIndex !== -1) {
283
+ templateIndex = starIndex + 1;
284
+ matchIndex++;
285
+ searchIndex = matchIndex;
268
286
  } else {
269
- createDebug.names.push(new RegExp("^" + namespaces + "$"));
287
+ return false;
270
288
  }
271
289
  }
290
+ while (templateIndex < template.length && template[templateIndex] === "*") {
291
+ templateIndex++;
292
+ }
293
+ return templateIndex === template.length;
272
294
  }
273
295
  function disable() {
274
296
  const namespaces = [
275
- ...createDebug.names.map(toNamespace),
276
- ...createDebug.skips.map(toNamespace).map((namespace) => "-" + namespace)
297
+ ...createDebug.names,
298
+ ...createDebug.skips.map((namespace) => "-" + namespace)
277
299
  ].join(",");
278
300
  createDebug.enable("");
279
301
  return namespaces;
280
302
  }
281
303
  function enabled(name) {
282
- if (name[name.length - 1] === "*") {
283
- return true;
284
- }
285
- let i;
286
- let len;
287
- for (i = 0, len = createDebug.skips.length; i < len; i++) {
288
- if (createDebug.skips[i].test(name)) {
304
+ for (const skip of createDebug.skips) {
305
+ if (matchesTemplate(name, skip)) {
289
306
  return false;
290
307
  }
291
308
  }
292
- for (i = 0, len = createDebug.names.length; i < len; i++) {
293
- if (createDebug.names[i].test(name)) {
309
+ for (const ns of createDebug.names) {
310
+ if (matchesTemplate(name, ns)) {
294
311
  return true;
295
312
  }
296
313
  }
297
314
  return false;
298
315
  }
299
- function toNamespace(regexp) {
300
- return regexp.toString().substring(2, regexp.toString().length - 2).replace(/\.\*\?$/, "*");
301
- }
302
316
  function coerce(val) {
303
317
  if (val instanceof Error) {
304
318
  return val.stack || val.message;
@@ -315,9 +329,9 @@ var require_common = __commonJS({
315
329
  }
316
330
  });
317
331
 
318
- // ../../node_modules/debug/src/browser.js
332
+ // ../../node_modules/list-it/node_modules/debug/src/browser.js
319
333
  var require_browser = __commonJS({
320
- "../../node_modules/debug/src/browser.js"(exports2, module2) {
334
+ "../../node_modules/list-it/node_modules/debug/src/browser.js"(exports2, module2) {
321
335
  "use strict";
322
336
  exports2.formatArgs = formatArgs;
323
337
  exports2.save = save;
@@ -418,10 +432,11 @@ var require_browser = __commonJS({
418
432
  if (typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
419
433
  return false;
420
434
  }
435
+ let m;
421
436
  return typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773
422
437
  typeof window !== "undefined" && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31?
423
438
  // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
424
- typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker
439
+ typeof navigator !== "undefined" && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker
425
440
  typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
426
441
  }
427
442
  function formatArgs(args) {
@@ -459,7 +474,7 @@ var require_browser = __commonJS({
459
474
  function load() {
460
475
  let r;
461
476
  try {
462
- r = exports2.storage.getItem("debug");
477
+ r = exports2.storage.getItem("debug") || exports2.storage.getItem("DEBUG");
463
478
  } catch (error) {
464
479
  }
465
480
  if (!r && typeof process !== "undefined" && "env" in process) {
@@ -600,9 +615,9 @@ var require_supports_color = __commonJS({
600
615
  }
601
616
  });
602
617
 
603
- // ../../node_modules/debug/src/node.js
618
+ // ../../node_modules/list-it/node_modules/debug/src/node.js
604
619
  var require_node = __commonJS({
605
- "../../node_modules/debug/src/node.js"(exports2, module2) {
620
+ "../../node_modules/list-it/node_modules/debug/src/node.js"(exports2, module2) {
606
621
  "use strict";
607
622
  var tty = require("tty");
608
623
  var util = require("util");
@@ -743,7 +758,7 @@ var require_node = __commonJS({
743
758
  return (/* @__PURE__ */ new Date()).toISOString() + " ";
744
759
  }
745
760
  function log(...args) {
746
- return process.stderr.write(util.format(...args) + "\n");
761
+ return process.stderr.write(util.formatWithOptions(exports2.inspectOpts, ...args) + "\n");
747
762
  }
748
763
  function save(namespaces) {
749
764
  if (namespaces) {
@@ -775,9 +790,9 @@ var require_node = __commonJS({
775
790
  }
776
791
  });
777
792
 
778
- // ../../node_modules/debug/src/index.js
793
+ // ../../node_modules/list-it/node_modules/debug/src/index.js
779
794
  var require_src = __commonJS({
780
- "../../node_modules/debug/src/index.js"(exports2, module2) {
795
+ "../../node_modules/list-it/node_modules/debug/src/index.js"(exports2, module2) {
781
796
  "use strict";
782
797
  if (typeof process === "undefined" || process.type === "renderer" || process.browser === true || process.__nwjs) {
783
798
  module2.exports = require_browser();
@@ -1634,6 +1649,7 @@ var import_progress = __toESM(require("progress"));
1634
1649
  var import_chalk = __toESM(require("chalk"));
1635
1650
  var import_connectors = require("@tradejs/connectors");
1636
1651
  var import_connectors2 = require("@tradejs/node/connectors");
1652
+ var import_strategies = require("@tradejs/node/strategies");
1637
1653
  var import_timescale = require("@tradejs/infra/timescale");
1638
1654
 
1639
1655
  // src/lib/binanceMarketData.ts
@@ -1643,14 +1659,6 @@ var MARKET_FEATURE_INTERVAL_MS = {
1643
1659
  "15m": 9e5,
1644
1660
  "1h": 36e5
1645
1661
  };
1646
- var STABLE_QUOTE_SYMBOLS = /* @__PURE__ */ new Set([
1647
- "USDCUSDT",
1648
- "FDUSDUSDT",
1649
- "TUSDUSDT",
1650
- "BUSDUSDT",
1651
- "USDPUSDT",
1652
- "DAIUSDT"
1653
- ]);
1654
1662
  var safeDivide = (num, den) => Number.isFinite(num) && Number.isFinite(den) && den !== 0 ? num / den : null;
1655
1663
  var mean = (values) => values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
1656
1664
  var standardDeviation = (values) => {
@@ -1694,9 +1702,6 @@ var classifyBtcAltRegime = ({
1694
1702
  if (btcVsAltReturn24h < -5e-3) return "alt_lead";
1695
1703
  return "neutral";
1696
1704
  };
1697
- var selectBreadthUniverseFromTickers = (tickers, limit) => tickers.filter(
1698
- (ticker) => ticker.symbol.endsWith("USDT") && ticker.symbol !== "BTCUSDT" && !STABLE_QUOTE_SYMBOLS.has(ticker.symbol) && Number.isFinite(ticker.turnover24h) && ticker.turnover24h > 0
1699
- ).sort((a, b) => b.turnover24h - a.turnover24h).slice(0, Math.max(0, limit)).map((ticker) => ticker.symbol);
1700
1705
  var aggregateAggTradesToRows = ({
1701
1706
  symbol,
1702
1707
  interval,
@@ -2178,10 +2183,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
2178
2183
  process.env.BINANCE_MARKET_CONTEXT_TRADE_FLOW_SYMBOL_LIMIT,
2179
2184
  2
2180
2185
  );
2181
- const breadthLimit = asInt(
2182
- process.env.BINANCE_MARKET_CONTEXT_BREADTH_LIMIT,
2183
- 30
2184
- );
2185
2186
  const breadthChunkDays = asFloat(
2186
2187
  process.env.BINANCE_MARKET_CONTEXT_BREADTH_CHUNK_DAYS,
2187
2188
  30
@@ -2304,88 +2305,113 @@ var backfillBinanceMarketContext = async (params, enabled) => {
2304
2305
  }
2305
2306
  }
2306
2307
  if (includeBreadth) {
2307
- const tickers = await connector.getTickers();
2308
- const breadthSymbols = selectBreadthUniverseFromTickers(
2309
- tickers,
2310
- breadthLimit
2311
- );
2312
- const universe = `binance_top${breadthSymbols.length}_usdt`;
2313
- const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
2314
- universe,
2315
- interval,
2316
- startMs: breadthStartMs,
2317
- endMs
2318
- });
2319
- const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
2320
- if (!hasCoverage({
2321
- coverage,
2308
+ const breadthUniverses = (0, import_strategies.getBinanceBreadthUniverses)();
2309
+ const breadthSymbols = breadthUniverses.at(-1)?.symbols ?? [];
2310
+ const connectorInterval = marketIntervalToConnectorInterval(interval);
2311
+ const chunks = buildBreadthBackfillChunks({
2322
2312
  startMs: breadthStartMs,
2323
2313
  endMs,
2324
- intervalMs
2325
- }) || !hasBtcAltMetrics) {
2326
- const connectorInterval = marketIntervalToConnectorInterval(interval);
2327
- const chunks = buildBreadthBackfillChunks({
2328
- startMs: breadthStartMs,
2329
- endMs,
2330
- intervalMs,
2331
- chunkDays: breadthChunkDays
2332
- });
2333
- const missingChunks = filterMissingBreadthBackfillChunks({
2334
- chunks,
2335
- coverage,
2336
- intervalMs
2337
- });
2338
- const bar = new import_progress.default(
2339
- "breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
2340
- {
2341
- total: Math.max(1, missingChunks.length * breadthSymbols.length),
2342
- width: 24
2343
- }
2344
- );
2345
- let candlesRead = 0;
2346
- let skippedBreadthChunks = chunks.length - missingChunks.length;
2347
- if (!missingChunks.length) {
2348
- bar.tick(1, {
2349
- candles: 0,
2350
- skip: skippedBreadthChunks,
2351
- chunk: "cached",
2352
- symbol: universe
2314
+ intervalMs,
2315
+ chunkDays: breadthChunkDays
2316
+ });
2317
+ const plans = await Promise.all(
2318
+ breadthUniverses.map(async (definition) => {
2319
+ const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
2320
+ universe: definition.universe,
2321
+ interval,
2322
+ startMs: breadthStartMs,
2323
+ endMs
2353
2324
  });
2354
- }
2355
- for (let chunkIndex = 0; chunkIndex < missingChunks.length; chunkIndex += 1) {
2356
- const chunk = missingChunks[chunkIndex];
2357
- const originalChunkIndex = chunks.findIndex(
2358
- (item) => item.startMs === chunk.startMs && item.endMs === chunk.endMs
2359
- );
2360
- const candlesBySymbol = {};
2361
- for (const symbol of breadthSymbols) {
2362
- const candles = await connector.kline({
2363
- symbol,
2364
- interval: connectorInterval,
2365
- start: chunk.fetchStartMs,
2366
- end: chunk.endMs,
2367
- silent: true
2368
- });
2369
- candlesBySymbol[symbol] = candles;
2370
- candlesRead += candles.length;
2371
- bar.tick(1, {
2372
- candles: candlesRead,
2373
- skip: skippedBreadthChunks,
2374
- chunk: `${originalChunkIndex + 1}/${chunks.length}`,
2375
- symbol
2376
- });
2325
+ const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
2326
+ const missingChunks2 = !hasCoverage({
2327
+ coverage,
2328
+ startMs: breadthStartMs,
2329
+ endMs,
2330
+ intervalMs
2331
+ }) || !hasBtcAltMetrics ? !hasBtcAltMetrics ? chunks : filterMissingBreadthBackfillChunks({
2332
+ chunks,
2333
+ coverage,
2334
+ intervalMs
2335
+ }) : [];
2336
+ if (!missingChunks2.length) {
2337
+ console.log(
2338
+ import_chalk.default.gray(`breadth cached: universe=${definition.universe}`)
2339
+ );
2377
2340
  }
2378
- const btcCandles = await connector.kline({
2379
- symbol: "BTCUSDT",
2341
+ return {
2342
+ definition,
2343
+ missingChunkKeys: new Set(
2344
+ missingChunks2.map((chunk) => `${chunk.startMs}:${chunk.endMs}`)
2345
+ )
2346
+ };
2347
+ })
2348
+ );
2349
+ const missingChunks = chunks.filter((chunk) => {
2350
+ const key = `${chunk.startMs}:${chunk.endMs}`;
2351
+ return plans.some((plan) => plan.missingChunkKeys.has(key));
2352
+ });
2353
+ const bar = new import_progress.default(
2354
+ "breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
2355
+ {
2356
+ total: Math.max(1, missingChunks.length * breadthSymbols.length),
2357
+ width: 24
2358
+ }
2359
+ );
2360
+ let candlesRead = 0;
2361
+ const skippedBreadthChunks = chunks.length - missingChunks.length;
2362
+ if (!missingChunks.length) {
2363
+ bar.tick(1, {
2364
+ candles: 0,
2365
+ skip: skippedBreadthChunks,
2366
+ chunk: "cached",
2367
+ symbol: "top5/top10/top30/top50/top100"
2368
+ });
2369
+ }
2370
+ for (let chunkIndex = 0; chunkIndex < missingChunks.length; chunkIndex += 1) {
2371
+ const chunk = missingChunks[chunkIndex];
2372
+ const originalChunkIndex = chunks.findIndex(
2373
+ (item) => item.startMs === chunk.startMs && item.endMs === chunk.endMs
2374
+ );
2375
+ const candlesBySymbol = {};
2376
+ for (const symbol of breadthSymbols) {
2377
+ const candles = await connector.kline({
2378
+ symbol,
2380
2379
  interval: connectorInterval,
2381
2380
  start: chunk.fetchStartMs,
2382
2381
  end: chunk.endMs,
2383
2382
  silent: true
2384
2383
  });
2384
+ candlesBySymbol[symbol] = candles;
2385
+ candlesRead += candles.length;
2386
+ bar.tick(1, {
2387
+ candles: candlesRead,
2388
+ skip: skippedBreadthChunks,
2389
+ chunk: `${originalChunkIndex + 1}/${chunks.length}`,
2390
+ symbol
2391
+ });
2392
+ }
2393
+ const btcCandles = await connector.kline({
2394
+ symbol: "BTCUSDT",
2395
+ interval: connectorInterval,
2396
+ start: chunk.fetchStartMs,
2397
+ end: chunk.endMs,
2398
+ silent: true
2399
+ });
2400
+ const chunkKey = `${chunk.startMs}:${chunk.endMs}`;
2401
+ for (const plan of plans) {
2402
+ if (!plan.missingChunkKeys.has(chunkKey)) {
2403
+ continue;
2404
+ }
2405
+ const symbolSet = new Set(plan.definition.symbols);
2406
+ const universeCandles = Object.fromEntries(
2407
+ Object.entries(candlesBySymbol).filter(
2408
+ ([symbol]) => symbolSet.has(symbol)
2409
+ )
2410
+ );
2385
2411
  const rows = buildMarketBreadthRows({
2386
- universe,
2412
+ universe: plan.definition.universe,
2387
2413
  interval,
2388
- candlesBySymbol,
2414
+ candlesBySymbol: universeCandles,
2389
2415
  btcCandles
2390
2416
  }).filter((row) => {
2391
2417
  const ts = row.ts.getTime();
@@ -2394,8 +2420,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
2394
2420
  await (0, import_timescale.upsertMarketBreadthRows)(rows);
2395
2421
  breadthRows += rows.length;
2396
2422
  }
2397
- } else {
2398
- console.log(import_chalk.default.gray(`breadth cached: universe=${universe}`));
2399
2423
  }
2400
2424
  }
2401
2425
  console.log(