@tradejs/cli 2.0.5 → 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 +3145 -1827
- 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 +149 -125
- package/dist/lib/runEnvironment.js +100 -55
- package/dist/lib/runFormatting.js +52 -37
- 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 +252 -125
- package/dist/scripts/aiPocketSearch.js +52 -37
- package/dist/scripts/aiTrain.js +52 -37
- package/dist/scripts/backtest.js +222 -144
- package/dist/scripts/binanceBreadthUniversesUpdate.js +120 -0
- package/dist/scripts/binanceMarketIngest.js +32 -33
- package/dist/scripts/candlesMigrateProvider.js +648 -419
- package/dist/scripts/doctor.js +648 -419
- package/dist/scripts/mlExport.js +57 -39
- package/dist/scripts/replay.js +1040 -255
- package/dist/scripts/replayRunner.js +1040 -255
- package/dist/scripts/replayRuntimeEvidence.js +3 -1
- package/dist/scripts/resolveExportStrategy.js +7 -2
- package/dist/scripts/results.js +52 -37
- package/dist/scripts/runtimeParity.js +193 -141
- package/dist/scripts/signals.js +433 -144
- package/dist/scripts/signalsDaemon.js +433 -144
- package/dist/scripts/signalsSummary.js +40 -11
- package/package.json +15 -14
|
@@ -34,9 +34,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
34
34
|
));
|
|
35
35
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
36
36
|
|
|
37
|
-
// ../../node_modules/
|
|
37
|
+
// ../../node_modules/ms/index.js
|
|
38
38
|
var require_ms = __commonJS({
|
|
39
|
-
"../../node_modules/
|
|
39
|
+
"../../node_modules/ms/index.js"(exports2, module2) {
|
|
40
40
|
"use strict";
|
|
41
41
|
var s = 1e3;
|
|
42
42
|
var m = s * 60;
|
|
@@ -151,9 +151,9 @@ var require_ms = __commonJS({
|
|
|
151
151
|
}
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
-
// ../../node_modules/debug/src/common.js
|
|
154
|
+
// ../../node_modules/list-it/node_modules/debug/src/common.js
|
|
155
155
|
var require_common = __commonJS({
|
|
156
|
-
"../../node_modules/debug/src/common.js"(exports2, module2) {
|
|
156
|
+
"../../node_modules/list-it/node_modules/debug/src/common.js"(exports2, module2) {
|
|
157
157
|
"use strict";
|
|
158
158
|
function setup(env) {
|
|
159
159
|
createDebug.debug = createDebug;
|
|
@@ -255,50 +255,64 @@ var require_common = __commonJS({
|
|
|
255
255
|
createDebug.namespaces = namespaces;
|
|
256
256
|
createDebug.names = [];
|
|
257
257
|
createDebug.skips = [];
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
258
|
+
const split = (typeof namespaces === "string" ? namespaces : "").trim().replace(/\s+/g, ",").split(",").filter(Boolean);
|
|
259
|
+
for (const ns of split) {
|
|
260
|
+
if (ns[0] === "-") {
|
|
261
|
+
createDebug.skips.push(ns.slice(1));
|
|
262
|
+
} else {
|
|
263
|
+
createDebug.names.push(ns);
|
|
264
264
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function matchesTemplate(search, template) {
|
|
268
|
+
let searchIndex = 0;
|
|
269
|
+
let templateIndex = 0;
|
|
270
|
+
let starIndex = -1;
|
|
271
|
+
let matchIndex = 0;
|
|
272
|
+
while (searchIndex < search.length) {
|
|
273
|
+
if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) {
|
|
274
|
+
if (template[templateIndex] === "*") {
|
|
275
|
+
starIndex = templateIndex;
|
|
276
|
+
matchIndex = searchIndex;
|
|
277
|
+
templateIndex++;
|
|
278
|
+
} else {
|
|
279
|
+
searchIndex++;
|
|
280
|
+
templateIndex++;
|
|
281
|
+
}
|
|
282
|
+
} else if (starIndex !== -1) {
|
|
283
|
+
templateIndex = starIndex + 1;
|
|
284
|
+
matchIndex++;
|
|
285
|
+
searchIndex = matchIndex;
|
|
268
286
|
} else {
|
|
269
|
-
|
|
287
|
+
return false;
|
|
270
288
|
}
|
|
271
289
|
}
|
|
290
|
+
while (templateIndex < template.length && template[templateIndex] === "*") {
|
|
291
|
+
templateIndex++;
|
|
292
|
+
}
|
|
293
|
+
return templateIndex === template.length;
|
|
272
294
|
}
|
|
273
295
|
function disable() {
|
|
274
296
|
const namespaces = [
|
|
275
|
-
...createDebug.names
|
|
276
|
-
...createDebug.skips.map(
|
|
297
|
+
...createDebug.names,
|
|
298
|
+
...createDebug.skips.map((namespace) => "-" + namespace)
|
|
277
299
|
].join(",");
|
|
278
300
|
createDebug.enable("");
|
|
279
301
|
return namespaces;
|
|
280
302
|
}
|
|
281
303
|
function enabled(name) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
let i;
|
|
286
|
-
let len;
|
|
287
|
-
for (i = 0, len = createDebug.skips.length; i < len; i++) {
|
|
288
|
-
if (createDebug.skips[i].test(name)) {
|
|
304
|
+
for (const skip of createDebug.skips) {
|
|
305
|
+
if (matchesTemplate(name, skip)) {
|
|
289
306
|
return false;
|
|
290
307
|
}
|
|
291
308
|
}
|
|
292
|
-
for (
|
|
293
|
-
if (
|
|
309
|
+
for (const ns of createDebug.names) {
|
|
310
|
+
if (matchesTemplate(name, ns)) {
|
|
294
311
|
return true;
|
|
295
312
|
}
|
|
296
313
|
}
|
|
297
314
|
return false;
|
|
298
315
|
}
|
|
299
|
-
function toNamespace(regexp) {
|
|
300
|
-
return regexp.toString().substring(2, regexp.toString().length - 2).replace(/\.\*\?$/, "*");
|
|
301
|
-
}
|
|
302
316
|
function coerce(val) {
|
|
303
317
|
if (val instanceof Error) {
|
|
304
318
|
return val.stack || val.message;
|
|
@@ -315,9 +329,9 @@ var require_common = __commonJS({
|
|
|
315
329
|
}
|
|
316
330
|
});
|
|
317
331
|
|
|
318
|
-
// ../../node_modules/debug/src/browser.js
|
|
332
|
+
// ../../node_modules/list-it/node_modules/debug/src/browser.js
|
|
319
333
|
var require_browser = __commonJS({
|
|
320
|
-
"../../node_modules/debug/src/browser.js"(exports2, module2) {
|
|
334
|
+
"../../node_modules/list-it/node_modules/debug/src/browser.js"(exports2, module2) {
|
|
321
335
|
"use strict";
|
|
322
336
|
exports2.formatArgs = formatArgs;
|
|
323
337
|
exports2.save = save;
|
|
@@ -418,10 +432,11 @@ var require_browser = __commonJS({
|
|
|
418
432
|
if (typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
|
|
419
433
|
return false;
|
|
420
434
|
}
|
|
435
|
+
let m;
|
|
421
436
|
return typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773
|
|
422
437
|
typeof window !== "undefined" && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31?
|
|
423
438
|
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
|
|
424
|
-
typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(
|
|
439
|
+
typeof navigator !== "undefined" && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker
|
|
425
440
|
typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
|
|
426
441
|
}
|
|
427
442
|
function formatArgs(args2) {
|
|
@@ -459,7 +474,7 @@ var require_browser = __commonJS({
|
|
|
459
474
|
function load() {
|
|
460
475
|
let r;
|
|
461
476
|
try {
|
|
462
|
-
r = exports2.storage.getItem("debug");
|
|
477
|
+
r = exports2.storage.getItem("debug") || exports2.storage.getItem("DEBUG");
|
|
463
478
|
} catch (error) {
|
|
464
479
|
}
|
|
465
480
|
if (!r && typeof process !== "undefined" && "env" in process) {
|
|
@@ -600,9 +615,9 @@ var require_supports_color = __commonJS({
|
|
|
600
615
|
}
|
|
601
616
|
});
|
|
602
617
|
|
|
603
|
-
// ../../node_modules/debug/src/node.js
|
|
618
|
+
// ../../node_modules/list-it/node_modules/debug/src/node.js
|
|
604
619
|
var require_node = __commonJS({
|
|
605
|
-
"../../node_modules/debug/src/node.js"(exports2, module2) {
|
|
620
|
+
"../../node_modules/list-it/node_modules/debug/src/node.js"(exports2, module2) {
|
|
606
621
|
"use strict";
|
|
607
622
|
var tty = require("tty");
|
|
608
623
|
var util = require("util");
|
|
@@ -743,7 +758,7 @@ var require_node = __commonJS({
|
|
|
743
758
|
return (/* @__PURE__ */ new Date()).toISOString() + " ";
|
|
744
759
|
}
|
|
745
760
|
function log(...args2) {
|
|
746
|
-
return process.stderr.write(util.
|
|
761
|
+
return process.stderr.write(util.formatWithOptions(exports2.inspectOpts, ...args2) + "\n");
|
|
747
762
|
}
|
|
748
763
|
function save(namespaces) {
|
|
749
764
|
if (namespaces) {
|
|
@@ -775,9 +790,9 @@ var require_node = __commonJS({
|
|
|
775
790
|
}
|
|
776
791
|
});
|
|
777
792
|
|
|
778
|
-
// ../../node_modules/debug/src/index.js
|
|
793
|
+
// ../../node_modules/list-it/node_modules/debug/src/index.js
|
|
779
794
|
var require_src = __commonJS({
|
|
780
|
-
"../../node_modules/debug/src/index.js"(exports2, module2) {
|
|
795
|
+
"../../node_modules/list-it/node_modules/debug/src/index.js"(exports2, module2) {
|
|
781
796
|
"use strict";
|
|
782
797
|
if (typeof process === "undefined" || process.type === "renderer" || process.browser === true || process.__nwjs) {
|
|
783
798
|
module2.exports = require_browser();
|
|
@@ -1631,8 +1646,9 @@ __export(replayRunner_exports, {
|
|
|
1631
1646
|
module.exports = __toCommonJS(replayRunner_exports);
|
|
1632
1647
|
var import_chalk12 = __toESM(require("chalk"));
|
|
1633
1648
|
var import_constants6 = require("@tradejs/core/constants");
|
|
1649
|
+
var import_data2 = require("@tradejs/core/data");
|
|
1634
1650
|
var import_time7 = require("@tradejs/core/time");
|
|
1635
|
-
var
|
|
1651
|
+
var import_redis6 = require("@tradejs/infra/redis");
|
|
1636
1652
|
|
|
1637
1653
|
// src/lib/runFormatting.ts
|
|
1638
1654
|
var import_date_fns = require("date-fns");
|
|
@@ -1694,11 +1710,30 @@ var resolveTimeWindow = ({
|
|
|
1694
1710
|
endTime,
|
|
1695
1711
|
nowMs = Date.now(),
|
|
1696
1712
|
defaultStartMs,
|
|
1697
|
-
defaultEndMs
|
|
1713
|
+
defaultEndMs,
|
|
1714
|
+
closedIntervalMs
|
|
1698
1715
|
}) => {
|
|
1699
1716
|
const explicitStart = toEpochMs(startTime);
|
|
1700
1717
|
const explicitEnd = toEpochMs(endTime);
|
|
1701
1718
|
const resolvedDays = toPositiveNumber(days);
|
|
1719
|
+
const alignToLastClosedInterval = (window2, preserveDuration) => {
|
|
1720
|
+
if (typeof closedIntervalMs !== "number" || !Number.isFinite(closedIntervalMs) || closedIntervalMs <= 0) {
|
|
1721
|
+
return window2;
|
|
1722
|
+
}
|
|
1723
|
+
const alignedEnd = Math.floor(window2.end / closedIntervalMs) * closedIntervalMs - 1;
|
|
1724
|
+
const delta = window2.end - alignedEnd;
|
|
1725
|
+
const alignedStart = preserveDuration ? window2.start - delta : window2.start;
|
|
1726
|
+
if (alignedStart >= alignedEnd) {
|
|
1727
|
+
throw new Error(
|
|
1728
|
+
`Invalid closed-candle window: start (${alignedStart}) must be less than end (${alignedEnd})`
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
return {
|
|
1732
|
+
...window2,
|
|
1733
|
+
start: Math.trunc(alignedStart),
|
|
1734
|
+
end: Math.trunc(alignedEnd)
|
|
1735
|
+
};
|
|
1736
|
+
};
|
|
1702
1737
|
if (explicitStart != null || explicitEnd != null) {
|
|
1703
1738
|
const resolvedStart = explicitStart ?? (resolvedDays != null && explicitEnd != null ? explicitEnd - resolvedDays * ONE_DAY_MS : defaultStartMs);
|
|
1704
1739
|
const resolvedEnd2 = explicitEnd ?? (resolvedDays != null && explicitStart != null ? explicitStart + resolvedDays * ONE_DAY_MS : defaultEndMs ?? nowMs);
|
|
@@ -1707,11 +1742,14 @@ var resolveTimeWindow = ({
|
|
|
1707
1742
|
`Invalid time window: startTime (${resolvedStart}) must be less than endTime (${resolvedEnd2})`
|
|
1708
1743
|
);
|
|
1709
1744
|
}
|
|
1710
|
-
return
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1745
|
+
return alignToLastClosedInterval(
|
|
1746
|
+
{
|
|
1747
|
+
start: resolvedStart,
|
|
1748
|
+
end: resolvedEnd2,
|
|
1749
|
+
source: "explicit"
|
|
1750
|
+
},
|
|
1751
|
+
explicitStart == null && resolvedDays != null
|
|
1752
|
+
);
|
|
1715
1753
|
}
|
|
1716
1754
|
if (resolvedDays != null) {
|
|
1717
1755
|
const resolvedEnd2 = defaultEndMs ?? nowMs;
|
|
@@ -1721,11 +1759,14 @@ var resolveTimeWindow = ({
|
|
|
1721
1759
|
`Invalid time window: computed start (${resolvedStart}) must be less than end (${resolvedEnd2})`
|
|
1722
1760
|
);
|
|
1723
1761
|
}
|
|
1724
|
-
return
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1762
|
+
return alignToLastClosedInterval(
|
|
1763
|
+
{
|
|
1764
|
+
start: Math.trunc(resolvedStart),
|
|
1765
|
+
end: Math.trunc(resolvedEnd2),
|
|
1766
|
+
source: "days"
|
|
1767
|
+
},
|
|
1768
|
+
true
|
|
1769
|
+
);
|
|
1729
1770
|
}
|
|
1730
1771
|
const resolvedEnd = defaultEndMs ?? nowMs;
|
|
1731
1772
|
if (defaultStartMs >= resolvedEnd) {
|
|
@@ -1733,11 +1774,14 @@ var resolveTimeWindow = ({
|
|
|
1733
1774
|
`Invalid default time window: start (${defaultStartMs}) must be less than end (${resolvedEnd})`
|
|
1734
1775
|
);
|
|
1735
1776
|
}
|
|
1736
|
-
return
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1777
|
+
return alignToLastClosedInterval(
|
|
1778
|
+
{
|
|
1779
|
+
start: Math.trunc(defaultStartMs),
|
|
1780
|
+
end: Math.trunc(resolvedEnd),
|
|
1781
|
+
source: "default"
|
|
1782
|
+
},
|
|
1783
|
+
true
|
|
1784
|
+
);
|
|
1741
1785
|
};
|
|
1742
1786
|
|
|
1743
1787
|
// src/lib/runtimeStrategyBacktest.ts
|
|
@@ -2177,7 +2221,8 @@ var prepareRunEnvironment = async ({
|
|
|
2177
2221
|
accountId,
|
|
2178
2222
|
deploymentId,
|
|
2179
2223
|
assetClasses,
|
|
2180
|
-
deployment
|
|
2224
|
+
deployment,
|
|
2225
|
+
closedIntervalMs
|
|
2181
2226
|
}) => {
|
|
2182
2227
|
const connectorName = await resolveRunConnectorName({
|
|
2183
2228
|
value: connector,
|
|
@@ -2240,7 +2285,8 @@ var prepareRunEnvironment = async ({
|
|
|
2240
2285
|
startTime,
|
|
2241
2286
|
endTime,
|
|
2242
2287
|
defaultStartMs: (0, import_time2.getTimestamp)(import_constants.BACKTEST_DEFAULT_DAYS),
|
|
2243
|
-
defaultEndMs: (0, import_time2.getTimestamp)()
|
|
2288
|
+
defaultEndMs: (0, import_time2.getTimestamp)(),
|
|
2289
|
+
closedIntervalMs
|
|
2244
2290
|
});
|
|
2245
2291
|
const preloadStart = (0, import_time2.getBacktestPreloadStart)(
|
|
2246
2292
|
window2.start,
|
|
@@ -2309,6 +2355,7 @@ var import_progress = __toESM(require("progress"));
|
|
|
2309
2355
|
var import_chalk4 = __toESM(require("chalk"));
|
|
2310
2356
|
var import_connectors4 = require("@tradejs/connectors");
|
|
2311
2357
|
var import_connectors5 = require("@tradejs/node/connectors");
|
|
2358
|
+
var import_strategies = require("@tradejs/node/strategies");
|
|
2312
2359
|
var import_timescale = require("@tradejs/infra/timescale");
|
|
2313
2360
|
|
|
2314
2361
|
// src/lib/binanceMarketData.ts
|
|
@@ -2318,14 +2365,6 @@ var MARKET_FEATURE_INTERVAL_MS = {
|
|
|
2318
2365
|
"15m": 9e5,
|
|
2319
2366
|
"1h": 36e5
|
|
2320
2367
|
};
|
|
2321
|
-
var STABLE_QUOTE_SYMBOLS = /* @__PURE__ */ new Set([
|
|
2322
|
-
"USDCUSDT",
|
|
2323
|
-
"FDUSDUSDT",
|
|
2324
|
-
"TUSDUSDT",
|
|
2325
|
-
"BUSDUSDT",
|
|
2326
|
-
"USDPUSDT",
|
|
2327
|
-
"DAIUSDT"
|
|
2328
|
-
]);
|
|
2329
2368
|
var safeDivide = (num, den) => Number.isFinite(num) && Number.isFinite(den) && den !== 0 ? num / den : null;
|
|
2330
2369
|
var mean = (values) => values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
|
|
2331
2370
|
var standardDeviation = (values) => {
|
|
@@ -2369,9 +2408,6 @@ var classifyBtcAltRegime = ({
|
|
|
2369
2408
|
if (btcVsAltReturn24h < -5e-3) return "alt_lead";
|
|
2370
2409
|
return "neutral";
|
|
2371
2410
|
};
|
|
2372
|
-
var selectBreadthUniverseFromTickers = (tickers, limit) => tickers.filter(
|
|
2373
|
-
(ticker) => ticker.symbol.endsWith("USDT") && ticker.symbol !== "BTCUSDT" && !STABLE_QUOTE_SYMBOLS.has(ticker.symbol) && Number.isFinite(ticker.turnover24h) && ticker.turnover24h > 0
|
|
2374
|
-
).sort((a, b) => b.turnover24h - a.turnover24h).slice(0, Math.max(0, limit)).map((ticker) => ticker.symbol);
|
|
2375
2411
|
var aggregateAggTradesToRows = ({
|
|
2376
2412
|
symbol,
|
|
2377
2413
|
interval,
|
|
@@ -2853,10 +2889,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
2853
2889
|
process.env.BINANCE_MARKET_CONTEXT_TRADE_FLOW_SYMBOL_LIMIT,
|
|
2854
2890
|
2
|
|
2855
2891
|
);
|
|
2856
|
-
const breadthLimit = asInt(
|
|
2857
|
-
process.env.BINANCE_MARKET_CONTEXT_BREADTH_LIMIT,
|
|
2858
|
-
30
|
|
2859
|
-
);
|
|
2860
2892
|
const breadthChunkDays = asFloat(
|
|
2861
2893
|
process.env.BINANCE_MARKET_CONTEXT_BREADTH_CHUNK_DAYS,
|
|
2862
2894
|
30
|
|
@@ -2979,88 +3011,113 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
2979
3011
|
}
|
|
2980
3012
|
}
|
|
2981
3013
|
if (includeBreadth) {
|
|
2982
|
-
const
|
|
2983
|
-
const breadthSymbols =
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
);
|
|
2987
|
-
const universe = `binance_top${breadthSymbols.length}_usdt`;
|
|
2988
|
-
const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
|
|
2989
|
-
universe,
|
|
2990
|
-
interval,
|
|
2991
|
-
startMs: breadthStartMs,
|
|
2992
|
-
endMs
|
|
2993
|
-
});
|
|
2994
|
-
const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
|
|
2995
|
-
if (!hasCoverage({
|
|
2996
|
-
coverage,
|
|
3014
|
+
const breadthUniverses = (0, import_strategies.getBinanceBreadthUniverses)();
|
|
3015
|
+
const breadthSymbols = breadthUniverses.at(-1)?.symbols ?? [];
|
|
3016
|
+
const connectorInterval = marketIntervalToConnectorInterval(interval);
|
|
3017
|
+
const chunks = buildBreadthBackfillChunks({
|
|
2997
3018
|
startMs: breadthStartMs,
|
|
2998
3019
|
endMs,
|
|
2999
|
-
intervalMs
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
chunks,
|
|
3010
|
-
coverage,
|
|
3011
|
-
intervalMs
|
|
3012
|
-
});
|
|
3013
|
-
const bar = new import_progress.default(
|
|
3014
|
-
"breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
|
|
3015
|
-
{
|
|
3016
|
-
total: Math.max(1, missingChunks.length * breadthSymbols.length),
|
|
3017
|
-
width: 24
|
|
3018
|
-
}
|
|
3019
|
-
);
|
|
3020
|
-
let candlesRead = 0;
|
|
3021
|
-
let skippedBreadthChunks = chunks.length - missingChunks.length;
|
|
3022
|
-
if (!missingChunks.length) {
|
|
3023
|
-
bar.tick(1, {
|
|
3024
|
-
candles: 0,
|
|
3025
|
-
skip: skippedBreadthChunks,
|
|
3026
|
-
chunk: "cached",
|
|
3027
|
-
symbol: universe
|
|
3020
|
+
intervalMs,
|
|
3021
|
+
chunkDays: breadthChunkDays
|
|
3022
|
+
});
|
|
3023
|
+
const plans = await Promise.all(
|
|
3024
|
+
breadthUniverses.map(async (definition) => {
|
|
3025
|
+
const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
|
|
3026
|
+
universe: definition.universe,
|
|
3027
|
+
interval,
|
|
3028
|
+
startMs: breadthStartMs,
|
|
3029
|
+
endMs
|
|
3028
3030
|
});
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
candlesBySymbol[symbol] = candles;
|
|
3045
|
-
candlesRead += candles.length;
|
|
3046
|
-
bar.tick(1, {
|
|
3047
|
-
candles: candlesRead,
|
|
3048
|
-
skip: skippedBreadthChunks,
|
|
3049
|
-
chunk: `${originalChunkIndex + 1}/${chunks.length}`,
|
|
3050
|
-
symbol
|
|
3051
|
-
});
|
|
3031
|
+
const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
|
|
3032
|
+
const missingChunks2 = !hasCoverage({
|
|
3033
|
+
coverage,
|
|
3034
|
+
startMs: breadthStartMs,
|
|
3035
|
+
endMs,
|
|
3036
|
+
intervalMs
|
|
3037
|
+
}) || !hasBtcAltMetrics ? !hasBtcAltMetrics ? chunks : filterMissingBreadthBackfillChunks({
|
|
3038
|
+
chunks,
|
|
3039
|
+
coverage,
|
|
3040
|
+
intervalMs
|
|
3041
|
+
}) : [];
|
|
3042
|
+
if (!missingChunks2.length) {
|
|
3043
|
+
console.log(
|
|
3044
|
+
import_chalk4.default.gray(`breadth cached: universe=${definition.universe}`)
|
|
3045
|
+
);
|
|
3052
3046
|
}
|
|
3053
|
-
|
|
3054
|
-
|
|
3047
|
+
return {
|
|
3048
|
+
definition,
|
|
3049
|
+
missingChunkKeys: new Set(
|
|
3050
|
+
missingChunks2.map((chunk) => `${chunk.startMs}:${chunk.endMs}`)
|
|
3051
|
+
)
|
|
3052
|
+
};
|
|
3053
|
+
})
|
|
3054
|
+
);
|
|
3055
|
+
const missingChunks = chunks.filter((chunk) => {
|
|
3056
|
+
const key = `${chunk.startMs}:${chunk.endMs}`;
|
|
3057
|
+
return plans.some((plan) => plan.missingChunkKeys.has(key));
|
|
3058
|
+
});
|
|
3059
|
+
const bar = new import_progress.default(
|
|
3060
|
+
"breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
|
|
3061
|
+
{
|
|
3062
|
+
total: Math.max(1, missingChunks.length * breadthSymbols.length),
|
|
3063
|
+
width: 24
|
|
3064
|
+
}
|
|
3065
|
+
);
|
|
3066
|
+
let candlesRead = 0;
|
|
3067
|
+
const skippedBreadthChunks = chunks.length - missingChunks.length;
|
|
3068
|
+
if (!missingChunks.length) {
|
|
3069
|
+
bar.tick(1, {
|
|
3070
|
+
candles: 0,
|
|
3071
|
+
skip: skippedBreadthChunks,
|
|
3072
|
+
chunk: "cached",
|
|
3073
|
+
symbol: "top5/top10/top30/top50/top100"
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
for (let chunkIndex = 0; chunkIndex < missingChunks.length; chunkIndex += 1) {
|
|
3077
|
+
const chunk = missingChunks[chunkIndex];
|
|
3078
|
+
const originalChunkIndex = chunks.findIndex(
|
|
3079
|
+
(item) => item.startMs === chunk.startMs && item.endMs === chunk.endMs
|
|
3080
|
+
);
|
|
3081
|
+
const candlesBySymbol = {};
|
|
3082
|
+
for (const symbol of breadthSymbols) {
|
|
3083
|
+
const candles = await connector.kline({
|
|
3084
|
+
symbol,
|
|
3055
3085
|
interval: connectorInterval,
|
|
3056
3086
|
start: chunk.fetchStartMs,
|
|
3057
3087
|
end: chunk.endMs,
|
|
3058
3088
|
silent: true
|
|
3059
3089
|
});
|
|
3090
|
+
candlesBySymbol[symbol] = candles;
|
|
3091
|
+
candlesRead += candles.length;
|
|
3092
|
+
bar.tick(1, {
|
|
3093
|
+
candles: candlesRead,
|
|
3094
|
+
skip: skippedBreadthChunks,
|
|
3095
|
+
chunk: `${originalChunkIndex + 1}/${chunks.length}`,
|
|
3096
|
+
symbol
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
3099
|
+
const btcCandles = await connector.kline({
|
|
3100
|
+
symbol: "BTCUSDT",
|
|
3101
|
+
interval: connectorInterval,
|
|
3102
|
+
start: chunk.fetchStartMs,
|
|
3103
|
+
end: chunk.endMs,
|
|
3104
|
+
silent: true
|
|
3105
|
+
});
|
|
3106
|
+
const chunkKey = `${chunk.startMs}:${chunk.endMs}`;
|
|
3107
|
+
for (const plan of plans) {
|
|
3108
|
+
if (!plan.missingChunkKeys.has(chunkKey)) {
|
|
3109
|
+
continue;
|
|
3110
|
+
}
|
|
3111
|
+
const symbolSet = new Set(plan.definition.symbols);
|
|
3112
|
+
const universeCandles = Object.fromEntries(
|
|
3113
|
+
Object.entries(candlesBySymbol).filter(
|
|
3114
|
+
([symbol]) => symbolSet.has(symbol)
|
|
3115
|
+
)
|
|
3116
|
+
);
|
|
3060
3117
|
const rows = buildMarketBreadthRows({
|
|
3061
|
-
universe,
|
|
3118
|
+
universe: plan.definition.universe,
|
|
3062
3119
|
interval,
|
|
3063
|
-
candlesBySymbol,
|
|
3120
|
+
candlesBySymbol: universeCandles,
|
|
3064
3121
|
btcCandles
|
|
3065
3122
|
}).filter((row) => {
|
|
3066
3123
|
const ts = row.ts.getTime();
|
|
@@ -3069,8 +3126,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
3069
3126
|
await (0, import_timescale.upsertMarketBreadthRows)(rows);
|
|
3070
3127
|
breadthRows += rows.length;
|
|
3071
3128
|
}
|
|
3072
|
-
} else {
|
|
3073
|
-
console.log(import_chalk4.default.gray(`breadth cached: universe=${universe}`));
|
|
3074
3129
|
}
|
|
3075
3130
|
}
|
|
3076
3131
|
console.log(
|
|
@@ -4295,27 +4350,27 @@ var createCoinMarketCapFetchStats = () => ({
|
|
|
4295
4350
|
totalCredits: 0,
|
|
4296
4351
|
byPath: /* @__PURE__ */ new Map()
|
|
4297
4352
|
});
|
|
4298
|
-
var getCoinMarketCapPathLabel = (
|
|
4299
|
-
if (
|
|
4300
|
-
if (
|
|
4301
|
-
if (
|
|
4302
|
-
if (
|
|
4303
|
-
if (
|
|
4304
|
-
if (
|
|
4305
|
-
return
|
|
4306
|
-
};
|
|
4307
|
-
var recordCoinMarketCapFetchStats = (stats,
|
|
4353
|
+
var getCoinMarketCapPathLabel = (path3) => {
|
|
4354
|
+
if (path3.includes("global-metrics")) return "global";
|
|
4355
|
+
if (path3.includes("cryptocurrency/quotes")) return "reference";
|
|
4356
|
+
if (path3.includes("exchange/quotes")) return "exchangeLiquidity";
|
|
4357
|
+
if (path3.includes("fear-and-greed")) return "fearGreed";
|
|
4358
|
+
if (path3.includes("cmc100")) return "cmc100";
|
|
4359
|
+
if (path3.includes("cmc20")) return "cmc20";
|
|
4360
|
+
return path3;
|
|
4361
|
+
};
|
|
4362
|
+
var recordCoinMarketCapFetchStats = (stats, path3, creditCount) => {
|
|
4308
4363
|
if (!stats) return;
|
|
4309
|
-
const existing = stats.byPath.get(
|
|
4364
|
+
const existing = stats.byPath.get(path3) ?? { requests: 0, credits: 0 };
|
|
4310
4365
|
existing.requests += 1;
|
|
4311
4366
|
existing.credits += creditCount ?? 0;
|
|
4312
4367
|
stats.requests += 1;
|
|
4313
4368
|
stats.totalCredits += creditCount ?? 0;
|
|
4314
|
-
stats.byPath.set(
|
|
4369
|
+
stats.byPath.set(path3, existing);
|
|
4315
4370
|
};
|
|
4316
4371
|
var formatCoinMarketCapFetchStats = (stats) => {
|
|
4317
4372
|
const byPath = [...stats.byPath.entries()].map(
|
|
4318
|
-
([
|
|
4373
|
+
([path3, item]) => `${getCoinMarketCapPathLabel(path3)}=${item.requests}req/${item.credits}cr`
|
|
4319
4374
|
).join(" ");
|
|
4320
4375
|
return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
|
|
4321
4376
|
};
|
|
@@ -5231,8 +5286,12 @@ var prepareMarketContextForRun = async (params) => {
|
|
|
5231
5286
|
};
|
|
5232
5287
|
|
|
5233
5288
|
// src/lib/backtest/runState.ts
|
|
5289
|
+
var import_types = require("@tradejs/types");
|
|
5234
5290
|
var successTests = 0;
|
|
5235
5291
|
var errorTests = 0;
|
|
5292
|
+
var warningCounts = {
|
|
5293
|
+
[import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY]: 0
|
|
5294
|
+
};
|
|
5236
5295
|
var errorMessages = [];
|
|
5237
5296
|
var topResults = [];
|
|
5238
5297
|
var bestTickerResults = /* @__PURE__ */ new Map();
|
|
@@ -5277,6 +5336,9 @@ var getRuntimeCompareContext = () => ({
|
|
|
5277
5336
|
var resetRunState = () => {
|
|
5278
5337
|
successTests = 0;
|
|
5279
5338
|
errorTests = 0;
|
|
5339
|
+
for (const code of Object.values(import_types.BACKTEST_WARNING_CODES)) {
|
|
5340
|
+
warningCounts[code] = 0;
|
|
5341
|
+
}
|
|
5280
5342
|
errorMessages.length = 0;
|
|
5281
5343
|
topResults = [];
|
|
5282
5344
|
bestTickerResults.clear();
|
|
@@ -5421,13 +5483,16 @@ var REPLAY_RUNTIME_COMPARISON_HEADERS = [
|
|
|
5421
5483
|
import_chalk8.default.yellow("RT TRADES"),
|
|
5422
5484
|
import_chalk8.default.yellow("RT PNL"),
|
|
5423
5485
|
import_chalk8.default.green("MATCHED"),
|
|
5486
|
+
import_chalk8.default.red("ORDER FAILED"),
|
|
5424
5487
|
import_chalk8.default.yellow("RT ONLY"),
|
|
5425
|
-
import_chalk8.default.magenta("BT ONLY")
|
|
5488
|
+
import_chalk8.default.magenta("BT ONLY"),
|
|
5489
|
+
import_chalk8.default.red("UNMATCHED")
|
|
5426
5490
|
];
|
|
5427
5491
|
var REPLAY_RESULTS_CONFIG = "replay";
|
|
5428
5492
|
var REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS = 1;
|
|
5429
5493
|
var REPLAY_RUNTIME_COMPARE_TOLERANCE_MS = REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS * 15 * 60 * 1e3;
|
|
5430
5494
|
var formatReplayRuntimeCompareTolerance = () => `${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS} ${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS === 1 ? "bar" : "bars"}`;
|
|
5495
|
+
var getReplayRuntimeUnmatchedCount = (row) => row.orderFailed + row.runtimeOnly + row.backtestOnly;
|
|
5431
5496
|
var buildReplayStrategyConfig = ({
|
|
5432
5497
|
strategyConfig,
|
|
5433
5498
|
interval
|
|
@@ -5492,12 +5557,13 @@ var compactReplaySignal = (signal) => {
|
|
|
5492
5557
|
var import_chalk9 = __toESM(require("chalk"));
|
|
5493
5558
|
var import_progress3 = __toESM(require("progress"));
|
|
5494
5559
|
var import_backtest = require("@tradejs/core/backtest");
|
|
5495
|
-
var
|
|
5560
|
+
var import_strategies2 = require("@tradejs/core/strategies");
|
|
5496
5561
|
var import_time3 = require("@tradejs/core/time");
|
|
5497
5562
|
var import_logger = require("@tradejs/infra/logger");
|
|
5563
|
+
var import_redis3 = require("@tradejs/infra/redis");
|
|
5498
5564
|
var import_connectors6 = require("@tradejs/node/connectors");
|
|
5499
5565
|
var import_cli4 = require("@tradejs/node/cli");
|
|
5500
|
-
var
|
|
5566
|
+
var import_strategies3 = require("@tradejs/node/strategies");
|
|
5501
5567
|
|
|
5502
5568
|
// src/lib/replay/portfolioReplayConnector.ts
|
|
5503
5569
|
var import_constants3 = require("@tradejs/core/constants");
|
|
@@ -5947,21 +6013,231 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
|
|
|
5947
6013
|
}
|
|
5948
6014
|
};
|
|
5949
6015
|
|
|
6016
|
+
// src/lib/runtimeLineage.ts
|
|
6017
|
+
var import_node_child_process = require("child_process");
|
|
6018
|
+
var import_node_crypto = require("crypto");
|
|
6019
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
6020
|
+
var import_node_path = __toESM(require("path"));
|
|
6021
|
+
var RUNTIME_CONTEXT_ENV_KEYS = [
|
|
6022
|
+
"AI_MODE",
|
|
6023
|
+
"MIN_AI_QUALITY",
|
|
6024
|
+
"INTERVAL",
|
|
6025
|
+
"DERIVATIVES_CONTEXT_ENABLED",
|
|
6026
|
+
"DERIVATIVES_CONTEXT_TARGET_ENABLED",
|
|
6027
|
+
"DERIVATIVES_CONTEXT_LOOKBACK_HOURS",
|
|
6028
|
+
"DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS",
|
|
6029
|
+
"DERIVATIVES_CONTEXT_EXCHANGE_PRIORITY",
|
|
6030
|
+
"COINMARKETCAP_CONTEXT_ENABLED",
|
|
6031
|
+
"COINMARKETCAP_CONTEXT_MAX_AGE_MS",
|
|
6032
|
+
"COINMARKETCAP_CONTEXT_EXCHANGE_LIQUIDITY_ENABLED",
|
|
6033
|
+
"COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS",
|
|
6034
|
+
"COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED",
|
|
6035
|
+
"COINMARKETCAP_CONTEXT_HISTORICAL_ACCESS_MONTHS",
|
|
6036
|
+
"COINMARKETCAP_CONTEXT_BACKFILL_ENABLED",
|
|
6037
|
+
"COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
|
|
6038
|
+
"COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS"
|
|
6039
|
+
];
|
|
6040
|
+
var RUNTIME_CONTEXT_DATA_MODEL = {
|
|
6041
|
+
derivativesSourceIntervals: "15m",
|
|
6042
|
+
derivativesDerivedIntervals: "1h",
|
|
6043
|
+
derivativesHourlyFallback: "stored-1h",
|
|
6044
|
+
derivativesDataModelVersion: 2,
|
|
6045
|
+
coinMarketCapAvailabilityModel: "closed-interval-all-daily",
|
|
6046
|
+
coinMarketCapDataModelVersion: 3,
|
|
6047
|
+
binanceBreadthUniverseModel: "fixed-versioned-top5-top10-top30-top50-top100",
|
|
6048
|
+
binanceBreadthDataModelVersion: 2
|
|
6049
|
+
};
|
|
6050
|
+
var normalizeForStableJson = (value) => {
|
|
6051
|
+
if (Array.isArray(value)) {
|
|
6052
|
+
return value.map(normalizeForStableJson);
|
|
6053
|
+
}
|
|
6054
|
+
if (value && typeof value === "object") {
|
|
6055
|
+
return Object.fromEntries(
|
|
6056
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeForStableJson(entry)])
|
|
6057
|
+
);
|
|
6058
|
+
}
|
|
6059
|
+
return value;
|
|
6060
|
+
};
|
|
6061
|
+
var fingerprintRuntimeValue = (value) => (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
|
|
6062
|
+
var gitLineageCache = /* @__PURE__ */ new Map();
|
|
6063
|
+
var gateFingerprintCache = /* @__PURE__ */ new Map();
|
|
6064
|
+
var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
|
|
6065
|
+
var resolveGitLineage = (projectRoot) => {
|
|
6066
|
+
const cached = gitLineageCache.get(projectRoot);
|
|
6067
|
+
if (cached) {
|
|
6068
|
+
return cached;
|
|
6069
|
+
}
|
|
6070
|
+
const envSha = String(process.env.TRADEJS_GIT_SHA ?? "").trim();
|
|
6071
|
+
if (envSha && envSha !== "unknown") {
|
|
6072
|
+
const result = {
|
|
6073
|
+
gitSha: envSha,
|
|
6074
|
+
gitDirty: false
|
|
6075
|
+
};
|
|
6076
|
+
gitLineageCache.set(projectRoot, result);
|
|
6077
|
+
return result;
|
|
6078
|
+
}
|
|
6079
|
+
try {
|
|
6080
|
+
const gitSha = (0, import_node_child_process.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
6081
|
+
cwd: projectRoot,
|
|
6082
|
+
encoding: "utf8",
|
|
6083
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
6084
|
+
}).trim();
|
|
6085
|
+
const status = (0, import_node_child_process.execFileSync)(
|
|
6086
|
+
"git",
|
|
6087
|
+
["status", "--porcelain", "--untracked-files=normal"],
|
|
6088
|
+
{
|
|
6089
|
+
cwd: projectRoot,
|
|
6090
|
+
encoding: "utf8",
|
|
6091
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
6092
|
+
}
|
|
6093
|
+
).trim();
|
|
6094
|
+
const result = {
|
|
6095
|
+
gitSha: gitSha || null,
|
|
6096
|
+
gitDirty: status.length > 0
|
|
6097
|
+
};
|
|
6098
|
+
gitLineageCache.set(projectRoot, result);
|
|
6099
|
+
return result;
|
|
6100
|
+
} catch {
|
|
6101
|
+
const result = {
|
|
6102
|
+
gitSha: null,
|
|
6103
|
+
gitDirty: null
|
|
6104
|
+
};
|
|
6105
|
+
gitLineageCache.set(projectRoot, result);
|
|
6106
|
+
return result;
|
|
6107
|
+
}
|
|
6108
|
+
};
|
|
6109
|
+
var readOptionalFile = async (filePath) => {
|
|
6110
|
+
try {
|
|
6111
|
+
return await import_promises.default.readFile(filePath);
|
|
6112
|
+
} catch {
|
|
6113
|
+
return null;
|
|
6114
|
+
}
|
|
6115
|
+
};
|
|
6116
|
+
var resolveBinanceBreadthFingerprint = async (projectRoot) => {
|
|
6117
|
+
if (binanceBreadthFingerprintCache.has(projectRoot)) {
|
|
6118
|
+
return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
|
|
6119
|
+
}
|
|
6120
|
+
const content = await readOptionalFile(
|
|
6121
|
+
import_node_path.default.join(
|
|
6122
|
+
projectRoot,
|
|
6123
|
+
"packages/node/src/config/binanceBreadthUniverses.json"
|
|
6124
|
+
)
|
|
6125
|
+
);
|
|
6126
|
+
let result = null;
|
|
6127
|
+
if (content) {
|
|
6128
|
+
try {
|
|
6129
|
+
const parsed = JSON.parse(content.toString("utf8"));
|
|
6130
|
+
result = typeof parsed.fingerprint === "string" && parsed.fingerprint.trim() ? parsed.fingerprint.trim() : null;
|
|
6131
|
+
} catch {
|
|
6132
|
+
result = null;
|
|
6133
|
+
}
|
|
6134
|
+
}
|
|
6135
|
+
binanceBreadthFingerprintCache.set(projectRoot, result);
|
|
6136
|
+
return result;
|
|
6137
|
+
};
|
|
6138
|
+
var resolveGateFingerprint = async ({
|
|
6139
|
+
projectRoot,
|
|
6140
|
+
strategyName,
|
|
6141
|
+
gitSha
|
|
6142
|
+
}) => {
|
|
6143
|
+
const cacheKey = `${projectRoot}:${strategyName}:${gitSha ?? "unknown"}`;
|
|
6144
|
+
const cached = gateFingerprintCache.get(cacheKey);
|
|
6145
|
+
if (cached) {
|
|
6146
|
+
return cached;
|
|
6147
|
+
}
|
|
6148
|
+
const relativeCandidates = [
|
|
6149
|
+
`packages/strategies/src/${strategyName}/adapters/ai.ts`,
|
|
6150
|
+
`packages/strategies/src/${strategyName}/guardrails.ts`,
|
|
6151
|
+
`packages/strategies/src/${strategyName}/pockets.ts`,
|
|
6152
|
+
`packages/strategies/src/${strategyName}/config.ts`,
|
|
6153
|
+
"packages/node/src/ai.ts"
|
|
6154
|
+
];
|
|
6155
|
+
const optionalEntries = await Promise.all(
|
|
6156
|
+
relativeCandidates.map(async (relativePath) => {
|
|
6157
|
+
const content = await readOptionalFile(
|
|
6158
|
+
import_node_path.default.join(projectRoot, relativePath)
|
|
6159
|
+
);
|
|
6160
|
+
return content == null ? null : { relativePath, content };
|
|
6161
|
+
})
|
|
6162
|
+
);
|
|
6163
|
+
const sourceEntries = [];
|
|
6164
|
+
for (const entry of optionalEntries) {
|
|
6165
|
+
if (entry != null) {
|
|
6166
|
+
sourceEntries.push(entry);
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
6170
|
+
hash.update(strategyName);
|
|
6171
|
+
if (sourceEntries.length) {
|
|
6172
|
+
for (const entry of sourceEntries) {
|
|
6173
|
+
hash.update(entry.relativePath);
|
|
6174
|
+
hash.update(entry.content);
|
|
6175
|
+
}
|
|
6176
|
+
} else {
|
|
6177
|
+
hash.update(gitSha ?? "unknown-git-sha");
|
|
6178
|
+
}
|
|
6179
|
+
const fingerprint = hash.digest("hex").slice(0, 16);
|
|
6180
|
+
gateFingerprintCache.set(cacheKey, fingerprint);
|
|
6181
|
+
return fingerprint;
|
|
6182
|
+
};
|
|
6183
|
+
var buildRuntimeLineage = async ({
|
|
6184
|
+
projectRoot,
|
|
6185
|
+
strategyName,
|
|
6186
|
+
config,
|
|
6187
|
+
runContext,
|
|
6188
|
+
env = process.env
|
|
6189
|
+
}) => {
|
|
6190
|
+
const git = resolveGitLineage(projectRoot);
|
|
6191
|
+
const context = {
|
|
6192
|
+
...Object.fromEntries(
|
|
6193
|
+
RUNTIME_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
|
|
6194
|
+
),
|
|
6195
|
+
...RUNTIME_CONTEXT_DATA_MODEL,
|
|
6196
|
+
binanceBreadthUniverseFingerprint: await resolveBinanceBreadthFingerprint(projectRoot),
|
|
6197
|
+
...runContext
|
|
6198
|
+
};
|
|
6199
|
+
return {
|
|
6200
|
+
schemaVersion: 1,
|
|
6201
|
+
...git,
|
|
6202
|
+
gateFingerprint: await resolveGateFingerprint({
|
|
6203
|
+
projectRoot,
|
|
6204
|
+
strategyName,
|
|
6205
|
+
gitSha: git.gitSha
|
|
6206
|
+
}),
|
|
6207
|
+
configFingerprint: fingerprintRuntimeValue(config),
|
|
6208
|
+
contextFingerprint: fingerprintRuntimeValue(context)
|
|
6209
|
+
};
|
|
6210
|
+
};
|
|
6211
|
+
var runtimeLineageKey = (lineage) => [
|
|
6212
|
+
lineage.schemaVersion,
|
|
6213
|
+
lineage.gitSha ?? "unknown",
|
|
6214
|
+
lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
|
|
6215
|
+
lineage.gateFingerprint,
|
|
6216
|
+
lineage.configFingerprint,
|
|
6217
|
+
lineage.contextFingerprint
|
|
6218
|
+
].join(":");
|
|
6219
|
+
var runtimeLineagesMatch = (left, right) => left != null && right != null && runtimeLineageKey(left) === runtimeLineageKey(right);
|
|
6220
|
+
|
|
5950
6221
|
// src/lib/replay/historicalSignalsReplay.ts
|
|
5951
6222
|
var loadRuntimeStrategies = async (runtimeStrategies) => {
|
|
5952
6223
|
const strategies = await Promise.all(
|
|
5953
6224
|
runtimeStrategies.map(async ({ strategyName, strategyConfig }) => {
|
|
5954
|
-
const strategyCreator = await (0,
|
|
6225
|
+
const strategyCreator = await (0, import_strategies3.getStrategyCreator)(
|
|
5955
6226
|
strategyName,
|
|
5956
6227
|
replayProjectRoot
|
|
5957
6228
|
);
|
|
5958
6229
|
if (!strategyCreator) {
|
|
5959
6230
|
throw new Error(`Unknown strategy: ${strategyName}`);
|
|
5960
6231
|
}
|
|
6232
|
+
const strategyResults = await (0, import_redis3.getData)(
|
|
6233
|
+
import_redis3.redisKeys.strategyResults(replayUserName, strategyName),
|
|
6234
|
+
{}
|
|
6235
|
+
);
|
|
5961
6236
|
return {
|
|
5962
6237
|
strategyName,
|
|
5963
6238
|
strategyCreator,
|
|
5964
|
-
strategyConfig
|
|
6239
|
+
strategyConfig,
|
|
6240
|
+
strategyResults
|
|
5965
6241
|
};
|
|
5966
6242
|
})
|
|
5967
6243
|
);
|
|
@@ -6062,6 +6338,7 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6062
6338
|
}) => {
|
|
6063
6339
|
const startedAt = Date.now();
|
|
6064
6340
|
const signals = [];
|
|
6341
|
+
const runtimeLineages = [];
|
|
6065
6342
|
const projectConfig = await (0, import_cli4.loadTradejsConfig)(replayProjectRoot);
|
|
6066
6343
|
const projectHooks = projectConfig.hooks;
|
|
6067
6344
|
const loadedStrategies = await loadRuntimeStrategies(runtimeStrategies);
|
|
@@ -6149,26 +6426,54 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6149
6426
|
sharedReplayKeyPrefixes.push(sharedIndicatorsReplayKey);
|
|
6150
6427
|
const strategiesForSymbol = await Promise.all(
|
|
6151
6428
|
loadedStrategies.map(
|
|
6152
|
-
async ({
|
|
6429
|
+
async ({
|
|
6153
6430
|
strategyName,
|
|
6431
|
+
strategyCreator,
|
|
6154
6432
|
strategyConfig,
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6433
|
+
strategyResults
|
|
6434
|
+
}) => {
|
|
6435
|
+
const runtimeLineage = await buildRuntimeLineage({
|
|
6436
|
+
projectRoot: replayProjectRoot,
|
|
6437
|
+
strategyName,
|
|
6438
|
+
config: {
|
|
6439
|
+
configId: "config",
|
|
6159
6440
|
strategyConfig,
|
|
6160
|
-
|
|
6161
|
-
}
|
|
6441
|
+
symbolResultConfig: strategyResults?.[symbol]?.config ?? null
|
|
6442
|
+
},
|
|
6443
|
+
runContext: {
|
|
6444
|
+
connectorName: connectorName.toLowerCase(),
|
|
6445
|
+
interval: String(interval),
|
|
6446
|
+
universe: preparedRun.universe ?? null
|
|
6447
|
+
}
|
|
6448
|
+
});
|
|
6449
|
+
runtimeLineages.push({
|
|
6450
|
+
strategy: strategyName,
|
|
6162
6451
|
symbol,
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6452
|
+
lineage: runtimeLineage
|
|
6453
|
+
});
|
|
6454
|
+
return {
|
|
6455
|
+
strategyName,
|
|
6456
|
+
strategyConfig,
|
|
6457
|
+
runtimeLineage,
|
|
6458
|
+
run: await strategyCreator({
|
|
6459
|
+
userName: replayUserName,
|
|
6460
|
+
connectorName,
|
|
6461
|
+
runtimeConfigId: "config",
|
|
6462
|
+
config: buildReplayStrategyConfig({
|
|
6463
|
+
strategyConfig,
|
|
6464
|
+
interval
|
|
6465
|
+
}),
|
|
6466
|
+
symbol,
|
|
6467
|
+
data: preparedData.prevData,
|
|
6468
|
+
btcData: preparedData.btcPrevData,
|
|
6469
|
+
ethData: preparedData.ethPrevData,
|
|
6470
|
+
btcBinanceData: preparedData.btcBinancePrevData,
|
|
6471
|
+
btcCoinbaseData: preparedData.btcCoinbasePrevData,
|
|
6472
|
+
connector: replayConnector,
|
|
6473
|
+
sharedIndicatorsReplayKey
|
|
6474
|
+
})
|
|
6475
|
+
};
|
|
6476
|
+
}
|
|
6172
6477
|
)
|
|
6173
6478
|
);
|
|
6174
6479
|
const symbolRuntime = {
|
|
@@ -6262,7 +6567,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6262
6567
|
ethCandle?.timestamp === timestamp ? ethCandle : void 0
|
|
6263
6568
|
);
|
|
6264
6569
|
if (result && typeof result !== "string") {
|
|
6265
|
-
|
|
6570
|
+
result.runtimeLineage = strategyRuntime.runtimeLineage;
|
|
6571
|
+
await (0, import_strategies3.enrichSignalWithBinanceMarketContext)({
|
|
6266
6572
|
signal: result,
|
|
6267
6573
|
env: "PARITY"
|
|
6268
6574
|
});
|
|
@@ -6286,8 +6592,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6286
6592
|
}
|
|
6287
6593
|
} finally {
|
|
6288
6594
|
for (const keyPrefix of sharedReplayKeyPrefixes) {
|
|
6289
|
-
(0,
|
|
6290
|
-
(0,
|
|
6595
|
+
(0, import_strategies2.releaseStrategyIndicatorsReplayCache)(keyPrefix);
|
|
6596
|
+
(0, import_strategies2.releaseStrategyReplayCache)(keyPrefix);
|
|
6291
6597
|
}
|
|
6292
6598
|
}
|
|
6293
6599
|
const artifacts = replayConnector.getReplayArtifacts();
|
|
@@ -6321,7 +6627,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6321
6627
|
orderLog: artifacts.orderLog,
|
|
6322
6628
|
positionLog: artifacts.positionLog,
|
|
6323
6629
|
cycleCount: orderedTimestamps.length,
|
|
6324
|
-
abortedCycles
|
|
6630
|
+
abortedCycles,
|
|
6631
|
+
runtimeLineages
|
|
6325
6632
|
};
|
|
6326
6633
|
};
|
|
6327
6634
|
|
|
@@ -6805,7 +7112,7 @@ var summarizeTradeParityByStrategy = ({
|
|
|
6805
7112
|
};
|
|
6806
7113
|
|
|
6807
7114
|
// src/lib/runtimeSignalsLoader.ts
|
|
6808
|
-
var
|
|
7115
|
+
var import_redis4 = require("@tradejs/infra/redis");
|
|
6809
7116
|
|
|
6810
7117
|
// src/lib/runtimeSignalsStorage.ts
|
|
6811
7118
|
var import_constants4 = require("@tradejs/core/constants");
|
|
@@ -6855,14 +7162,14 @@ var loadRuntimeSignals = async (userName, {
|
|
|
6855
7162
|
startTime,
|
|
6856
7163
|
endTime
|
|
6857
7164
|
} = {}) => {
|
|
6858
|
-
const bucketPrefix =
|
|
7165
|
+
const bucketPrefix = import_redis4.redisKeys.runtimeSignalBuckets(userName);
|
|
6859
7166
|
const bucketKeys = filterBucketKeysByWindow(
|
|
6860
|
-
await (0,
|
|
7167
|
+
await (0, import_redis4.getKeys)(bucketPrefix),
|
|
6861
7168
|
bucketPrefix,
|
|
6862
7169
|
startTime,
|
|
6863
7170
|
endTime
|
|
6864
7171
|
);
|
|
6865
|
-
const refs = (await Promise.all(bucketKeys.map((key) => (0,
|
|
7172
|
+
const refs = (await Promise.all(bucketKeys.map((key) => (0, import_redis4.getHashJsonValues)(key)))).flat().filter(isRuntimeSignalBucketRef);
|
|
6866
7173
|
const seen = /* @__PURE__ */ new Set();
|
|
6867
7174
|
const payloads = await Promise.all(
|
|
6868
7175
|
refs.map(async (ref) => {
|
|
@@ -6871,8 +7178,8 @@ var loadRuntimeSignals = async (userName, {
|
|
|
6871
7178
|
return null;
|
|
6872
7179
|
}
|
|
6873
7180
|
seen.add(dedupeKey);
|
|
6874
|
-
const signal = await (0,
|
|
6875
|
-
|
|
7181
|
+
const signal = await (0, import_redis4.getData)(
|
|
7182
|
+
import_redis4.redisKeys.storeSignal(ref.symbol, ref.signalId),
|
|
6876
7183
|
null
|
|
6877
7184
|
);
|
|
6878
7185
|
return isSignalRecord(signal) ? signal : null;
|
|
@@ -6884,25 +7191,42 @@ var loadRuntimeSignalEvaluations = async (userName, {
|
|
|
6884
7191
|
startTime,
|
|
6885
7192
|
endTime
|
|
6886
7193
|
} = {}) => {
|
|
6887
|
-
const bucketPrefix =
|
|
7194
|
+
const bucketPrefix = import_redis4.redisKeys.runtimeSignalEvaluationBuckets(userName);
|
|
6888
7195
|
const bucketKeys = filterBucketKeysByWindow(
|
|
6889
|
-
await (0,
|
|
7196
|
+
await (0, import_redis4.getKeys)(bucketPrefix),
|
|
6890
7197
|
bucketPrefix,
|
|
6891
7198
|
startTime,
|
|
6892
7199
|
endTime
|
|
6893
7200
|
);
|
|
6894
|
-
const evaluations = (await Promise.all(bucketKeys.map((key) => (0,
|
|
7201
|
+
const evaluations = (await Promise.all(bucketKeys.map((key) => (0, import_redis4.getHashJsonValues)(key)))).flat().filter(isRuntimeSignalEvaluationRecord);
|
|
6895
7202
|
const deduped = /* @__PURE__ */ new Map();
|
|
6896
7203
|
for (const evaluation of evaluations) {
|
|
6897
7204
|
deduped.set(evaluation.evaluationId, evaluation);
|
|
6898
7205
|
}
|
|
6899
7206
|
return sortByTimestamp([...deduped.values()]);
|
|
6900
7207
|
};
|
|
7208
|
+
var loadRuntimeLineageScopes = async (userName, {
|
|
7209
|
+
startTime,
|
|
7210
|
+
endTime
|
|
7211
|
+
}) => {
|
|
7212
|
+
const records = (await Promise.all(
|
|
7213
|
+
(0, import_time4.getRuntimeStorageDayKeys)(startTime, endTime).map(
|
|
7214
|
+
(dayKey) => (0, import_redis4.getHashJsonValues)(
|
|
7215
|
+
import_redis4.redisKeys.runtimeLineageScopeBucket(userName, dayKey)
|
|
7216
|
+
)
|
|
7217
|
+
)
|
|
7218
|
+
)).flat().filter(
|
|
7219
|
+
(record) => record != null && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.firstTimestamp === "number" && typeof record.lastTimestamp === "number" && record.lineage != null
|
|
7220
|
+
);
|
|
7221
|
+
return records.sort(
|
|
7222
|
+
(left, right) => left.firstTimestamp - right.firstTimestamp || left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol)
|
|
7223
|
+
);
|
|
7224
|
+
};
|
|
6901
7225
|
|
|
6902
7226
|
// src/lib/runtimeTradeSync.ts
|
|
6903
7227
|
var import_constants5 = require("@tradejs/core/constants");
|
|
6904
7228
|
var import_time5 = require("@tradejs/core/time");
|
|
6905
|
-
var
|
|
7229
|
+
var import_redis5 = require("@tradejs/infra/redis");
|
|
6906
7230
|
var CLOSED_PNL_LIMIT = 100;
|
|
6907
7231
|
var CLOSED_PNL_RECONCILIATION_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
|
|
6908
7232
|
var EXCHANGE_HISTORY_MAX_RANGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -6996,6 +7320,18 @@ var consumeClosedPnlMatch = (buckets, trade) => {
|
|
|
6996
7320
|
const [row] = rows.splice(matchIndex, 1);
|
|
6997
7321
|
return row ?? null;
|
|
6998
7322
|
};
|
|
7323
|
+
var isRuntimeTradeInConnectorScope = (trade, connector) => {
|
|
7324
|
+
if (trade.deploymentId && trade.deploymentId !== connector.deploymentId) {
|
|
7325
|
+
return false;
|
|
7326
|
+
}
|
|
7327
|
+
if (trade.accountId && trade.accountId !== connector.accountId) {
|
|
7328
|
+
return false;
|
|
7329
|
+
}
|
|
7330
|
+
if (trade.universe && connector.universe && trade.universe !== connector.universe) {
|
|
7331
|
+
return false;
|
|
7332
|
+
}
|
|
7333
|
+
return true;
|
|
7334
|
+
};
|
|
6999
7335
|
var syncRuntimeTrades = async ({
|
|
7000
7336
|
userName,
|
|
7001
7337
|
connector,
|
|
@@ -7019,16 +7355,23 @@ var syncRuntimeTrades = async ({
|
|
|
7019
7355
|
const openPositionsBySymbol = new Map(
|
|
7020
7356
|
openPositions.map((position) => [position.symbol, position])
|
|
7021
7357
|
);
|
|
7022
|
-
const
|
|
7023
|
-
const
|
|
7358
|
+
const activeOrderIdByKey = /* @__PURE__ */ new Map();
|
|
7359
|
+
const activeTradeKeys = [
|
|
7360
|
+
...new Set(
|
|
7361
|
+
trades.filter((trade) => isRuntimeTradeInConnectorScope(trade, connector)).map(
|
|
7362
|
+
(trade) => import_redis5.redisKeys.runtimeActiveTrade(
|
|
7363
|
+
userName,
|
|
7364
|
+
trade.symbol,
|
|
7365
|
+
trade.deploymentId ?? trade.accountId
|
|
7366
|
+
)
|
|
7367
|
+
)
|
|
7368
|
+
)
|
|
7369
|
+
];
|
|
7024
7370
|
await Promise.all(
|
|
7025
|
-
|
|
7026
|
-
const activeRef = await (0,
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
);
|
|
7030
|
-
activeOrderIdBySymbol.set(
|
|
7031
|
-
symbol,
|
|
7371
|
+
activeTradeKeys.map(async (key) => {
|
|
7372
|
+
const activeRef = await (0, import_redis5.getData)(key, null);
|
|
7373
|
+
activeOrderIdByKey.set(
|
|
7374
|
+
key,
|
|
7032
7375
|
typeof activeRef?.orderId === "string" ? activeRef.orderId : null
|
|
7033
7376
|
);
|
|
7034
7377
|
})
|
|
@@ -7047,12 +7390,21 @@ var syncRuntimeTrades = async ({
|
|
|
7047
7390
|
}
|
|
7048
7391
|
const syncedTrades = [];
|
|
7049
7392
|
for (const trade of trades) {
|
|
7393
|
+
if (!isRuntimeTradeInConnectorScope(trade, connector)) {
|
|
7394
|
+
syncedTrades.push(trade);
|
|
7395
|
+
continue;
|
|
7396
|
+
}
|
|
7050
7397
|
if (trade.status !== "active" && !isRuntimeTradeSyncFallbackClose(trade)) {
|
|
7051
7398
|
syncedTrades.push(trade);
|
|
7052
7399
|
continue;
|
|
7053
7400
|
}
|
|
7401
|
+
const activeTradeKey = import_redis5.redisKeys.runtimeActiveTrade(
|
|
7402
|
+
userName,
|
|
7403
|
+
trade.symbol,
|
|
7404
|
+
trade.deploymentId ?? trade.accountId
|
|
7405
|
+
);
|
|
7054
7406
|
const openPosition = openPositionsBySymbol.get(trade.symbol);
|
|
7055
|
-
const activeOrderId =
|
|
7407
|
+
const activeOrderId = activeOrderIdByKey.get(activeTradeKey);
|
|
7056
7408
|
const isCurrentActiveTrade = activeOrderId === trade.orderId;
|
|
7057
7409
|
if (trade.status === "active" && !openPositionsReliable) {
|
|
7058
7410
|
syncedTrades.push({
|
|
@@ -7070,15 +7422,15 @@ var syncRuntimeTrades = async ({
|
|
|
7070
7422
|
currentPnl: openPosition.unrealizedPnl,
|
|
7071
7423
|
lastSyncedAt: endTime
|
|
7072
7424
|
};
|
|
7073
|
-
await (0,
|
|
7074
|
-
|
|
7425
|
+
await (0, import_redis5.setData)(
|
|
7426
|
+
import_redis5.redisKeys.runtimeTrade(userName, trade.orderId),
|
|
7075
7427
|
nextTrade2,
|
|
7076
7428
|
{
|
|
7077
7429
|
expire: 0
|
|
7078
7430
|
}
|
|
7079
7431
|
);
|
|
7080
|
-
await (0,
|
|
7081
|
-
|
|
7432
|
+
await (0, import_redis5.setHashJsonField)(
|
|
7433
|
+
import_redis5.redisKeys.runtimeTradeBucket(
|
|
7082
7434
|
userName,
|
|
7083
7435
|
(0, import_time5.getRuntimeStorageDayKey)(trade.entryTimestamp)
|
|
7084
7436
|
),
|
|
@@ -7091,6 +7443,7 @@ var syncRuntimeTrades = async ({
|
|
|
7091
7443
|
}
|
|
7092
7444
|
const matchedClosedPnl = consumeClosedPnlMatch(closedPnlBuckets, trade);
|
|
7093
7445
|
if (!matchedClosedPnl) {
|
|
7446
|
+
syncedTrades.push(trade);
|
|
7094
7447
|
continue;
|
|
7095
7448
|
}
|
|
7096
7449
|
const nextTrade = {
|
|
@@ -7111,11 +7464,11 @@ var syncRuntimeTrades = async ({
|
|
|
7111
7464
|
lastSyncedAt: endTime
|
|
7112
7465
|
};
|
|
7113
7466
|
await Promise.all([
|
|
7114
|
-
(0,
|
|
7467
|
+
(0, import_redis5.setData)(import_redis5.redisKeys.runtimeTrade(userName, trade.orderId), nextTrade, {
|
|
7115
7468
|
expire: import_constants5.TTL_1M
|
|
7116
7469
|
}),
|
|
7117
|
-
(0,
|
|
7118
|
-
|
|
7470
|
+
(0, import_redis5.setHashJsonField)(
|
|
7471
|
+
import_redis5.redisKeys.runtimeTradeBucket(
|
|
7119
7472
|
userName,
|
|
7120
7473
|
(0, import_time5.getRuntimeStorageDayKey)(trade.entryTimestamp)
|
|
7121
7474
|
),
|
|
@@ -7123,7 +7476,7 @@ var syncRuntimeTrades = async ({
|
|
|
7123
7476
|
nextTrade,
|
|
7124
7477
|
{ expire: import_constants5.TTL_1M }
|
|
7125
7478
|
),
|
|
7126
|
-
...isCurrentActiveTrade ? [(0,
|
|
7479
|
+
...isCurrentActiveTrade ? [(0, import_redis5.delKey)(activeTradeKey)] : []
|
|
7127
7480
|
]);
|
|
7128
7481
|
syncedTrades.push(nextTrade);
|
|
7129
7482
|
}
|
|
@@ -7733,6 +8086,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
7733
8086
|
),
|
|
7734
8087
|
limit
|
|
7735
8088
|
),
|
|
8089
|
+
orderFailed: [],
|
|
7736
8090
|
runtimeOnly: cappedRuntimeOnlyDetails,
|
|
7737
8091
|
backtestOnly: cappedBacktestOnlyDetails,
|
|
7738
8092
|
nearestCandidates: cappedNearestCandidates,
|
|
@@ -7752,6 +8106,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
7752
8106
|
};
|
|
7753
8107
|
var buildReplayExchangeComparisonDetails = ({
|
|
7754
8108
|
matched,
|
|
8109
|
+
orderFailed = [],
|
|
7755
8110
|
exchangeOnly,
|
|
7756
8111
|
backtestOnly,
|
|
7757
8112
|
exchangeEntries,
|
|
@@ -7802,7 +8157,7 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
7802
8157
|
limit
|
|
7803
8158
|
);
|
|
7804
8159
|
return {
|
|
7805
|
-
capped: matched.length > limit || exchangeOnlyDetails.length > limit || backtestOnlyDetails.length > limit || nearestCandidates.length > limit,
|
|
8160
|
+
capped: matched.length > limit || orderFailed.length > limit || exchangeOnlyDetails.length > limit || backtestOnlyDetails.length > limit || nearestCandidates.length > limit,
|
|
7806
8161
|
limit,
|
|
7807
8162
|
matched: capReplayDetails(
|
|
7808
8163
|
matched.map(
|
|
@@ -7818,6 +8173,27 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
7818
8173
|
),
|
|
7819
8174
|
limit
|
|
7820
8175
|
),
|
|
8176
|
+
orderFailed: capReplayDetails(
|
|
8177
|
+
orderFailed.map((item) => {
|
|
8178
|
+
const detail = buildMatchedReplayDetail({
|
|
8179
|
+
runtime: toExchangeDetail(item.exchange),
|
|
8180
|
+
backtest: toBacktestParityDetail(
|
|
8181
|
+
item.backtest,
|
|
8182
|
+
backtestTimestampOffsetMs
|
|
8183
|
+
),
|
|
8184
|
+
timestampDiffMs: item.timestampDiffMs,
|
|
8185
|
+
priceDeltaPct: item.priceDeltaPct
|
|
8186
|
+
});
|
|
8187
|
+
return {
|
|
8188
|
+
runtime: detail.runtime,
|
|
8189
|
+
backtest: detail.backtest,
|
|
8190
|
+
timestampDiffMs: detail.timestampDiffMs,
|
|
8191
|
+
priceDeltaPct: detail.priceDeltaPct,
|
|
8192
|
+
reason: item.reason
|
|
8193
|
+
};
|
|
8194
|
+
}),
|
|
8195
|
+
limit
|
|
8196
|
+
),
|
|
7821
8197
|
runtimeOnly: cappedExchangeOnlyDetails,
|
|
7822
8198
|
backtestOnly: cappedBacktestOnlyDetails,
|
|
7823
8199
|
nearestCandidates: cappedNearestCandidates,
|
|
@@ -7853,11 +8229,147 @@ var buildReplaySignalEvaluations = (signals) => signals.map((signal) => ({
|
|
|
7853
8229
|
reason: signal.orderSkipReason || signal.orderStatus || "SIGNAL",
|
|
7854
8230
|
signalId: signal.signalId,
|
|
7855
8231
|
direction: signal.direction,
|
|
8232
|
+
runtimeLineage: signal.runtimeLineage,
|
|
7856
8233
|
orderStatus: signal.orderStatus,
|
|
7857
8234
|
orderSkipReason: signal.orderSkipReason,
|
|
7858
8235
|
...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
|
|
7859
8236
|
...signal.ml ? { ml: signal.ml } : {}
|
|
7860
8237
|
}));
|
|
8238
|
+
var lineageScopeKey = (strategy, symbol) => `${strategy}::${symbol}`;
|
|
8239
|
+
var buildExpectedLineageByScope = (replayLineages) => new Map(
|
|
8240
|
+
replayLineages.map((record) => [
|
|
8241
|
+
lineageScopeKey(record.strategy, record.symbol),
|
|
8242
|
+
record.lineage
|
|
8243
|
+
])
|
|
8244
|
+
);
|
|
8245
|
+
var hasExpectedLineage = ({
|
|
8246
|
+
strategy,
|
|
8247
|
+
symbol,
|
|
8248
|
+
lineage,
|
|
8249
|
+
expectedByScope
|
|
8250
|
+
}) => runtimeLineagesMatch(
|
|
8251
|
+
lineage,
|
|
8252
|
+
expectedByScope.get(lineageScopeKey(strategy, symbol))
|
|
8253
|
+
);
|
|
8254
|
+
var filterReplayComparisonByLineage = ({
|
|
8255
|
+
replayLineages,
|
|
8256
|
+
runtimeTrades,
|
|
8257
|
+
runtimeSignals,
|
|
8258
|
+
runtimeSignalEvaluations,
|
|
8259
|
+
runtimeLineageScopes,
|
|
8260
|
+
backtestEntries
|
|
8261
|
+
}) => {
|
|
8262
|
+
const expectedByScope = buildExpectedLineageByScope(replayLineages);
|
|
8263
|
+
const comparableRuntimeSignals = runtimeSignals.filter(
|
|
8264
|
+
(signal) => hasExpectedLineage({
|
|
8265
|
+
strategy: signal.strategy,
|
|
8266
|
+
symbol: signal.symbol,
|
|
8267
|
+
lineage: signal.runtimeLineage,
|
|
8268
|
+
expectedByScope
|
|
8269
|
+
})
|
|
8270
|
+
);
|
|
8271
|
+
const comparableRuntimeEvaluations = runtimeSignalEvaluations.filter(
|
|
8272
|
+
(evaluation) => hasExpectedLineage({
|
|
8273
|
+
strategy: evaluation.strategy,
|
|
8274
|
+
symbol: evaluation.symbol,
|
|
8275
|
+
lineage: evaluation.runtimeLineage,
|
|
8276
|
+
expectedByScope
|
|
8277
|
+
})
|
|
8278
|
+
);
|
|
8279
|
+
const comparableRuntimeLineageScopes = runtimeLineageScopes.filter(
|
|
8280
|
+
(scope) => hasExpectedLineage({
|
|
8281
|
+
strategy: scope.strategy,
|
|
8282
|
+
symbol: scope.symbol,
|
|
8283
|
+
lineage: scope.lineage,
|
|
8284
|
+
expectedByScope
|
|
8285
|
+
})
|
|
8286
|
+
);
|
|
8287
|
+
const deploymentWindows = /* @__PURE__ */ new Map();
|
|
8288
|
+
for (const scope of comparableRuntimeLineageScopes) {
|
|
8289
|
+
const key = lineageScopeKey(scope.strategy, scope.symbol);
|
|
8290
|
+
const existing = deploymentWindows.get(key);
|
|
8291
|
+
deploymentWindows.set(key, {
|
|
8292
|
+
firstTimestamp: existing == null ? scope.firstTimestamp : Math.min(existing.firstTimestamp, scope.firstTimestamp),
|
|
8293
|
+
lastTimestamp: existing == null ? scope.lastTimestamp : Math.max(existing.lastTimestamp, scope.lastTimestamp)
|
|
8294
|
+
});
|
|
8295
|
+
}
|
|
8296
|
+
const lineageBySignalId = /* @__PURE__ */ new Map();
|
|
8297
|
+
for (const signal of runtimeSignals) {
|
|
8298
|
+
if (signal.runtimeLineage) {
|
|
8299
|
+
lineageBySignalId.set(signal.signalId, signal.runtimeLineage);
|
|
8300
|
+
}
|
|
8301
|
+
}
|
|
8302
|
+
for (const evaluation of runtimeSignalEvaluations) {
|
|
8303
|
+
if (evaluation.signalId && evaluation.runtimeLineage) {
|
|
8304
|
+
lineageBySignalId.set(evaluation.signalId, evaluation.runtimeLineage);
|
|
8305
|
+
}
|
|
8306
|
+
}
|
|
8307
|
+
const comparableRuntimeTrades = runtimeTrades.filter((trade) => {
|
|
8308
|
+
const lineage = trade.signalId ? lineageBySignalId.get(trade.signalId) : void 0;
|
|
8309
|
+
if (lineage) {
|
|
8310
|
+
return hasExpectedLineage({
|
|
8311
|
+
strategy: trade.strategy,
|
|
8312
|
+
symbol: trade.symbol,
|
|
8313
|
+
lineage,
|
|
8314
|
+
expectedByScope
|
|
8315
|
+
});
|
|
8316
|
+
}
|
|
8317
|
+
const window2 = deploymentWindows.get(
|
|
8318
|
+
lineageScopeKey(trade.strategy, trade.symbol)
|
|
8319
|
+
);
|
|
8320
|
+
const signalTimestamp = trade.signalTimestamp ?? trade.entryTimestamp;
|
|
8321
|
+
return window2 != null && signalTimestamp >= window2.firstTimestamp && signalTimestamp <= window2.lastTimestamp;
|
|
8322
|
+
});
|
|
8323
|
+
for (const artifact of [
|
|
8324
|
+
...comparableRuntimeSignals,
|
|
8325
|
+
...comparableRuntimeEvaluations
|
|
8326
|
+
]) {
|
|
8327
|
+
const key = lineageScopeKey(artifact.strategy, artifact.symbol);
|
|
8328
|
+
const existing = deploymentWindows.get(key);
|
|
8329
|
+
deploymentWindows.set(key, {
|
|
8330
|
+
firstTimestamp: existing == null ? artifact.timestamp : Math.min(existing.firstTimestamp, artifact.timestamp),
|
|
8331
|
+
lastTimestamp: existing == null ? artifact.timestamp : Math.max(existing.lastTimestamp, artifact.timestamp)
|
|
8332
|
+
});
|
|
8333
|
+
}
|
|
8334
|
+
const comparableBacktestEntries = backtestEntries.filter((entry) => {
|
|
8335
|
+
const window2 = deploymentWindows.get(
|
|
8336
|
+
lineageScopeKey(entry.strategy, entry.symbol)
|
|
8337
|
+
);
|
|
8338
|
+
const signalTimestamp = entry.signalTimestamp ?? entry.timestamp;
|
|
8339
|
+
return window2 != null && signalTimestamp >= window2.firstTimestamp && signalTimestamp <= window2.lastTimestamp;
|
|
8340
|
+
});
|
|
8341
|
+
const comparableScopeKeys = new Set(deploymentWindows.keys());
|
|
8342
|
+
return {
|
|
8343
|
+
runtimeTrades: comparableRuntimeTrades,
|
|
8344
|
+
runtimeSignals: comparableRuntimeSignals,
|
|
8345
|
+
runtimeSignalEvaluations: comparableRuntimeEvaluations,
|
|
8346
|
+
backtestEntries: comparableBacktestEntries,
|
|
8347
|
+
lineage: {
|
|
8348
|
+
enforced: true,
|
|
8349
|
+
replayScopes: expectedByScope.size,
|
|
8350
|
+
comparableScopes: comparableScopeKeys.size,
|
|
8351
|
+
excludedRuntimeTrades: runtimeTrades.length - comparableRuntimeTrades.length,
|
|
8352
|
+
excludedRuntimeSignals: runtimeSignals.length - comparableRuntimeSignals.length,
|
|
8353
|
+
excludedRuntimeEvaluations: runtimeSignalEvaluations.length - comparableRuntimeEvaluations.length,
|
|
8354
|
+
excludedRuntimeLineageScopes: runtimeLineageScopes.length - comparableRuntimeLineageScopes.length,
|
|
8355
|
+
excludedExchangeEntries: 0,
|
|
8356
|
+
excludedBacktestEntries: backtestEntries.length - comparableBacktestEntries.length,
|
|
8357
|
+
reason: comparableScopeKeys.size > 0 ? null : "no_runtime_artifacts_with_matching_lineage",
|
|
8358
|
+
replay: [...expectedByScope.entries()].map(([scope, lineage]) => {
|
|
8359
|
+
const separator = scope.indexOf("::");
|
|
8360
|
+
return {
|
|
8361
|
+
strategy: scope.slice(0, separator),
|
|
8362
|
+
symbol: scope.slice(separator + 2),
|
|
8363
|
+
lineage
|
|
8364
|
+
};
|
|
8365
|
+
}).sort(
|
|
8366
|
+
(left, right) => left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || runtimeLineageKey(left.lineage).localeCompare(
|
|
8367
|
+
runtimeLineageKey(right.lineage)
|
|
8368
|
+
)
|
|
8369
|
+
)
|
|
8370
|
+
}
|
|
8371
|
+
};
|
|
8372
|
+
};
|
|
7861
8373
|
var formatDrilldownSummary = (summary) => Object.entries(summary).filter(([, count]) => count > 0).map(([classification, count]) => `${classification}=${count}`).join(", ");
|
|
7862
8374
|
var toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
7863
8375
|
var sumOptionalNumbers = (values) => {
|
|
@@ -7881,6 +8393,14 @@ var firstFiniteNumber = (values) => {
|
|
|
7881
8393
|
return null;
|
|
7882
8394
|
};
|
|
7883
8395
|
var firstString = (values) => values.find((value) => typeof value === "string" && value.trim());
|
|
8396
|
+
var summarizeBacktestPnlByStrategy = (entries) => {
|
|
8397
|
+
const totals = /* @__PURE__ */ new Map();
|
|
8398
|
+
for (const entry of entries) {
|
|
8399
|
+
const pnl = typeof entry.expectedPnl === "number" && Number.isFinite(entry.expectedPnl) ? entry.expectedPnl : 0;
|
|
8400
|
+
totals.set(entry.strategy, (totals.get(entry.strategy) ?? 0) + pnl);
|
|
8401
|
+
}
|
|
8402
|
+
return totals;
|
|
8403
|
+
};
|
|
7884
8404
|
var getExchangeExecutionMergeKey = (entry) => {
|
|
7885
8405
|
const orderId = firstString([entry.orderId, entry.orderLinkId]);
|
|
7886
8406
|
return orderId ? `${entry.symbol}::${entry.direction}::${orderId}` : null;
|
|
@@ -8147,16 +8667,89 @@ var compareExchangeEntriesToBacktest = ({
|
|
|
8147
8667
|
backtestOnly
|
|
8148
8668
|
};
|
|
8149
8669
|
};
|
|
8670
|
+
var findExchangeRuntimeOutcome = ({
|
|
8671
|
+
exchangeEntry,
|
|
8672
|
+
strategyName,
|
|
8673
|
+
runtimeSignals,
|
|
8674
|
+
runtimeSignalEvaluations,
|
|
8675
|
+
toleranceMs,
|
|
8676
|
+
signalTimestampOffsetMs
|
|
8677
|
+
}) => {
|
|
8678
|
+
const candidates = [];
|
|
8679
|
+
for (const signal of runtimeSignals) {
|
|
8680
|
+
if (signal.strategy === strategyName && signal.symbol === exchangeEntry.symbol && signal.direction === exchangeEntry.direction) {
|
|
8681
|
+
candidates.push({
|
|
8682
|
+
timestamp: signal.timestamp,
|
|
8683
|
+
orderStatus: signal.orderStatus,
|
|
8684
|
+
reason: signal.orderFailureReason || signal.orderSkipReason || signal.orderStatus
|
|
8685
|
+
});
|
|
8686
|
+
}
|
|
8687
|
+
}
|
|
8688
|
+
for (const evaluation of runtimeSignalEvaluations) {
|
|
8689
|
+
if (evaluation.strategy === strategyName && evaluation.symbol === exchangeEntry.symbol && (evaluation.direction == null || evaluation.direction === exchangeEntry.direction)) {
|
|
8690
|
+
candidates.push({
|
|
8691
|
+
timestamp: evaluation.timestamp,
|
|
8692
|
+
orderStatus: evaluation.orderStatus,
|
|
8693
|
+
reason: evaluation.orderSkipReason || evaluation.reason || evaluation.orderStatus
|
|
8694
|
+
});
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
return candidates.map((candidate) => ({
|
|
8698
|
+
...candidate,
|
|
8699
|
+
timestampDiffMs: Math.abs(
|
|
8700
|
+
candidate.timestamp + signalTimestampOffsetMs - exchangeEntry.entryTimestamp
|
|
8701
|
+
)
|
|
8702
|
+
})).filter((candidate) => candidate.timestampDiffMs <= toleranceMs).sort(
|
|
8703
|
+
(left, right) => left.timestampDiffMs - right.timestampDiffMs || Number(right.orderStatus === "failed") - Number(left.orderStatus === "failed")
|
|
8704
|
+
)[0] ?? null;
|
|
8705
|
+
};
|
|
8706
|
+
var splitExchangeMatchesByRuntimeOrderStatus = ({
|
|
8707
|
+
matched,
|
|
8708
|
+
strategyNameByOrderLinkKey,
|
|
8709
|
+
runtimeSignals,
|
|
8710
|
+
runtimeSignalEvaluations,
|
|
8711
|
+
toleranceMs,
|
|
8712
|
+
signalTimestampOffsetMs
|
|
8713
|
+
}) => {
|
|
8714
|
+
const orderFailed = [];
|
|
8715
|
+
const completed = matched.filter((item) => {
|
|
8716
|
+
const strategyName = resolveReplayStrategyNameFromExchangeEntry({
|
|
8717
|
+
exchangeEntry: item.exchange,
|
|
8718
|
+
strategyNameByOrderLinkKey
|
|
8719
|
+
});
|
|
8720
|
+
if (!strategyName) {
|
|
8721
|
+
return true;
|
|
8722
|
+
}
|
|
8723
|
+
const outcome = findExchangeRuntimeOutcome({
|
|
8724
|
+
exchangeEntry: item.exchange,
|
|
8725
|
+
strategyName,
|
|
8726
|
+
runtimeSignals,
|
|
8727
|
+
runtimeSignalEvaluations,
|
|
8728
|
+
toleranceMs,
|
|
8729
|
+
signalTimestampOffsetMs
|
|
8730
|
+
});
|
|
8731
|
+
if (outcome?.orderStatus !== "failed") {
|
|
8732
|
+
return true;
|
|
8733
|
+
}
|
|
8734
|
+
orderFailed.push({
|
|
8735
|
+
...item,
|
|
8736
|
+
reason: outcome.reason || "orderStatus=failed"
|
|
8737
|
+
});
|
|
8738
|
+
return false;
|
|
8739
|
+
});
|
|
8740
|
+
return { completed, orderFailed };
|
|
8741
|
+
};
|
|
8150
8742
|
var saveAndPrintReplayExchangeComparison = async ({
|
|
8151
8743
|
liveStrategySummaries,
|
|
8152
8744
|
backtestEntries,
|
|
8153
8745
|
replaySignals,
|
|
8154
8746
|
replaySignalEvaluations,
|
|
8155
8747
|
runtimeSignals,
|
|
8156
|
-
runtimeSignalEvaluations
|
|
8748
|
+
runtimeSignalEvaluations,
|
|
8749
|
+
lineage
|
|
8157
8750
|
}) => {
|
|
8158
8751
|
const { connector, connectorName, window: window2 } = getRuntimeCompareContext();
|
|
8159
|
-
const
|
|
8752
|
+
const rawExchangeEntries = await loadExchangeEntriesForComparison({
|
|
8160
8753
|
connector,
|
|
8161
8754
|
startTime: window2.start,
|
|
8162
8755
|
endTime: window2.end
|
|
@@ -8164,11 +8757,36 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8164
8757
|
const strategyNameByOrderLinkKey = buildStrategyNameByOrderLinkKey(
|
|
8165
8758
|
liveStrategySummaries.map((summary) => summary.strategyName)
|
|
8166
8759
|
);
|
|
8760
|
+
const exchangeOutcomeByEntry = /* @__PURE__ */ new Map();
|
|
8761
|
+
const exchangeEntries = rawExchangeEntries.filter((entry) => {
|
|
8762
|
+
const strategyName = resolveReplayStrategyNameFromExchangeEntry({
|
|
8763
|
+
exchangeEntry: entry,
|
|
8764
|
+
strategyNameByOrderLinkKey
|
|
8765
|
+
});
|
|
8766
|
+
if (!strategyName) {
|
|
8767
|
+
return false;
|
|
8768
|
+
}
|
|
8769
|
+
const outcome = findExchangeRuntimeOutcome({
|
|
8770
|
+
exchangeEntry: entry,
|
|
8771
|
+
strategyName,
|
|
8772
|
+
runtimeSignals,
|
|
8773
|
+
runtimeSignalEvaluations,
|
|
8774
|
+
toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
|
|
8775
|
+
signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
|
|
8776
|
+
});
|
|
8777
|
+
exchangeOutcomeByEntry.set(entry, outcome);
|
|
8778
|
+
return outcome != null;
|
|
8779
|
+
});
|
|
8780
|
+
const comparisonLineage = {
|
|
8781
|
+
...lineage,
|
|
8782
|
+
excludedExchangeEntries: lineage.excludedExchangeEntries + rawExchangeEntries.length - exchangeEntries.length
|
|
8783
|
+
};
|
|
8784
|
+
const backtestPnlByStrategy = summarizeBacktestPnlByStrategy(backtestEntries);
|
|
8167
8785
|
if (!exchangeEntries.length) {
|
|
8168
8786
|
console.log("");
|
|
8169
8787
|
console.log(
|
|
8170
8788
|
import_chalk11.default.yellow(
|
|
8171
|
-
`SIGNALS REPLAY VS EXCHANGE: no exchange entry executions found for ${connectorName} in ${(0, import_time6.formatUnix)(
|
|
8789
|
+
`SIGNALS REPLAY VS EXCHANGE: no lineage-linked exchange entry executions found for ${connectorName} in ${(0, import_time6.formatUnix)(
|
|
8172
8790
|
window2.start
|
|
8173
8791
|
)} -> ${(0, import_time6.formatUnix)(window2.end)}`
|
|
8174
8792
|
)
|
|
@@ -8195,18 +8813,21 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8195
8813
|
runtimeEntriesCount: 0,
|
|
8196
8814
|
backtestEntriesCount: backtestEntries.length,
|
|
8197
8815
|
matchedCount: 0,
|
|
8816
|
+
orderFailedCount: 0,
|
|
8198
8817
|
runtimeOnlyCount: 0,
|
|
8199
8818
|
backtestOnlyCount: backtestEntries.length,
|
|
8200
8819
|
details: details2,
|
|
8820
|
+
lineage: comparisonLineage,
|
|
8201
8821
|
rows: liveStrategySummaries.map((summary) => ({
|
|
8202
8822
|
strategyName: summary.strategyName,
|
|
8203
8823
|
backtestEntries: backtestEntries.filter(
|
|
8204
8824
|
(entry) => entry.strategy === summary.strategyName
|
|
8205
8825
|
).length,
|
|
8206
|
-
backtestNetProfit: summary.
|
|
8826
|
+
backtestNetProfit: backtestPnlByStrategy.get(summary.strategyName) ?? 0,
|
|
8207
8827
|
runtimeTrades: 0,
|
|
8208
8828
|
runtimePnl: 0,
|
|
8209
8829
|
matched: 0,
|
|
8830
|
+
orderFailed: 0,
|
|
8210
8831
|
runtimeOnly: 0,
|
|
8211
8832
|
backtestOnly: backtestEntries.filter(
|
|
8212
8833
|
(entry) => entry.strategy === summary.strategyName
|
|
@@ -8220,6 +8841,14 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8220
8841
|
toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
|
|
8221
8842
|
backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
|
|
8222
8843
|
});
|
|
8844
|
+
const { completed: matched, orderFailed } = splitExchangeMatchesByRuntimeOrderStatus({
|
|
8845
|
+
matched: comparison.matched,
|
|
8846
|
+
strategyNameByOrderLinkKey,
|
|
8847
|
+
runtimeSignals,
|
|
8848
|
+
runtimeSignalEvaluations,
|
|
8849
|
+
toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
|
|
8850
|
+
signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
|
|
8851
|
+
});
|
|
8223
8852
|
const liveSummaryByStrategy = new Map(
|
|
8224
8853
|
liveStrategySummaries.map((summary) => [summary.strategyName, summary])
|
|
8225
8854
|
);
|
|
@@ -8232,10 +8861,11 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8232
8861
|
const next = {
|
|
8233
8862
|
strategyName,
|
|
8234
8863
|
backtestEntries: 0,
|
|
8235
|
-
backtestNetProfit:
|
|
8864
|
+
backtestNetProfit: backtestPnlByStrategy.get(strategyName) ?? 0,
|
|
8236
8865
|
runtimeTrades: 0,
|
|
8237
8866
|
runtimePnl: 0,
|
|
8238
8867
|
matched: 0,
|
|
8868
|
+
orderFailed: 0,
|
|
8239
8869
|
runtimeOnly: 0,
|
|
8240
8870
|
backtestOnly: 0
|
|
8241
8871
|
};
|
|
@@ -8248,7 +8878,7 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8248
8878
|
for (const entry of backtestEntries) {
|
|
8249
8879
|
ensureRow(entry.strategy).backtestEntries += 1;
|
|
8250
8880
|
}
|
|
8251
|
-
for (const item of
|
|
8881
|
+
for (const item of matched) {
|
|
8252
8882
|
const row = ensureRow(item.backtest.strategy);
|
|
8253
8883
|
row.runtimeTrades += 1;
|
|
8254
8884
|
row.matched += 1;
|
|
@@ -8256,6 +8886,11 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8256
8886
|
row.runtimePnl += item.exchange.closedPnl;
|
|
8257
8887
|
}
|
|
8258
8888
|
}
|
|
8889
|
+
for (const item of orderFailed) {
|
|
8890
|
+
const row = ensureRow(item.backtest.strategy);
|
|
8891
|
+
row.runtimeTrades += 1;
|
|
8892
|
+
row.orderFailed += 1;
|
|
8893
|
+
}
|
|
8259
8894
|
for (const entry of comparison.backtestOnly) {
|
|
8260
8895
|
ensureRow(entry.strategy).backtestOnly += 1;
|
|
8261
8896
|
}
|
|
@@ -8274,7 +8909,8 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8274
8909
|
}
|
|
8275
8910
|
}
|
|
8276
8911
|
const details = buildReplayExchangeComparisonDetails({
|
|
8277
|
-
matched
|
|
8912
|
+
matched,
|
|
8913
|
+
orderFailed,
|
|
8278
8914
|
exchangeOnly: comparison.exchangeOnly,
|
|
8279
8915
|
backtestOnly: comparison.backtestOnly,
|
|
8280
8916
|
exchangeEntries,
|
|
@@ -8301,8 +8937,10 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8301
8937
|
import_chalk11.default.yellow(String(row.runtimeTrades)),
|
|
8302
8938
|
rtPnlColor(`${row.runtimePnl.toFixed(2)}$`),
|
|
8303
8939
|
import_chalk11.default.green(String(row.matched)),
|
|
8940
|
+
import_chalk11.default.red(String(row.orderFailed)),
|
|
8304
8941
|
import_chalk11.default.yellow(String(row.runtimeOnly)),
|
|
8305
|
-
import_chalk11.default.magenta(String(row.backtestOnly))
|
|
8942
|
+
import_chalk11.default.magenta(String(row.backtestOnly)),
|
|
8943
|
+
import_chalk11.default.red(String(getReplayRuntimeUnmatchedCount(row)))
|
|
8306
8944
|
];
|
|
8307
8945
|
});
|
|
8308
8946
|
console.log("");
|
|
@@ -8330,17 +8968,20 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8330
8968
|
windowTradesCount: exchangeEntries.length,
|
|
8331
8969
|
runtimeEntriesCount: exchangeEntries.length,
|
|
8332
8970
|
backtestEntriesCount: backtestEntries.length,
|
|
8333
|
-
matchedCount:
|
|
8971
|
+
matchedCount: matched.length,
|
|
8972
|
+
orderFailedCount: orderFailed.length,
|
|
8334
8973
|
runtimeOnlyCount: comparison.exchangeOnly.length,
|
|
8335
8974
|
backtestOnlyCount: comparison.backtestOnly.length,
|
|
8336
8975
|
rows,
|
|
8337
|
-
details
|
|
8976
|
+
details,
|
|
8977
|
+
lineage: comparisonLineage
|
|
8338
8978
|
};
|
|
8339
8979
|
};
|
|
8340
8980
|
var saveAndPrintReplayRuntimeComparison = async ({
|
|
8341
8981
|
liveStrategySummaries,
|
|
8342
8982
|
backtestEntries,
|
|
8343
|
-
replaySignals
|
|
8983
|
+
replaySignals,
|
|
8984
|
+
replayLineages
|
|
8344
8985
|
}) => {
|
|
8345
8986
|
const { connector, connectorName, window: window2 } = getRuntimeCompareContext();
|
|
8346
8987
|
if (!connector || !window2) {
|
|
@@ -8350,8 +8991,16 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8350
8991
|
liveStrategySummaries.map((summary) => summary.strategyName)
|
|
8351
8992
|
);
|
|
8352
8993
|
const replaySignalEvaluations = buildReplaySignalEvaluations(replaySignals);
|
|
8353
|
-
const [
|
|
8354
|
-
|
|
8994
|
+
const [
|
|
8995
|
+
rawRuntimeTrades,
|
|
8996
|
+
runtimeSignals,
|
|
8997
|
+
runtimeSignalEvaluations,
|
|
8998
|
+
runtimeLineageScopes
|
|
8999
|
+
] = await Promise.all([
|
|
9000
|
+
loadRuntimeTrades(replayUserName, {
|
|
9001
|
+
startTime: window2.start,
|
|
9002
|
+
endTime: window2.end
|
|
9003
|
+
}),
|
|
8355
9004
|
loadRuntimeSignals(replayUserName, {
|
|
8356
9005
|
startTime: window2.start,
|
|
8357
9006
|
endTime: window2.end
|
|
@@ -8359,6 +9008,10 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8359
9008
|
loadRuntimeSignalEvaluations(replayUserName, {
|
|
8360
9009
|
startTime: window2.start,
|
|
8361
9010
|
endTime: window2.end
|
|
9011
|
+
}),
|
|
9012
|
+
loadRuntimeLineageScopes(replayUserName, {
|
|
9013
|
+
startTime: window2.start,
|
|
9014
|
+
endTime: window2.end
|
|
8362
9015
|
})
|
|
8363
9016
|
]);
|
|
8364
9017
|
const syncedRuntimeTrades = await syncRuntimeTrades({
|
|
@@ -8400,7 +9053,16 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8400
9053
|
}
|
|
8401
9054
|
}
|
|
8402
9055
|
});
|
|
8403
|
-
const
|
|
9056
|
+
const comparable = filterReplayComparisonByLineage({
|
|
9057
|
+
replayLineages,
|
|
9058
|
+
runtimeTrades: syncedRuntimeTrades,
|
|
9059
|
+
runtimeSignals,
|
|
9060
|
+
runtimeSignalEvaluations,
|
|
9061
|
+
runtimeLineageScopes,
|
|
9062
|
+
backtestEntries
|
|
9063
|
+
});
|
|
9064
|
+
const comparableBacktestEntries = comparable.backtestEntries;
|
|
9065
|
+
const windowRuntimeTrades = comparable.runtimeTrades.filter(
|
|
8404
9066
|
(trade) => trade.entryTimestamp >= window2.start && trade.entryTimestamp < window2.end && relevantStrategies.has(trade.strategy)
|
|
8405
9067
|
);
|
|
8406
9068
|
if (!windowRuntimeTrades.length) {
|
|
@@ -8409,17 +9071,18 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8409
9071
|
import_chalk11.default.yellow(
|
|
8410
9072
|
`SIGNALS REPLAY VS RUNTIME: no local runtime trades found for ${connectorName} in ${(0, import_time6.formatUnix)(
|
|
8411
9073
|
window2.start
|
|
8412
|
-
)} -> ${(0, import_time6.formatUnix)(window2.end)}
|
|
9074
|
+
)} -> ${(0, import_time6.formatUnix)(window2.end)} with matching lineage; checking lineage-linked exchange executions`
|
|
8413
9075
|
)
|
|
8414
9076
|
);
|
|
8415
9077
|
console.log("");
|
|
8416
9078
|
return saveAndPrintReplayExchangeComparison({
|
|
8417
9079
|
liveStrategySummaries,
|
|
8418
|
-
backtestEntries,
|
|
9080
|
+
backtestEntries: comparableBacktestEntries,
|
|
8419
9081
|
replaySignals,
|
|
8420
9082
|
replaySignalEvaluations,
|
|
8421
|
-
runtimeSignals,
|
|
8422
|
-
runtimeSignalEvaluations
|
|
9083
|
+
runtimeSignals: comparable.runtimeSignals,
|
|
9084
|
+
runtimeSignalEvaluations: comparable.runtimeSignalEvaluations,
|
|
9085
|
+
lineage: comparable.lineage
|
|
8423
9086
|
});
|
|
8424
9087
|
}
|
|
8425
9088
|
const runtimeSummaries = summarizeRuntimeTradesByStrategy(windowRuntimeTrades);
|
|
@@ -8430,7 +9093,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8430
9093
|
const runtimeDedupe = dedupeRuntimeParityEntries(rawRuntimeEntries);
|
|
8431
9094
|
const comparison = compareTradeParityEntries({
|
|
8432
9095
|
runtimeEntries: runtimeDedupe.entries,
|
|
8433
|
-
backtestEntries,
|
|
9096
|
+
backtestEntries: comparableBacktestEntries,
|
|
8434
9097
|
toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
|
|
8435
9098
|
backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
|
|
8436
9099
|
});
|
|
@@ -8439,18 +9102,18 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8439
9102
|
runtimeOnly: comparison.runtimeOnly,
|
|
8440
9103
|
backtestOnly: comparison.backtestOnly,
|
|
8441
9104
|
runtimeEntries: runtimeDedupe.entries,
|
|
8442
|
-
backtestEntries,
|
|
9105
|
+
backtestEntries: comparableBacktestEntries,
|
|
8443
9106
|
toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
|
|
8444
9107
|
backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs(),
|
|
8445
|
-
runtimeSignals,
|
|
8446
|
-
runtimeSignalEvaluations,
|
|
9108
|
+
runtimeSignals: comparable.runtimeSignals,
|
|
9109
|
+
runtimeSignalEvaluations: comparable.runtimeSignalEvaluations,
|
|
8447
9110
|
replaySignals,
|
|
8448
9111
|
replaySignalEvaluations
|
|
8449
9112
|
});
|
|
8450
9113
|
const parityRows = summarizeTradeParityByStrategy({
|
|
8451
9114
|
runtimeEntries: runtimeDedupe.entries,
|
|
8452
9115
|
runtimeDuplicateEntries: runtimeDedupe.duplicateEntries,
|
|
8453
|
-
backtestEntries,
|
|
9116
|
+
backtestEntries: comparableBacktestEntries,
|
|
8454
9117
|
matchedEntries: comparison.matched,
|
|
8455
9118
|
runtimeOnlyEntries: comparison.runtimeOnly,
|
|
8456
9119
|
backtestOnlyEntries: comparison.backtestOnly
|
|
@@ -8459,22 +9122,25 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8459
9122
|
const liveSummaryByStrategy = new Map(
|
|
8460
9123
|
liveStrategySummaries.map((summary) => [summary.strategyName, summary])
|
|
8461
9124
|
);
|
|
9125
|
+
const backtestPnlByStrategy = summarizeBacktestPnlByStrategy(
|
|
9126
|
+
comparableBacktestEntries
|
|
9127
|
+
);
|
|
8462
9128
|
const strategyNames = /* @__PURE__ */ new Set([
|
|
8463
9129
|
...liveSummaryByStrategy.keys(),
|
|
8464
9130
|
...runtimeSummaryByStrategy.keys(),
|
|
8465
9131
|
...parityByStrategy.keys()
|
|
8466
9132
|
]);
|
|
8467
9133
|
const rows = [...strategyNames].sort((left, right) => left.localeCompare(right)).map((strategyName) => {
|
|
8468
|
-
const liveSummary = liveSummaryByStrategy.get(strategyName);
|
|
8469
9134
|
const runtimeSummary = runtimeSummaryByStrategy.get(strategyName);
|
|
8470
9135
|
const parity = parityByStrategy.get(strategyName);
|
|
8471
9136
|
return {
|
|
8472
9137
|
strategyName,
|
|
8473
9138
|
backtestEntries: parity?.backtest ?? 0,
|
|
8474
|
-
backtestNetProfit:
|
|
9139
|
+
backtestNetProfit: backtestPnlByStrategy.get(strategyName) ?? 0,
|
|
8475
9140
|
runtimeTrades: runtimeSummary?.trades ?? 0,
|
|
8476
9141
|
runtimePnl: runtimeSummary?.totalPnl ?? 0,
|
|
8477
9142
|
matched: parity?.matched ?? 0,
|
|
9143
|
+
orderFailed: 0,
|
|
8478
9144
|
runtimeOnly: parity?.runtimeOnly ?? 0,
|
|
8479
9145
|
backtestOnly: parity?.backtestOnly ?? 0
|
|
8480
9146
|
};
|
|
@@ -8489,8 +9155,10 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8489
9155
|
import_chalk11.default.yellow(String(row.runtimeTrades)),
|
|
8490
9156
|
rtPnlColor(`${row.runtimePnl.toFixed(2)}$`),
|
|
8491
9157
|
import_chalk11.default.green(String(row.matched)),
|
|
9158
|
+
import_chalk11.default.red(String(row.orderFailed)),
|
|
8492
9159
|
import_chalk11.default.yellow(String(row.runtimeOnly)),
|
|
8493
|
-
import_chalk11.default.magenta(String(row.backtestOnly))
|
|
9160
|
+
import_chalk11.default.magenta(String(row.backtestOnly)),
|
|
9161
|
+
import_chalk11.default.red(String(getReplayRuntimeUnmatchedCount(row)))
|
|
8494
9162
|
];
|
|
8495
9163
|
});
|
|
8496
9164
|
console.log("");
|
|
@@ -8517,18 +9185,20 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8517
9185
|
syncedTradesCount: syncedRuntimeTrades.length,
|
|
8518
9186
|
windowTradesCount: windowRuntimeTrades.length,
|
|
8519
9187
|
runtimeEntriesCount: runtimeDedupe.entries.length,
|
|
8520
|
-
backtestEntriesCount:
|
|
9188
|
+
backtestEntriesCount: comparableBacktestEntries.length,
|
|
8521
9189
|
matchedCount: comparison.matched.length,
|
|
9190
|
+
orderFailedCount: 0,
|
|
8522
9191
|
runtimeOnlyCount: comparison.runtimeOnly.length,
|
|
8523
9192
|
backtestOnlyCount: comparison.backtestOnly.length,
|
|
8524
9193
|
rows,
|
|
8525
|
-
details
|
|
9194
|
+
details,
|
|
9195
|
+
lineage: comparable.lineage
|
|
8526
9196
|
};
|
|
8527
9197
|
};
|
|
8528
9198
|
|
|
8529
9199
|
// src/lib/replay/outputReport.ts
|
|
8530
|
-
var
|
|
8531
|
-
var
|
|
9200
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
9201
|
+
var import_node_path2 = __toESM(require("path"));
|
|
8532
9202
|
var import_data = require("@tradejs/core/data");
|
|
8533
9203
|
var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
|
|
8534
9204
|
timeZone: "Europe/Moscow",
|
|
@@ -8563,6 +9233,7 @@ var buildPerTradeAnalysis = (runtimeComparison) => {
|
|
|
8563
9233
|
if (!details) {
|
|
8564
9234
|
return {
|
|
8565
9235
|
matched: [],
|
|
9236
|
+
orderFailed: [],
|
|
8566
9237
|
runtimeOnly: [],
|
|
8567
9238
|
backtestOnly: []
|
|
8568
9239
|
};
|
|
@@ -8591,6 +9262,17 @@ var buildPerTradeAnalysis = (runtimeComparison) => {
|
|
|
8591
9262
|
slippage: item.slippage
|
|
8592
9263
|
}
|
|
8593
9264
|
})),
|
|
9265
|
+
orderFailed: details.orderFailed.map((item) => ({
|
|
9266
|
+
status: "orderFailed",
|
|
9267
|
+
strategy: entryStrategy(item.backtest),
|
|
9268
|
+
symbol: item.backtest.symbol,
|
|
9269
|
+
direction: item.backtest.direction,
|
|
9270
|
+
runtime: item.runtime,
|
|
9271
|
+
backtest: item.backtest,
|
|
9272
|
+
reason: item.reason,
|
|
9273
|
+
timestampDiffMs: item.timestampDiffMs,
|
|
9274
|
+
entryPriceDeltaPct: item.priceDeltaPct
|
|
9275
|
+
})),
|
|
8594
9276
|
runtimeOnly: details.runtimeOnly.map((entry) => ({
|
|
8595
9277
|
status: "runtimeOnly",
|
|
8596
9278
|
strategy: entryStrategy(entry),
|
|
@@ -8674,6 +9356,26 @@ var renderRuntimeOnlyRows = (perTrade) => perTrade.length ? createMarkdownTable(
|
|
|
8674
9356
|
];
|
|
8675
9357
|
})
|
|
8676
9358
|
) : "_No runtime-only trades._";
|
|
9359
|
+
var renderOrderFailedRows = (perTrade) => perTrade.length ? createMarkdownTable(
|
|
9360
|
+
[
|
|
9361
|
+
"Strategy",
|
|
9362
|
+
"Symbol",
|
|
9363
|
+
"Dir",
|
|
9364
|
+
"BT time",
|
|
9365
|
+
"RT time",
|
|
9366
|
+
"Entry delta",
|
|
9367
|
+
"Reason"
|
|
9368
|
+
],
|
|
9369
|
+
perTrade.map((item) => [
|
|
9370
|
+
item.strategy,
|
|
9371
|
+
item.symbol,
|
|
9372
|
+
item.direction,
|
|
9373
|
+
formatMsk(item.backtest.timestamp),
|
|
9374
|
+
formatMsk(item.runtime.timestamp),
|
|
9375
|
+
formatPct(item.entryPriceDeltaPct),
|
|
9376
|
+
item.reason
|
|
9377
|
+
])
|
|
9378
|
+
) : "_No failed runtime orders._";
|
|
8677
9379
|
var renderBacktestOnlyRows = (perTrade) => perTrade.length ? createMarkdownTable(
|
|
8678
9380
|
[
|
|
8679
9381
|
"Strategy",
|
|
@@ -8711,8 +9413,10 @@ var renderStrategyRows = (runtimeComparison) => runtimeComparison?.rows.length ?
|
|
|
8711
9413
|
"RT trades",
|
|
8712
9414
|
"RT PnL",
|
|
8713
9415
|
"Matched",
|
|
9416
|
+
"Order failed",
|
|
8714
9417
|
"RT only",
|
|
8715
|
-
"BT only"
|
|
9418
|
+
"BT only",
|
|
9419
|
+
"Unmatched"
|
|
8716
9420
|
],
|
|
8717
9421
|
runtimeComparison.rows.map((row) => [
|
|
8718
9422
|
row.strategyName,
|
|
@@ -8721,10 +9425,69 @@ var renderStrategyRows = (runtimeComparison) => runtimeComparison?.rows.length ?
|
|
|
8721
9425
|
row.runtimeTrades,
|
|
8722
9426
|
formatMoney(row.runtimePnl),
|
|
8723
9427
|
row.matched,
|
|
9428
|
+
row.orderFailed,
|
|
8724
9429
|
row.runtimeOnly,
|
|
8725
|
-
row.backtestOnly
|
|
9430
|
+
row.backtestOnly,
|
|
9431
|
+
getReplayRuntimeUnmatchedCount(row)
|
|
8726
9432
|
])
|
|
8727
9433
|
) : "_No runtime comparison rows._";
|
|
9434
|
+
var renderLineageRows = (runtimeComparison) => {
|
|
9435
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
9436
|
+
for (const record of runtimeComparison?.lineage.replay ?? []) {
|
|
9437
|
+
const existing = grouped.get(record.strategy) ?? {
|
|
9438
|
+
strategy: record.strategy,
|
|
9439
|
+
scopes: 0,
|
|
9440
|
+
gitShas: /* @__PURE__ */ new Set(),
|
|
9441
|
+
dirty: /* @__PURE__ */ new Set(),
|
|
9442
|
+
gates: /* @__PURE__ */ new Set(),
|
|
9443
|
+
configs: /* @__PURE__ */ new Set(),
|
|
9444
|
+
contexts: /* @__PURE__ */ new Set()
|
|
9445
|
+
};
|
|
9446
|
+
existing.scopes += 1;
|
|
9447
|
+
existing.gitShas.add(record.lineage.gitSha ?? "unknown");
|
|
9448
|
+
existing.dirty.add(
|
|
9449
|
+
record.lineage.gitDirty == null ? "unknown" : String(record.lineage.gitDirty)
|
|
9450
|
+
);
|
|
9451
|
+
existing.gates.add(record.lineage.gateFingerprint);
|
|
9452
|
+
existing.configs.add(record.lineage.configFingerprint);
|
|
9453
|
+
existing.contexts.add(record.lineage.contextFingerprint);
|
|
9454
|
+
grouped.set(record.strategy, existing);
|
|
9455
|
+
}
|
|
9456
|
+
const formatSet = (values) => {
|
|
9457
|
+
const sorted = [...values].sort();
|
|
9458
|
+
return sorted.length <= 3 ? sorted.join(", ") : `${sorted.slice(0, 3).join(", ")} \u2026 (+${sorted.length - 3})`;
|
|
9459
|
+
};
|
|
9460
|
+
return grouped.size ? createMarkdownTable(
|
|
9461
|
+
[
|
|
9462
|
+
"Strategy",
|
|
9463
|
+
"Scopes",
|
|
9464
|
+
"Git SHA",
|
|
9465
|
+
"Dirty",
|
|
9466
|
+
"Gate",
|
|
9467
|
+
"Config variants",
|
|
9468
|
+
"Context"
|
|
9469
|
+
],
|
|
9470
|
+
[...grouped.values()].sort((left, right) => left.strategy.localeCompare(right.strategy)).map(
|
|
9471
|
+
({
|
|
9472
|
+
strategy,
|
|
9473
|
+
scopes,
|
|
9474
|
+
gitShas,
|
|
9475
|
+
dirty,
|
|
9476
|
+
gates,
|
|
9477
|
+
configs,
|
|
9478
|
+
contexts
|
|
9479
|
+
}) => [
|
|
9480
|
+
strategy,
|
|
9481
|
+
scopes,
|
|
9482
|
+
formatSet(gitShas),
|
|
9483
|
+
formatSet(dirty),
|
|
9484
|
+
formatSet(gates),
|
|
9485
|
+
`${configs.size}: ${formatSet(configs)}`,
|
|
9486
|
+
formatSet(contexts)
|
|
9487
|
+
]
|
|
9488
|
+
)
|
|
9489
|
+
) : "_No replay lineage records._";
|
|
9490
|
+
};
|
|
8728
9491
|
var writeReplayOutputReport = async ({
|
|
8729
9492
|
projectRoot,
|
|
8730
9493
|
timestamp,
|
|
@@ -8739,8 +9502,8 @@ var writeReplayOutputReport = async ({
|
|
|
8739
9502
|
strategySnapshot,
|
|
8740
9503
|
runtimeComparison
|
|
8741
9504
|
}) => {
|
|
8742
|
-
const outputDir =
|
|
8743
|
-
await
|
|
9505
|
+
const outputDir = import_node_path2.default.resolve(projectRoot, "data/replay/output");
|
|
9506
|
+
await import_promises2.default.mkdir(outputDir, { recursive: true });
|
|
8744
9507
|
const perTradeAnalysis = buildPerTradeAnalysis(runtimeComparison);
|
|
8745
9508
|
const report = {
|
|
8746
9509
|
reportType: "replay-output",
|
|
@@ -8766,15 +9529,17 @@ var writeReplayOutputReport = async ({
|
|
|
8766
9529
|
cycleCount: replayResult.cycleCount,
|
|
8767
9530
|
abortedCycles: replayResult.abortedCycles,
|
|
8768
9531
|
matched: runtimeComparison?.matchedCount ?? 0,
|
|
9532
|
+
orderFailed: runtimeComparison?.orderFailedCount ?? 0,
|
|
8769
9533
|
runtimeOnly: runtimeComparison?.runtimeOnlyCount ?? 0,
|
|
8770
9534
|
backtestOnly: runtimeComparison?.backtestOnlyCount ?? 0
|
|
8771
9535
|
},
|
|
8772
9536
|
resultsByStrategies: strategySnapshot.summaries,
|
|
9537
|
+
replayLineage: replayResult.runtimeLineages,
|
|
8773
9538
|
runtimeComparison,
|
|
8774
9539
|
perTradeAnalysis
|
|
8775
9540
|
};
|
|
8776
|
-
const markdownPath =
|
|
8777
|
-
const jsonPath =
|
|
9541
|
+
const markdownPath = import_node_path2.default.join(outputDir, `${timestamp}-replay-report.md`);
|
|
9542
|
+
const jsonPath = import_node_path2.default.join(outputDir, `${timestamp}-replay-report.json`);
|
|
8778
9543
|
const lines = [
|
|
8779
9544
|
"# Replay Report",
|
|
8780
9545
|
"",
|
|
@@ -8794,13 +9559,26 @@ var writeReplayOutputReport = async ({
|
|
|
8794
9559
|
["Window", `${formatMsk(window2.start)} - ${formatMsk(window2.end)} MSK`],
|
|
8795
9560
|
["Runtime compare mode", runtimeComparison?.mode ?? "none"],
|
|
8796
9561
|
["Matched", runtimeComparison?.matchedCount ?? 0],
|
|
9562
|
+
["Order failed", runtimeComparison?.orderFailedCount ?? 0],
|
|
8797
9563
|
["Runtime-only", runtimeComparison?.runtimeOnlyCount ?? 0],
|
|
8798
9564
|
["Backtest-only", runtimeComparison?.backtestOnlyCount ?? 0],
|
|
9565
|
+
[
|
|
9566
|
+
"Comparable lineage scopes",
|
|
9567
|
+
runtimeComparison ? `${runtimeComparison.lineage.comparableScopes}/${runtimeComparison.lineage.replayScopes}` : "0/0"
|
|
9568
|
+
],
|
|
9569
|
+
[
|
|
9570
|
+
"Lineage exclusions",
|
|
9571
|
+
runtimeComparison ? `runtime=${runtimeComparison.lineage.excludedRuntimeTrades}, exchange=${runtimeComparison.lineage.excludedExchangeEntries}, backtest=${runtimeComparison.lineage.excludedBacktestEntries}` : ""
|
|
9572
|
+
],
|
|
8799
9573
|
["Command", `\`${process.argv.join(" ")}\``],
|
|
8800
9574
|
["JSON report", jsonPath]
|
|
8801
9575
|
]
|
|
8802
9576
|
),
|
|
8803
9577
|
"",
|
|
9578
|
+
"## Replay Lineage",
|
|
9579
|
+
"",
|
|
9580
|
+
renderLineageRows(runtimeComparison),
|
|
9581
|
+
"",
|
|
8804
9582
|
"## Strategy Comparison",
|
|
8805
9583
|
"",
|
|
8806
9584
|
renderStrategyRows(runtimeComparison),
|
|
@@ -8809,6 +9587,10 @@ var writeReplayOutputReport = async ({
|
|
|
8809
9587
|
"",
|
|
8810
9588
|
renderMatchedRows(perTradeAnalysis.matched),
|
|
8811
9589
|
"",
|
|
9590
|
+
"## Failed Runtime Orders",
|
|
9591
|
+
"",
|
|
9592
|
+
renderOrderFailedRows(perTradeAnalysis.orderFailed),
|
|
9593
|
+
"",
|
|
8812
9594
|
"## Runtime-Only Trades",
|
|
8813
9595
|
"",
|
|
8814
9596
|
renderRuntimeOnlyRows(perTradeAnalysis.runtimeOnly),
|
|
@@ -8823,8 +9605,8 @@ var writeReplayOutputReport = async ({
|
|
|
8823
9605
|
""
|
|
8824
9606
|
];
|
|
8825
9607
|
await Promise.all([
|
|
8826
|
-
|
|
8827
|
-
|
|
9608
|
+
import_promises2.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
|
|
9609
|
+
import_promises2.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
|
|
8828
9610
|
`, "utf8")
|
|
8829
9611
|
]);
|
|
8830
9612
|
return {
|
|
@@ -8863,7 +9645,8 @@ var finishReplay = async ({
|
|
|
8863
9645
|
const replayRuntimeComparison = await saveAndPrintReplayRuntimeComparison({
|
|
8864
9646
|
liveStrategySummaries: replayStrategySnapshot.summaries,
|
|
8865
9647
|
backtestEntries: replayStrategySnapshot.backtestEntries,
|
|
8866
|
-
replaySignals: replayResult.signals
|
|
9648
|
+
replaySignals: replayResult.signals,
|
|
9649
|
+
replayLineages: replayResult.runtimeLineages
|
|
8867
9650
|
});
|
|
8868
9651
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
8869
9652
|
const durationSeconds = Number(
|
|
@@ -8879,7 +9662,7 @@ var finishReplay = async ({
|
|
|
8879
9662
|
replayResult.orderLog[replayResult.orderLog.length - 1]?.timestamp ?? finishedAt.getTime()
|
|
8880
9663
|
)}`
|
|
8881
9664
|
}) : null;
|
|
8882
|
-
const replayKey =
|
|
9665
|
+
const replayKey = import_redis6.redisKeys.backtestResults(
|
|
8883
9666
|
replayUserName,
|
|
8884
9667
|
REPLAY_RESULTS_CONFIG,
|
|
8885
9668
|
timestamp
|
|
@@ -8900,7 +9683,7 @@ var finishReplay = async ({
|
|
|
8900
9683
|
});
|
|
8901
9684
|
console.log(import_chalk12.default.green(`Replay report: ${outputReport.markdownPath}`));
|
|
8902
9685
|
console.log(import_chalk12.default.green(`Replay report JSON: ${outputReport.jsonPath}`));
|
|
8903
|
-
await (0,
|
|
9686
|
+
await (0, import_redis6.setData)(
|
|
8904
9687
|
replayKey,
|
|
8905
9688
|
{
|
|
8906
9689
|
config: REPLAY_RESULTS_CONFIG,
|
|
@@ -8921,6 +9704,7 @@ var finishReplay = async ({
|
|
|
8921
9704
|
cycleCount: replayResult.cycleCount,
|
|
8922
9705
|
abortedCycles: replayResult.abortedCycles,
|
|
8923
9706
|
signalsCount: replayResult.signals.length,
|
|
9707
|
+
replayLineage: replayResult.runtimeLineages,
|
|
8924
9708
|
strategyCharts: replayChartSnapshot,
|
|
8925
9709
|
outputReport
|
|
8926
9710
|
},
|
|
@@ -8931,8 +9715,8 @@ var finishReplay = async ({
|
|
|
8931
9715
|
if (replayChartSnapshot) {
|
|
8932
9716
|
await Promise.all(
|
|
8933
9717
|
replayChartSnapshot.strategies.map(
|
|
8934
|
-
(card) => (0,
|
|
8935
|
-
|
|
9718
|
+
(card) => (0, import_redis6.setData)(
|
|
9719
|
+
import_redis6.redisKeys.strategyChartCard(replayUserName, "replay", card.cardId),
|
|
8936
9720
|
card,
|
|
8937
9721
|
{
|
|
8938
9722
|
expire: import_constants6.TTL_1M
|
|
@@ -8957,7 +9741,8 @@ var replayBacktest = async () => {
|
|
|
8957
9741
|
endTime: replayFlags.endTime,
|
|
8958
9742
|
cacheOnly: replayFlags.cacheOnly,
|
|
8959
9743
|
interval: replayInterval,
|
|
8960
|
-
projectRoot: replayProjectRoot
|
|
9744
|
+
projectRoot: replayProjectRoot,
|
|
9745
|
+
closedIntervalMs: (0, import_data2.intervalToMs)(replayInterval)
|
|
8961
9746
|
});
|
|
8962
9747
|
if (!preparedRun || isReplayUpdateOnlyRun) {
|
|
8963
9748
|
return;
|