@alphafox/cli 0.3.8 → 0.3.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/cache/clean.d.ts +1 -0
- package/dist/cache/clean.js +12 -0
- package/dist/cache/inspect.d.ts +7 -0
- package/dist/cache/inspect.js +31 -0
- package/dist/cache/paths.d.ts +5 -0
- package/dist/cache/paths.js +41 -0
- package/dist/cache/run-command.d.ts +7 -0
- package/dist/cache/run-command.js +85 -0
- package/dist/catalog/generated/registry.json +2 -607
- package/dist/catalog/generated/schemas.json +9037 -13195
- package/dist/commands/run.js +14 -9
- package/dist/engine-backtest/run-command.js +2 -0
- package/dist/engine-backtest/sweep-command.js +2 -0
- package/dist/engine-backtest/types.d.ts +1 -0
- package/dist/skills-manifest.json +58 -46
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/e2e-staging.md +6 -4
- package/package.json +1 -1
- package/skills/account/SKILL.md +1 -1
- package/skills/admin/SKILL.md +1 -1
- package/skills/alphafox/SKILL.md +14 -3
- package/skills/alphafox-shared/SKILL.md +1 -1
- package/skills/auth/SKILL.md +1 -1
- package/skills/cache/SKILL.md +38 -0
- package/skills/engine-backtest/SKILL.md +2 -1
- package/skills/exchange/SKILL.md +1 -1
- package/skills/market/SKILL.md +1 -1
- package/skills/notification/SKILL.md +1 -1
- package/skills/strategy/SKILL.md +1 -1
- package/skills/trading/SKILL.md +1 -1
- package/vendor/backtest-runner/index.d.ts +15 -0
- package/vendor/backtest-runner/index.mjs +4 -0
- package/vendor/backtest-runner/lib/tape-loader.mjs +127 -53
|
@@ -39,8 +39,47 @@ const FUNDING_INTERVALS = [
|
|
|
39
39
|
{ interval: "8h", spacingMs: 28_800_000 },
|
|
40
40
|
];
|
|
41
41
|
|
|
42
|
+
/** Independent symbol×timeframe (and funding) fetches. Pagination stays serial. */
|
|
43
|
+
export const DEFAULT_TAPE_SERIES_CONCURRENCY = 4;
|
|
44
|
+
export const MAX_TAPE_SERIES_CONCURRENCY = 8;
|
|
45
|
+
|
|
42
46
|
const exchangePromises = new Map();
|
|
43
47
|
|
|
48
|
+
export function resolveTapeSeriesConcurrency(value) {
|
|
49
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 1) {
|
|
50
|
+
return Math.min(
|
|
51
|
+
MAX_TAPE_SERIES_CONCURRENCY,
|
|
52
|
+
Math.max(1, Math.floor(value))
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return DEFAULT_TAPE_SERIES_CONCURRENCY;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function mapWithConcurrency(items, concurrency, worker) {
|
|
59
|
+
const list = [...items];
|
|
60
|
+
if (list.length === 0) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const limit = Math.min(
|
|
64
|
+
list.length,
|
|
65
|
+
Math.max(1, Math.floor(Number(concurrency)) || 1)
|
|
66
|
+
);
|
|
67
|
+
const results = new Array(list.length);
|
|
68
|
+
let nextIndex = 0;
|
|
69
|
+
async function runWorker() {
|
|
70
|
+
while (true) {
|
|
71
|
+
const index = nextIndex;
|
|
72
|
+
nextIndex += 1;
|
|
73
|
+
if (index >= list.length) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
results[index] = await worker(list[index], index);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
await Promise.all(Array.from({ length: limit }, () => runWorker()));
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
|
|
44
83
|
export function effectiveTapeEndMs(
|
|
45
84
|
requestedToMs,
|
|
46
85
|
nowMs = Date.now(),
|
|
@@ -141,7 +180,13 @@ export async function loadTape(request, options = {}) {
|
|
|
141
180
|
|
|
142
181
|
const exchangeDefinition = resolveRequestExchange(request);
|
|
143
182
|
const onProgress = request.onProgress ?? options.onProgress;
|
|
144
|
-
const cache = resolveTapeCache(
|
|
183
|
+
const cache = resolveTapeCache({
|
|
184
|
+
...options,
|
|
185
|
+
cacheDir: options.cacheDir ?? request.cacheDir,
|
|
186
|
+
});
|
|
187
|
+
const seriesConcurrency = resolveTapeSeriesConcurrency(
|
|
188
|
+
options.seriesConcurrency ?? request.seriesConcurrency
|
|
189
|
+
);
|
|
145
190
|
const dataQualityMode = request.dataQualityMode ?? "strict";
|
|
146
191
|
const baseTimeframe = resolvePlanBaseTimeframe({
|
|
147
192
|
baseTimeframe: request.baseTimeframe,
|
|
@@ -233,17 +278,36 @@ export async function loadTape(request, options = {}) {
|
|
|
233
278
|
const buffers = {};
|
|
234
279
|
const chartSeries = [];
|
|
235
280
|
const series = [];
|
|
236
|
-
const
|
|
281
|
+
const seriesJobs = request.symbols.flatMap((symbol) =>
|
|
282
|
+
timeframes.map((timeframe) => ({ symbol, timeframe }))
|
|
283
|
+
);
|
|
284
|
+
const totalSeries = seriesJobs.length;
|
|
237
285
|
const dataIssues = [];
|
|
238
286
|
const coverageWarnings = [];
|
|
239
|
-
|
|
240
|
-
let
|
|
241
|
-
|
|
242
|
-
|
|
287
|
+
const seriesFractions = new Array(totalSeries).fill(0);
|
|
288
|
+
let lastOhlcvDetail = "";
|
|
289
|
+
const reportOhlcv = (index, fraction, detail) => {
|
|
290
|
+
seriesFractions[index] = fraction;
|
|
291
|
+
lastOhlcvDetail = detail;
|
|
292
|
+
const completed =
|
|
293
|
+
seriesFractions.reduce((sum, value) => sum + value, 0) / totalSeries;
|
|
294
|
+
onProgress?.({
|
|
295
|
+
stage: "ohlcv",
|
|
296
|
+
detail: lastOhlcvDetail,
|
|
297
|
+
fraction:
|
|
298
|
+
MARKETS_PROGRESS_END +
|
|
299
|
+
(OHLCV_PROGRESS_END - MARKETS_PROGRESS_END) * completed,
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
const loadedSeries = await mapWithConcurrency(
|
|
303
|
+
seriesJobs,
|
|
304
|
+
seriesConcurrency,
|
|
305
|
+
async (job, index) => {
|
|
243
306
|
request.signal?.throwIfAborted();
|
|
244
|
-
|
|
307
|
+
const { symbol, timeframe } = job;
|
|
308
|
+
const detail = `${symbol} ${timeframe}`;
|
|
245
309
|
try {
|
|
246
|
-
loaded = await loadSeriesWithCache(
|
|
310
|
+
const loaded = await loadSeriesWithCache(
|
|
247
311
|
exchange,
|
|
248
312
|
exchangeDefinition,
|
|
249
313
|
runtimeConfig,
|
|
@@ -255,46 +319,51 @@ export async function loadTape(request, options = {}) {
|
|
|
255
319
|
requirementWarmups.get(`${symbol}\u0000${timeframe}`) ?? 0,
|
|
256
320
|
timeframe === baseTimeframe,
|
|
257
321
|
dataQualityMode,
|
|
258
|
-
(fraction) =>
|
|
259
|
-
onProgress?.({
|
|
260
|
-
stage: "ohlcv",
|
|
261
|
-
detail: `${symbol} ${timeframe}`,
|
|
262
|
-
fraction:
|
|
263
|
-
MARKETS_PROGRESS_END +
|
|
264
|
-
(OHLCV_PROGRESS_END - MARKETS_PROGRESS_END) *
|
|
265
|
-
((seriesDone + fraction) / totalSeries),
|
|
266
|
-
});
|
|
267
|
-
},
|
|
322
|
+
(fraction) => reportOhlcv(index, fraction, detail),
|
|
268
323
|
cacheUntilMs,
|
|
269
324
|
cache,
|
|
270
325
|
request.signal
|
|
271
326
|
);
|
|
327
|
+
reportOhlcv(index, 1, detail);
|
|
328
|
+
return { ok: true, job, loaded };
|
|
272
329
|
} catch (error) {
|
|
273
330
|
request.signal?.throwIfAborted();
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
coverageWarnings.push(
|
|
280
|
-
formatCoverageSoftWarning(loaded.softIssues, loaded.coverageRatio)
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
const { rows } = loaded;
|
|
284
|
-
const bufferKey = `k${bufferSequence++}`;
|
|
285
|
-
const buffer = encodeOhlcvColumns(rows);
|
|
286
|
-
buffers[bufferKey] = buffer;
|
|
287
|
-
if (timeframe === baseTimeframe) {
|
|
288
|
-
chartSeries.push({
|
|
289
|
-
symbol,
|
|
290
|
-
timeframe: baseTimeframe,
|
|
291
|
-
rows: rows.length,
|
|
292
|
-
buffer: buffer.slice(0),
|
|
293
|
-
});
|
|
331
|
+
reportOhlcv(index, 1, detail);
|
|
332
|
+
return {
|
|
333
|
+
ok: false,
|
|
334
|
+
issues: toTapeDataIssues(error, symbol, timeframe),
|
|
335
|
+
};
|
|
294
336
|
}
|
|
295
|
-
series.push({ symbol, timeframe, buffer: bufferKey, rows: rows.length });
|
|
296
|
-
seriesDone++;
|
|
297
337
|
}
|
|
338
|
+
);
|
|
339
|
+
let bufferSequence = 0;
|
|
340
|
+
for (const result of loadedSeries) {
|
|
341
|
+
if (!result.ok) {
|
|
342
|
+
dataIssues.push(...result.issues);
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (result.loaded.softIssues.length > 0) {
|
|
346
|
+
coverageWarnings.push(
|
|
347
|
+
formatCoverageSoftWarning(
|
|
348
|
+
result.loaded.softIssues,
|
|
349
|
+
result.loaded.coverageRatio
|
|
350
|
+
)
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
const { symbol, timeframe } = result.job;
|
|
354
|
+
const { rows } = result.loaded;
|
|
355
|
+
const bufferKey = `k${bufferSequence++}`;
|
|
356
|
+
const buffer = encodeOhlcvColumns(rows);
|
|
357
|
+
buffers[bufferKey] = buffer;
|
|
358
|
+
if (timeframe === baseTimeframe) {
|
|
359
|
+
chartSeries.push({
|
|
360
|
+
symbol,
|
|
361
|
+
timeframe: baseTimeframe,
|
|
362
|
+
rows: rows.length,
|
|
363
|
+
buffer: buffer.slice(0),
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
series.push({ symbol, timeframe, buffer: bufferKey, rows: rows.length });
|
|
298
367
|
}
|
|
299
368
|
if (dataIssues.length > 0) {
|
|
300
369
|
throw new TapeDataUnavailableError(dataIssues);
|
|
@@ -302,19 +371,24 @@ export async function loadTape(request, options = {}) {
|
|
|
302
371
|
|
|
303
372
|
let fundingRates;
|
|
304
373
|
if (request.needsFunding) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
374
|
+
const fundingEntries = await mapWithConcurrency(
|
|
375
|
+
request.symbols,
|
|
376
|
+
seriesConcurrency,
|
|
377
|
+
async (symbol) => {
|
|
378
|
+
request.signal?.throwIfAborted();
|
|
379
|
+
const samples = await loadFundingHistory(
|
|
380
|
+
exchange,
|
|
381
|
+
exchangeDefinition,
|
|
382
|
+
runtimeConfig,
|
|
383
|
+
symbol,
|
|
384
|
+
request.fromMs,
|
|
385
|
+
tapeToMs,
|
|
386
|
+
request.signal
|
|
387
|
+
);
|
|
388
|
+
return [symbol, samples];
|
|
389
|
+
}
|
|
390
|
+
);
|
|
391
|
+
fundingRates = Object.fromEntries(fundingEntries);
|
|
318
392
|
}
|
|
319
393
|
request.signal?.throwIfAborted();
|
|
320
394
|
onProgress?.({
|