@tradejs/cli 2.0.6 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2448 -1374
- package/dist/lib/aiExport.js +1822 -0
- package/dist/lib/binanceMarketContextBackfill.js +97 -88
- package/dist/lib/binanceMarketData.js +6 -3
- package/dist/lib/marketContextPrepare.js +97 -88
- package/dist/lib/runEnvironment.js +48 -18
- package/dist/lib/runtimeLineage.js +255 -0
- package/dist/lib/runtimeParityDetails.js +24 -1
- package/dist/lib/runtimeSignalsLoader.js +19 -0
- package/dist/lib/runtimeSignalsStorage.js +3 -0
- package/dist/lib/runtimeTradeSync.js +40 -11
- package/dist/lib/timeWindow.js +44 -16
- package/dist/scripts/aiExport.js +200 -88
- package/dist/scripts/backtest.js +170 -107
- package/dist/scripts/binanceBreadthUniversesUpdate.js +120 -0
- package/dist/scripts/binanceMarketIngest.js +32 -33
- package/dist/scripts/mlExport.js +5 -2
- package/dist/scripts/replay.js +988 -218
- package/dist/scripts/replayRunner.js +988 -218
- package/dist/scripts/replayRuntimeEvidence.js +3 -1
- package/dist/scripts/resolveExportStrategy.js +7 -2
- package/dist/scripts/runtimeParity.js +141 -104
- package/dist/scripts/signals.js +381 -107
- package/dist/scripts/signalsDaemon.js +381 -107
- package/dist/scripts/signalsSummary.js +40 -11
- package/package.json +10 -9
package/dist/scripts/backtest.js
CHANGED
|
@@ -1681,6 +1681,7 @@ var import_time3 = require("@tradejs/core/time");
|
|
|
1681
1681
|
var import_backtestArtifacts = require("@tradejs/infra/backtestArtifacts");
|
|
1682
1682
|
var import_redis4 = require("@tradejs/infra/redis");
|
|
1683
1683
|
var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
|
|
1684
|
+
var import_types3 = require("@tradejs/types");
|
|
1684
1685
|
|
|
1685
1686
|
// src/lib/runFormatting.ts
|
|
1686
1687
|
var import_date_fns = require("date-fns");
|
|
@@ -1710,6 +1711,7 @@ var import_progress = __toESM(require("progress"));
|
|
|
1710
1711
|
var import_chalk = __toESM(require("chalk"));
|
|
1711
1712
|
var import_connectors = require("@tradejs/connectors");
|
|
1712
1713
|
var import_connectors2 = require("@tradejs/node/connectors");
|
|
1714
|
+
var import_strategies = require("@tradejs/node/strategies");
|
|
1713
1715
|
var import_timescale = require("@tradejs/infra/timescale");
|
|
1714
1716
|
|
|
1715
1717
|
// src/lib/binanceMarketData.ts
|
|
@@ -1719,14 +1721,6 @@ var MARKET_FEATURE_INTERVAL_MS = {
|
|
|
1719
1721
|
"15m": 9e5,
|
|
1720
1722
|
"1h": 36e5
|
|
1721
1723
|
};
|
|
1722
|
-
var STABLE_QUOTE_SYMBOLS = /* @__PURE__ */ new Set([
|
|
1723
|
-
"USDCUSDT",
|
|
1724
|
-
"FDUSDUSDT",
|
|
1725
|
-
"TUSDUSDT",
|
|
1726
|
-
"BUSDUSDT",
|
|
1727
|
-
"USDPUSDT",
|
|
1728
|
-
"DAIUSDT"
|
|
1729
|
-
]);
|
|
1730
1724
|
var safeDivide = (num, den) => Number.isFinite(num) && Number.isFinite(den) && den !== 0 ? num / den : null;
|
|
1731
1725
|
var mean = (values) => values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
|
|
1732
1726
|
var standardDeviation = (values) => {
|
|
@@ -1770,9 +1764,6 @@ var classifyBtcAltRegime = ({
|
|
|
1770
1764
|
if (btcVsAltReturn24h < -5e-3) return "alt_lead";
|
|
1771
1765
|
return "neutral";
|
|
1772
1766
|
};
|
|
1773
|
-
var selectBreadthUniverseFromTickers = (tickers, limit) => tickers.filter(
|
|
1774
|
-
(ticker) => ticker.symbol.endsWith("USDT") && ticker.symbol !== "BTCUSDT" && !STABLE_QUOTE_SYMBOLS.has(ticker.symbol) && Number.isFinite(ticker.turnover24h) && ticker.turnover24h > 0
|
|
1775
|
-
).sort((a, b) => b.turnover24h - a.turnover24h).slice(0, Math.max(0, limit)).map((ticker) => ticker.symbol);
|
|
1776
1767
|
var aggregateAggTradesToRows = ({
|
|
1777
1768
|
symbol,
|
|
1778
1769
|
interval: interval2,
|
|
@@ -2254,10 +2245,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
2254
2245
|
process.env.BINANCE_MARKET_CONTEXT_TRADE_FLOW_SYMBOL_LIMIT,
|
|
2255
2246
|
2
|
|
2256
2247
|
);
|
|
2257
|
-
const breadthLimit = asInt(
|
|
2258
|
-
process.env.BINANCE_MARKET_CONTEXT_BREADTH_LIMIT,
|
|
2259
|
-
30
|
|
2260
|
-
);
|
|
2261
2248
|
const breadthChunkDays = asFloat(
|
|
2262
2249
|
process.env.BINANCE_MARKET_CONTEXT_BREADTH_CHUNK_DAYS,
|
|
2263
2250
|
30
|
|
@@ -2380,88 +2367,113 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
2380
2367
|
}
|
|
2381
2368
|
}
|
|
2382
2369
|
if (includeBreadth) {
|
|
2383
|
-
const
|
|
2384
|
-
const breadthSymbols =
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
);
|
|
2388
|
-
const universe = `binance_top${breadthSymbols.length}_usdt`;
|
|
2389
|
-
const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
|
|
2390
|
-
universe,
|
|
2391
|
-
interval: interval2,
|
|
2392
|
-
startMs: breadthStartMs,
|
|
2393
|
-
endMs
|
|
2394
|
-
});
|
|
2395
|
-
const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
|
|
2396
|
-
if (!hasCoverage({
|
|
2397
|
-
coverage,
|
|
2370
|
+
const breadthUniverses = (0, import_strategies.getBinanceBreadthUniverses)();
|
|
2371
|
+
const breadthSymbols = breadthUniverses.at(-1)?.symbols ?? [];
|
|
2372
|
+
const connectorInterval = marketIntervalToConnectorInterval(interval2);
|
|
2373
|
+
const chunks = buildBreadthBackfillChunks({
|
|
2398
2374
|
startMs: breadthStartMs,
|
|
2399
2375
|
endMs,
|
|
2400
|
-
intervalMs
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
chunks,
|
|
2411
|
-
coverage,
|
|
2412
|
-
intervalMs
|
|
2413
|
-
});
|
|
2414
|
-
const bar = new import_progress.default(
|
|
2415
|
-
"breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
|
|
2416
|
-
{
|
|
2417
|
-
total: Math.max(1, missingChunks.length * breadthSymbols.length),
|
|
2418
|
-
width: 24
|
|
2419
|
-
}
|
|
2420
|
-
);
|
|
2421
|
-
let candlesRead = 0;
|
|
2422
|
-
let skippedBreadthChunks = chunks.length - missingChunks.length;
|
|
2423
|
-
if (!missingChunks.length) {
|
|
2424
|
-
bar.tick(1, {
|
|
2425
|
-
candles: 0,
|
|
2426
|
-
skip: skippedBreadthChunks,
|
|
2427
|
-
chunk: "cached",
|
|
2428
|
-
symbol: universe
|
|
2376
|
+
intervalMs,
|
|
2377
|
+
chunkDays: breadthChunkDays
|
|
2378
|
+
});
|
|
2379
|
+
const plans = await Promise.all(
|
|
2380
|
+
breadthUniverses.map(async (definition) => {
|
|
2381
|
+
const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
|
|
2382
|
+
universe: definition.universe,
|
|
2383
|
+
interval: interval2,
|
|
2384
|
+
startMs: breadthStartMs,
|
|
2385
|
+
endMs
|
|
2429
2386
|
});
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
candlesBySymbol[symbol] = candles;
|
|
2446
|
-
candlesRead += candles.length;
|
|
2447
|
-
bar.tick(1, {
|
|
2448
|
-
candles: candlesRead,
|
|
2449
|
-
skip: skippedBreadthChunks,
|
|
2450
|
-
chunk: `${originalChunkIndex + 1}/${chunks.length}`,
|
|
2451
|
-
symbol
|
|
2452
|
-
});
|
|
2387
|
+
const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
|
|
2388
|
+
const missingChunks2 = !hasCoverage({
|
|
2389
|
+
coverage,
|
|
2390
|
+
startMs: breadthStartMs,
|
|
2391
|
+
endMs,
|
|
2392
|
+
intervalMs
|
|
2393
|
+
}) || !hasBtcAltMetrics ? !hasBtcAltMetrics ? chunks : filterMissingBreadthBackfillChunks({
|
|
2394
|
+
chunks,
|
|
2395
|
+
coverage,
|
|
2396
|
+
intervalMs
|
|
2397
|
+
}) : [];
|
|
2398
|
+
if (!missingChunks2.length) {
|
|
2399
|
+
console.log(
|
|
2400
|
+
import_chalk.default.gray(`breadth cached: universe=${definition.universe}`)
|
|
2401
|
+
);
|
|
2453
2402
|
}
|
|
2454
|
-
|
|
2455
|
-
|
|
2403
|
+
return {
|
|
2404
|
+
definition,
|
|
2405
|
+
missingChunkKeys: new Set(
|
|
2406
|
+
missingChunks2.map((chunk) => `${chunk.startMs}:${chunk.endMs}`)
|
|
2407
|
+
)
|
|
2408
|
+
};
|
|
2409
|
+
})
|
|
2410
|
+
);
|
|
2411
|
+
const missingChunks = chunks.filter((chunk) => {
|
|
2412
|
+
const key = `${chunk.startMs}:${chunk.endMs}`;
|
|
2413
|
+
return plans.some((plan) => plan.missingChunkKeys.has(key));
|
|
2414
|
+
});
|
|
2415
|
+
const bar = new import_progress.default(
|
|
2416
|
+
"breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
|
|
2417
|
+
{
|
|
2418
|
+
total: Math.max(1, missingChunks.length * breadthSymbols.length),
|
|
2419
|
+
width: 24
|
|
2420
|
+
}
|
|
2421
|
+
);
|
|
2422
|
+
let candlesRead = 0;
|
|
2423
|
+
const skippedBreadthChunks = chunks.length - missingChunks.length;
|
|
2424
|
+
if (!missingChunks.length) {
|
|
2425
|
+
bar.tick(1, {
|
|
2426
|
+
candles: 0,
|
|
2427
|
+
skip: skippedBreadthChunks,
|
|
2428
|
+
chunk: "cached",
|
|
2429
|
+
symbol: "top5/top10/top30/top50/top100"
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
for (let chunkIndex = 0; chunkIndex < missingChunks.length; chunkIndex += 1) {
|
|
2433
|
+
const chunk = missingChunks[chunkIndex];
|
|
2434
|
+
const originalChunkIndex = chunks.findIndex(
|
|
2435
|
+
(item) => item.startMs === chunk.startMs && item.endMs === chunk.endMs
|
|
2436
|
+
);
|
|
2437
|
+
const candlesBySymbol = {};
|
|
2438
|
+
for (const symbol of breadthSymbols) {
|
|
2439
|
+
const candles = await connector.kline({
|
|
2440
|
+
symbol,
|
|
2456
2441
|
interval: connectorInterval,
|
|
2457
2442
|
start: chunk.fetchStartMs,
|
|
2458
2443
|
end: chunk.endMs,
|
|
2459
2444
|
silent: true
|
|
2460
2445
|
});
|
|
2446
|
+
candlesBySymbol[symbol] = candles;
|
|
2447
|
+
candlesRead += candles.length;
|
|
2448
|
+
bar.tick(1, {
|
|
2449
|
+
candles: candlesRead,
|
|
2450
|
+
skip: skippedBreadthChunks,
|
|
2451
|
+
chunk: `${originalChunkIndex + 1}/${chunks.length}`,
|
|
2452
|
+
symbol
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
const btcCandles = await connector.kline({
|
|
2456
|
+
symbol: "BTCUSDT",
|
|
2457
|
+
interval: connectorInterval,
|
|
2458
|
+
start: chunk.fetchStartMs,
|
|
2459
|
+
end: chunk.endMs,
|
|
2460
|
+
silent: true
|
|
2461
|
+
});
|
|
2462
|
+
const chunkKey = `${chunk.startMs}:${chunk.endMs}`;
|
|
2463
|
+
for (const plan of plans) {
|
|
2464
|
+
if (!plan.missingChunkKeys.has(chunkKey)) {
|
|
2465
|
+
continue;
|
|
2466
|
+
}
|
|
2467
|
+
const symbolSet = new Set(plan.definition.symbols);
|
|
2468
|
+
const universeCandles = Object.fromEntries(
|
|
2469
|
+
Object.entries(candlesBySymbol).filter(
|
|
2470
|
+
([symbol]) => symbolSet.has(symbol)
|
|
2471
|
+
)
|
|
2472
|
+
);
|
|
2461
2473
|
const rows = buildMarketBreadthRows({
|
|
2462
|
-
universe,
|
|
2474
|
+
universe: plan.definition.universe,
|
|
2463
2475
|
interval: interval2,
|
|
2464
|
-
candlesBySymbol,
|
|
2476
|
+
candlesBySymbol: universeCandles,
|
|
2465
2477
|
btcCandles
|
|
2466
2478
|
}).filter((row) => {
|
|
2467
2479
|
const ts = row.ts.getTime();
|
|
@@ -2470,8 +2482,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
2470
2482
|
await (0, import_timescale.upsertMarketBreadthRows)(rows);
|
|
2471
2483
|
breadthRows += rows.length;
|
|
2472
2484
|
}
|
|
2473
|
-
} else {
|
|
2474
|
-
console.log(import_chalk.default.gray(`breadth cached: universe=${universe}`));
|
|
2475
2485
|
}
|
|
2476
2486
|
}
|
|
2477
2487
|
console.log(
|
|
@@ -4666,11 +4676,30 @@ var resolveTimeWindow = ({
|
|
|
4666
4676
|
endTime,
|
|
4667
4677
|
nowMs = Date.now(),
|
|
4668
4678
|
defaultStartMs,
|
|
4669
|
-
defaultEndMs
|
|
4679
|
+
defaultEndMs,
|
|
4680
|
+
closedIntervalMs
|
|
4670
4681
|
}) => {
|
|
4671
4682
|
const explicitStart = toEpochMs(startTime);
|
|
4672
4683
|
const explicitEnd = toEpochMs(endTime);
|
|
4673
4684
|
const resolvedDays = toPositiveNumber(days);
|
|
4685
|
+
const alignToLastClosedInterval = (window2, preserveDuration) => {
|
|
4686
|
+
if (typeof closedIntervalMs !== "number" || !Number.isFinite(closedIntervalMs) || closedIntervalMs <= 0) {
|
|
4687
|
+
return window2;
|
|
4688
|
+
}
|
|
4689
|
+
const alignedEnd = Math.floor(window2.end / closedIntervalMs) * closedIntervalMs - 1;
|
|
4690
|
+
const delta = window2.end - alignedEnd;
|
|
4691
|
+
const alignedStart = preserveDuration ? window2.start - delta : window2.start;
|
|
4692
|
+
if (alignedStart >= alignedEnd) {
|
|
4693
|
+
throw new Error(
|
|
4694
|
+
`Invalid closed-candle window: start (${alignedStart}) must be less than end (${alignedEnd})`
|
|
4695
|
+
);
|
|
4696
|
+
}
|
|
4697
|
+
return {
|
|
4698
|
+
...window2,
|
|
4699
|
+
start: Math.trunc(alignedStart),
|
|
4700
|
+
end: Math.trunc(alignedEnd)
|
|
4701
|
+
};
|
|
4702
|
+
};
|
|
4674
4703
|
if (explicitStart != null || explicitEnd != null) {
|
|
4675
4704
|
const resolvedStart = explicitStart ?? (resolvedDays != null && explicitEnd != null ? explicitEnd - resolvedDays * ONE_DAY_MS : defaultStartMs);
|
|
4676
4705
|
const resolvedEnd2 = explicitEnd ?? (resolvedDays != null && explicitStart != null ? explicitStart + resolvedDays * ONE_DAY_MS : defaultEndMs ?? nowMs);
|
|
@@ -4679,11 +4708,14 @@ var resolveTimeWindow = ({
|
|
|
4679
4708
|
`Invalid time window: startTime (${resolvedStart}) must be less than endTime (${resolvedEnd2})`
|
|
4680
4709
|
);
|
|
4681
4710
|
}
|
|
4682
|
-
return
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4711
|
+
return alignToLastClosedInterval(
|
|
4712
|
+
{
|
|
4713
|
+
start: resolvedStart,
|
|
4714
|
+
end: resolvedEnd2,
|
|
4715
|
+
source: "explicit"
|
|
4716
|
+
},
|
|
4717
|
+
explicitStart == null && resolvedDays != null
|
|
4718
|
+
);
|
|
4687
4719
|
}
|
|
4688
4720
|
if (resolvedDays != null) {
|
|
4689
4721
|
const resolvedEnd2 = defaultEndMs ?? nowMs;
|
|
@@ -4693,11 +4725,14 @@ var resolveTimeWindow = ({
|
|
|
4693
4725
|
`Invalid time window: computed start (${resolvedStart}) must be less than end (${resolvedEnd2})`
|
|
4694
4726
|
);
|
|
4695
4727
|
}
|
|
4696
|
-
return
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4728
|
+
return alignToLastClosedInterval(
|
|
4729
|
+
{
|
|
4730
|
+
start: Math.trunc(resolvedStart),
|
|
4731
|
+
end: Math.trunc(resolvedEnd2),
|
|
4732
|
+
source: "days"
|
|
4733
|
+
},
|
|
4734
|
+
true
|
|
4735
|
+
);
|
|
4701
4736
|
}
|
|
4702
4737
|
const resolvedEnd = defaultEndMs ?? nowMs;
|
|
4703
4738
|
if (defaultStartMs >= resolvedEnd) {
|
|
@@ -4705,11 +4740,14 @@ var resolveTimeWindow = ({
|
|
|
4705
4740
|
`Invalid default time window: start (${defaultStartMs}) must be less than end (${resolvedEnd})`
|
|
4706
4741
|
);
|
|
4707
4742
|
}
|
|
4708
|
-
return
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4743
|
+
return alignToLastClosedInterval(
|
|
4744
|
+
{
|
|
4745
|
+
start: Math.trunc(defaultStartMs),
|
|
4746
|
+
end: Math.trunc(resolvedEnd),
|
|
4747
|
+
source: "default"
|
|
4748
|
+
},
|
|
4749
|
+
true
|
|
4750
|
+
);
|
|
4713
4751
|
};
|
|
4714
4752
|
|
|
4715
4753
|
// src/lib/runtimeStrategyBacktest.ts
|
|
@@ -5095,7 +5133,8 @@ var prepareRunEnvironment = async ({
|
|
|
5095
5133
|
accountId,
|
|
5096
5134
|
deploymentId,
|
|
5097
5135
|
assetClasses,
|
|
5098
|
-
deployment
|
|
5136
|
+
deployment,
|
|
5137
|
+
closedIntervalMs
|
|
5099
5138
|
}) => {
|
|
5100
5139
|
const connectorName = await resolveRunConnectorName({
|
|
5101
5140
|
value: connector,
|
|
@@ -5158,7 +5197,8 @@ var prepareRunEnvironment = async ({
|
|
|
5158
5197
|
startTime,
|
|
5159
5198
|
endTime,
|
|
5160
5199
|
defaultStartMs: (0, import_time2.getTimestamp)(import_constants2.BACKTEST_DEFAULT_DAYS),
|
|
5161
|
-
defaultEndMs: (0, import_time2.getTimestamp)()
|
|
5200
|
+
defaultEndMs: (0, import_time2.getTimestamp)(),
|
|
5201
|
+
closedIntervalMs
|
|
5162
5202
|
});
|
|
5163
5203
|
const preloadStart = (0, import_time2.getBacktestPreloadStart)(
|
|
5164
5204
|
window2.start,
|
|
@@ -5553,8 +5593,12 @@ var markBacktestRunStatus = async ({
|
|
|
5553
5593
|
};
|
|
5554
5594
|
|
|
5555
5595
|
// src/lib/backtest/runState.ts
|
|
5596
|
+
var import_types2 = require("@tradejs/types");
|
|
5556
5597
|
var successTests = 0;
|
|
5557
5598
|
var errorTests = 0;
|
|
5599
|
+
var warningCounts = {
|
|
5600
|
+
[import_types2.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY]: 0
|
|
5601
|
+
};
|
|
5558
5602
|
var errorMessages = [];
|
|
5559
5603
|
var topResults = [];
|
|
5560
5604
|
var bestTickerResults = /* @__PURE__ */ new Map();
|
|
@@ -5601,6 +5645,14 @@ var addResultToAggregate = (aggregate, result) => {
|
|
|
5601
5645
|
aggregate.winRateCount += 1;
|
|
5602
5646
|
}
|
|
5603
5647
|
};
|
|
5648
|
+
var addResultWarnings = (result) => {
|
|
5649
|
+
for (const code of Object.values(import_types2.BACKTEST_WARNING_CODES)) {
|
|
5650
|
+
const count = Number(result.warningCounts?.[code] ?? 0);
|
|
5651
|
+
if (Number.isFinite(count) && count > 0) {
|
|
5652
|
+
warningCounts[code] = (warningCounts[code] ?? 0) + count;
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
};
|
|
5604
5656
|
var getAggregateAverageProfit = (aggregate) => aggregate.count > 0 ? aggregate.netProfitSum / aggregate.count : 0;
|
|
5605
5657
|
var getAggregateWinRate = (aggregate) => {
|
|
5606
5658
|
const closedTrades = aggregate.wins + aggregate.losses;
|
|
@@ -5626,6 +5678,7 @@ var markTestsStarted = () => {
|
|
|
5626
5678
|
var getRunCounters = () => ({
|
|
5627
5679
|
successTests,
|
|
5628
5680
|
errorTests,
|
|
5681
|
+
warningCounts: { ...warningCounts },
|
|
5629
5682
|
errors: [...errorMessages]
|
|
5630
5683
|
});
|
|
5631
5684
|
var getTopResults = () => topResults;
|
|
@@ -5639,6 +5692,7 @@ var setBestTickerResultForSymbol = (symbol, result) => {
|
|
|
5639
5692
|
};
|
|
5640
5693
|
var recordResultAggregates = (result) => {
|
|
5641
5694
|
addResultToAggregate(progressStats, result);
|
|
5695
|
+
addResultWarnings(result);
|
|
5642
5696
|
const configId = result.test.configId || result.test.name;
|
|
5643
5697
|
const existing = configResultBuckets.get(configId);
|
|
5644
5698
|
const bucket = existing ?? {
|
|
@@ -5688,6 +5742,9 @@ var setRuntimeCompareContext = ({
|
|
|
5688
5742
|
var resetRunState = () => {
|
|
5689
5743
|
successTests = 0;
|
|
5690
5744
|
errorTests = 0;
|
|
5745
|
+
for (const code of Object.values(import_types2.BACKTEST_WARNING_CODES)) {
|
|
5746
|
+
warningCounts[code] = 0;
|
|
5747
|
+
}
|
|
5691
5748
|
errorMessages.length = 0;
|
|
5692
5749
|
topResults = [];
|
|
5693
5750
|
bestTickerResults.clear();
|
|
@@ -6284,9 +6341,15 @@ var printRunOutro = () => {
|
|
|
6284
6341
|
import_chalk9.default.gray(`backtest total: done in ${formatDuration(getRunStartedAt())}`)
|
|
6285
6342
|
);
|
|
6286
6343
|
console.log("");
|
|
6287
|
-
const { successTests: successTests2, errorTests: errorTests2 } = getRunCounters();
|
|
6344
|
+
const { successTests: successTests2, errorTests: errorTests2, warningCounts: warningCounts2 } = getRunCounters();
|
|
6288
6345
|
console.log(`${import_chalk9.default.green("SUCCESS TESTS")}: ${successTests2}`);
|
|
6289
6346
|
console.log(`${import_chalk9.default.red("ERRORS")}: ${errorTests2}`);
|
|
6347
|
+
const takeProfitCrossedWarnings = warningCounts2[import_types3.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY] ?? 0;
|
|
6348
|
+
console.log(
|
|
6349
|
+
import_chalk9.default.yellow(
|
|
6350
|
+
`WARNINGS (${import_types3.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY}): ${takeProfitCrossedWarnings}`
|
|
6351
|
+
)
|
|
6352
|
+
);
|
|
6290
6353
|
console.log("");
|
|
6291
6354
|
};
|
|
6292
6355
|
|
|
@@ -0,0 +1,120 @@
|
|
|
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/scripts/binanceBreadthUniversesUpdate.ts
|
|
31
|
+
var binanceBreadthUniversesUpdate_exports = {};
|
|
32
|
+
__export(binanceBreadthUniversesUpdate_exports, {
|
|
33
|
+
BINANCE_BREADTH_UNIVERSES_RELATIVE_PATH: () => BINANCE_BREADTH_UNIVERSES_RELATIVE_PATH,
|
|
34
|
+
main: () => main
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(binanceBreadthUniversesUpdate_exports);
|
|
37
|
+
var import_config = require("dotenv/config");
|
|
38
|
+
var import_args = __toESM(require("args"));
|
|
39
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
40
|
+
var import_node_path = __toESM(require("path"));
|
|
41
|
+
var import_chalk = __toESM(require("chalk"));
|
|
42
|
+
var import_connectors = require("@tradejs/connectors");
|
|
43
|
+
var import_connectors2 = require("@tradejs/node/connectors");
|
|
44
|
+
var import_strategies = require("@tradejs/node/strategies");
|
|
45
|
+
|
|
46
|
+
// src/lib/binanceMarketData.ts
|
|
47
|
+
var STABLE_QUOTE_SYMBOLS = /* @__PURE__ */ new Set([
|
|
48
|
+
"USDCUSDT",
|
|
49
|
+
"FDUSDUSDT",
|
|
50
|
+
"TUSDUSDT",
|
|
51
|
+
"BUSDUSDT",
|
|
52
|
+
"USDPUSDT",
|
|
53
|
+
"DAIUSDT"
|
|
54
|
+
]);
|
|
55
|
+
var selectBreadthUniverseFromTickers = (tickers, limit) => tickers.filter(
|
|
56
|
+
(ticker) => ticker.symbol.endsWith("USDT") && ticker.symbol !== "BTCUSDT" && !STABLE_QUOTE_SYMBOLS.has(ticker.symbol) && Number.isFinite(ticker.turnover24h) && ticker.turnover24h > 0
|
|
57
|
+
).sort((a, b) => b.turnover24h - a.turnover24h).slice(0, Math.max(0, limit)).map((ticker) => ticker.symbol);
|
|
58
|
+
|
|
59
|
+
// src/scripts/binanceBreadthUniversesUpdate.ts
|
|
60
|
+
import_args.default.example(
|
|
61
|
+
"yarn breadth:universes:update --write",
|
|
62
|
+
"Refresh the versioned Binance top5/top10/top30/top50/top100 breadth snapshot"
|
|
63
|
+
);
|
|
64
|
+
import_args.default.option(
|
|
65
|
+
["w", "write"],
|
|
66
|
+
"Write the snapshot; without this flag only print the proposed update",
|
|
67
|
+
false
|
|
68
|
+
);
|
|
69
|
+
import_args.default.option(["u", "user"], "Connector user name", "root");
|
|
70
|
+
var flags = import_args.default.parse(process.argv);
|
|
71
|
+
var BINANCE_BREADTH_UNIVERSES_RELATIVE_PATH = "packages/node/src/config/binanceBreadthUniverses.json";
|
|
72
|
+
var main = async () => {
|
|
73
|
+
const projectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
|
|
74
|
+
const connectorCreator = await (0, import_connectors2.getConnectorCreatorByName)(
|
|
75
|
+
import_connectors.ConnectorNames.Binance,
|
|
76
|
+
projectRoot
|
|
77
|
+
);
|
|
78
|
+
if (!connectorCreator) {
|
|
79
|
+
throw new Error("Binance connector is not registered");
|
|
80
|
+
}
|
|
81
|
+
const connector = await connectorCreator({
|
|
82
|
+
userName: String(flags.user || "root"),
|
|
83
|
+
universe: "crypto"
|
|
84
|
+
});
|
|
85
|
+
const rankedSymbols = selectBreadthUniverseFromTickers(
|
|
86
|
+
await connector.getTickers(),
|
|
87
|
+
100
|
|
88
|
+
);
|
|
89
|
+
const snapshot = (0, import_strategies.buildBinanceBreadthUniverseSnapshot)({ rankedSymbols });
|
|
90
|
+
const serialized = `${JSON.stringify(snapshot, null, 2)}
|
|
91
|
+
`;
|
|
92
|
+
console.log(
|
|
93
|
+
import_chalk.default.cyan(
|
|
94
|
+
`Binance breadth snapshot ${snapshot.fingerprint}: ${Object.entries(
|
|
95
|
+
snapshot.universes
|
|
96
|
+
).map(
|
|
97
|
+
([key, definition]) => `${key}=${definition.size}:${definition.fingerprint}`
|
|
98
|
+
).join(" ")}`
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
if (!flags.write) {
|
|
102
|
+
console.log(serialized);
|
|
103
|
+
console.log(
|
|
104
|
+
import_chalk.default.yellow("Dry run only. Pass --write to update the snapshot file.")
|
|
105
|
+
);
|
|
106
|
+
return snapshot;
|
|
107
|
+
}
|
|
108
|
+
const outputPath = import_node_path.default.join(
|
|
109
|
+
projectRoot,
|
|
110
|
+
BINANCE_BREADTH_UNIVERSES_RELATIVE_PATH
|
|
111
|
+
);
|
|
112
|
+
await import_promises.default.writeFile(outputPath, serialized, "utf8");
|
|
113
|
+
console.log(import_chalk.default.green(`Updated ${outputPath}`));
|
|
114
|
+
return snapshot;
|
|
115
|
+
};
|
|
116
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
117
|
+
0 && (module.exports = {
|
|
118
|
+
BINANCE_BREADTH_UNIVERSES_RELATIVE_PATH,
|
|
119
|
+
main
|
|
120
|
+
});
|
|
@@ -37,6 +37,7 @@ var import_config = require("dotenv/config");
|
|
|
37
37
|
var import_args = __toESM(require("args"));
|
|
38
38
|
var import_chalk = __toESM(require("chalk"));
|
|
39
39
|
var import_connectors = require("@tradejs/connectors");
|
|
40
|
+
var import_strategies = require("@tradejs/node/strategies");
|
|
40
41
|
var import_timescale = require("@tradejs/infra/timescale");
|
|
41
42
|
|
|
42
43
|
// src/lib/binanceMarketData.ts
|
|
@@ -46,14 +47,6 @@ var MARKET_FEATURE_INTERVAL_MS = {
|
|
|
46
47
|
"15m": 9e5,
|
|
47
48
|
"1h": 36e5
|
|
48
49
|
};
|
|
49
|
-
var STABLE_QUOTE_SYMBOLS = /* @__PURE__ */ new Set([
|
|
50
|
-
"USDCUSDT",
|
|
51
|
-
"FDUSDUSDT",
|
|
52
|
-
"TUSDUSDT",
|
|
53
|
-
"BUSDUSDT",
|
|
54
|
-
"USDPUSDT",
|
|
55
|
-
"DAIUSDT"
|
|
56
|
-
]);
|
|
57
50
|
var safeDivide = (num, den) => Number.isFinite(num) && Number.isFinite(den) && den !== 0 ? num / den : null;
|
|
58
51
|
var mean = (values) => values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
|
|
59
52
|
var standardDeviation = (values) => {
|
|
@@ -106,23 +99,23 @@ var normalizeMarketFeatureInterval = (value) => {
|
|
|
106
99
|
return "15m";
|
|
107
100
|
};
|
|
108
101
|
var normalizeBinanceSymbols = (value) => String(value || "").split(",").map((item) => item.trim().toUpperCase()).filter(Boolean);
|
|
109
|
-
var selectBreadthUniverseFromTickers = (tickers, limit) => tickers.filter(
|
|
110
|
-
(ticker) => ticker.symbol.endsWith("USDT") && ticker.symbol !== "BTCUSDT" && !STABLE_QUOTE_SYMBOLS.has(ticker.symbol) && Number.isFinite(ticker.turnover24h) && ticker.turnover24h > 0
|
|
111
|
-
).sort((a, b) => b.turnover24h - a.turnover24h).slice(0, Math.max(0, limit)).map((ticker) => ticker.symbol);
|
|
112
102
|
var estimateBinanceMarketDataVolume = ({
|
|
113
103
|
symbols,
|
|
114
104
|
days,
|
|
115
105
|
interval,
|
|
116
106
|
includeAggTrades,
|
|
117
107
|
includeBreadth,
|
|
118
|
-
|
|
108
|
+
breadthSizes
|
|
119
109
|
}) => {
|
|
120
110
|
const intervalMs = MARKET_FEATURE_INTERVAL_MS[interval];
|
|
121
111
|
const bucketRowsPerSymbol = Math.ceil(days * 864e5 / intervalMs);
|
|
122
112
|
const aggTradeBucketRows = includeAggTrades ? symbols.length * bucketRowsPerSymbol : 0;
|
|
123
|
-
const
|
|
113
|
+
const normalizedBreadthSizes = breadthSizes.filter(
|
|
114
|
+
(size) => Number.isFinite(size) && size > 0
|
|
115
|
+
);
|
|
116
|
+
const breadthSymbols = includeBreadth ? Math.max(0, ...normalizedBreadthSizes) : 0;
|
|
124
117
|
const breadthCandleRows = breadthSymbols * bucketRowsPerSymbol;
|
|
125
|
-
const breadthRows = includeBreadth ? bucketRowsPerSymbol : 0;
|
|
118
|
+
const breadthRows = includeBreadth ? bucketRowsPerSymbol * normalizedBreadthSizes.length : 0;
|
|
126
119
|
return {
|
|
127
120
|
interval,
|
|
128
121
|
days,
|
|
@@ -379,11 +372,6 @@ import_args.default.option(
|
|
|
379
372
|
["w", "write"],
|
|
380
373
|
"Write rows to Timescale; without this flag only estimate"
|
|
381
374
|
);
|
|
382
|
-
import_args.default.option(
|
|
383
|
-
["B", "breadthLimit"],
|
|
384
|
-
"Top USDT symbols used for breadth universe",
|
|
385
|
-
30
|
|
386
|
-
);
|
|
387
375
|
import_args.default.option(
|
|
388
376
|
["M", "batchMinutes"],
|
|
389
377
|
"aggTrades request window size in minutes",
|
|
@@ -463,7 +451,7 @@ var main = async () => {
|
|
|
463
451
|
const interval = normalizeMarketFeatureInterval(flags.interval);
|
|
464
452
|
const hours = flags.hours == null ? null : asFloat(flags.hours, 0);
|
|
465
453
|
const days = hours != null && hours > 0 ? hours / 24 : asFloat(flags.days, 1);
|
|
466
|
-
const
|
|
454
|
+
const breadthUniverses = (0, import_strategies.getBinanceBreadthUniverses)();
|
|
467
455
|
const batchMinutes = asInt(flags.batchMinutes, 15);
|
|
468
456
|
const requestDelayMs = asInt(flags.requestDelayMs, 75);
|
|
469
457
|
const includeAll = Boolean(flags.all);
|
|
@@ -481,7 +469,7 @@ var main = async () => {
|
|
|
481
469
|
interval,
|
|
482
470
|
includeAggTrades: modes.includeAggTrades,
|
|
483
471
|
includeBreadth: modes.includeBreadth,
|
|
484
|
-
|
|
472
|
+
breadthSizes: breadthUniverses.map(({ size }) => size)
|
|
485
473
|
});
|
|
486
474
|
printEstimate(estimate);
|
|
487
475
|
if (!flags.write) {
|
|
@@ -520,11 +508,7 @@ var main = async () => {
|
|
|
520
508
|
}
|
|
521
509
|
}
|
|
522
510
|
if (modes.includeBreadth) {
|
|
523
|
-
const
|
|
524
|
-
const breadthSymbols = selectBreadthUniverseFromTickers(
|
|
525
|
-
tickers,
|
|
526
|
-
breadthLimit
|
|
527
|
-
);
|
|
511
|
+
const breadthSymbols = breadthUniverses.at(-1)?.symbols ?? [];
|
|
528
512
|
const connectorInterval = intervalToConnectorInterval(interval);
|
|
529
513
|
const candlesBySymbol = {};
|
|
530
514
|
for (const symbol of breadthSymbols) {
|
|
@@ -541,14 +525,29 @@ var main = async () => {
|
|
|
541
525
|
process.stdout.write(`${candles.length}
|
|
542
526
|
`);
|
|
543
527
|
}
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
528
|
+
const btcCandles = await connector.kline({
|
|
529
|
+
symbol: "BTCUSDT",
|
|
530
|
+
interval: connectorInterval,
|
|
531
|
+
start: fromMs,
|
|
532
|
+
end: toMs,
|
|
533
|
+
silent: true
|
|
549
534
|
});
|
|
550
|
-
|
|
551
|
-
|
|
535
|
+
for (const definition of breadthUniverses) {
|
|
536
|
+
const symbolSet = new Set(definition.symbols);
|
|
537
|
+
const universeCandles = Object.fromEntries(
|
|
538
|
+
Object.entries(candlesBySymbol).filter(
|
|
539
|
+
([symbol]) => symbolSet.has(symbol)
|
|
540
|
+
)
|
|
541
|
+
);
|
|
542
|
+
const rows = buildMarketBreadthRows({
|
|
543
|
+
universe: definition.universe,
|
|
544
|
+
interval,
|
|
545
|
+
candlesBySymbol: universeCandles,
|
|
546
|
+
btcCandles
|
|
547
|
+
});
|
|
548
|
+
await (0, import_timescale.upsertMarketBreadthRows)(rows);
|
|
549
|
+
breadthRows += rows.length;
|
|
550
|
+
}
|
|
552
551
|
}
|
|
553
552
|
console.log(import_chalk.default.green("Binance market ingest done"));
|
|
554
553
|
console.log(`aggTrades raw rows: ${aggTradesRaw}`);
|