@tradejs/cli 1.0.8 → 1.0.10

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 (76) hide show
  1. package/dist/cli.js +29748 -13641
  2. package/dist/lib/aiPocketSearch.js +1316 -0
  3. package/dist/lib/aiTrainCharts.js +711 -0
  4. package/dist/lib/aiTrainDataset.js +40 -0
  5. package/dist/lib/aiTrainEvaluationDump.js +76 -0
  6. package/dist/lib/aiTrainMetrics.js +148 -8
  7. package/dist/lib/aiTrainOptions.js +130 -0
  8. package/dist/lib/aiTrainQuarantine.js +199 -0
  9. package/dist/lib/aiTrainResearch.js +467 -0
  10. package/dist/lib/binanceMarketContextBackfill.js +841 -0
  11. package/dist/lib/binanceMarketData.js +392 -0
  12. package/dist/lib/coinMarketCapContextBackfill.js +1088 -0
  13. package/dist/lib/continuity.js +60 -0
  14. package/dist/lib/derivativesContextBackfill.js +490 -77
  15. package/dist/lib/executionCalibration.js +705 -0
  16. package/dist/lib/marketContextPrepare.js +4403 -0
  17. package/dist/lib/paritySummary.js +101 -0
  18. package/dist/lib/runBot.js +1 -0
  19. package/dist/lib/runEnvironment.js +2254 -0
  20. package/dist/lib/runFormatting.js +1657 -0
  21. package/dist/lib/runtimeDebugEvidence.js +526 -0
  22. package/dist/lib/runtimeModeConfig.js +53 -0
  23. package/dist/lib/runtimeParity.js +88 -11
  24. package/dist/lib/runtimeParityDetails.js +751 -0
  25. package/dist/lib/runtimeRedis.js +157 -0
  26. package/dist/lib/runtimeSignalsLoader.js +187 -0
  27. package/dist/lib/runtimeSignalsStorage.js +185 -0
  28. package/dist/lib/runtimeStrategyBacktest.js +134 -0
  29. package/dist/lib/runtimeTradeSync.js +263 -0
  30. package/dist/lib/telegramReports.js +9 -0
  31. package/dist/lib/tickerUniverseCache.js +116 -0
  32. package/dist/lib/timeWindow.js +3 -3
  33. package/dist/scripts/agentRun.js +28 -8
  34. package/dist/scripts/aiExport.js +1721 -7
  35. package/dist/scripts/aiExportSelect.js +16 -4
  36. package/dist/scripts/aiPocketSearch.js +4190 -0
  37. package/dist/scripts/aiTrain.js +1866 -87
  38. package/dist/scripts/backtest.js +4509 -818
  39. package/dist/scripts/binanceMarketIngest.js +565 -0
  40. package/dist/scripts/bot.js +16 -2
  41. package/dist/scripts/candlesMigrateProvider.js +32 -22
  42. package/dist/scripts/cleanDir.js +15 -2
  43. package/dist/scripts/cleanRedis.js +15 -2
  44. package/dist/scripts/cleanTests.js +38 -2
  45. package/dist/scripts/cleanupMarketContext.js +69 -0
  46. package/dist/scripts/continuity.js +148 -67
  47. package/dist/scripts/derivativesIngest.js +14 -4
  48. package/dist/scripts/derivativesIngestCoinalyzeAll.js +14 -4
  49. package/dist/scripts/doctor.js +14 -4
  50. package/dist/scripts/executionCalibration.js +792 -0
  51. package/dist/scripts/infraCommon.js +10 -0
  52. package/dist/scripts/infraDown.js +14 -4
  53. package/dist/scripts/infraInit.js +24 -4
  54. package/dist/scripts/infraUp.js +14 -4
  55. package/dist/scripts/marketWs.js +222 -0
  56. package/dist/scripts/migration.js +15 -2
  57. package/dist/scripts/mlExport.js +1698 -10
  58. package/dist/scripts/mlExportSelect.js +16 -4
  59. package/dist/scripts/mlInspect.js +13 -3
  60. package/dist/scripts/mlTrainLatestSelect.js +14 -4
  61. package/dist/scripts/replay.js +8824 -0
  62. package/dist/scripts/replayRunner.js +8827 -0
  63. package/dist/scripts/replayRuntimeEvidence.js +868 -0
  64. package/dist/scripts/researchAuto.js +289 -60
  65. package/dist/scripts/results.js +3 -3
  66. package/dist/scripts/runtimeEvidence.js +605 -0
  67. package/dist/scripts/runtimeParity.js +6360 -1004
  68. package/dist/scripts/serverHealth.js +588 -0
  69. package/dist/scripts/signals.js +5623 -486
  70. package/dist/scripts/signalsDaemon.js +6167 -0
  71. package/dist/scripts/signalsSummary.js +954 -312
  72. package/dist/scripts/test-ml.js +25 -3
  73. package/dist/scripts/test.js +26 -1
  74. package/dist/scripts/user-add.js +15 -2
  75. package/dist/workers/testerWorker.js +130 -21
  76. package/package.json +18 -12
@@ -0,0 +1,841 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/lib/binanceMarketContextBackfill.ts
31
+ var binanceMarketContextBackfill_exports = {};
32
+ __export(binanceMarketContextBackfill_exports, {
33
+ backfillBinanceMarketContextForBacktest: () => backfillBinanceMarketContextForBacktest,
34
+ backfillBinanceMarketContextForReplay: () => backfillBinanceMarketContextForReplay,
35
+ backfillBinanceMarketContextForSignals: () => backfillBinanceMarketContextForSignals,
36
+ buildBreadthBackfillChunks: () => buildBreadthBackfillChunks,
37
+ buildTradeFlowBackfillChunks: () => buildTradeFlowBackfillChunks,
38
+ filterMissingBreadthBackfillChunks: () => filterMissingBreadthBackfillChunks,
39
+ resolveBinanceMarketContextBackfillWindow: () => resolveBinanceMarketContextBackfillWindow,
40
+ shouldBackfillBinanceMarketContextForBacktest: () => shouldBackfillBinanceMarketContextForBacktest,
41
+ shouldBackfillBinanceMarketContextForReplay: () => shouldBackfillBinanceMarketContextForReplay,
42
+ shouldBackfillBinanceMarketContextForSignals: () => shouldBackfillBinanceMarketContextForSignals
43
+ });
44
+ module.exports = __toCommonJS(binanceMarketContextBackfill_exports);
45
+ var import_progress = __toESM(require("progress"));
46
+ var import_chalk = __toESM(require("chalk"));
47
+ var import_connectors = require("@tradejs/connectors");
48
+ var import_connectors2 = require("@tradejs/node/connectors");
49
+ var import_timescale = require("@tradejs/infra/timescale");
50
+
51
+ // src/lib/binanceMarketData.ts
52
+ var MARKET_FEATURE_INTERVAL_MS = {
53
+ "1m": 6e4,
54
+ "5m": 3e5,
55
+ "15m": 9e5,
56
+ "1h": 36e5
57
+ };
58
+ var STABLE_QUOTE_SYMBOLS = /* @__PURE__ */ new Set([
59
+ "USDCUSDT",
60
+ "FDUSDUSDT",
61
+ "TUSDUSDT",
62
+ "BUSDUSDT",
63
+ "USDPUSDT",
64
+ "DAIUSDT"
65
+ ]);
66
+ var safeDivide = (num, den) => Number.isFinite(num) && Number.isFinite(den) && den !== 0 ? num / den : null;
67
+ var mean = (values) => values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
68
+ var standardDeviation = (values) => {
69
+ const avg = mean(values);
70
+ if (avg == null || values.length < 2) return null;
71
+ const variance = values.reduce((sum, value) => sum + (value - avg) ** 2, 0) / values.length;
72
+ return Math.sqrt(variance);
73
+ };
74
+ var buildPrefixSum = (values) => {
75
+ const prefix = [0];
76
+ for (const value of values) prefix.push(prefix[prefix.length - 1] + value);
77
+ return prefix;
78
+ };
79
+ var sumPrefixRange = (prefix, startIndex, endIndexInclusive) => {
80
+ if (endIndexInclusive < startIndex) return 0;
81
+ const start = Math.max(0, startIndex);
82
+ const end = Math.min(prefix.length - 2, endIndexInclusive);
83
+ if (end < start) return 0;
84
+ return prefix[end + 1] - prefix[start];
85
+ };
86
+ var windowReturn = (candles, index, lookbackBars) => {
87
+ const previous = candles[index - lookbackBars];
88
+ const current = candles[index];
89
+ return previous && previous.close > 0 ? (current.close - previous.close) / previous.close : null;
90
+ };
91
+ var classifyBtcAltRegime = ({
92
+ btcReturn24h,
93
+ altBasketReturn24h,
94
+ btcVsAltReturn24h
95
+ }) => {
96
+ if (btcReturn24h == null || altBasketReturn24h == null || btcVsAltReturn24h == null) {
97
+ return "unknown";
98
+ }
99
+ if (btcReturn24h < -0.015 && altBasketReturn24h < -0.025) {
100
+ return "risk_off";
101
+ }
102
+ if (btcReturn24h > 5e-3 && altBasketReturn24h > 5e-3 && altBasketReturn24h > btcReturn24h) {
103
+ return "risk_on";
104
+ }
105
+ if (btcVsAltReturn24h > 5e-3) return "btc_lead";
106
+ if (btcVsAltReturn24h < -5e-3) return "alt_lead";
107
+ return "neutral";
108
+ };
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
+ var aggregateAggTradesToRows = ({
113
+ symbol,
114
+ interval,
115
+ trades,
116
+ source = "binance_agg_trades"
117
+ }) => {
118
+ const intervalMs = MARKET_FEATURE_INTERVAL_MS[interval];
119
+ const buckets = /* @__PURE__ */ new Map();
120
+ for (const trade of trades) {
121
+ const bucketTs = Math.floor(trade.timestamp / intervalMs) * intervalMs;
122
+ const row = buckets.get(bucketTs) ?? {
123
+ symbol,
124
+ interval,
125
+ ts: new Date(bucketTs),
126
+ trades: 0,
127
+ buyBaseVolume: 0,
128
+ sellBaseVolume: 0,
129
+ buyQuoteVolume: 0,
130
+ sellQuoteVolume: 0,
131
+ netBaseDelta: 0,
132
+ netQuoteDelta: 0,
133
+ buyPressurePct: null,
134
+ source
135
+ };
136
+ const quote = trade.price * trade.quantity;
137
+ const isAggressiveSell = trade.isBuyerMaker;
138
+ row.trades += 1;
139
+ if (isAggressiveSell) {
140
+ row.sellBaseVolume = (row.sellBaseVolume ?? 0) + trade.quantity;
141
+ row.sellQuoteVolume = (row.sellQuoteVolume ?? 0) + quote;
142
+ } else {
143
+ row.buyBaseVolume = (row.buyBaseVolume ?? 0) + trade.quantity;
144
+ row.buyQuoteVolume = (row.buyQuoteVolume ?? 0) + quote;
145
+ }
146
+ row.netBaseDelta = (row.buyBaseVolume ?? 0) - (row.sellBaseVolume ?? 0);
147
+ row.netQuoteDelta = (row.buyQuoteVolume ?? 0) - (row.sellQuoteVolume ?? 0);
148
+ row.buyPressurePct = safeDivide(
149
+ row.buyBaseVolume ?? 0,
150
+ (row.buyBaseVolume ?? 0) + (row.sellBaseVolume ?? 0)
151
+ );
152
+ buckets.set(bucketTs, row);
153
+ }
154
+ return [...buckets.values()].sort((a, b) => a.ts.getTime() - b.ts.getTime());
155
+ };
156
+ var buildKlineTradeFlowRows = ({
157
+ symbol,
158
+ interval,
159
+ candles,
160
+ source = "binance_klines"
161
+ }) => candles.map((candle) => {
162
+ const buyBaseVolume = candle.takerBuyBaseVolume;
163
+ const buyQuoteVolume = candle.takerBuyQuoteVolume;
164
+ if (buyBaseVolume == null || buyQuoteVolume == null || !Number.isFinite(buyBaseVolume) || !Number.isFinite(buyQuoteVolume)) {
165
+ return null;
166
+ }
167
+ const sellBaseVolume = candle.takerSellBaseVolume != null && Number.isFinite(candle.takerSellBaseVolume) ? candle.takerSellBaseVolume : Math.max(0, candle.volume - buyBaseVolume);
168
+ const sellQuoteVolume = candle.takerSellQuoteVolume != null && Number.isFinite(candle.takerSellQuoteVolume) ? candle.takerSellQuoteVolume : Math.max(0, candle.turnover - buyQuoteVolume);
169
+ const trades = candle.trades != null && Number.isFinite(candle.trades) ? Math.max(0, Math.trunc(candle.trades)) : 0;
170
+ return {
171
+ symbol,
172
+ interval,
173
+ ts: new Date(candle.timestamp),
174
+ trades,
175
+ buyBaseVolume,
176
+ sellBaseVolume,
177
+ buyQuoteVolume,
178
+ sellQuoteVolume,
179
+ netBaseDelta: buyBaseVolume - sellBaseVolume,
180
+ netQuoteDelta: buyQuoteVolume - sellQuoteVolume,
181
+ buyPressurePct: safeDivide(
182
+ buyBaseVolume,
183
+ buyBaseVolume + sellBaseVolume
184
+ ),
185
+ source
186
+ };
187
+ }).filter((row) => row != null);
188
+ var buildMarketBreadthRows = ({
189
+ universe,
190
+ interval,
191
+ candlesBySymbol,
192
+ btcCandles,
193
+ source = "binance_klines"
194
+ }) => {
195
+ const timestamps = /* @__PURE__ */ new Set();
196
+ const indexedSymbols = Object.values(candlesBySymbol).map((candles) => ({
197
+ candles,
198
+ byTimestamp: new Map(
199
+ candles.map((candle, index) => [candle.timestamp, index])
200
+ ),
201
+ turnoverPrefix: buildPrefixSum(
202
+ candles.map((candle) => Math.max(0, candle.turnover ?? 0))
203
+ ),
204
+ closePrefix: buildPrefixSum(candles.map((candle) => candle.close))
205
+ }));
206
+ const btcIndexed = btcCandles ? {
207
+ candles: btcCandles,
208
+ byTimestamp: new Map(
209
+ btcCandles.map((candle, index) => [candle.timestamp, index])
210
+ ),
211
+ turnoverPrefix: buildPrefixSum(
212
+ btcCandles.map((candle) => Math.max(0, candle.turnover ?? 0))
213
+ )
214
+ } : null;
215
+ const intervalMs = MARKET_FEATURE_INTERVAL_MS[interval];
216
+ const bars1h = Math.max(1, Math.round(36e5 / intervalMs));
217
+ const bars4h = Math.max(1, Math.round(144e5 / intervalMs));
218
+ const bars24h = Math.max(1, Math.round(864e5 / intervalMs));
219
+ for (const candles of Object.values(candlesBySymbol)) {
220
+ for (const candle of candles) timestamps.add(candle.timestamp);
221
+ }
222
+ return [...timestamps].sort((a, b) => a - b).map((timestamp) => {
223
+ const returns = [];
224
+ const weightedReturns = [];
225
+ let advancers = 0;
226
+ let decliners = 0;
227
+ let unchanged = 0;
228
+ let aboveMa20 = 0;
229
+ let aboveMa20Eligible = 0;
230
+ let aboveMa50 = 0;
231
+ let aboveMa50Eligible = 0;
232
+ const altReturns1h = [];
233
+ const altReturns4h = [];
234
+ const altReturns24h = [];
235
+ let altTurnover1h = 0;
236
+ let altTurnover24h = 0;
237
+ let previousAltTurnover24h = 0;
238
+ for (const indexed of indexedSymbols) {
239
+ const { candles, byTimestamp, turnoverPrefix, closePrefix } = indexed;
240
+ const index = byTimestamp.get(timestamp);
241
+ if (index == null) continue;
242
+ const candle = candles[index];
243
+ const previous = candles[index - 1];
244
+ const ret = previous && previous.close > 0 ? (candle.close - previous.close) / previous.close : 0;
245
+ if (ret > 0) advancers += 1;
246
+ else if (ret < 0) decliners += 1;
247
+ else unchanged += 1;
248
+ returns.push(ret);
249
+ weightedReturns.push({
250
+ value: ret,
251
+ weight: Math.max(0, candle.turnover ?? 0)
252
+ });
253
+ const ma20Start = index - 19;
254
+ if (ma20Start >= 0) {
255
+ aboveMa20Eligible += 1;
256
+ const ma20 = sumPrefixRange(closePrefix, ma20Start, index) / 20;
257
+ if (candle.close > ma20) aboveMa20 += 1;
258
+ }
259
+ const ma50Start = index - 49;
260
+ if (ma50Start >= 0) {
261
+ aboveMa50Eligible += 1;
262
+ const ma50 = sumPrefixRange(closePrefix, ma50Start, index) / 50;
263
+ if (candle.close > ma50) aboveMa50 += 1;
264
+ }
265
+ const altReturn1h = windowReturn(candles, index, bars1h);
266
+ const altReturn4h = windowReturn(candles, index, bars4h);
267
+ const altReturn24h = windowReturn(candles, index, bars24h);
268
+ if (altReturn1h != null) altReturns1h.push(altReturn1h);
269
+ if (altReturn4h != null) altReturns4h.push(altReturn4h);
270
+ if (altReturn24h != null) altReturns24h.push(altReturn24h);
271
+ altTurnover1h += sumPrefixRange(
272
+ turnoverPrefix,
273
+ index - bars1h + 1,
274
+ index
275
+ );
276
+ altTurnover24h += sumPrefixRange(
277
+ turnoverPrefix,
278
+ index - bars24h + 1,
279
+ index
280
+ );
281
+ previousAltTurnover24h += sumPrefixRange(
282
+ turnoverPrefix,
283
+ index - bars24h * 2 + 1,
284
+ index - bars24h
285
+ );
286
+ }
287
+ const weightSum = weightedReturns.reduce(
288
+ (sum, item) => sum + item.weight,
289
+ 0
290
+ );
291
+ const volumeWeightedReturn = weightSum > 0 ? weightedReturns.reduce(
292
+ (sum, item) => sum + item.value * item.weight,
293
+ 0
294
+ ) / weightSum : null;
295
+ const btcIndex = btcIndexed?.byTimestamp.get(timestamp);
296
+ const btcReturn1h = btcIndexed && btcIndex != null ? windowReturn(btcIndexed.candles, btcIndex, bars1h) : null;
297
+ const btcReturn4h = btcIndexed && btcIndex != null ? windowReturn(btcIndexed.candles, btcIndex, bars4h) : null;
298
+ const btcReturn24h = btcIndexed && btcIndex != null ? windowReturn(btcIndexed.candles, btcIndex, bars24h) : null;
299
+ const btcTurnover1h = btcIndexed && btcIndex != null ? sumPrefixRange(
300
+ btcIndexed.turnoverPrefix,
301
+ btcIndex - bars1h + 1,
302
+ btcIndex
303
+ ) : null;
304
+ const btcTurnover24h = btcIndexed && btcIndex != null ? sumPrefixRange(
305
+ btcIndexed.turnoverPrefix,
306
+ btcIndex - bars24h + 1,
307
+ btcIndex
308
+ ) : null;
309
+ const previousBtcTurnover24h = btcIndexed && btcIndex != null ? sumPrefixRange(
310
+ btcIndexed.turnoverPrefix,
311
+ btcIndex - bars24h * 2 + 1,
312
+ btcIndex - bars24h
313
+ ) : null;
314
+ const altBasketReturn1h = mean(altReturns1h);
315
+ const altBasketReturn4h = mean(altReturns4h);
316
+ const altBasketReturn24h = mean(altReturns24h);
317
+ const btcVsAltReturn1h = btcReturn1h == null || altBasketReturn1h == null ? null : btcReturn1h - altBasketReturn1h;
318
+ const btcVsAltReturn4h = btcReturn4h == null || altBasketReturn4h == null ? null : btcReturn4h - altBasketReturn4h;
319
+ const btcVsAltReturn24h = btcReturn24h == null || altBasketReturn24h == null ? null : btcReturn24h - altBasketReturn24h;
320
+ const btcTurnoverShare1h = btcTurnover1h == null ? null : safeDivide(btcTurnover1h, btcTurnover1h + altTurnover1h);
321
+ const btcTurnoverShare24h = btcTurnover24h == null ? null : safeDivide(btcTurnover24h, btcTurnover24h + altTurnover24h);
322
+ const previousBtcTurnoverShare24h = previousBtcTurnover24h == null ? null : safeDivide(
323
+ previousBtcTurnover24h,
324
+ previousBtcTurnover24h + previousAltTurnover24h
325
+ );
326
+ const btcTurnoverShareChange24h = btcTurnoverShare24h == null || previousBtcTurnoverShare24h == null ? null : btcTurnoverShare24h - previousBtcTurnoverShare24h;
327
+ return {
328
+ universe,
329
+ interval,
330
+ ts: new Date(timestamp),
331
+ symbolsCount: returns.length,
332
+ advancers,
333
+ decliners,
334
+ unchanged,
335
+ advanceDeclineRatio: safeDivide(advancers, decliners || 1),
336
+ pctAboveMa20: safeDivide(aboveMa20, aboveMa20Eligible),
337
+ pctAboveMa50: safeDivide(aboveMa50, aboveMa50Eligible),
338
+ equalWeightedReturn: mean(returns),
339
+ volumeWeightedReturn,
340
+ dispersion: standardDeviation(returns),
341
+ btcReturn1h,
342
+ btcReturn4h,
343
+ btcReturn24h,
344
+ altBasketReturn1h,
345
+ altBasketReturn4h,
346
+ altBasketReturn24h,
347
+ btcVsAltReturn1h,
348
+ btcVsAltReturn4h,
349
+ btcVsAltReturn24h,
350
+ btcTurnoverShare1h,
351
+ btcTurnoverShare24h,
352
+ btcTurnoverShareChange24h,
353
+ altVolToBtcVol24h: btcTurnover24h == null ? null : safeDivide(altTurnover24h, btcTurnover24h),
354
+ altDispersion24h: standardDeviation(altReturns24h),
355
+ btcAltRegime: classifyBtcAltRegime({
356
+ btcReturn24h,
357
+ altBasketReturn24h,
358
+ btcVsAltReturn24h
359
+ }),
360
+ source
361
+ };
362
+ }).filter((row) => row.symbolsCount > 0);
363
+ };
364
+
365
+ // src/lib/binanceMarketContextBackfill.ts
366
+ var DAY_MS = 864e5;
367
+ var sleep = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
368
+ var asInt = (value, fallback) => {
369
+ const parsed = Number.parseInt(String(value ?? ""), 10);
370
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
371
+ };
372
+ var asFloat = (value, fallback) => {
373
+ const parsed = Number.parseFloat(String(value ?? ""));
374
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
375
+ };
376
+ var parseEnabledFlag = (value, defaultValue) => {
377
+ const normalized = String(value ?? "").trim().toLowerCase();
378
+ if (!normalized) return defaultValue;
379
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
380
+ if (["0", "false", "no", "off"].includes(normalized)) return false;
381
+ return defaultValue;
382
+ };
383
+ var uniqueSymbols = (symbols) => [
384
+ ...new Set(symbols.map((item) => item.trim().toUpperCase()).filter(Boolean))
385
+ ];
386
+ var getReferenceSymbols = () => {
387
+ const symbols = (process.env.BINANCE_MARKET_CONTEXT_REFERENCE_SYMBOLS || "BTCUSDT,ETHUSDT").split(",").map((item) => item.trim().toUpperCase()).filter(Boolean);
388
+ return symbols.length ? [...new Set(symbols)] : ["BTCUSDT", "ETHUSDT"];
389
+ };
390
+ var intervalToMarketFeatureInterval = (interval) => {
391
+ const normalized = String(interval).trim().toLowerCase();
392
+ if (normalized === "1" || normalized === "1m") return "1m";
393
+ if (normalized === "5" || normalized === "5m") return "5m";
394
+ if (normalized === "60" || normalized === "1h") return "1h";
395
+ return "15m";
396
+ };
397
+ var marketIntervalToConnectorInterval = (interval) => {
398
+ if (interval === "1m") return "1";
399
+ if (interval === "5m") return "5";
400
+ if (interval === "1h") return "60";
401
+ return "15";
402
+ };
403
+ var resolveBinanceMarketContextBackfillWindow = ({
404
+ startMs,
405
+ endMs,
406
+ preloadStartMs
407
+ }) => {
408
+ const breadthLookbackDays = asFloat(
409
+ process.env.BINANCE_MARKET_CONTEXT_BREADTH_BACKFILL_LOOKBACK_DAYS,
410
+ 3
411
+ );
412
+ const breadthWarmupStartMs = startMs - breadthLookbackDays * DAY_MS;
413
+ return {
414
+ breadthStartMs: preloadStartMs ?? breadthWarmupStartMs,
415
+ tradeFlowStartMs: preloadStartMs ?? startMs,
416
+ endMs
417
+ };
418
+ };
419
+ var hasCoverage = ({
420
+ coverage,
421
+ startMs,
422
+ endMs,
423
+ intervalMs
424
+ }) => {
425
+ if (!coverage) return false;
426
+ const expectedRows = Math.max(1, Math.floor((endMs - startMs) / intervalMs));
427
+ return coverage.firstMs <= startMs + intervalMs && coverage.lastMs >= endMs - intervalMs && coverage.rows >= Math.floor(expectedRows * 0.9);
428
+ };
429
+ var buildBreadthBackfillChunks = ({
430
+ startMs,
431
+ endMs,
432
+ intervalMs,
433
+ chunkDays
434
+ }) => {
435
+ const chunkMs = Math.max(intervalMs, chunkDays * DAY_MS);
436
+ const warmupBars = Math.max(50, Math.ceil(DAY_MS * 2 / intervalMs));
437
+ const warmupMs = warmupBars * intervalMs;
438
+ const chunks = [];
439
+ let cursor = startMs;
440
+ while (cursor <= endMs) {
441
+ const chunkEndMs = Math.min(endMs, cursor + chunkMs - 1);
442
+ chunks.push({
443
+ startMs: cursor,
444
+ endMs: chunkEndMs,
445
+ fetchStartMs: Math.max(startMs, cursor - warmupMs)
446
+ });
447
+ cursor = chunkEndMs + 1;
448
+ }
449
+ return chunks;
450
+ };
451
+ var buildTradeFlowBackfillChunks = ({
452
+ startMs,
453
+ endMs,
454
+ intervalMs,
455
+ chunkDays
456
+ }) => {
457
+ const chunkMs = Math.max(intervalMs, chunkDays * DAY_MS);
458
+ const chunks = [];
459
+ let cursor = startMs;
460
+ while (cursor <= endMs) {
461
+ const chunkEndMs = Math.min(endMs, cursor + chunkMs - 1);
462
+ chunks.push({ startMs: cursor, endMs: chunkEndMs });
463
+ cursor = chunkEndMs + 1;
464
+ }
465
+ return chunks;
466
+ };
467
+ var filterMissingBreadthBackfillChunks = ({
468
+ chunks,
469
+ coverage,
470
+ intervalMs
471
+ }) => chunks.filter(
472
+ (chunk) => !hasCoverage({
473
+ coverage,
474
+ startMs: chunk.startMs,
475
+ endMs: chunk.endMs,
476
+ intervalMs
477
+ })
478
+ );
479
+ var fetchAggTradesForWindow = async ({
480
+ connector,
481
+ symbol,
482
+ fromMs,
483
+ toMs,
484
+ batchMinutes,
485
+ requestDelayMs
486
+ }) => {
487
+ if (!connector.getAggTrades) return [];
488
+ const rows = [];
489
+ const batchMs = Math.max(1, batchMinutes) * 6e4;
490
+ let cursor = fromMs;
491
+ while (cursor <= toMs) {
492
+ const endTime = Math.min(toMs, cursor + batchMs - 1);
493
+ let pageCursor = cursor;
494
+ while (pageCursor <= endTime) {
495
+ const page = await connector.getAggTrades({
496
+ symbol,
497
+ startTime: pageCursor,
498
+ endTime,
499
+ limit: 1e3
500
+ });
501
+ if (!page.length) break;
502
+ rows.push(...page);
503
+ const lastTs = page[page.length - 1]?.timestamp;
504
+ if (page.length < 1e3 || !Number.isFinite(lastTs)) break;
505
+ const nextCursor = Math.max(pageCursor + 1, Number(lastTs) + 1);
506
+ if (nextCursor > endTime) break;
507
+ pageCursor = nextCursor;
508
+ await sleep(requestDelayMs);
509
+ }
510
+ cursor = endTime + 1;
511
+ await sleep(requestDelayMs);
512
+ }
513
+ return rows;
514
+ };
515
+ var getBinanceConnector = async ({
516
+ projectRoot,
517
+ userName
518
+ }) => {
519
+ const creator = await (0, import_connectors2.getConnectorCreatorByName)(
520
+ import_connectors.ConnectorNames.Binance,
521
+ projectRoot
522
+ );
523
+ if (!creator) {
524
+ throw new Error(
525
+ "Binance connector is required for market context backfill"
526
+ );
527
+ }
528
+ return creator({ userName });
529
+ };
530
+ var shouldBackfillBinanceMarketContextForBacktest = ({
531
+ aiEnabled,
532
+ cacheOnly,
533
+ mlEnabled
534
+ }) => parseEnabledFlag(
535
+ process.env.BINANCE_MARKET_CONTEXT_BACKFILL_ENABLED,
536
+ (aiEnabled || mlEnabled) && !cacheOnly
537
+ );
538
+ var shouldBackfillBinanceMarketContextForSignals = ({
539
+ cacheOnly
540
+ }) => parseEnabledFlag(
541
+ process.env.BINANCE_MARKET_CONTEXT_BACKFILL_ENABLED,
542
+ !cacheOnly
543
+ );
544
+ var shouldBackfillBinanceMarketContextForReplay = ({
545
+ cacheOnly
546
+ }) => parseEnabledFlag(
547
+ process.env.BINANCE_MARKET_CONTEXT_BACKFILL_ENABLED,
548
+ !cacheOnly
549
+ );
550
+ var skippedBackfillResult = () => ({
551
+ skipped: true,
552
+ tradeFlowRows: 0,
553
+ depthRows: 0,
554
+ breadthRows: 0,
555
+ skippedSymbols: 0
556
+ });
557
+ var backfillBinanceMarketContext = async (params, enabled) => {
558
+ const symbols = uniqueSymbols(params.symbols);
559
+ if (!enabled || !symbols.length) {
560
+ return skippedBackfillResult();
561
+ }
562
+ const interval = intervalToMarketFeatureInterval(params.interval);
563
+ const intervalMs = MARKET_FEATURE_INTERVAL_MS[interval];
564
+ const { breadthStartMs, tradeFlowStartMs, endMs } = resolveBinanceMarketContextBackfillWindow(params);
565
+ if (endMs <= Math.min(breadthStartMs, tradeFlowStartMs)) {
566
+ return skippedBackfillResult();
567
+ }
568
+ await (0, import_timescale.waitForDbReady)();
569
+ const connector = await getBinanceConnector({
570
+ projectRoot: params.projectRoot,
571
+ userName: params.userName
572
+ });
573
+ const includeTradeFlow = parseEnabledFlag(
574
+ process.env.BINANCE_MARKET_CONTEXT_BACKFILL_TRADE_FLOW,
575
+ true
576
+ );
577
+ const includeBreadth = parseEnabledFlag(
578
+ process.env.BINANCE_MARKET_CONTEXT_BACKFILL_BREADTH,
579
+ true
580
+ );
581
+ const requestDelayMs = asInt(
582
+ process.env.BINANCE_MARKET_CONTEXT_BACKFILL_REQUEST_DELAY_MS,
583
+ 75
584
+ );
585
+ const batchMinutes = asInt(
586
+ process.env.BINANCE_MARKET_CONTEXT_BACKFILL_BATCH_MINUTES,
587
+ 60
588
+ );
589
+ const symbolLimit = asInt(
590
+ process.env.BINANCE_MARKET_CONTEXT_TRADE_FLOW_SYMBOL_LIMIT,
591
+ 2
592
+ );
593
+ const breadthLimit = asInt(
594
+ process.env.BINANCE_MARKET_CONTEXT_BREADTH_LIMIT,
595
+ 30
596
+ );
597
+ const breadthChunkDays = asFloat(
598
+ process.env.BINANCE_MARKET_CONTEXT_BREADTH_CHUNK_DAYS,
599
+ 30
600
+ );
601
+ const tradeFlowChunkDays = asFloat(
602
+ process.env.BINANCE_MARKET_CONTEXT_TRADE_FLOW_CHUNK_DAYS,
603
+ 30
604
+ );
605
+ const tradeFlowSource = String(
606
+ process.env.BINANCE_MARKET_CONTEXT_TRADE_FLOW_SOURCE || "klines"
607
+ ).trim().toLowerCase();
608
+ const referenceSymbols = getReferenceSymbols().slice(0, symbolLimit);
609
+ const skippedSymbols = Math.max(0, symbols.length - referenceSymbols.length);
610
+ let tradeFlowRows = 0;
611
+ let breadthRows = 0;
612
+ console.log(
613
+ import_chalk.default.cyan(
614
+ `binance market context backfill: requestedSymbols=${symbols.length}, referenceSymbols=${referenceSymbols.length}, interval=${interval}, tradeFlowWindow=${new Date(tradeFlowStartMs).toISOString()}..${new Date(endMs).toISOString()}, breadthWindow=${new Date(breadthStartMs).toISOString()}..${new Date(endMs).toISOString()}`
615
+ )
616
+ );
617
+ if (includeTradeFlow && referenceSymbols.length) {
618
+ const coverage = await (0, import_timescale.getMarketTradeFlowCoverage)({
619
+ symbols: referenceSymbols,
620
+ interval,
621
+ startMs: tradeFlowStartMs,
622
+ endMs
623
+ });
624
+ const missingSymbols = referenceSymbols.filter(
625
+ (symbol) => !hasCoverage({
626
+ coverage: coverage.get(symbol),
627
+ startMs: tradeFlowStartMs,
628
+ endMs,
629
+ intervalMs
630
+ })
631
+ );
632
+ const bar = new import_progress.default(
633
+ "tradeFlow :current/:total [:bar][:percent] :etas(s) rows=:rows skip=:skip :symbol",
634
+ {
635
+ total: Math.max(1, missingSymbols.length),
636
+ width: 24
637
+ }
638
+ );
639
+ if (!missingSymbols.length) {
640
+ bar.tick(1, { rows: 0, skip: referenceSymbols.length, symbol: "cached" });
641
+ }
642
+ const connectorInterval = marketIntervalToConnectorInterval(interval);
643
+ const chunks = buildTradeFlowBackfillChunks({
644
+ startMs: tradeFlowStartMs,
645
+ endMs,
646
+ intervalMs,
647
+ chunkDays: tradeFlowChunkDays
648
+ });
649
+ const chunkBar = missingSymbols.length ? new import_progress.default(
650
+ "tradeFlow chunks :current/:total [:bar][:percent] :etas(s) rows=:rows skip=:skip chunk=:chunk :symbol",
651
+ {
652
+ total: Math.max(1, chunks.length * missingSymbols.length),
653
+ width: 24
654
+ }
655
+ ) : null;
656
+ let skippedTradeFlowChunks = 0;
657
+ for (const symbol of missingSymbols) {
658
+ const symbolCoverage = coverage.get(symbol);
659
+ for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex += 1) {
660
+ const chunk = chunks[chunkIndex];
661
+ if (hasCoverage({
662
+ coverage: symbolCoverage,
663
+ startMs: chunk.startMs,
664
+ endMs: chunk.endMs,
665
+ intervalMs
666
+ })) {
667
+ skippedTradeFlowChunks += 1;
668
+ chunkBar?.tick(1, {
669
+ rows: tradeFlowRows,
670
+ skip: skippedTradeFlowChunks,
671
+ chunk: `${chunkIndex + 1}/${chunks.length}`,
672
+ symbol
673
+ });
674
+ continue;
675
+ }
676
+ const rows = tradeFlowSource === "agg_trades" ? aggregateAggTradesToRows({
677
+ symbol,
678
+ interval,
679
+ trades: await fetchAggTradesForWindow({
680
+ connector,
681
+ symbol,
682
+ fromMs: chunk.startMs,
683
+ toMs: chunk.endMs,
684
+ batchMinutes,
685
+ requestDelayMs
686
+ })
687
+ }) : buildKlineTradeFlowRows({
688
+ symbol,
689
+ interval,
690
+ candles: await connector.kline({
691
+ symbol,
692
+ interval: connectorInterval,
693
+ start: chunk.startMs,
694
+ end: chunk.endMs,
695
+ silent: true
696
+ })
697
+ });
698
+ const boundedRows = rows.filter((row) => {
699
+ const ts = row.ts.getTime();
700
+ return ts >= chunk.startMs && ts <= chunk.endMs;
701
+ });
702
+ await (0, import_timescale.upsertMarketTradeFlowRows)(boundedRows);
703
+ tradeFlowRows += boundedRows.length;
704
+ chunkBar?.tick(1, {
705
+ rows: tradeFlowRows,
706
+ skip: skippedTradeFlowChunks,
707
+ chunk: `${chunkIndex + 1}/${chunks.length}`,
708
+ symbol
709
+ });
710
+ }
711
+ bar.tick(1, {
712
+ rows: tradeFlowRows,
713
+ skip: referenceSymbols.length - missingSymbols.length + skippedSymbols,
714
+ symbol
715
+ });
716
+ }
717
+ }
718
+ 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,
734
+ startMs: breadthStartMs,
735
+ 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
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,
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
+ });
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 rows = buildMarketBreadthRows({
798
+ universe,
799
+ interval,
800
+ candlesBySymbol,
801
+ btcCandles
802
+ }).filter((row) => {
803
+ const ts = row.ts.getTime();
804
+ return ts >= chunk.startMs && ts <= chunk.endMs;
805
+ });
806
+ await (0, import_timescale.upsertMarketBreadthRows)(rows);
807
+ breadthRows += rows.length;
808
+ }
809
+ } else {
810
+ console.log(import_chalk.default.gray(`breadth cached: universe=${universe}`));
811
+ }
812
+ }
813
+ console.log(
814
+ import_chalk.default.green(
815
+ `binance market context backfill done: tradeFlowRows=${tradeFlowRows}, depthRows=0, breadthRows=${breadthRows}, skippedSymbols=${skippedSymbols}`
816
+ )
817
+ );
818
+ return {
819
+ skipped: false,
820
+ tradeFlowRows,
821
+ depthRows: 0,
822
+ breadthRows,
823
+ skippedSymbols
824
+ };
825
+ };
826
+ var backfillBinanceMarketContextForBacktest = (params) => backfillBinanceMarketContext(params, true);
827
+ var backfillBinanceMarketContextForSignals = (params) => backfillBinanceMarketContext(params, true);
828
+ var backfillBinanceMarketContextForReplay = (params) => backfillBinanceMarketContext(params, true);
829
+ // Annotate the CommonJS export names for ESM import in node:
830
+ 0 && (module.exports = {
831
+ backfillBinanceMarketContextForBacktest,
832
+ backfillBinanceMarketContextForReplay,
833
+ backfillBinanceMarketContextForSignals,
834
+ buildBreadthBackfillChunks,
835
+ buildTradeFlowBackfillChunks,
836
+ filterMissingBreadthBackfillChunks,
837
+ resolveBinanceMarketContextBackfillWindow,
838
+ shouldBackfillBinanceMarketContextForBacktest,
839
+ shouldBackfillBinanceMarketContextForReplay,
840
+ shouldBackfillBinanceMarketContextForSignals
841
+ });