@tradejs/cli 2.0.6 → 2.0.8
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
|
@@ -46,6 +46,7 @@ var import_progress = __toESM(require("progress"));
|
|
|
46
46
|
var import_chalk = __toESM(require("chalk"));
|
|
47
47
|
var import_connectors = require("@tradejs/connectors");
|
|
48
48
|
var import_connectors2 = require("@tradejs/node/connectors");
|
|
49
|
+
var import_strategies = require("@tradejs/node/strategies");
|
|
49
50
|
var import_timescale = require("@tradejs/infra/timescale");
|
|
50
51
|
|
|
51
52
|
// src/lib/binanceMarketData.ts
|
|
@@ -55,14 +56,6 @@ var MARKET_FEATURE_INTERVAL_MS = {
|
|
|
55
56
|
"15m": 9e5,
|
|
56
57
|
"1h": 36e5
|
|
57
58
|
};
|
|
58
|
-
var STABLE_QUOTE_SYMBOLS = /* @__PURE__ */ new Set([
|
|
59
|
-
"USDCUSDT",
|
|
60
|
-
"FDUSDUSDT",
|
|
61
|
-
"TUSDUSDT",
|
|
62
|
-
"BUSDUSDT",
|
|
63
|
-
"USDPUSDT",
|
|
64
|
-
"DAIUSDT"
|
|
65
|
-
]);
|
|
66
59
|
var safeDivide = (num, den) => Number.isFinite(num) && Number.isFinite(den) && den !== 0 ? num / den : null;
|
|
67
60
|
var mean = (values) => values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
|
|
68
61
|
var standardDeviation = (values) => {
|
|
@@ -106,9 +99,6 @@ var classifyBtcAltRegime = ({
|
|
|
106
99
|
if (btcVsAltReturn24h < -5e-3) return "alt_lead";
|
|
107
100
|
return "neutral";
|
|
108
101
|
};
|
|
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 aggregateAggTradesToRows = ({
|
|
113
103
|
symbol,
|
|
114
104
|
interval,
|
|
@@ -590,10 +580,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
590
580
|
process.env.BINANCE_MARKET_CONTEXT_TRADE_FLOW_SYMBOL_LIMIT,
|
|
591
581
|
2
|
|
592
582
|
);
|
|
593
|
-
const breadthLimit = asInt(
|
|
594
|
-
process.env.BINANCE_MARKET_CONTEXT_BREADTH_LIMIT,
|
|
595
|
-
30
|
|
596
|
-
);
|
|
597
583
|
const breadthChunkDays = asFloat(
|
|
598
584
|
process.env.BINANCE_MARKET_CONTEXT_BREADTH_CHUNK_DAYS,
|
|
599
585
|
30
|
|
@@ -716,88 +702,113 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
716
702
|
}
|
|
717
703
|
}
|
|
718
704
|
if (includeBreadth) {
|
|
719
|
-
const
|
|
720
|
-
const breadthSymbols =
|
|
721
|
-
|
|
722
|
-
|
|
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,
|
|
705
|
+
const breadthUniverses = (0, import_strategies.getBinanceBreadthUniverses)();
|
|
706
|
+
const breadthSymbols = breadthUniverses.at(-1)?.symbols ?? [];
|
|
707
|
+
const connectorInterval = marketIntervalToConnectorInterval(interval);
|
|
708
|
+
const chunks = buildBreadthBackfillChunks({
|
|
734
709
|
startMs: breadthStartMs,
|
|
735
710
|
endMs,
|
|
736
|
-
intervalMs
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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
|
|
711
|
+
intervalMs,
|
|
712
|
+
chunkDays: breadthChunkDays
|
|
713
|
+
});
|
|
714
|
+
const plans = await Promise.all(
|
|
715
|
+
breadthUniverses.map(async (definition) => {
|
|
716
|
+
const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
|
|
717
|
+
universe: definition.universe,
|
|
718
|
+
interval,
|
|
719
|
+
startMs: breadthStartMs,
|
|
720
|
+
endMs
|
|
765
721
|
});
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
-
});
|
|
722
|
+
const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
|
|
723
|
+
const missingChunks2 = !hasCoverage({
|
|
724
|
+
coverage,
|
|
725
|
+
startMs: breadthStartMs,
|
|
726
|
+
endMs,
|
|
727
|
+
intervalMs
|
|
728
|
+
}) || !hasBtcAltMetrics ? !hasBtcAltMetrics ? chunks : filterMissingBreadthBackfillChunks({
|
|
729
|
+
chunks,
|
|
730
|
+
coverage,
|
|
731
|
+
intervalMs
|
|
732
|
+
}) : [];
|
|
733
|
+
if (!missingChunks2.length) {
|
|
734
|
+
console.log(
|
|
735
|
+
import_chalk.default.gray(`breadth cached: universe=${definition.universe}`)
|
|
736
|
+
);
|
|
789
737
|
}
|
|
790
|
-
|
|
791
|
-
|
|
738
|
+
return {
|
|
739
|
+
definition,
|
|
740
|
+
missingChunkKeys: new Set(
|
|
741
|
+
missingChunks2.map((chunk) => `${chunk.startMs}:${chunk.endMs}`)
|
|
742
|
+
)
|
|
743
|
+
};
|
|
744
|
+
})
|
|
745
|
+
);
|
|
746
|
+
const missingChunks = chunks.filter((chunk) => {
|
|
747
|
+
const key = `${chunk.startMs}:${chunk.endMs}`;
|
|
748
|
+
return plans.some((plan) => plan.missingChunkKeys.has(key));
|
|
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
|
+
const skippedBreadthChunks = chunks.length - missingChunks.length;
|
|
759
|
+
if (!missingChunks.length) {
|
|
760
|
+
bar.tick(1, {
|
|
761
|
+
candles: 0,
|
|
762
|
+
skip: skippedBreadthChunks,
|
|
763
|
+
chunk: "cached",
|
|
764
|
+
symbol: "top5/top10/top30/top50/top100"
|
|
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,
|
|
792
776
|
interval: connectorInterval,
|
|
793
777
|
start: chunk.fetchStartMs,
|
|
794
778
|
end: chunk.endMs,
|
|
795
779
|
silent: true
|
|
796
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 chunkKey = `${chunk.startMs}:${chunk.endMs}`;
|
|
798
|
+
for (const plan of plans) {
|
|
799
|
+
if (!plan.missingChunkKeys.has(chunkKey)) {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
const symbolSet = new Set(plan.definition.symbols);
|
|
803
|
+
const universeCandles = Object.fromEntries(
|
|
804
|
+
Object.entries(candlesBySymbol).filter(
|
|
805
|
+
([symbol]) => symbolSet.has(symbol)
|
|
806
|
+
)
|
|
807
|
+
);
|
|
797
808
|
const rows = buildMarketBreadthRows({
|
|
798
|
-
universe,
|
|
809
|
+
universe: plan.definition.universe,
|
|
799
810
|
interval,
|
|
800
|
-
candlesBySymbol,
|
|
811
|
+
candlesBySymbol: universeCandles,
|
|
801
812
|
btcCandles
|
|
802
813
|
}).filter((row) => {
|
|
803
814
|
const ts = row.ts.getTime();
|
|
@@ -806,8 +817,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
806
817
|
await (0, import_timescale.upsertMarketBreadthRows)(rows);
|
|
807
818
|
breadthRows += rows.length;
|
|
808
819
|
}
|
|
809
|
-
} else {
|
|
810
|
-
console.log(import_chalk.default.gray(`breadth cached: universe=${universe}`));
|
|
811
820
|
}
|
|
812
821
|
}
|
|
813
822
|
console.log(
|
|
@@ -106,14 +106,17 @@ var estimateBinanceMarketDataVolume = ({
|
|
|
106
106
|
interval,
|
|
107
107
|
includeAggTrades,
|
|
108
108
|
includeBreadth,
|
|
109
|
-
|
|
109
|
+
breadthSizes
|
|
110
110
|
}) => {
|
|
111
111
|
const intervalMs = MARKET_FEATURE_INTERVAL_MS[interval];
|
|
112
112
|
const bucketRowsPerSymbol = Math.ceil(days * 864e5 / intervalMs);
|
|
113
113
|
const aggTradeBucketRows = includeAggTrades ? symbols.length * bucketRowsPerSymbol : 0;
|
|
114
|
-
const
|
|
114
|
+
const normalizedBreadthSizes = breadthSizes.filter(
|
|
115
|
+
(size) => Number.isFinite(size) && size > 0
|
|
116
|
+
);
|
|
117
|
+
const breadthSymbols = includeBreadth ? Math.max(0, ...normalizedBreadthSizes) : 0;
|
|
115
118
|
const breadthCandleRows = breadthSymbols * bucketRowsPerSymbol;
|
|
116
|
-
const breadthRows = includeBreadth ? bucketRowsPerSymbol : 0;
|
|
119
|
+
const breadthRows = includeBreadth ? bucketRowsPerSymbol * normalizedBreadthSizes.length : 0;
|
|
117
120
|
return {
|
|
118
121
|
interval,
|
|
119
122
|
days,
|
|
@@ -1649,6 +1649,7 @@ var import_progress = __toESM(require("progress"));
|
|
|
1649
1649
|
var import_chalk = __toESM(require("chalk"));
|
|
1650
1650
|
var import_connectors = require("@tradejs/connectors");
|
|
1651
1651
|
var import_connectors2 = require("@tradejs/node/connectors");
|
|
1652
|
+
var import_strategies = require("@tradejs/node/strategies");
|
|
1652
1653
|
var import_timescale = require("@tradejs/infra/timescale");
|
|
1653
1654
|
|
|
1654
1655
|
// src/lib/binanceMarketData.ts
|
|
@@ -1658,14 +1659,6 @@ var MARKET_FEATURE_INTERVAL_MS = {
|
|
|
1658
1659
|
"15m": 9e5,
|
|
1659
1660
|
"1h": 36e5
|
|
1660
1661
|
};
|
|
1661
|
-
var STABLE_QUOTE_SYMBOLS = /* @__PURE__ */ new Set([
|
|
1662
|
-
"USDCUSDT",
|
|
1663
|
-
"FDUSDUSDT",
|
|
1664
|
-
"TUSDUSDT",
|
|
1665
|
-
"BUSDUSDT",
|
|
1666
|
-
"USDPUSDT",
|
|
1667
|
-
"DAIUSDT"
|
|
1668
|
-
]);
|
|
1669
1662
|
var safeDivide = (num, den) => Number.isFinite(num) && Number.isFinite(den) && den !== 0 ? num / den : null;
|
|
1670
1663
|
var mean = (values) => values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
|
|
1671
1664
|
var standardDeviation = (values) => {
|
|
@@ -1709,9 +1702,6 @@ var classifyBtcAltRegime = ({
|
|
|
1709
1702
|
if (btcVsAltReturn24h < -5e-3) return "alt_lead";
|
|
1710
1703
|
return "neutral";
|
|
1711
1704
|
};
|
|
1712
|
-
var selectBreadthUniverseFromTickers = (tickers, limit) => tickers.filter(
|
|
1713
|
-
(ticker) => ticker.symbol.endsWith("USDT") && ticker.symbol !== "BTCUSDT" && !STABLE_QUOTE_SYMBOLS.has(ticker.symbol) && Number.isFinite(ticker.turnover24h) && ticker.turnover24h > 0
|
|
1714
|
-
).sort((a, b) => b.turnover24h - a.turnover24h).slice(0, Math.max(0, limit)).map((ticker) => ticker.symbol);
|
|
1715
1705
|
var aggregateAggTradesToRows = ({
|
|
1716
1706
|
symbol,
|
|
1717
1707
|
interval,
|
|
@@ -2193,10 +2183,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
2193
2183
|
process.env.BINANCE_MARKET_CONTEXT_TRADE_FLOW_SYMBOL_LIMIT,
|
|
2194
2184
|
2
|
|
2195
2185
|
);
|
|
2196
|
-
const breadthLimit = asInt(
|
|
2197
|
-
process.env.BINANCE_MARKET_CONTEXT_BREADTH_LIMIT,
|
|
2198
|
-
30
|
|
2199
|
-
);
|
|
2200
2186
|
const breadthChunkDays = asFloat(
|
|
2201
2187
|
process.env.BINANCE_MARKET_CONTEXT_BREADTH_CHUNK_DAYS,
|
|
2202
2188
|
30
|
|
@@ -2319,88 +2305,113 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
2319
2305
|
}
|
|
2320
2306
|
}
|
|
2321
2307
|
if (includeBreadth) {
|
|
2322
|
-
const
|
|
2323
|
-
const breadthSymbols =
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
);
|
|
2327
|
-
const universe = `binance_top${breadthSymbols.length}_usdt`;
|
|
2328
|
-
const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
|
|
2329
|
-
universe,
|
|
2330
|
-
interval,
|
|
2331
|
-
startMs: breadthStartMs,
|
|
2332
|
-
endMs
|
|
2333
|
-
});
|
|
2334
|
-
const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
|
|
2335
|
-
if (!hasCoverage({
|
|
2336
|
-
coverage,
|
|
2308
|
+
const breadthUniverses = (0, import_strategies.getBinanceBreadthUniverses)();
|
|
2309
|
+
const breadthSymbols = breadthUniverses.at(-1)?.symbols ?? [];
|
|
2310
|
+
const connectorInterval = marketIntervalToConnectorInterval(interval);
|
|
2311
|
+
const chunks = buildBreadthBackfillChunks({
|
|
2337
2312
|
startMs: breadthStartMs,
|
|
2338
2313
|
endMs,
|
|
2339
|
-
intervalMs
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
chunks,
|
|
2350
|
-
coverage,
|
|
2351
|
-
intervalMs
|
|
2352
|
-
});
|
|
2353
|
-
const bar = new import_progress.default(
|
|
2354
|
-
"breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
|
|
2355
|
-
{
|
|
2356
|
-
total: Math.max(1, missingChunks.length * breadthSymbols.length),
|
|
2357
|
-
width: 24
|
|
2358
|
-
}
|
|
2359
|
-
);
|
|
2360
|
-
let candlesRead = 0;
|
|
2361
|
-
let skippedBreadthChunks = chunks.length - missingChunks.length;
|
|
2362
|
-
if (!missingChunks.length) {
|
|
2363
|
-
bar.tick(1, {
|
|
2364
|
-
candles: 0,
|
|
2365
|
-
skip: skippedBreadthChunks,
|
|
2366
|
-
chunk: "cached",
|
|
2367
|
-
symbol: universe
|
|
2314
|
+
intervalMs,
|
|
2315
|
+
chunkDays: breadthChunkDays
|
|
2316
|
+
});
|
|
2317
|
+
const plans = await Promise.all(
|
|
2318
|
+
breadthUniverses.map(async (definition) => {
|
|
2319
|
+
const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
|
|
2320
|
+
universe: definition.universe,
|
|
2321
|
+
interval,
|
|
2322
|
+
startMs: breadthStartMs,
|
|
2323
|
+
endMs
|
|
2368
2324
|
});
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
candlesBySymbol[symbol] = candles;
|
|
2385
|
-
candlesRead += candles.length;
|
|
2386
|
-
bar.tick(1, {
|
|
2387
|
-
candles: candlesRead,
|
|
2388
|
-
skip: skippedBreadthChunks,
|
|
2389
|
-
chunk: `${originalChunkIndex + 1}/${chunks.length}`,
|
|
2390
|
-
symbol
|
|
2391
|
-
});
|
|
2325
|
+
const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
|
|
2326
|
+
const missingChunks2 = !hasCoverage({
|
|
2327
|
+
coverage,
|
|
2328
|
+
startMs: breadthStartMs,
|
|
2329
|
+
endMs,
|
|
2330
|
+
intervalMs
|
|
2331
|
+
}) || !hasBtcAltMetrics ? !hasBtcAltMetrics ? chunks : filterMissingBreadthBackfillChunks({
|
|
2332
|
+
chunks,
|
|
2333
|
+
coverage,
|
|
2334
|
+
intervalMs
|
|
2335
|
+
}) : [];
|
|
2336
|
+
if (!missingChunks2.length) {
|
|
2337
|
+
console.log(
|
|
2338
|
+
import_chalk.default.gray(`breadth cached: universe=${definition.universe}`)
|
|
2339
|
+
);
|
|
2392
2340
|
}
|
|
2393
|
-
|
|
2394
|
-
|
|
2341
|
+
return {
|
|
2342
|
+
definition,
|
|
2343
|
+
missingChunkKeys: new Set(
|
|
2344
|
+
missingChunks2.map((chunk) => `${chunk.startMs}:${chunk.endMs}`)
|
|
2345
|
+
)
|
|
2346
|
+
};
|
|
2347
|
+
})
|
|
2348
|
+
);
|
|
2349
|
+
const missingChunks = chunks.filter((chunk) => {
|
|
2350
|
+
const key = `${chunk.startMs}:${chunk.endMs}`;
|
|
2351
|
+
return plans.some((plan) => plan.missingChunkKeys.has(key));
|
|
2352
|
+
});
|
|
2353
|
+
const bar = new import_progress.default(
|
|
2354
|
+
"breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
|
|
2355
|
+
{
|
|
2356
|
+
total: Math.max(1, missingChunks.length * breadthSymbols.length),
|
|
2357
|
+
width: 24
|
|
2358
|
+
}
|
|
2359
|
+
);
|
|
2360
|
+
let candlesRead = 0;
|
|
2361
|
+
const skippedBreadthChunks = chunks.length - missingChunks.length;
|
|
2362
|
+
if (!missingChunks.length) {
|
|
2363
|
+
bar.tick(1, {
|
|
2364
|
+
candles: 0,
|
|
2365
|
+
skip: skippedBreadthChunks,
|
|
2366
|
+
chunk: "cached",
|
|
2367
|
+
symbol: "top5/top10/top30/top50/top100"
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
for (let chunkIndex = 0; chunkIndex < missingChunks.length; chunkIndex += 1) {
|
|
2371
|
+
const chunk = missingChunks[chunkIndex];
|
|
2372
|
+
const originalChunkIndex = chunks.findIndex(
|
|
2373
|
+
(item) => item.startMs === chunk.startMs && item.endMs === chunk.endMs
|
|
2374
|
+
);
|
|
2375
|
+
const candlesBySymbol = {};
|
|
2376
|
+
for (const symbol of breadthSymbols) {
|
|
2377
|
+
const candles = await connector.kline({
|
|
2378
|
+
symbol,
|
|
2395
2379
|
interval: connectorInterval,
|
|
2396
2380
|
start: chunk.fetchStartMs,
|
|
2397
2381
|
end: chunk.endMs,
|
|
2398
2382
|
silent: true
|
|
2399
2383
|
});
|
|
2384
|
+
candlesBySymbol[symbol] = candles;
|
|
2385
|
+
candlesRead += candles.length;
|
|
2386
|
+
bar.tick(1, {
|
|
2387
|
+
candles: candlesRead,
|
|
2388
|
+
skip: skippedBreadthChunks,
|
|
2389
|
+
chunk: `${originalChunkIndex + 1}/${chunks.length}`,
|
|
2390
|
+
symbol
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
const btcCandles = await connector.kline({
|
|
2394
|
+
symbol: "BTCUSDT",
|
|
2395
|
+
interval: connectorInterval,
|
|
2396
|
+
start: chunk.fetchStartMs,
|
|
2397
|
+
end: chunk.endMs,
|
|
2398
|
+
silent: true
|
|
2399
|
+
});
|
|
2400
|
+
const chunkKey = `${chunk.startMs}:${chunk.endMs}`;
|
|
2401
|
+
for (const plan of plans) {
|
|
2402
|
+
if (!plan.missingChunkKeys.has(chunkKey)) {
|
|
2403
|
+
continue;
|
|
2404
|
+
}
|
|
2405
|
+
const symbolSet = new Set(plan.definition.symbols);
|
|
2406
|
+
const universeCandles = Object.fromEntries(
|
|
2407
|
+
Object.entries(candlesBySymbol).filter(
|
|
2408
|
+
([symbol]) => symbolSet.has(symbol)
|
|
2409
|
+
)
|
|
2410
|
+
);
|
|
2400
2411
|
const rows = buildMarketBreadthRows({
|
|
2401
|
-
universe,
|
|
2412
|
+
universe: plan.definition.universe,
|
|
2402
2413
|
interval,
|
|
2403
|
-
candlesBySymbol,
|
|
2414
|
+
candlesBySymbol: universeCandles,
|
|
2404
2415
|
btcCandles
|
|
2405
2416
|
}).filter((row) => {
|
|
2406
2417
|
const ts = row.ts.getTime();
|
|
@@ -2409,8 +2420,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
2409
2420
|
await (0, import_timescale.upsertMarketBreadthRows)(rows);
|
|
2410
2421
|
breadthRows += rows.length;
|
|
2411
2422
|
}
|
|
2412
|
-
} else {
|
|
2413
|
-
console.log(import_chalk.default.gray(`breadth cached: universe=${universe}`));
|
|
2414
2423
|
}
|
|
2415
2424
|
}
|
|
2416
2425
|
console.log(
|
|
@@ -1675,11 +1675,30 @@ var resolveTimeWindow = ({
|
|
|
1675
1675
|
endTime,
|
|
1676
1676
|
nowMs = Date.now(),
|
|
1677
1677
|
defaultStartMs,
|
|
1678
|
-
defaultEndMs
|
|
1678
|
+
defaultEndMs,
|
|
1679
|
+
closedIntervalMs
|
|
1679
1680
|
}) => {
|
|
1680
1681
|
const explicitStart = toEpochMs(startTime);
|
|
1681
1682
|
const explicitEnd = toEpochMs(endTime);
|
|
1682
1683
|
const resolvedDays = toPositiveNumber(days);
|
|
1684
|
+
const alignToLastClosedInterval = (window2, preserveDuration) => {
|
|
1685
|
+
if (typeof closedIntervalMs !== "number" || !Number.isFinite(closedIntervalMs) || closedIntervalMs <= 0) {
|
|
1686
|
+
return window2;
|
|
1687
|
+
}
|
|
1688
|
+
const alignedEnd = Math.floor(window2.end / closedIntervalMs) * closedIntervalMs - 1;
|
|
1689
|
+
const delta = window2.end - alignedEnd;
|
|
1690
|
+
const alignedStart = preserveDuration ? window2.start - delta : window2.start;
|
|
1691
|
+
if (alignedStart >= alignedEnd) {
|
|
1692
|
+
throw new Error(
|
|
1693
|
+
`Invalid closed-candle window: start (${alignedStart}) must be less than end (${alignedEnd})`
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
return {
|
|
1697
|
+
...window2,
|
|
1698
|
+
start: Math.trunc(alignedStart),
|
|
1699
|
+
end: Math.trunc(alignedEnd)
|
|
1700
|
+
};
|
|
1701
|
+
};
|
|
1683
1702
|
if (explicitStart != null || explicitEnd != null) {
|
|
1684
1703
|
const resolvedStart = explicitStart ?? (resolvedDays != null && explicitEnd != null ? explicitEnd - resolvedDays * ONE_DAY_MS : defaultStartMs);
|
|
1685
1704
|
const resolvedEnd2 = explicitEnd ?? (resolvedDays != null && explicitStart != null ? explicitStart + resolvedDays * ONE_DAY_MS : defaultEndMs ?? nowMs);
|
|
@@ -1688,11 +1707,14 @@ var resolveTimeWindow = ({
|
|
|
1688
1707
|
`Invalid time window: startTime (${resolvedStart}) must be less than endTime (${resolvedEnd2})`
|
|
1689
1708
|
);
|
|
1690
1709
|
}
|
|
1691
|
-
return
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1710
|
+
return alignToLastClosedInterval(
|
|
1711
|
+
{
|
|
1712
|
+
start: resolvedStart,
|
|
1713
|
+
end: resolvedEnd2,
|
|
1714
|
+
source: "explicit"
|
|
1715
|
+
},
|
|
1716
|
+
explicitStart == null && resolvedDays != null
|
|
1717
|
+
);
|
|
1696
1718
|
}
|
|
1697
1719
|
if (resolvedDays != null) {
|
|
1698
1720
|
const resolvedEnd2 = defaultEndMs ?? nowMs;
|
|
@@ -1702,11 +1724,14 @@ var resolveTimeWindow = ({
|
|
|
1702
1724
|
`Invalid time window: computed start (${resolvedStart}) must be less than end (${resolvedEnd2})`
|
|
1703
1725
|
);
|
|
1704
1726
|
}
|
|
1705
|
-
return
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1727
|
+
return alignToLastClosedInterval(
|
|
1728
|
+
{
|
|
1729
|
+
start: Math.trunc(resolvedStart),
|
|
1730
|
+
end: Math.trunc(resolvedEnd2),
|
|
1731
|
+
source: "days"
|
|
1732
|
+
},
|
|
1733
|
+
true
|
|
1734
|
+
);
|
|
1710
1735
|
}
|
|
1711
1736
|
const resolvedEnd = defaultEndMs ?? nowMs;
|
|
1712
1737
|
if (defaultStartMs >= resolvedEnd) {
|
|
@@ -1714,11 +1739,14 @@ var resolveTimeWindow = ({
|
|
|
1714
1739
|
`Invalid default time window: start (${defaultStartMs}) must be less than end (${resolvedEnd})`
|
|
1715
1740
|
);
|
|
1716
1741
|
}
|
|
1717
|
-
return
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1742
|
+
return alignToLastClosedInterval(
|
|
1743
|
+
{
|
|
1744
|
+
start: Math.trunc(defaultStartMs),
|
|
1745
|
+
end: Math.trunc(resolvedEnd),
|
|
1746
|
+
source: "default"
|
|
1747
|
+
},
|
|
1748
|
+
true
|
|
1749
|
+
);
|
|
1722
1750
|
};
|
|
1723
1751
|
|
|
1724
1752
|
// src/lib/runtimeStrategyBacktest.ts
|
|
@@ -2135,7 +2163,8 @@ var prepareRunEnvironment = async ({
|
|
|
2135
2163
|
accountId,
|
|
2136
2164
|
deploymentId,
|
|
2137
2165
|
assetClasses,
|
|
2138
|
-
deployment
|
|
2166
|
+
deployment,
|
|
2167
|
+
closedIntervalMs
|
|
2139
2168
|
}) => {
|
|
2140
2169
|
const connectorName = await resolveRunConnectorName({
|
|
2141
2170
|
value: connector,
|
|
@@ -2198,7 +2227,8 @@ var prepareRunEnvironment = async ({
|
|
|
2198
2227
|
startTime,
|
|
2199
2228
|
endTime,
|
|
2200
2229
|
defaultStartMs: (0, import_time2.getTimestamp)(import_constants.BACKTEST_DEFAULT_DAYS),
|
|
2201
|
-
defaultEndMs: (0, import_time2.getTimestamp)()
|
|
2230
|
+
defaultEndMs: (0, import_time2.getTimestamp)(),
|
|
2231
|
+
closedIntervalMs
|
|
2202
2232
|
});
|
|
2203
2233
|
const preloadStart = (0, import_time2.getBacktestPreloadStart)(
|
|
2204
2234
|
window2.start,
|