@tradejs/cli 1.0.9 → 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.
- package/dist/cli.js +29497 -13537
- package/dist/lib/aiPocketSearch.js +1316 -0
- package/dist/lib/aiTrainCharts.js +711 -0
- package/dist/lib/aiTrainDataset.js +40 -0
- package/dist/lib/aiTrainEvaluationDump.js +76 -0
- package/dist/lib/aiTrainMetrics.js +148 -8
- package/dist/lib/aiTrainOptions.js +130 -0
- package/dist/lib/aiTrainQuarantine.js +199 -0
- package/dist/lib/aiTrainResearch.js +467 -0
- package/dist/lib/binanceMarketContextBackfill.js +841 -0
- package/dist/lib/binanceMarketData.js +392 -0
- package/dist/lib/coinMarketCapContextBackfill.js +1088 -0
- package/dist/lib/continuity.js +60 -0
- package/dist/lib/derivativesContextBackfill.js +490 -77
- package/dist/lib/executionCalibration.js +705 -0
- package/dist/lib/marketContextPrepare.js +4403 -0
- package/dist/lib/paritySummary.js +101 -0
- package/dist/lib/runBot.js +1 -0
- package/dist/lib/runEnvironment.js +2254 -0
- package/dist/lib/runFormatting.js +1657 -0
- package/dist/lib/runtimeDebugEvidence.js +526 -0
- package/dist/lib/runtimeModeConfig.js +53 -0
- package/dist/lib/runtimeParity.js +88 -11
- package/dist/lib/runtimeParityDetails.js +751 -0
- package/dist/lib/runtimeRedis.js +157 -0
- package/dist/lib/runtimeSignalsLoader.js +40 -6
- package/dist/lib/runtimeSignalsStorage.js +45 -22
- package/dist/lib/runtimeStrategyBacktest.js +134 -0
- package/dist/lib/runtimeTradeSync.js +263 -0
- package/dist/lib/telegramReports.js +9 -0
- package/dist/lib/tickerUniverseCache.js +116 -0
- package/dist/lib/timeWindow.js +3 -3
- package/dist/scripts/agentRun.js +14 -8
- package/dist/scripts/aiExport.js +1721 -7
- package/dist/scripts/aiExportSelect.js +16 -4
- package/dist/scripts/aiPocketSearch.js +4190 -0
- package/dist/scripts/aiTrain.js +1866 -87
- package/dist/scripts/backtest.js +4509 -818
- package/dist/scripts/binanceMarketIngest.js +565 -0
- package/dist/scripts/bot.js +16 -2
- package/dist/scripts/candlesMigrateProvider.js +32 -22
- package/dist/scripts/cleanDir.js +15 -2
- package/dist/scripts/cleanRedis.js +15 -2
- package/dist/scripts/cleanTests.js +38 -2
- package/dist/scripts/cleanupMarketContext.js +69 -0
- package/dist/scripts/continuity.js +148 -67
- package/dist/scripts/derivativesIngest.js +14 -4
- package/dist/scripts/derivativesIngestCoinalyzeAll.js +14 -4
- package/dist/scripts/doctor.js +14 -4
- package/dist/scripts/executionCalibration.js +792 -0
- package/dist/scripts/infraCommon.js +10 -0
- package/dist/scripts/infraDown.js +14 -4
- package/dist/scripts/infraInit.js +24 -4
- package/dist/scripts/infraUp.js +14 -4
- package/dist/scripts/marketWs.js +222 -0
- package/dist/scripts/migration.js +15 -2
- package/dist/scripts/mlExport.js +1698 -10
- package/dist/scripts/mlExportSelect.js +16 -4
- package/dist/scripts/mlInspect.js +13 -3
- package/dist/scripts/mlTrainLatestSelect.js +14 -4
- package/dist/scripts/replay.js +8824 -0
- package/dist/scripts/replayRunner.js +8827 -0
- package/dist/scripts/replayRuntimeEvidence.js +868 -0
- package/dist/scripts/researchAuto.js +286 -59
- package/dist/scripts/results.js +3 -3
- package/dist/scripts/runtimeEvidence.js +605 -0
- package/dist/scripts/runtimeParity.js +6362 -1043
- package/dist/scripts/serverHealth.js +588 -0
- package/dist/scripts/signals.js +5590 -511
- package/dist/scripts/signalsDaemon.js +6167 -0
- package/dist/scripts/signalsSummary.js +833 -248
- package/dist/scripts/test-ml.js +25 -3
- package/dist/scripts/test.js +26 -1
- package/dist/scripts/user-add.js +15 -2
- package/dist/workers/testerWorker.js +130 -21
- 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
|
+
});
|