@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
|
@@ -32,12 +32,19 @@ var derivativesContextBackfill_exports = {};
|
|
|
32
32
|
__export(derivativesContextBackfill_exports, {
|
|
33
33
|
backfillDerivativesContextForBacktest: () => backfillDerivativesContextForBacktest,
|
|
34
34
|
backfillDerivativesContextForSignals: () => backfillDerivativesContextForSignals,
|
|
35
|
+
formatCoinalyzeRequestError: () => formatCoinalyzeRequestError,
|
|
36
|
+
groupDerivativesContextMissingFetchRanges: () => groupDerivativesContextMissingFetchRanges,
|
|
37
|
+
hasDerivativesContextCoverageRange: () => hasDerivativesContextCoverageRange,
|
|
35
38
|
isBacktestDerivativesContextEnabled: () => isBacktestDerivativesContextEnabled,
|
|
39
|
+
isDerivativesTargetContextEnabled: () => isDerivativesTargetContextEnabled,
|
|
36
40
|
isSignalsDerivativesContextEnabled: () => isSignalsDerivativesContextEnabled,
|
|
37
41
|
resolveDerivativesContextBackfillSymbols: () => resolveDerivativesContextBackfillSymbols,
|
|
38
42
|
resolveDerivativesContextBackfillWindow: () => resolveDerivativesContextBackfillWindow,
|
|
43
|
+
resolveDerivativesContextIntervalBackfillWindow: () => resolveDerivativesContextIntervalBackfillWindow,
|
|
39
44
|
resolveDerivativesContextIntervals: () => resolveDerivativesContextIntervals,
|
|
40
45
|
resolveDerivativesContextLookbackMs: () => resolveDerivativesContextLookbackMs,
|
|
46
|
+
resolveDerivativesContextMissingCoverageFetchFromMs: () => resolveDerivativesContextMissingCoverageFetchFromMs,
|
|
47
|
+
resolveDerivativesContextMissingFetchFromMs: () => resolveDerivativesContextMissingFetchFromMs,
|
|
41
48
|
shouldBackfillDerivativesContextForBacktest: () => shouldBackfillDerivativesContextForBacktest,
|
|
42
49
|
shouldBackfillDerivativesContextForSignals: () => shouldBackfillDerivativesContextForSignals
|
|
43
50
|
});
|
|
@@ -57,12 +64,20 @@ var coinalyzeIntervalMap = {
|
|
|
57
64
|
"15m": "15min",
|
|
58
65
|
"1h": "1hour"
|
|
59
66
|
};
|
|
67
|
+
var derivativesIntervalMs = (interval) => interval === "1h" ? HOUR_MS : 15 * 60 * 1e3;
|
|
60
68
|
var lastRequestTs = 0;
|
|
61
69
|
var asInt = (value, fallback) => {
|
|
62
70
|
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
63
71
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
64
72
|
};
|
|
65
73
|
var parseList = (value) => String(value ?? "").split(",").map((item) => item.trim().toUpperCase()).filter(Boolean);
|
|
74
|
+
var parseBooleanFlag = (value, fallback = false) => {
|
|
75
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
76
|
+
if (!normalized) return fallback;
|
|
77
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
78
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
79
|
+
return fallback;
|
|
80
|
+
};
|
|
66
81
|
var normalizeSymbols = (symbols) => [
|
|
67
82
|
...new Set(
|
|
68
83
|
symbols.map(
|
|
@@ -70,7 +85,13 @@ var normalizeSymbols = (symbols) => [
|
|
|
70
85
|
).filter(Boolean)
|
|
71
86
|
)
|
|
72
87
|
];
|
|
73
|
-
var
|
|
88
|
+
var isDerivativesTargetContextEnabled = () => parseBooleanFlag(process.env.DERIVATIVES_CONTEXT_TARGET_ENABLED, false);
|
|
89
|
+
var resolveDerivativesContextBackfillSymbols = (requestedSymbols = []) => {
|
|
90
|
+
const referenceSymbols = (0, import_constants.resolveDerivativesContextReferenceSymbols)(
|
|
91
|
+
process.env.DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS
|
|
92
|
+
);
|
|
93
|
+
return isDerivativesTargetContextEnabled() ? normalizeSymbols([...referenceSymbols, ...requestedSymbols]) : referenceSymbols;
|
|
94
|
+
};
|
|
74
95
|
var chunkArray = (items, size) => {
|
|
75
96
|
const chunks = [];
|
|
76
97
|
for (let i = 0; i < items.length; i += size) {
|
|
@@ -84,7 +105,7 @@ var isBacktestDerivativesContextEnabled = () => {
|
|
|
84
105
|
var isSignalsDerivativesContextEnabled = () => isDerivativesContextBackfillEnabled("CRON");
|
|
85
106
|
var isDerivativesContextBackfillEnabled = (env) => {
|
|
86
107
|
const normalized = String(process.env.DERIVATIVES_CONTEXT_ENABLED ?? "").trim().toLowerCase();
|
|
87
|
-
if (!normalized) return
|
|
108
|
+
if (!normalized) return true;
|
|
88
109
|
if (["1", "true", "yes", "on"].includes(normalized)) {
|
|
89
110
|
return true;
|
|
90
111
|
}
|
|
@@ -110,11 +131,18 @@ var resolveDerivativesContextLookbackMs = () => {
|
|
|
110
131
|
return normalizedHours * HOUR_MS;
|
|
111
132
|
};
|
|
112
133
|
var resolveDerivativesContextBackfillWindow = (params) => {
|
|
113
|
-
const {
|
|
134
|
+
const {
|
|
135
|
+
mode = "signals",
|
|
136
|
+
startMs,
|
|
137
|
+
endMs,
|
|
138
|
+
preloadStartMs,
|
|
139
|
+
nowMs = Date.now()
|
|
140
|
+
} = params;
|
|
114
141
|
const safeEndMs = Math.min(endMs, nowMs);
|
|
115
142
|
const safeStartMs = Math.max(0, Math.min(startMs, safeEndMs));
|
|
116
143
|
const lookbackFromMs = safeStartMs - resolveDerivativesContextLookbackMs();
|
|
117
|
-
const
|
|
144
|
+
const defaultFromMs = mode === "backtest" ? safeStartMs : lookbackFromMs;
|
|
145
|
+
const requestedFromMs = mode === "signals" ? defaultFromMs : preloadStartMs ?? defaultFromMs;
|
|
118
146
|
const fromMs = Math.max(
|
|
119
147
|
0,
|
|
120
148
|
Math.min(
|
|
@@ -128,6 +156,104 @@ var resolveDerivativesContextBackfillWindow = (params) => {
|
|
|
128
156
|
testStartMs: Math.trunc(safeStartMs)
|
|
129
157
|
};
|
|
130
158
|
};
|
|
159
|
+
var resolveDerivativesContextIntervalBackfillWindow = (params) => {
|
|
160
|
+
const intervalMs = derivativesIntervalMs(params.interval);
|
|
161
|
+
return {
|
|
162
|
+
fromMs: Math.floor(params.fromMs / intervalMs) * intervalMs,
|
|
163
|
+
toMs: Math.floor(params.toMs / intervalMs) * intervalMs,
|
|
164
|
+
intervalMs
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
var resolveDerivativesContextMissingFetchFromMs = (params) => {
|
|
168
|
+
const min = params.edges?.min;
|
|
169
|
+
const max = params.edges?.max;
|
|
170
|
+
if (min == null || max == null) {
|
|
171
|
+
return params.fromMs;
|
|
172
|
+
}
|
|
173
|
+
if (min > params.fromMs) {
|
|
174
|
+
return params.fromMs;
|
|
175
|
+
}
|
|
176
|
+
if (max < params.toMs) {
|
|
177
|
+
return Math.max(params.fromMs, max + params.intervalMs);
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
};
|
|
181
|
+
var countBackfillWindows = (params) => {
|
|
182
|
+
let count = 0;
|
|
183
|
+
let cursor = params.fromMs;
|
|
184
|
+
while (cursor < params.toMs) {
|
|
185
|
+
const toMs = Math.min(params.toMs, cursor + params.batchMs);
|
|
186
|
+
count += 1;
|
|
187
|
+
cursor = toMs + params.intervalMs;
|
|
188
|
+
}
|
|
189
|
+
return count;
|
|
190
|
+
};
|
|
191
|
+
var buildBackfillWindows = (params) => {
|
|
192
|
+
const windows = [];
|
|
193
|
+
let cursor = params.fromMs;
|
|
194
|
+
while (cursor < params.toMs) {
|
|
195
|
+
const toMs = Math.min(params.toMs, cursor + params.batchMs);
|
|
196
|
+
windows.push({ fromMs: cursor, toMs });
|
|
197
|
+
cursor = toMs + params.intervalMs;
|
|
198
|
+
}
|
|
199
|
+
return windows;
|
|
200
|
+
};
|
|
201
|
+
var hasDerivativesWindowCoverage = (params) => params.edges?.min != null && params.edges?.max != null && params.edges.min <= params.fromMs && params.edges.max >= params.toMs;
|
|
202
|
+
var hasDerivativesContextCoverageRange = (ranges, fromMs, toMs) => (ranges ?? []).some(
|
|
203
|
+
(range) => Number.isFinite(range.fromMs) && Number.isFinite(range.toMs) && range.fromMs <= fromMs && range.toMs >= toMs
|
|
204
|
+
);
|
|
205
|
+
var resolveDerivativesContextMissingCoverageFetchFromMs = (params) => {
|
|
206
|
+
const ranges = (params.ranges ?? []).filter(
|
|
207
|
+
(range) => Number.isFinite(range.fromMs) && Number.isFinite(range.toMs)
|
|
208
|
+
).sort((a, b) => a.fromMs - b.fromMs);
|
|
209
|
+
let coveredUntil;
|
|
210
|
+
for (const range of ranges) {
|
|
211
|
+
if (range.toMs < params.fromMs) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (coveredUntil == null) {
|
|
215
|
+
if (range.fromMs > params.fromMs) {
|
|
216
|
+
return params.fromMs;
|
|
217
|
+
}
|
|
218
|
+
coveredUntil = range.toMs;
|
|
219
|
+
if (coveredUntil >= params.toMs) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (range.fromMs > coveredUntil + params.intervalMs) {
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
coveredUntil = Math.max(coveredUntil, range.toMs);
|
|
228
|
+
if (coveredUntil >= params.toMs) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (coveredUntil == null) {
|
|
233
|
+
return params.fromMs;
|
|
234
|
+
}
|
|
235
|
+
return Math.max(params.fromMs, coveredUntil + params.intervalMs);
|
|
236
|
+
};
|
|
237
|
+
var groupDerivativesContextMissingFetchRanges = (ranges) => {
|
|
238
|
+
const groups = /* @__PURE__ */ new Map();
|
|
239
|
+
for (const range of ranges) {
|
|
240
|
+
const fromMs = Math.trunc(range.fromMs);
|
|
241
|
+
const items = groups.get(fromMs) ?? [];
|
|
242
|
+
items.push(range.item);
|
|
243
|
+
groups.set(fromMs, items);
|
|
244
|
+
}
|
|
245
|
+
return [...groups.entries()].sort(([a], [b]) => a - b).map(([fromMs, items]) => ({ fromMs, items }));
|
|
246
|
+
};
|
|
247
|
+
var coverageKey = (params) => [
|
|
248
|
+
params.symbol.trim().toUpperCase(),
|
|
249
|
+
params.interval,
|
|
250
|
+
Math.trunc(params.fromMs),
|
|
251
|
+
Math.trunc(params.toMs)
|
|
252
|
+
].join(":");
|
|
253
|
+
var extendEdges = (current, fromMs, toMs) => ({
|
|
254
|
+
min: current?.min == null ? fromMs : Math.min(current.min, Math.trunc(fromMs)),
|
|
255
|
+
max: current?.max == null ? toMs : Math.max(current.max, Math.trunc(toMs))
|
|
256
|
+
});
|
|
131
257
|
var getCoinalyzeApiKey = async (userName) => {
|
|
132
258
|
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
133
259
|
return settings.COINALYZE_API_KEY.trim() || String(process.env.COINALYZE_API_KEY ?? "").trim();
|
|
@@ -141,10 +267,67 @@ var getRequestDelayMs = () => Math.max(
|
|
|
141
267
|
)
|
|
142
268
|
);
|
|
143
269
|
var getRequestTimeoutMs = () => Math.max(5e3, asInt(process.env.COINALYZE_REQUEST_TIMEOUT_MS, 45e3));
|
|
270
|
+
var networkErrorCodes = /* @__PURE__ */ new Set([
|
|
271
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
272
|
+
"ETIMEDOUT",
|
|
273
|
+
"ECONNRESET",
|
|
274
|
+
"ECONNREFUSED",
|
|
275
|
+
"ENOTFOUND",
|
|
276
|
+
"EAI_AGAIN",
|
|
277
|
+
"EHOSTUNREACH"
|
|
278
|
+
]);
|
|
279
|
+
var getNestedErrorValue = (error, key) => {
|
|
280
|
+
let current = error;
|
|
281
|
+
const seen = /* @__PURE__ */ new Set();
|
|
282
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
283
|
+
seen.add(current);
|
|
284
|
+
const record = current;
|
|
285
|
+
const value = record[key];
|
|
286
|
+
if (typeof value === "string" && value.trim()) {
|
|
287
|
+
return value.trim();
|
|
288
|
+
}
|
|
289
|
+
current = record.cause;
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
};
|
|
293
|
+
var getCoinalyzeErrorCause = (error) => {
|
|
294
|
+
const code = getNestedErrorValue(error, "code");
|
|
295
|
+
const message = getNestedErrorValue(error, "message") ?? String(error || "unknown error");
|
|
296
|
+
return code ? `${code}: ${message}` : message;
|
|
297
|
+
};
|
|
298
|
+
var isRetryableCoinalyzeFetchError = (error) => {
|
|
299
|
+
const name = getNestedErrorValue(error, "name");
|
|
300
|
+
const code = getNestedErrorValue(error, "code");
|
|
301
|
+
const message = getNestedErrorValue(error, "message")?.toLowerCase() ?? "";
|
|
302
|
+
return name === "AbortError" || code != null && networkErrorCodes.has(code) || message.includes("aborted") || message.includes("fetch failed");
|
|
303
|
+
};
|
|
304
|
+
var formatCoinalyzeRequestError = ({
|
|
305
|
+
url,
|
|
306
|
+
error,
|
|
307
|
+
attempts,
|
|
308
|
+
timeoutMs
|
|
309
|
+
}) => {
|
|
310
|
+
const endpoint = (() => {
|
|
311
|
+
try {
|
|
312
|
+
const parsed = new URL(url);
|
|
313
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
314
|
+
} catch {
|
|
315
|
+
return url;
|
|
316
|
+
}
|
|
317
|
+
})();
|
|
318
|
+
return [
|
|
319
|
+
`Coinalyze request failed: ${endpoint}`,
|
|
320
|
+
`cause=${getCoinalyzeErrorCause(error)}`,
|
|
321
|
+
`attempts=${attempts}`,
|
|
322
|
+
`timeout=${timeoutMs}ms`,
|
|
323
|
+
"Set COINALYZE_REQUEST_TIMEOUT_MS/COINALYZE_MAX_RETRIES to retry longer, or DERIVATIVES_CONTEXT_ENABLED=live to skip derivatives backfill during backtests."
|
|
324
|
+
].join("; ");
|
|
325
|
+
};
|
|
144
326
|
var fetchJsonWithRateLimit = async (url, apiKey) => {
|
|
145
327
|
const requestDelayMs = getRequestDelayMs();
|
|
146
328
|
const requestTimeoutMs = getRequestTimeoutMs();
|
|
147
329
|
const maxRetries = asInt(process.env.COINALYZE_MAX_RETRIES, 4);
|
|
330
|
+
let lastError = null;
|
|
148
331
|
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
149
332
|
const waitMs = Math.max(0, lastRequestTs + requestDelayMs - Date.now());
|
|
150
333
|
if (waitMs > 0) {
|
|
@@ -165,12 +348,19 @@ var fetchJsonWithRateLimit = async (url, apiKey) => {
|
|
|
165
348
|
});
|
|
166
349
|
} catch (error) {
|
|
167
350
|
clearTimeout(timer);
|
|
168
|
-
|
|
169
|
-
if (attempt < maxRetries &&
|
|
351
|
+
lastError = error;
|
|
352
|
+
if (attempt < maxRetries && isRetryableCoinalyzeFetchError(error)) {
|
|
170
353
|
await (0, import_async.delay)(Math.min(12e3, 800 * 2 ** attempt));
|
|
171
354
|
continue;
|
|
172
355
|
}
|
|
173
|
-
throw
|
|
356
|
+
throw new Error(
|
|
357
|
+
formatCoinalyzeRequestError({
|
|
358
|
+
url,
|
|
359
|
+
error,
|
|
360
|
+
attempts: attempt + 1,
|
|
361
|
+
timeoutMs: requestTimeoutMs
|
|
362
|
+
})
|
|
363
|
+
);
|
|
174
364
|
} finally {
|
|
175
365
|
clearTimeout(timer);
|
|
176
366
|
}
|
|
@@ -187,7 +377,14 @@ var fetchJsonWithRateLimit = async (url, apiKey) => {
|
|
|
187
377
|
}
|
|
188
378
|
throw new Error(`Coinalyze ${response.status}: ${text}`);
|
|
189
379
|
}
|
|
190
|
-
throw new Error(
|
|
380
|
+
throw new Error(
|
|
381
|
+
formatCoinalyzeRequestError({
|
|
382
|
+
url,
|
|
383
|
+
error: lastError,
|
|
384
|
+
attempts: maxRetries + 1,
|
|
385
|
+
timeoutMs: requestTimeoutMs
|
|
386
|
+
})
|
|
387
|
+
);
|
|
191
388
|
};
|
|
192
389
|
var fetchCoinalyzeMarkets = async (apiKey) => {
|
|
193
390
|
const raw = await fetchJsonWithRateLimit(
|
|
@@ -292,7 +489,7 @@ var skippedBackfillResult = () => ({
|
|
|
292
489
|
failedWindows: 0,
|
|
293
490
|
skippedWindows: 0
|
|
294
491
|
});
|
|
295
|
-
var backfillDerivativesContext = async (params, enabled) => {
|
|
492
|
+
var backfillDerivativesContext = async (params, enabled, mode) => {
|
|
296
493
|
const { userName, startMs, endMs } = params;
|
|
297
494
|
const requestedSymbols = normalizeSymbols(params.symbols);
|
|
298
495
|
const symbols = resolveDerivativesContextBackfillSymbols(requestedSymbols);
|
|
@@ -303,17 +500,12 @@ var backfillDerivativesContext = async (params, enabled) => {
|
|
|
303
500
|
if (!intervals.length) {
|
|
304
501
|
return skippedBackfillResult();
|
|
305
502
|
}
|
|
306
|
-
const apiKey = await getCoinalyzeApiKey(userName);
|
|
307
|
-
if (!apiKey) {
|
|
308
|
-
throw new Error(
|
|
309
|
-
`Missing COINALYZE_API_KEY for derivatives context backfill (user=${userName})`
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
503
|
const {
|
|
313
504
|
fromMs,
|
|
314
505
|
toMs: safeEndMs,
|
|
315
506
|
testStartMs: safeStartMs
|
|
316
507
|
} = resolveDerivativesContextBackfillWindow({
|
|
508
|
+
mode,
|
|
317
509
|
startMs,
|
|
318
510
|
endMs,
|
|
319
511
|
preloadStartMs: params.preloadStartMs
|
|
@@ -321,6 +513,129 @@ var backfillDerivativesContext = async (params, enabled) => {
|
|
|
321
513
|
if (safeEndMs <= fromMs) {
|
|
322
514
|
return skippedBackfillResult();
|
|
323
515
|
}
|
|
516
|
+
const batchDays = asInt(
|
|
517
|
+
process.env.DERIVATIVES_CONTEXT_BACKFILL_BATCH_DAYS,
|
|
518
|
+
30
|
|
519
|
+
);
|
|
520
|
+
const batchMs = batchDays * DAY_MS;
|
|
521
|
+
const intervalWindows = intervals.map((interval) => ({
|
|
522
|
+
interval,
|
|
523
|
+
...resolveDerivativesContextIntervalBackfillWindow({
|
|
524
|
+
fromMs,
|
|
525
|
+
toMs: safeEndMs,
|
|
526
|
+
interval
|
|
527
|
+
})
|
|
528
|
+
})).filter((item) => item.toMs > item.fromMs);
|
|
529
|
+
if (!intervalWindows.length) {
|
|
530
|
+
return skippedBackfillResult();
|
|
531
|
+
}
|
|
532
|
+
await (0, import_timescale.waitForDbReady)();
|
|
533
|
+
const edgesByInterval = /* @__PURE__ */ new Map();
|
|
534
|
+
await Promise.all(
|
|
535
|
+
intervalWindows.map(async ({ interval }) => {
|
|
536
|
+
edgesByInterval.set(
|
|
537
|
+
interval,
|
|
538
|
+
await (0, import_timescale.getDerivativesDataEdgesForSymbols)(symbols, interval)
|
|
539
|
+
);
|
|
540
|
+
})
|
|
541
|
+
);
|
|
542
|
+
const coverageKeysByInterval = /* @__PURE__ */ new Map();
|
|
543
|
+
const coverageRangesByInterval = /* @__PURE__ */ new Map();
|
|
544
|
+
await Promise.all(
|
|
545
|
+
intervalWindows.map(async (window) => {
|
|
546
|
+
const coverageRows = await (0, import_timescale.getDerivativesBackfillCoverage)({
|
|
547
|
+
source: "coinalyze",
|
|
548
|
+
symbols,
|
|
549
|
+
interval: window.interval,
|
|
550
|
+
fromMs: window.fromMs,
|
|
551
|
+
toMs: window.toMs
|
|
552
|
+
});
|
|
553
|
+
coverageKeysByInterval.set(
|
|
554
|
+
window.interval,
|
|
555
|
+
new Set(
|
|
556
|
+
coverageRows.map(
|
|
557
|
+
(row) => coverageKey({
|
|
558
|
+
symbol: row.symbol,
|
|
559
|
+
interval: row.interval,
|
|
560
|
+
fromMs: row.fromMs,
|
|
561
|
+
toMs: row.toMs
|
|
562
|
+
})
|
|
563
|
+
)
|
|
564
|
+
)
|
|
565
|
+
);
|
|
566
|
+
const rangesBySymbol = /* @__PURE__ */ new Map();
|
|
567
|
+
for (const row of coverageRows) {
|
|
568
|
+
const symbol = row.symbol.toUpperCase();
|
|
569
|
+
const ranges = rangesBySymbol.get(symbol) ?? [];
|
|
570
|
+
ranges.push({ fromMs: row.fromMs, toMs: row.toMs });
|
|
571
|
+
rangesBySymbol.set(symbol, ranges);
|
|
572
|
+
}
|
|
573
|
+
coverageRangesByInterval.set(window.interval, rangesBySymbol);
|
|
574
|
+
})
|
|
575
|
+
);
|
|
576
|
+
const cachedWindows = intervalWindows.reduce(
|
|
577
|
+
(sum, window) => sum + countBackfillWindows({
|
|
578
|
+
fromMs: window.fromMs,
|
|
579
|
+
toMs: window.toMs,
|
|
580
|
+
batchMs,
|
|
581
|
+
intervalMs: window.intervalMs
|
|
582
|
+
}),
|
|
583
|
+
0
|
|
584
|
+
);
|
|
585
|
+
const allBackfillWindowsCached = intervalWindows.every((window) => {
|
|
586
|
+
const edgesBySymbol = edgesByInterval.get(window.interval);
|
|
587
|
+
const coverageKeys = coverageKeysByInterval.get(window.interval);
|
|
588
|
+
const backfillWindows = buildBackfillWindows({
|
|
589
|
+
fromMs: window.fromMs,
|
|
590
|
+
toMs: window.toMs,
|
|
591
|
+
batchMs,
|
|
592
|
+
intervalMs: window.intervalMs
|
|
593
|
+
});
|
|
594
|
+
return symbols.every(
|
|
595
|
+
(symbol) => backfillWindows.every((backfillWindow) => {
|
|
596
|
+
const normalizedSymbol = symbol.toUpperCase();
|
|
597
|
+
const coverageRanges = coverageRangesByInterval.get(window.interval)?.get(normalizedSymbol) ?? [];
|
|
598
|
+
return hasDerivativesWindowCoverage({
|
|
599
|
+
edges: edgesBySymbol?.get(normalizedSymbol),
|
|
600
|
+
fromMs: backfillWindow.fromMs,
|
|
601
|
+
toMs: backfillWindow.toMs
|
|
602
|
+
}) || resolveDerivativesContextMissingCoverageFetchFromMs({
|
|
603
|
+
ranges: coverageRanges,
|
|
604
|
+
fromMs: backfillWindow.fromMs,
|
|
605
|
+
toMs: backfillWindow.toMs,
|
|
606
|
+
intervalMs: window.intervalMs
|
|
607
|
+
}) == null || coverageKeys?.has(
|
|
608
|
+
coverageKey({
|
|
609
|
+
symbol: normalizedSymbol,
|
|
610
|
+
interval: window.interval,
|
|
611
|
+
fromMs: backfillWindow.fromMs,
|
|
612
|
+
toMs: backfillWindow.toMs
|
|
613
|
+
})
|
|
614
|
+
);
|
|
615
|
+
})
|
|
616
|
+
);
|
|
617
|
+
});
|
|
618
|
+
if (allBackfillWindowsCached) {
|
|
619
|
+
console.log(
|
|
620
|
+
import_chalk.default.gray(
|
|
621
|
+
`derivatives context backfill: cached symbols=${symbols.length}, requestedSymbols=${requestedSymbols.length}, intervals=${intervals.join(",")}, window=${new Date(fromMs).toISOString()}..${new Date(safeEndMs).toISOString()}`
|
|
622
|
+
)
|
|
623
|
+
);
|
|
624
|
+
return {
|
|
625
|
+
skipped: false,
|
|
626
|
+
rows: 0,
|
|
627
|
+
matchedSymbols: symbols.length,
|
|
628
|
+
unmatchedSymbols: 0,
|
|
629
|
+
failedWindows: 0,
|
|
630
|
+
skippedWindows: cachedWindows
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
const apiKey = await getCoinalyzeApiKey(userName);
|
|
634
|
+
if (!apiKey) {
|
|
635
|
+
throw new Error(
|
|
636
|
+
`Missing COINALYZE_API_KEY for derivatives context backfill (user=${userName})`
|
|
637
|
+
);
|
|
638
|
+
}
|
|
324
639
|
const exchangePriority = parseList(
|
|
325
640
|
process.env.COINALYZE_EXCHANGE_PRIORITY || process.env.DERIVATIVES_CONTEXT_EXCHANGE_PRIORITY || "A,6,0"
|
|
326
641
|
);
|
|
@@ -335,21 +650,24 @@ var backfillDerivativesContext = async (params, enabled) => {
|
|
|
335
650
|
);
|
|
336
651
|
if (!matches.length) {
|
|
337
652
|
throw new Error(
|
|
338
|
-
"No matched derivatives
|
|
653
|
+
"No matched derivatives context symbols between requested symbols and Coinalyze"
|
|
339
654
|
);
|
|
340
655
|
}
|
|
341
|
-
await (0, import_timescale.waitForDbReady)();
|
|
342
|
-
const batchDays = asInt(
|
|
343
|
-
process.env.DERIVATIVES_CONTEXT_BACKFILL_BATCH_DAYS,
|
|
344
|
-
30
|
|
345
|
-
);
|
|
346
656
|
const symbolBatchSize = asInt(
|
|
347
657
|
process.env.DERIVATIVES_CONTEXT_BACKFILL_SYMBOL_BATCH_SIZE,
|
|
348
658
|
8
|
|
349
659
|
);
|
|
350
660
|
const symbolBatches = chunkArray(matches, symbolBatchSize);
|
|
351
|
-
const windowsPerPair =
|
|
352
|
-
|
|
661
|
+
const windowsPerPair = intervalWindows.reduce(
|
|
662
|
+
(sum, window) => sum + countBackfillWindows({
|
|
663
|
+
fromMs: window.fromMs,
|
|
664
|
+
toMs: window.toMs,
|
|
665
|
+
batchMs,
|
|
666
|
+
intervalMs: window.intervalMs
|
|
667
|
+
}),
|
|
668
|
+
0
|
|
669
|
+
);
|
|
670
|
+
const totalWindows = symbolBatches.length * windowsPerPair;
|
|
353
671
|
const bar = new import_progress.default(
|
|
354
672
|
":current/:total [:bar][:percent] :eta(s) :batch rows=:rows fail=:fail skip=:skip",
|
|
355
673
|
{
|
|
@@ -365,68 +683,148 @@ var backfillDerivativesContext = async (params, enabled) => {
|
|
|
365
683
|
let skippedWindows = 0;
|
|
366
684
|
console.log(
|
|
367
685
|
import_chalk.default.cyan(
|
|
368
|
-
`derivatives context backfill:
|
|
686
|
+
`derivatives context backfill: symbols=${matches.length}, requestedSymbols=${requestedSymbols.length}, unmatched=${unmatched.length}, intervals=${intervals.join(",")}, window=${new Date(fromMs).toISOString()}..${new Date(safeEndMs).toISOString()}, testStart=${new Date(safeStartMs).toISOString()}`
|
|
369
687
|
)
|
|
370
688
|
);
|
|
371
|
-
for (const
|
|
372
|
-
const
|
|
689
|
+
for (const window of intervalWindows) {
|
|
690
|
+
const { interval, intervalMs } = window;
|
|
691
|
+
const edgesBySymbol = edgesByInterval.get(interval) ?? await (0, import_timescale.getDerivativesDataEdgesForSymbols)(
|
|
373
692
|
matches.map((item) => item.symbol),
|
|
374
693
|
interval
|
|
375
694
|
);
|
|
695
|
+
const coverageKeys = coverageKeysByInterval.get(interval) ?? /* @__PURE__ */ new Set();
|
|
696
|
+
coverageKeysByInterval.set(interval, coverageKeys);
|
|
697
|
+
const coverageRangesBySymbol = coverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
|
|
698
|
+
coverageRangesByInterval.set(interval, coverageRangesBySymbol);
|
|
376
699
|
for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
|
|
377
700
|
const batch = symbolBatches[batchIdx];
|
|
378
|
-
let cursor = fromMs;
|
|
379
|
-
while (cursor <
|
|
380
|
-
const toMs = Math.min(
|
|
381
|
-
const
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
701
|
+
let cursor = window.fromMs;
|
|
702
|
+
while (cursor < window.toMs) {
|
|
703
|
+
const toMs = Math.min(window.toMs, cursor + batchMs);
|
|
704
|
+
const missingRanges = batch.map((item) => {
|
|
705
|
+
const key = coverageKey({
|
|
706
|
+
symbol: item.symbol,
|
|
707
|
+
interval,
|
|
708
|
+
fromMs: cursor,
|
|
709
|
+
toMs
|
|
710
|
+
});
|
|
711
|
+
if (coverageKeys.has(key)) {
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
const normalizedSymbol = item.symbol.toUpperCase();
|
|
715
|
+
const coverageFromMs = resolveDerivativesContextMissingCoverageFetchFromMs({
|
|
716
|
+
ranges: coverageRangesBySymbol.get(normalizedSymbol),
|
|
717
|
+
fromMs: cursor,
|
|
718
|
+
toMs,
|
|
719
|
+
intervalMs
|
|
720
|
+
});
|
|
721
|
+
if (coverageFromMs == null) {
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
const edges = edgesBySymbol.get(normalizedSymbol);
|
|
725
|
+
const edgesFromMs = resolveDerivativesContextMissingFetchFromMs({
|
|
726
|
+
edges,
|
|
727
|
+
fromMs: cursor,
|
|
728
|
+
toMs,
|
|
729
|
+
intervalMs
|
|
730
|
+
});
|
|
731
|
+
if (edgesFromMs == null) {
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
return { item, fromMs: Math.max(coverageFromMs, edgesFromMs) };
|
|
735
|
+
}).filter(
|
|
736
|
+
(item) => item != null
|
|
737
|
+
);
|
|
385
738
|
try {
|
|
386
|
-
if (!
|
|
739
|
+
if (!missingRanges.length) {
|
|
387
740
|
skippedWindows += 1;
|
|
388
741
|
} else {
|
|
389
|
-
const
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
apiKey,
|
|
413
|
-
interval,
|
|
414
|
-
fromMs: cursor,
|
|
415
|
-
toMs
|
|
416
|
-
});
|
|
417
|
-
const rows = missingBatch.flatMap((item) => {
|
|
418
|
-
const marketSymbol = item.marketSymbol.toUpperCase();
|
|
419
|
-
const points = (0, import_indicators.mergeCoinalyzeMetrics)({
|
|
420
|
-
symbol: item.symbol,
|
|
421
|
-
oiRaw: oiMap.get(marketSymbol) ?? [],
|
|
422
|
-
fundingRaw: fundingMap.get(marketSymbol) ?? [],
|
|
423
|
-
liqRaw: liqMap.get(marketSymbol) ?? []
|
|
742
|
+
const groupedMissingRanges = groupDerivativesContextMissingFetchRanges(missingRanges);
|
|
743
|
+
for (const group of groupedMissingRanges) {
|
|
744
|
+
const missingBatch = group.items;
|
|
745
|
+
const marketSymbols = missingBatch.map(
|
|
746
|
+
(item) => item.marketSymbol
|
|
747
|
+
);
|
|
748
|
+
const oiMap = await fetchMetricBatch({
|
|
749
|
+
endpoint: oiPath,
|
|
750
|
+
metric: "oi",
|
|
751
|
+
marketSymbols,
|
|
752
|
+
apiKey,
|
|
753
|
+
interval,
|
|
754
|
+
fromMs: group.fromMs,
|
|
755
|
+
toMs
|
|
756
|
+
});
|
|
757
|
+
const fundingMap = await fetchMetricBatch({
|
|
758
|
+
endpoint: fundingPath,
|
|
759
|
+
metric: "funding",
|
|
760
|
+
marketSymbols,
|
|
761
|
+
apiKey,
|
|
762
|
+
interval,
|
|
763
|
+
fromMs: group.fromMs,
|
|
764
|
+
toMs
|
|
424
765
|
});
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
766
|
+
const liqMap = await fetchMetricBatch({
|
|
767
|
+
endpoint: liqPath,
|
|
768
|
+
metric: "liq",
|
|
769
|
+
marketSymbols,
|
|
770
|
+
apiKey,
|
|
771
|
+
interval,
|
|
772
|
+
fromMs: group.fromMs,
|
|
773
|
+
toMs
|
|
774
|
+
});
|
|
775
|
+
const rows = missingBatch.flatMap((item) => {
|
|
776
|
+
const marketSymbol = item.marketSymbol.toUpperCase();
|
|
777
|
+
const points = (0, import_indicators.mergeCoinalyzeMetrics)({
|
|
778
|
+
symbol: item.symbol,
|
|
779
|
+
oiRaw: oiMap.get(marketSymbol) ?? [],
|
|
780
|
+
fundingRaw: fundingMap.get(marketSymbol) ?? [],
|
|
781
|
+
liqRaw: liqMap.get(marketSymbol) ?? []
|
|
782
|
+
});
|
|
783
|
+
return (0, import_indicators.coinalyzePointsToRows)(points, interval, "coinalyze");
|
|
784
|
+
});
|
|
785
|
+
if (rows.length) {
|
|
786
|
+
await (0, import_timescale.upsertDerivatives)(rows);
|
|
787
|
+
totalRows += rows.length;
|
|
788
|
+
}
|
|
789
|
+
const rowsCountBySymbol = /* @__PURE__ */ new Map();
|
|
790
|
+
for (const row of rows) {
|
|
791
|
+
const symbol = row.symbol.toUpperCase();
|
|
792
|
+
rowsCountBySymbol.set(
|
|
793
|
+
symbol,
|
|
794
|
+
(rowsCountBySymbol.get(symbol) ?? 0) + 1
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
const coverageRows = missingBatch.map((item) => {
|
|
798
|
+
const normalizedSymbol = item.symbol.toUpperCase();
|
|
799
|
+
const rowsCount = rowsCountBySymbol.get(normalizedSymbol) ?? 0;
|
|
800
|
+
return {
|
|
801
|
+
source: "coinalyze",
|
|
802
|
+
symbol: item.symbol,
|
|
803
|
+
interval,
|
|
804
|
+
fromMs: cursor,
|
|
805
|
+
toMs,
|
|
806
|
+
rowsCount
|
|
807
|
+
};
|
|
808
|
+
});
|
|
809
|
+
await (0, import_timescale.upsertDerivativesBackfillCoverage)(coverageRows);
|
|
810
|
+
for (const coverageRow of coverageRows) {
|
|
811
|
+
const symbol = coverageRow.symbol.toUpperCase();
|
|
812
|
+
edgesBySymbol.set(
|
|
813
|
+
symbol,
|
|
814
|
+
extendEdges(edgesBySymbol.get(symbol), cursor, toMs)
|
|
815
|
+
);
|
|
816
|
+
coverageKeys.add(
|
|
817
|
+
coverageKey({
|
|
818
|
+
symbol,
|
|
819
|
+
interval,
|
|
820
|
+
fromMs: cursor,
|
|
821
|
+
toMs
|
|
822
|
+
})
|
|
823
|
+
);
|
|
824
|
+
const coverageRanges = coverageRangesBySymbol.get(symbol) ?? [];
|
|
825
|
+
coverageRanges.push({ fromMs: cursor, toMs });
|
|
826
|
+
coverageRangesBySymbol.set(symbol, coverageRanges);
|
|
827
|
+
}
|
|
430
828
|
}
|
|
431
829
|
}
|
|
432
830
|
} catch (error) {
|
|
@@ -446,7 +844,7 @@ var backfillDerivativesContext = async (params, enabled) => {
|
|
|
446
844
|
skip: skippedWindows
|
|
447
845
|
});
|
|
448
846
|
}
|
|
449
|
-
cursor = toMs +
|
|
847
|
+
cursor = toMs + intervalMs;
|
|
450
848
|
}
|
|
451
849
|
}
|
|
452
850
|
}
|
|
@@ -470,18 +868,33 @@ var backfillDerivativesContext = async (params, enabled) => {
|
|
|
470
868
|
skippedWindows
|
|
471
869
|
};
|
|
472
870
|
};
|
|
473
|
-
var backfillDerivativesContextForBacktest = async (params) => backfillDerivativesContext(
|
|
474
|
-
|
|
871
|
+
var backfillDerivativesContextForBacktest = async (params) => backfillDerivativesContext(
|
|
872
|
+
params,
|
|
873
|
+
isBacktestDerivativesContextEnabled(),
|
|
874
|
+
"backtest"
|
|
875
|
+
);
|
|
876
|
+
var backfillDerivativesContextForSignals = async (params) => backfillDerivativesContext(
|
|
877
|
+
params,
|
|
878
|
+
isSignalsDerivativesContextEnabled(),
|
|
879
|
+
"signals"
|
|
880
|
+
);
|
|
475
881
|
// Annotate the CommonJS export names for ESM import in node:
|
|
476
882
|
0 && (module.exports = {
|
|
477
883
|
backfillDerivativesContextForBacktest,
|
|
478
884
|
backfillDerivativesContextForSignals,
|
|
885
|
+
formatCoinalyzeRequestError,
|
|
886
|
+
groupDerivativesContextMissingFetchRanges,
|
|
887
|
+
hasDerivativesContextCoverageRange,
|
|
479
888
|
isBacktestDerivativesContextEnabled,
|
|
889
|
+
isDerivativesTargetContextEnabled,
|
|
480
890
|
isSignalsDerivativesContextEnabled,
|
|
481
891
|
resolveDerivativesContextBackfillSymbols,
|
|
482
892
|
resolveDerivativesContextBackfillWindow,
|
|
893
|
+
resolveDerivativesContextIntervalBackfillWindow,
|
|
483
894
|
resolveDerivativesContextIntervals,
|
|
484
895
|
resolveDerivativesContextLookbackMs,
|
|
896
|
+
resolveDerivativesContextMissingCoverageFetchFromMs,
|
|
897
|
+
resolveDerivativesContextMissingFetchFromMs,
|
|
485
898
|
shouldBackfillDerivativesContextForBacktest,
|
|
486
899
|
shouldBackfillDerivativesContextForSignals
|
|
487
900
|
});
|