@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
package/dist/scripts/replay.js
CHANGED
|
@@ -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();
|
|
@@ -1628,8 +1643,9 @@ module.exports = __toCommonJS(replay_exports);
|
|
|
1628
1643
|
// src/scripts/replayRunner.ts
|
|
1629
1644
|
var import_chalk12 = __toESM(require("chalk"));
|
|
1630
1645
|
var import_constants6 = require("@tradejs/core/constants");
|
|
1646
|
+
var import_data2 = require("@tradejs/core/data");
|
|
1631
1647
|
var import_time7 = require("@tradejs/core/time");
|
|
1632
|
-
var
|
|
1648
|
+
var import_redis6 = require("@tradejs/infra/redis");
|
|
1633
1649
|
|
|
1634
1650
|
// src/lib/runFormatting.ts
|
|
1635
1651
|
var import_date_fns = require("date-fns");
|
|
@@ -1691,11 +1707,30 @@ var resolveTimeWindow = ({
|
|
|
1691
1707
|
endTime,
|
|
1692
1708
|
nowMs = Date.now(),
|
|
1693
1709
|
defaultStartMs,
|
|
1694
|
-
defaultEndMs
|
|
1710
|
+
defaultEndMs,
|
|
1711
|
+
closedIntervalMs
|
|
1695
1712
|
}) => {
|
|
1696
1713
|
const explicitStart = toEpochMs(startTime);
|
|
1697
1714
|
const explicitEnd = toEpochMs(endTime);
|
|
1698
1715
|
const resolvedDays = toPositiveNumber(days);
|
|
1716
|
+
const alignToLastClosedInterval = (window2, preserveDuration) => {
|
|
1717
|
+
if (typeof closedIntervalMs !== "number" || !Number.isFinite(closedIntervalMs) || closedIntervalMs <= 0) {
|
|
1718
|
+
return window2;
|
|
1719
|
+
}
|
|
1720
|
+
const alignedEnd = Math.floor(window2.end / closedIntervalMs) * closedIntervalMs - 1;
|
|
1721
|
+
const delta = window2.end - alignedEnd;
|
|
1722
|
+
const alignedStart = preserveDuration ? window2.start - delta : window2.start;
|
|
1723
|
+
if (alignedStart >= alignedEnd) {
|
|
1724
|
+
throw new Error(
|
|
1725
|
+
`Invalid closed-candle window: start (${alignedStart}) must be less than end (${alignedEnd})`
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1728
|
+
return {
|
|
1729
|
+
...window2,
|
|
1730
|
+
start: Math.trunc(alignedStart),
|
|
1731
|
+
end: Math.trunc(alignedEnd)
|
|
1732
|
+
};
|
|
1733
|
+
};
|
|
1699
1734
|
if (explicitStart != null || explicitEnd != null) {
|
|
1700
1735
|
const resolvedStart = explicitStart ?? (resolvedDays != null && explicitEnd != null ? explicitEnd - resolvedDays * ONE_DAY_MS : defaultStartMs);
|
|
1701
1736
|
const resolvedEnd2 = explicitEnd ?? (resolvedDays != null && explicitStart != null ? explicitStart + resolvedDays * ONE_DAY_MS : defaultEndMs ?? nowMs);
|
|
@@ -1704,11 +1739,14 @@ var resolveTimeWindow = ({
|
|
|
1704
1739
|
`Invalid time window: startTime (${resolvedStart}) must be less than endTime (${resolvedEnd2})`
|
|
1705
1740
|
);
|
|
1706
1741
|
}
|
|
1707
|
-
return
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1742
|
+
return alignToLastClosedInterval(
|
|
1743
|
+
{
|
|
1744
|
+
start: resolvedStart,
|
|
1745
|
+
end: resolvedEnd2,
|
|
1746
|
+
source: "explicit"
|
|
1747
|
+
},
|
|
1748
|
+
explicitStart == null && resolvedDays != null
|
|
1749
|
+
);
|
|
1712
1750
|
}
|
|
1713
1751
|
if (resolvedDays != null) {
|
|
1714
1752
|
const resolvedEnd2 = defaultEndMs ?? nowMs;
|
|
@@ -1718,11 +1756,14 @@ var resolveTimeWindow = ({
|
|
|
1718
1756
|
`Invalid time window: computed start (${resolvedStart}) must be less than end (${resolvedEnd2})`
|
|
1719
1757
|
);
|
|
1720
1758
|
}
|
|
1721
|
-
return
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1759
|
+
return alignToLastClosedInterval(
|
|
1760
|
+
{
|
|
1761
|
+
start: Math.trunc(resolvedStart),
|
|
1762
|
+
end: Math.trunc(resolvedEnd2),
|
|
1763
|
+
source: "days"
|
|
1764
|
+
},
|
|
1765
|
+
true
|
|
1766
|
+
);
|
|
1726
1767
|
}
|
|
1727
1768
|
const resolvedEnd = defaultEndMs ?? nowMs;
|
|
1728
1769
|
if (defaultStartMs >= resolvedEnd) {
|
|
@@ -1730,11 +1771,14 @@ var resolveTimeWindow = ({
|
|
|
1730
1771
|
`Invalid default time window: start (${defaultStartMs}) must be less than end (${resolvedEnd})`
|
|
1731
1772
|
);
|
|
1732
1773
|
}
|
|
1733
|
-
return
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1774
|
+
return alignToLastClosedInterval(
|
|
1775
|
+
{
|
|
1776
|
+
start: Math.trunc(defaultStartMs),
|
|
1777
|
+
end: Math.trunc(resolvedEnd),
|
|
1778
|
+
source: "default"
|
|
1779
|
+
},
|
|
1780
|
+
true
|
|
1781
|
+
);
|
|
1738
1782
|
};
|
|
1739
1783
|
|
|
1740
1784
|
// src/lib/runtimeStrategyBacktest.ts
|
|
@@ -2174,7 +2218,8 @@ var prepareRunEnvironment = async ({
|
|
|
2174
2218
|
accountId,
|
|
2175
2219
|
deploymentId,
|
|
2176
2220
|
assetClasses,
|
|
2177
|
-
deployment
|
|
2221
|
+
deployment,
|
|
2222
|
+
closedIntervalMs
|
|
2178
2223
|
}) => {
|
|
2179
2224
|
const connectorName = await resolveRunConnectorName({
|
|
2180
2225
|
value: connector,
|
|
@@ -2237,7 +2282,8 @@ var prepareRunEnvironment = async ({
|
|
|
2237
2282
|
startTime,
|
|
2238
2283
|
endTime,
|
|
2239
2284
|
defaultStartMs: (0, import_time2.getTimestamp)(import_constants.BACKTEST_DEFAULT_DAYS),
|
|
2240
|
-
defaultEndMs: (0, import_time2.getTimestamp)()
|
|
2285
|
+
defaultEndMs: (0, import_time2.getTimestamp)(),
|
|
2286
|
+
closedIntervalMs
|
|
2241
2287
|
});
|
|
2242
2288
|
const preloadStart = (0, import_time2.getBacktestPreloadStart)(
|
|
2243
2289
|
window2.start,
|
|
@@ -2306,6 +2352,7 @@ var import_progress = __toESM(require("progress"));
|
|
|
2306
2352
|
var import_chalk4 = __toESM(require("chalk"));
|
|
2307
2353
|
var import_connectors4 = require("@tradejs/connectors");
|
|
2308
2354
|
var import_connectors5 = require("@tradejs/node/connectors");
|
|
2355
|
+
var import_strategies = require("@tradejs/node/strategies");
|
|
2309
2356
|
var import_timescale = require("@tradejs/infra/timescale");
|
|
2310
2357
|
|
|
2311
2358
|
// src/lib/binanceMarketData.ts
|
|
@@ -2315,14 +2362,6 @@ var MARKET_FEATURE_INTERVAL_MS = {
|
|
|
2315
2362
|
"15m": 9e5,
|
|
2316
2363
|
"1h": 36e5
|
|
2317
2364
|
};
|
|
2318
|
-
var STABLE_QUOTE_SYMBOLS = /* @__PURE__ */ new Set([
|
|
2319
|
-
"USDCUSDT",
|
|
2320
|
-
"FDUSDUSDT",
|
|
2321
|
-
"TUSDUSDT",
|
|
2322
|
-
"BUSDUSDT",
|
|
2323
|
-
"USDPUSDT",
|
|
2324
|
-
"DAIUSDT"
|
|
2325
|
-
]);
|
|
2326
2365
|
var safeDivide = (num, den) => Number.isFinite(num) && Number.isFinite(den) && den !== 0 ? num / den : null;
|
|
2327
2366
|
var mean = (values) => values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
|
|
2328
2367
|
var standardDeviation = (values) => {
|
|
@@ -2366,9 +2405,6 @@ var classifyBtcAltRegime = ({
|
|
|
2366
2405
|
if (btcVsAltReturn24h < -5e-3) return "alt_lead";
|
|
2367
2406
|
return "neutral";
|
|
2368
2407
|
};
|
|
2369
|
-
var selectBreadthUniverseFromTickers = (tickers, limit) => tickers.filter(
|
|
2370
|
-
(ticker) => ticker.symbol.endsWith("USDT") && ticker.symbol !== "BTCUSDT" && !STABLE_QUOTE_SYMBOLS.has(ticker.symbol) && Number.isFinite(ticker.turnover24h) && ticker.turnover24h > 0
|
|
2371
|
-
).sort((a, b) => b.turnover24h - a.turnover24h).slice(0, Math.max(0, limit)).map((ticker) => ticker.symbol);
|
|
2372
2408
|
var aggregateAggTradesToRows = ({
|
|
2373
2409
|
symbol,
|
|
2374
2410
|
interval,
|
|
@@ -2850,10 +2886,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
2850
2886
|
process.env.BINANCE_MARKET_CONTEXT_TRADE_FLOW_SYMBOL_LIMIT,
|
|
2851
2887
|
2
|
|
2852
2888
|
);
|
|
2853
|
-
const breadthLimit = asInt(
|
|
2854
|
-
process.env.BINANCE_MARKET_CONTEXT_BREADTH_LIMIT,
|
|
2855
|
-
30
|
|
2856
|
-
);
|
|
2857
2889
|
const breadthChunkDays = asFloat(
|
|
2858
2890
|
process.env.BINANCE_MARKET_CONTEXT_BREADTH_CHUNK_DAYS,
|
|
2859
2891
|
30
|
|
@@ -2976,88 +3008,113 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
2976
3008
|
}
|
|
2977
3009
|
}
|
|
2978
3010
|
if (includeBreadth) {
|
|
2979
|
-
const
|
|
2980
|
-
const breadthSymbols =
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
);
|
|
2984
|
-
const universe = `binance_top${breadthSymbols.length}_usdt`;
|
|
2985
|
-
const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
|
|
2986
|
-
universe,
|
|
2987
|
-
interval,
|
|
2988
|
-
startMs: breadthStartMs,
|
|
2989
|
-
endMs
|
|
2990
|
-
});
|
|
2991
|
-
const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
|
|
2992
|
-
if (!hasCoverage({
|
|
2993
|
-
coverage,
|
|
3011
|
+
const breadthUniverses = (0, import_strategies.getBinanceBreadthUniverses)();
|
|
3012
|
+
const breadthSymbols = breadthUniverses.at(-1)?.symbols ?? [];
|
|
3013
|
+
const connectorInterval = marketIntervalToConnectorInterval(interval);
|
|
3014
|
+
const chunks = buildBreadthBackfillChunks({
|
|
2994
3015
|
startMs: breadthStartMs,
|
|
2995
3016
|
endMs,
|
|
2996
|
-
intervalMs
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
chunks,
|
|
3007
|
-
coverage,
|
|
3008
|
-
intervalMs
|
|
3009
|
-
});
|
|
3010
|
-
const bar = new import_progress.default(
|
|
3011
|
-
"breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
|
|
3012
|
-
{
|
|
3013
|
-
total: Math.max(1, missingChunks.length * breadthSymbols.length),
|
|
3014
|
-
width: 24
|
|
3015
|
-
}
|
|
3016
|
-
);
|
|
3017
|
-
let candlesRead = 0;
|
|
3018
|
-
let skippedBreadthChunks = chunks.length - missingChunks.length;
|
|
3019
|
-
if (!missingChunks.length) {
|
|
3020
|
-
bar.tick(1, {
|
|
3021
|
-
candles: 0,
|
|
3022
|
-
skip: skippedBreadthChunks,
|
|
3023
|
-
chunk: "cached",
|
|
3024
|
-
symbol: universe
|
|
3017
|
+
intervalMs,
|
|
3018
|
+
chunkDays: breadthChunkDays
|
|
3019
|
+
});
|
|
3020
|
+
const plans = await Promise.all(
|
|
3021
|
+
breadthUniverses.map(async (definition) => {
|
|
3022
|
+
const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
|
|
3023
|
+
universe: definition.universe,
|
|
3024
|
+
interval,
|
|
3025
|
+
startMs: breadthStartMs,
|
|
3026
|
+
endMs
|
|
3025
3027
|
});
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
candlesBySymbol[symbol] = candles;
|
|
3042
|
-
candlesRead += candles.length;
|
|
3043
|
-
bar.tick(1, {
|
|
3044
|
-
candles: candlesRead,
|
|
3045
|
-
skip: skippedBreadthChunks,
|
|
3046
|
-
chunk: `${originalChunkIndex + 1}/${chunks.length}`,
|
|
3047
|
-
symbol
|
|
3048
|
-
});
|
|
3028
|
+
const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
|
|
3029
|
+
const missingChunks2 = !hasCoverage({
|
|
3030
|
+
coverage,
|
|
3031
|
+
startMs: breadthStartMs,
|
|
3032
|
+
endMs,
|
|
3033
|
+
intervalMs
|
|
3034
|
+
}) || !hasBtcAltMetrics ? !hasBtcAltMetrics ? chunks : filterMissingBreadthBackfillChunks({
|
|
3035
|
+
chunks,
|
|
3036
|
+
coverage,
|
|
3037
|
+
intervalMs
|
|
3038
|
+
}) : [];
|
|
3039
|
+
if (!missingChunks2.length) {
|
|
3040
|
+
console.log(
|
|
3041
|
+
import_chalk4.default.gray(`breadth cached: universe=${definition.universe}`)
|
|
3042
|
+
);
|
|
3049
3043
|
}
|
|
3050
|
-
|
|
3051
|
-
|
|
3044
|
+
return {
|
|
3045
|
+
definition,
|
|
3046
|
+
missingChunkKeys: new Set(
|
|
3047
|
+
missingChunks2.map((chunk) => `${chunk.startMs}:${chunk.endMs}`)
|
|
3048
|
+
)
|
|
3049
|
+
};
|
|
3050
|
+
})
|
|
3051
|
+
);
|
|
3052
|
+
const missingChunks = chunks.filter((chunk) => {
|
|
3053
|
+
const key = `${chunk.startMs}:${chunk.endMs}`;
|
|
3054
|
+
return plans.some((plan) => plan.missingChunkKeys.has(key));
|
|
3055
|
+
});
|
|
3056
|
+
const bar = new import_progress.default(
|
|
3057
|
+
"breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
|
|
3058
|
+
{
|
|
3059
|
+
total: Math.max(1, missingChunks.length * breadthSymbols.length),
|
|
3060
|
+
width: 24
|
|
3061
|
+
}
|
|
3062
|
+
);
|
|
3063
|
+
let candlesRead = 0;
|
|
3064
|
+
const skippedBreadthChunks = chunks.length - missingChunks.length;
|
|
3065
|
+
if (!missingChunks.length) {
|
|
3066
|
+
bar.tick(1, {
|
|
3067
|
+
candles: 0,
|
|
3068
|
+
skip: skippedBreadthChunks,
|
|
3069
|
+
chunk: "cached",
|
|
3070
|
+
symbol: "top5/top10/top30/top50/top100"
|
|
3071
|
+
});
|
|
3072
|
+
}
|
|
3073
|
+
for (let chunkIndex = 0; chunkIndex < missingChunks.length; chunkIndex += 1) {
|
|
3074
|
+
const chunk = missingChunks[chunkIndex];
|
|
3075
|
+
const originalChunkIndex = chunks.findIndex(
|
|
3076
|
+
(item) => item.startMs === chunk.startMs && item.endMs === chunk.endMs
|
|
3077
|
+
);
|
|
3078
|
+
const candlesBySymbol = {};
|
|
3079
|
+
for (const symbol of breadthSymbols) {
|
|
3080
|
+
const candles = await connector.kline({
|
|
3081
|
+
symbol,
|
|
3052
3082
|
interval: connectorInterval,
|
|
3053
3083
|
start: chunk.fetchStartMs,
|
|
3054
3084
|
end: chunk.endMs,
|
|
3055
3085
|
silent: true
|
|
3056
3086
|
});
|
|
3087
|
+
candlesBySymbol[symbol] = candles;
|
|
3088
|
+
candlesRead += candles.length;
|
|
3089
|
+
bar.tick(1, {
|
|
3090
|
+
candles: candlesRead,
|
|
3091
|
+
skip: skippedBreadthChunks,
|
|
3092
|
+
chunk: `${originalChunkIndex + 1}/${chunks.length}`,
|
|
3093
|
+
symbol
|
|
3094
|
+
});
|
|
3095
|
+
}
|
|
3096
|
+
const btcCandles = await connector.kline({
|
|
3097
|
+
symbol: "BTCUSDT",
|
|
3098
|
+
interval: connectorInterval,
|
|
3099
|
+
start: chunk.fetchStartMs,
|
|
3100
|
+
end: chunk.endMs,
|
|
3101
|
+
silent: true
|
|
3102
|
+
});
|
|
3103
|
+
const chunkKey = `${chunk.startMs}:${chunk.endMs}`;
|
|
3104
|
+
for (const plan of plans) {
|
|
3105
|
+
if (!plan.missingChunkKeys.has(chunkKey)) {
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3108
|
+
const symbolSet = new Set(plan.definition.symbols);
|
|
3109
|
+
const universeCandles = Object.fromEntries(
|
|
3110
|
+
Object.entries(candlesBySymbol).filter(
|
|
3111
|
+
([symbol]) => symbolSet.has(symbol)
|
|
3112
|
+
)
|
|
3113
|
+
);
|
|
3057
3114
|
const rows = buildMarketBreadthRows({
|
|
3058
|
-
universe,
|
|
3115
|
+
universe: plan.definition.universe,
|
|
3059
3116
|
interval,
|
|
3060
|
-
candlesBySymbol,
|
|
3117
|
+
candlesBySymbol: universeCandles,
|
|
3061
3118
|
btcCandles
|
|
3062
3119
|
}).filter((row) => {
|
|
3063
3120
|
const ts = row.ts.getTime();
|
|
@@ -3066,8 +3123,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
3066
3123
|
await (0, import_timescale.upsertMarketBreadthRows)(rows);
|
|
3067
3124
|
breadthRows += rows.length;
|
|
3068
3125
|
}
|
|
3069
|
-
} else {
|
|
3070
|
-
console.log(import_chalk4.default.gray(`breadth cached: universe=${universe}`));
|
|
3071
3126
|
}
|
|
3072
3127
|
}
|
|
3073
3128
|
console.log(
|
|
@@ -4292,27 +4347,27 @@ var createCoinMarketCapFetchStats = () => ({
|
|
|
4292
4347
|
totalCredits: 0,
|
|
4293
4348
|
byPath: /* @__PURE__ */ new Map()
|
|
4294
4349
|
});
|
|
4295
|
-
var getCoinMarketCapPathLabel = (
|
|
4296
|
-
if (
|
|
4297
|
-
if (
|
|
4298
|
-
if (
|
|
4299
|
-
if (
|
|
4300
|
-
if (
|
|
4301
|
-
if (
|
|
4302
|
-
return
|
|
4303
|
-
};
|
|
4304
|
-
var recordCoinMarketCapFetchStats = (stats,
|
|
4350
|
+
var getCoinMarketCapPathLabel = (path3) => {
|
|
4351
|
+
if (path3.includes("global-metrics")) return "global";
|
|
4352
|
+
if (path3.includes("cryptocurrency/quotes")) return "reference";
|
|
4353
|
+
if (path3.includes("exchange/quotes")) return "exchangeLiquidity";
|
|
4354
|
+
if (path3.includes("fear-and-greed")) return "fearGreed";
|
|
4355
|
+
if (path3.includes("cmc100")) return "cmc100";
|
|
4356
|
+
if (path3.includes("cmc20")) return "cmc20";
|
|
4357
|
+
return path3;
|
|
4358
|
+
};
|
|
4359
|
+
var recordCoinMarketCapFetchStats = (stats, path3, creditCount) => {
|
|
4305
4360
|
if (!stats) return;
|
|
4306
|
-
const existing = stats.byPath.get(
|
|
4361
|
+
const existing = stats.byPath.get(path3) ?? { requests: 0, credits: 0 };
|
|
4307
4362
|
existing.requests += 1;
|
|
4308
4363
|
existing.credits += creditCount ?? 0;
|
|
4309
4364
|
stats.requests += 1;
|
|
4310
4365
|
stats.totalCredits += creditCount ?? 0;
|
|
4311
|
-
stats.byPath.set(
|
|
4366
|
+
stats.byPath.set(path3, existing);
|
|
4312
4367
|
};
|
|
4313
4368
|
var formatCoinMarketCapFetchStats = (stats) => {
|
|
4314
4369
|
const byPath = [...stats.byPath.entries()].map(
|
|
4315
|
-
([
|
|
4370
|
+
([path3, item]) => `${getCoinMarketCapPathLabel(path3)}=${item.requests}req/${item.credits}cr`
|
|
4316
4371
|
).join(" ");
|
|
4317
4372
|
return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
|
|
4318
4373
|
};
|
|
@@ -5228,8 +5283,12 @@ var prepareMarketContextForRun = async (params) => {
|
|
|
5228
5283
|
};
|
|
5229
5284
|
|
|
5230
5285
|
// src/lib/backtest/runState.ts
|
|
5286
|
+
var import_types = require("@tradejs/types");
|
|
5231
5287
|
var successTests = 0;
|
|
5232
5288
|
var errorTests = 0;
|
|
5289
|
+
var warningCounts = {
|
|
5290
|
+
[import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY]: 0
|
|
5291
|
+
};
|
|
5233
5292
|
var errorMessages = [];
|
|
5234
5293
|
var topResults = [];
|
|
5235
5294
|
var bestTickerResults = /* @__PURE__ */ new Map();
|
|
@@ -5274,6 +5333,9 @@ var getRuntimeCompareContext = () => ({
|
|
|
5274
5333
|
var resetRunState = () => {
|
|
5275
5334
|
successTests = 0;
|
|
5276
5335
|
errorTests = 0;
|
|
5336
|
+
for (const code of Object.values(import_types.BACKTEST_WARNING_CODES)) {
|
|
5337
|
+
warningCounts[code] = 0;
|
|
5338
|
+
}
|
|
5277
5339
|
errorMessages.length = 0;
|
|
5278
5340
|
topResults = [];
|
|
5279
5341
|
bestTickerResults.clear();
|
|
@@ -5418,13 +5480,16 @@ var REPLAY_RUNTIME_COMPARISON_HEADERS = [
|
|
|
5418
5480
|
import_chalk8.default.yellow("RT TRADES"),
|
|
5419
5481
|
import_chalk8.default.yellow("RT PNL"),
|
|
5420
5482
|
import_chalk8.default.green("MATCHED"),
|
|
5483
|
+
import_chalk8.default.red("ORDER FAILED"),
|
|
5421
5484
|
import_chalk8.default.yellow("RT ONLY"),
|
|
5422
|
-
import_chalk8.default.magenta("BT ONLY")
|
|
5485
|
+
import_chalk8.default.magenta("BT ONLY"),
|
|
5486
|
+
import_chalk8.default.red("UNMATCHED")
|
|
5423
5487
|
];
|
|
5424
5488
|
var REPLAY_RESULTS_CONFIG = "replay";
|
|
5425
5489
|
var REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS = 1;
|
|
5426
5490
|
var REPLAY_RUNTIME_COMPARE_TOLERANCE_MS = REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS * 15 * 60 * 1e3;
|
|
5427
5491
|
var formatReplayRuntimeCompareTolerance = () => `${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS} ${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS === 1 ? "bar" : "bars"}`;
|
|
5492
|
+
var getReplayRuntimeUnmatchedCount = (row) => row.orderFailed + row.runtimeOnly + row.backtestOnly;
|
|
5428
5493
|
var buildReplayStrategyConfig = ({
|
|
5429
5494
|
strategyConfig,
|
|
5430
5495
|
interval
|
|
@@ -5489,12 +5554,13 @@ var compactReplaySignal = (signal) => {
|
|
|
5489
5554
|
var import_chalk9 = __toESM(require("chalk"));
|
|
5490
5555
|
var import_progress3 = __toESM(require("progress"));
|
|
5491
5556
|
var import_backtest = require("@tradejs/core/backtest");
|
|
5492
|
-
var
|
|
5557
|
+
var import_strategies2 = require("@tradejs/core/strategies");
|
|
5493
5558
|
var import_time3 = require("@tradejs/core/time");
|
|
5494
5559
|
var import_logger = require("@tradejs/infra/logger");
|
|
5560
|
+
var import_redis3 = require("@tradejs/infra/redis");
|
|
5495
5561
|
var import_connectors6 = require("@tradejs/node/connectors");
|
|
5496
5562
|
var import_cli4 = require("@tradejs/node/cli");
|
|
5497
|
-
var
|
|
5563
|
+
var import_strategies3 = require("@tradejs/node/strategies");
|
|
5498
5564
|
|
|
5499
5565
|
// src/lib/replay/portfolioReplayConnector.ts
|
|
5500
5566
|
var import_constants3 = require("@tradejs/core/constants");
|
|
@@ -5944,21 +6010,231 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
|
|
|
5944
6010
|
}
|
|
5945
6011
|
};
|
|
5946
6012
|
|
|
6013
|
+
// src/lib/runtimeLineage.ts
|
|
6014
|
+
var import_node_child_process = require("child_process");
|
|
6015
|
+
var import_node_crypto = require("crypto");
|
|
6016
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
6017
|
+
var import_node_path = __toESM(require("path"));
|
|
6018
|
+
var RUNTIME_CONTEXT_ENV_KEYS = [
|
|
6019
|
+
"AI_MODE",
|
|
6020
|
+
"MIN_AI_QUALITY",
|
|
6021
|
+
"INTERVAL",
|
|
6022
|
+
"DERIVATIVES_CONTEXT_ENABLED",
|
|
6023
|
+
"DERIVATIVES_CONTEXT_TARGET_ENABLED",
|
|
6024
|
+
"DERIVATIVES_CONTEXT_LOOKBACK_HOURS",
|
|
6025
|
+
"DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS",
|
|
6026
|
+
"DERIVATIVES_CONTEXT_EXCHANGE_PRIORITY",
|
|
6027
|
+
"COINMARKETCAP_CONTEXT_ENABLED",
|
|
6028
|
+
"COINMARKETCAP_CONTEXT_MAX_AGE_MS",
|
|
6029
|
+
"COINMARKETCAP_CONTEXT_EXCHANGE_LIQUIDITY_ENABLED",
|
|
6030
|
+
"COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS",
|
|
6031
|
+
"COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED",
|
|
6032
|
+
"COINMARKETCAP_CONTEXT_HISTORICAL_ACCESS_MONTHS",
|
|
6033
|
+
"COINMARKETCAP_CONTEXT_BACKFILL_ENABLED",
|
|
6034
|
+
"COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
|
|
6035
|
+
"COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS"
|
|
6036
|
+
];
|
|
6037
|
+
var RUNTIME_CONTEXT_DATA_MODEL = {
|
|
6038
|
+
derivativesSourceIntervals: "15m",
|
|
6039
|
+
derivativesDerivedIntervals: "1h",
|
|
6040
|
+
derivativesHourlyFallback: "stored-1h",
|
|
6041
|
+
derivativesDataModelVersion: 2,
|
|
6042
|
+
coinMarketCapAvailabilityModel: "closed-interval-all-daily",
|
|
6043
|
+
coinMarketCapDataModelVersion: 3,
|
|
6044
|
+
binanceBreadthUniverseModel: "fixed-versioned-top5-top10-top30-top50-top100",
|
|
6045
|
+
binanceBreadthDataModelVersion: 2
|
|
6046
|
+
};
|
|
6047
|
+
var normalizeForStableJson = (value) => {
|
|
6048
|
+
if (Array.isArray(value)) {
|
|
6049
|
+
return value.map(normalizeForStableJson);
|
|
6050
|
+
}
|
|
6051
|
+
if (value && typeof value === "object") {
|
|
6052
|
+
return Object.fromEntries(
|
|
6053
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeForStableJson(entry)])
|
|
6054
|
+
);
|
|
6055
|
+
}
|
|
6056
|
+
return value;
|
|
6057
|
+
};
|
|
6058
|
+
var fingerprintRuntimeValue = (value) => (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
|
|
6059
|
+
var gitLineageCache = /* @__PURE__ */ new Map();
|
|
6060
|
+
var gateFingerprintCache = /* @__PURE__ */ new Map();
|
|
6061
|
+
var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
|
|
6062
|
+
var resolveGitLineage = (projectRoot) => {
|
|
6063
|
+
const cached = gitLineageCache.get(projectRoot);
|
|
6064
|
+
if (cached) {
|
|
6065
|
+
return cached;
|
|
6066
|
+
}
|
|
6067
|
+
const envSha = String(process.env.TRADEJS_GIT_SHA ?? "").trim();
|
|
6068
|
+
if (envSha && envSha !== "unknown") {
|
|
6069
|
+
const result = {
|
|
6070
|
+
gitSha: envSha,
|
|
6071
|
+
gitDirty: false
|
|
6072
|
+
};
|
|
6073
|
+
gitLineageCache.set(projectRoot, result);
|
|
6074
|
+
return result;
|
|
6075
|
+
}
|
|
6076
|
+
try {
|
|
6077
|
+
const gitSha = (0, import_node_child_process.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
6078
|
+
cwd: projectRoot,
|
|
6079
|
+
encoding: "utf8",
|
|
6080
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
6081
|
+
}).trim();
|
|
6082
|
+
const status = (0, import_node_child_process.execFileSync)(
|
|
6083
|
+
"git",
|
|
6084
|
+
["status", "--porcelain", "--untracked-files=normal"],
|
|
6085
|
+
{
|
|
6086
|
+
cwd: projectRoot,
|
|
6087
|
+
encoding: "utf8",
|
|
6088
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
6089
|
+
}
|
|
6090
|
+
).trim();
|
|
6091
|
+
const result = {
|
|
6092
|
+
gitSha: gitSha || null,
|
|
6093
|
+
gitDirty: status.length > 0
|
|
6094
|
+
};
|
|
6095
|
+
gitLineageCache.set(projectRoot, result);
|
|
6096
|
+
return result;
|
|
6097
|
+
} catch {
|
|
6098
|
+
const result = {
|
|
6099
|
+
gitSha: null,
|
|
6100
|
+
gitDirty: null
|
|
6101
|
+
};
|
|
6102
|
+
gitLineageCache.set(projectRoot, result);
|
|
6103
|
+
return result;
|
|
6104
|
+
}
|
|
6105
|
+
};
|
|
6106
|
+
var readOptionalFile = async (filePath) => {
|
|
6107
|
+
try {
|
|
6108
|
+
return await import_promises.default.readFile(filePath);
|
|
6109
|
+
} catch {
|
|
6110
|
+
return null;
|
|
6111
|
+
}
|
|
6112
|
+
};
|
|
6113
|
+
var resolveBinanceBreadthFingerprint = async (projectRoot) => {
|
|
6114
|
+
if (binanceBreadthFingerprintCache.has(projectRoot)) {
|
|
6115
|
+
return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
|
|
6116
|
+
}
|
|
6117
|
+
const content = await readOptionalFile(
|
|
6118
|
+
import_node_path.default.join(
|
|
6119
|
+
projectRoot,
|
|
6120
|
+
"packages/node/src/config/binanceBreadthUniverses.json"
|
|
6121
|
+
)
|
|
6122
|
+
);
|
|
6123
|
+
let result = null;
|
|
6124
|
+
if (content) {
|
|
6125
|
+
try {
|
|
6126
|
+
const parsed = JSON.parse(content.toString("utf8"));
|
|
6127
|
+
result = typeof parsed.fingerprint === "string" && parsed.fingerprint.trim() ? parsed.fingerprint.trim() : null;
|
|
6128
|
+
} catch {
|
|
6129
|
+
result = null;
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
6132
|
+
binanceBreadthFingerprintCache.set(projectRoot, result);
|
|
6133
|
+
return result;
|
|
6134
|
+
};
|
|
6135
|
+
var resolveGateFingerprint = async ({
|
|
6136
|
+
projectRoot,
|
|
6137
|
+
strategyName,
|
|
6138
|
+
gitSha
|
|
6139
|
+
}) => {
|
|
6140
|
+
const cacheKey = `${projectRoot}:${strategyName}:${gitSha ?? "unknown"}`;
|
|
6141
|
+
const cached = gateFingerprintCache.get(cacheKey);
|
|
6142
|
+
if (cached) {
|
|
6143
|
+
return cached;
|
|
6144
|
+
}
|
|
6145
|
+
const relativeCandidates = [
|
|
6146
|
+
`packages/strategies/src/${strategyName}/adapters/ai.ts`,
|
|
6147
|
+
`packages/strategies/src/${strategyName}/guardrails.ts`,
|
|
6148
|
+
`packages/strategies/src/${strategyName}/pockets.ts`,
|
|
6149
|
+
`packages/strategies/src/${strategyName}/config.ts`,
|
|
6150
|
+
"packages/node/src/ai.ts"
|
|
6151
|
+
];
|
|
6152
|
+
const optionalEntries = await Promise.all(
|
|
6153
|
+
relativeCandidates.map(async (relativePath) => {
|
|
6154
|
+
const content = await readOptionalFile(
|
|
6155
|
+
import_node_path.default.join(projectRoot, relativePath)
|
|
6156
|
+
);
|
|
6157
|
+
return content == null ? null : { relativePath, content };
|
|
6158
|
+
})
|
|
6159
|
+
);
|
|
6160
|
+
const sourceEntries = [];
|
|
6161
|
+
for (const entry of optionalEntries) {
|
|
6162
|
+
if (entry != null) {
|
|
6163
|
+
sourceEntries.push(entry);
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
6167
|
+
hash.update(strategyName);
|
|
6168
|
+
if (sourceEntries.length) {
|
|
6169
|
+
for (const entry of sourceEntries) {
|
|
6170
|
+
hash.update(entry.relativePath);
|
|
6171
|
+
hash.update(entry.content);
|
|
6172
|
+
}
|
|
6173
|
+
} else {
|
|
6174
|
+
hash.update(gitSha ?? "unknown-git-sha");
|
|
6175
|
+
}
|
|
6176
|
+
const fingerprint = hash.digest("hex").slice(0, 16);
|
|
6177
|
+
gateFingerprintCache.set(cacheKey, fingerprint);
|
|
6178
|
+
return fingerprint;
|
|
6179
|
+
};
|
|
6180
|
+
var buildRuntimeLineage = async ({
|
|
6181
|
+
projectRoot,
|
|
6182
|
+
strategyName,
|
|
6183
|
+
config,
|
|
6184
|
+
runContext,
|
|
6185
|
+
env = process.env
|
|
6186
|
+
}) => {
|
|
6187
|
+
const git = resolveGitLineage(projectRoot);
|
|
6188
|
+
const context = {
|
|
6189
|
+
...Object.fromEntries(
|
|
6190
|
+
RUNTIME_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
|
|
6191
|
+
),
|
|
6192
|
+
...RUNTIME_CONTEXT_DATA_MODEL,
|
|
6193
|
+
binanceBreadthUniverseFingerprint: await resolveBinanceBreadthFingerprint(projectRoot),
|
|
6194
|
+
...runContext
|
|
6195
|
+
};
|
|
6196
|
+
return {
|
|
6197
|
+
schemaVersion: 1,
|
|
6198
|
+
...git,
|
|
6199
|
+
gateFingerprint: await resolveGateFingerprint({
|
|
6200
|
+
projectRoot,
|
|
6201
|
+
strategyName,
|
|
6202
|
+
gitSha: git.gitSha
|
|
6203
|
+
}),
|
|
6204
|
+
configFingerprint: fingerprintRuntimeValue(config),
|
|
6205
|
+
contextFingerprint: fingerprintRuntimeValue(context)
|
|
6206
|
+
};
|
|
6207
|
+
};
|
|
6208
|
+
var runtimeLineageKey = (lineage) => [
|
|
6209
|
+
lineage.schemaVersion,
|
|
6210
|
+
lineage.gitSha ?? "unknown",
|
|
6211
|
+
lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
|
|
6212
|
+
lineage.gateFingerprint,
|
|
6213
|
+
lineage.configFingerprint,
|
|
6214
|
+
lineage.contextFingerprint
|
|
6215
|
+
].join(":");
|
|
6216
|
+
var runtimeLineagesMatch = (left, right) => left != null && right != null && runtimeLineageKey(left) === runtimeLineageKey(right);
|
|
6217
|
+
|
|
5947
6218
|
// src/lib/replay/historicalSignalsReplay.ts
|
|
5948
6219
|
var loadRuntimeStrategies = async (runtimeStrategies) => {
|
|
5949
6220
|
const strategies = await Promise.all(
|
|
5950
6221
|
runtimeStrategies.map(async ({ strategyName, strategyConfig }) => {
|
|
5951
|
-
const strategyCreator = await (0,
|
|
6222
|
+
const strategyCreator = await (0, import_strategies3.getStrategyCreator)(
|
|
5952
6223
|
strategyName,
|
|
5953
6224
|
replayProjectRoot
|
|
5954
6225
|
);
|
|
5955
6226
|
if (!strategyCreator) {
|
|
5956
6227
|
throw new Error(`Unknown strategy: ${strategyName}`);
|
|
5957
6228
|
}
|
|
6229
|
+
const strategyResults = await (0, import_redis3.getData)(
|
|
6230
|
+
import_redis3.redisKeys.strategyResults(replayUserName, strategyName),
|
|
6231
|
+
{}
|
|
6232
|
+
);
|
|
5958
6233
|
return {
|
|
5959
6234
|
strategyName,
|
|
5960
6235
|
strategyCreator,
|
|
5961
|
-
strategyConfig
|
|
6236
|
+
strategyConfig,
|
|
6237
|
+
strategyResults
|
|
5962
6238
|
};
|
|
5963
6239
|
})
|
|
5964
6240
|
);
|
|
@@ -6059,6 +6335,7 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6059
6335
|
}) => {
|
|
6060
6336
|
const startedAt = Date.now();
|
|
6061
6337
|
const signals = [];
|
|
6338
|
+
const runtimeLineages = [];
|
|
6062
6339
|
const projectConfig = await (0, import_cli4.loadTradejsConfig)(replayProjectRoot);
|
|
6063
6340
|
const projectHooks = projectConfig.hooks;
|
|
6064
6341
|
const loadedStrategies = await loadRuntimeStrategies(runtimeStrategies);
|
|
@@ -6146,26 +6423,54 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6146
6423
|
sharedReplayKeyPrefixes.push(sharedIndicatorsReplayKey);
|
|
6147
6424
|
const strategiesForSymbol = await Promise.all(
|
|
6148
6425
|
loadedStrategies.map(
|
|
6149
|
-
async ({
|
|
6426
|
+
async ({
|
|
6150
6427
|
strategyName,
|
|
6428
|
+
strategyCreator,
|
|
6151
6429
|
strategyConfig,
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6430
|
+
strategyResults
|
|
6431
|
+
}) => {
|
|
6432
|
+
const runtimeLineage = await buildRuntimeLineage({
|
|
6433
|
+
projectRoot: replayProjectRoot,
|
|
6434
|
+
strategyName,
|
|
6435
|
+
config: {
|
|
6436
|
+
configId: "config",
|
|
6156
6437
|
strategyConfig,
|
|
6157
|
-
|
|
6158
|
-
}
|
|
6438
|
+
symbolResultConfig: strategyResults?.[symbol]?.config ?? null
|
|
6439
|
+
},
|
|
6440
|
+
runContext: {
|
|
6441
|
+
connectorName: connectorName.toLowerCase(),
|
|
6442
|
+
interval: String(interval),
|
|
6443
|
+
universe: preparedRun.universe ?? null
|
|
6444
|
+
}
|
|
6445
|
+
});
|
|
6446
|
+
runtimeLineages.push({
|
|
6447
|
+
strategy: strategyName,
|
|
6159
6448
|
symbol,
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6449
|
+
lineage: runtimeLineage
|
|
6450
|
+
});
|
|
6451
|
+
return {
|
|
6452
|
+
strategyName,
|
|
6453
|
+
strategyConfig,
|
|
6454
|
+
runtimeLineage,
|
|
6455
|
+
run: await strategyCreator({
|
|
6456
|
+
userName: replayUserName,
|
|
6457
|
+
connectorName,
|
|
6458
|
+
runtimeConfigId: "config",
|
|
6459
|
+
config: buildReplayStrategyConfig({
|
|
6460
|
+
strategyConfig,
|
|
6461
|
+
interval
|
|
6462
|
+
}),
|
|
6463
|
+
symbol,
|
|
6464
|
+
data: preparedData.prevData,
|
|
6465
|
+
btcData: preparedData.btcPrevData,
|
|
6466
|
+
ethData: preparedData.ethPrevData,
|
|
6467
|
+
btcBinanceData: preparedData.btcBinancePrevData,
|
|
6468
|
+
btcCoinbaseData: preparedData.btcCoinbasePrevData,
|
|
6469
|
+
connector: replayConnector,
|
|
6470
|
+
sharedIndicatorsReplayKey
|
|
6471
|
+
})
|
|
6472
|
+
};
|
|
6473
|
+
}
|
|
6169
6474
|
)
|
|
6170
6475
|
);
|
|
6171
6476
|
const symbolRuntime = {
|
|
@@ -6259,7 +6564,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6259
6564
|
ethCandle?.timestamp === timestamp ? ethCandle : void 0
|
|
6260
6565
|
);
|
|
6261
6566
|
if (result && typeof result !== "string") {
|
|
6262
|
-
|
|
6567
|
+
result.runtimeLineage = strategyRuntime.runtimeLineage;
|
|
6568
|
+
await (0, import_strategies3.enrichSignalWithBinanceMarketContext)({
|
|
6263
6569
|
signal: result,
|
|
6264
6570
|
env: "PARITY"
|
|
6265
6571
|
});
|
|
@@ -6283,8 +6589,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6283
6589
|
}
|
|
6284
6590
|
} finally {
|
|
6285
6591
|
for (const keyPrefix of sharedReplayKeyPrefixes) {
|
|
6286
|
-
(0,
|
|
6287
|
-
(0,
|
|
6592
|
+
(0, import_strategies2.releaseStrategyIndicatorsReplayCache)(keyPrefix);
|
|
6593
|
+
(0, import_strategies2.releaseStrategyReplayCache)(keyPrefix);
|
|
6288
6594
|
}
|
|
6289
6595
|
}
|
|
6290
6596
|
const artifacts = replayConnector.getReplayArtifacts();
|
|
@@ -6318,7 +6624,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6318
6624
|
orderLog: artifacts.orderLog,
|
|
6319
6625
|
positionLog: artifacts.positionLog,
|
|
6320
6626
|
cycleCount: orderedTimestamps.length,
|
|
6321
|
-
abortedCycles
|
|
6627
|
+
abortedCycles,
|
|
6628
|
+
runtimeLineages
|
|
6322
6629
|
};
|
|
6323
6630
|
};
|
|
6324
6631
|
|
|
@@ -6802,7 +7109,7 @@ var summarizeTradeParityByStrategy = ({
|
|
|
6802
7109
|
};
|
|
6803
7110
|
|
|
6804
7111
|
// src/lib/runtimeSignalsLoader.ts
|
|
6805
|
-
var
|
|
7112
|
+
var import_redis4 = require("@tradejs/infra/redis");
|
|
6806
7113
|
|
|
6807
7114
|
// src/lib/runtimeSignalsStorage.ts
|
|
6808
7115
|
var import_constants4 = require("@tradejs/core/constants");
|
|
@@ -6852,14 +7159,14 @@ var loadRuntimeSignals = async (userName, {
|
|
|
6852
7159
|
startTime,
|
|
6853
7160
|
endTime
|
|
6854
7161
|
} = {}) => {
|
|
6855
|
-
const bucketPrefix =
|
|
7162
|
+
const bucketPrefix = import_redis4.redisKeys.runtimeSignalBuckets(userName);
|
|
6856
7163
|
const bucketKeys = filterBucketKeysByWindow(
|
|
6857
|
-
await (0,
|
|
7164
|
+
await (0, import_redis4.getKeys)(bucketPrefix),
|
|
6858
7165
|
bucketPrefix,
|
|
6859
7166
|
startTime,
|
|
6860
7167
|
endTime
|
|
6861
7168
|
);
|
|
6862
|
-
const refs = (await Promise.all(bucketKeys.map((key) => (0,
|
|
7169
|
+
const refs = (await Promise.all(bucketKeys.map((key) => (0, import_redis4.getHashJsonValues)(key)))).flat().filter(isRuntimeSignalBucketRef);
|
|
6863
7170
|
const seen = /* @__PURE__ */ new Set();
|
|
6864
7171
|
const payloads = await Promise.all(
|
|
6865
7172
|
refs.map(async (ref) => {
|
|
@@ -6868,8 +7175,8 @@ var loadRuntimeSignals = async (userName, {
|
|
|
6868
7175
|
return null;
|
|
6869
7176
|
}
|
|
6870
7177
|
seen.add(dedupeKey);
|
|
6871
|
-
const signal = await (0,
|
|
6872
|
-
|
|
7178
|
+
const signal = await (0, import_redis4.getData)(
|
|
7179
|
+
import_redis4.redisKeys.storeSignal(ref.symbol, ref.signalId),
|
|
6873
7180
|
null
|
|
6874
7181
|
);
|
|
6875
7182
|
return isSignalRecord(signal) ? signal : null;
|
|
@@ -6881,25 +7188,42 @@ var loadRuntimeSignalEvaluations = async (userName, {
|
|
|
6881
7188
|
startTime,
|
|
6882
7189
|
endTime
|
|
6883
7190
|
} = {}) => {
|
|
6884
|
-
const bucketPrefix =
|
|
7191
|
+
const bucketPrefix = import_redis4.redisKeys.runtimeSignalEvaluationBuckets(userName);
|
|
6885
7192
|
const bucketKeys = filterBucketKeysByWindow(
|
|
6886
|
-
await (0,
|
|
7193
|
+
await (0, import_redis4.getKeys)(bucketPrefix),
|
|
6887
7194
|
bucketPrefix,
|
|
6888
7195
|
startTime,
|
|
6889
7196
|
endTime
|
|
6890
7197
|
);
|
|
6891
|
-
const evaluations = (await Promise.all(bucketKeys.map((key) => (0,
|
|
7198
|
+
const evaluations = (await Promise.all(bucketKeys.map((key) => (0, import_redis4.getHashJsonValues)(key)))).flat().filter(isRuntimeSignalEvaluationRecord);
|
|
6892
7199
|
const deduped = /* @__PURE__ */ new Map();
|
|
6893
7200
|
for (const evaluation of evaluations) {
|
|
6894
7201
|
deduped.set(evaluation.evaluationId, evaluation);
|
|
6895
7202
|
}
|
|
6896
7203
|
return sortByTimestamp([...deduped.values()]);
|
|
6897
7204
|
};
|
|
7205
|
+
var loadRuntimeLineageScopes = async (userName, {
|
|
7206
|
+
startTime,
|
|
7207
|
+
endTime
|
|
7208
|
+
}) => {
|
|
7209
|
+
const records = (await Promise.all(
|
|
7210
|
+
(0, import_time4.getRuntimeStorageDayKeys)(startTime, endTime).map(
|
|
7211
|
+
(dayKey) => (0, import_redis4.getHashJsonValues)(
|
|
7212
|
+
import_redis4.redisKeys.runtimeLineageScopeBucket(userName, dayKey)
|
|
7213
|
+
)
|
|
7214
|
+
)
|
|
7215
|
+
)).flat().filter(
|
|
7216
|
+
(record) => record != null && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.firstTimestamp === "number" && typeof record.lastTimestamp === "number" && record.lineage != null
|
|
7217
|
+
);
|
|
7218
|
+
return records.sort(
|
|
7219
|
+
(left, right) => left.firstTimestamp - right.firstTimestamp || left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol)
|
|
7220
|
+
);
|
|
7221
|
+
};
|
|
6898
7222
|
|
|
6899
7223
|
// src/lib/runtimeTradeSync.ts
|
|
6900
7224
|
var import_constants5 = require("@tradejs/core/constants");
|
|
6901
7225
|
var import_time5 = require("@tradejs/core/time");
|
|
6902
|
-
var
|
|
7226
|
+
var import_redis5 = require("@tradejs/infra/redis");
|
|
6903
7227
|
var CLOSED_PNL_LIMIT = 100;
|
|
6904
7228
|
var CLOSED_PNL_RECONCILIATION_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
|
|
6905
7229
|
var EXCHANGE_HISTORY_MAX_RANGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -6993,6 +7317,18 @@ var consumeClosedPnlMatch = (buckets, trade) => {
|
|
|
6993
7317
|
const [row] = rows.splice(matchIndex, 1);
|
|
6994
7318
|
return row ?? null;
|
|
6995
7319
|
};
|
|
7320
|
+
var isRuntimeTradeInConnectorScope = (trade, connector) => {
|
|
7321
|
+
if (trade.deploymentId && trade.deploymentId !== connector.deploymentId) {
|
|
7322
|
+
return false;
|
|
7323
|
+
}
|
|
7324
|
+
if (trade.accountId && trade.accountId !== connector.accountId) {
|
|
7325
|
+
return false;
|
|
7326
|
+
}
|
|
7327
|
+
if (trade.universe && connector.universe && trade.universe !== connector.universe) {
|
|
7328
|
+
return false;
|
|
7329
|
+
}
|
|
7330
|
+
return true;
|
|
7331
|
+
};
|
|
6996
7332
|
var syncRuntimeTrades = async ({
|
|
6997
7333
|
userName,
|
|
6998
7334
|
connector,
|
|
@@ -7016,16 +7352,23 @@ var syncRuntimeTrades = async ({
|
|
|
7016
7352
|
const openPositionsBySymbol = new Map(
|
|
7017
7353
|
openPositions.map((position) => [position.symbol, position])
|
|
7018
7354
|
);
|
|
7019
|
-
const
|
|
7020
|
-
const
|
|
7355
|
+
const activeOrderIdByKey = /* @__PURE__ */ new Map();
|
|
7356
|
+
const activeTradeKeys = [
|
|
7357
|
+
...new Set(
|
|
7358
|
+
trades.filter((trade) => isRuntimeTradeInConnectorScope(trade, connector)).map(
|
|
7359
|
+
(trade) => import_redis5.redisKeys.runtimeActiveTrade(
|
|
7360
|
+
userName,
|
|
7361
|
+
trade.symbol,
|
|
7362
|
+
trade.deploymentId ?? trade.accountId
|
|
7363
|
+
)
|
|
7364
|
+
)
|
|
7365
|
+
)
|
|
7366
|
+
];
|
|
7021
7367
|
await Promise.all(
|
|
7022
|
-
|
|
7023
|
-
const activeRef = await (0,
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
);
|
|
7027
|
-
activeOrderIdBySymbol.set(
|
|
7028
|
-
symbol,
|
|
7368
|
+
activeTradeKeys.map(async (key) => {
|
|
7369
|
+
const activeRef = await (0, import_redis5.getData)(key, null);
|
|
7370
|
+
activeOrderIdByKey.set(
|
|
7371
|
+
key,
|
|
7029
7372
|
typeof activeRef?.orderId === "string" ? activeRef.orderId : null
|
|
7030
7373
|
);
|
|
7031
7374
|
})
|
|
@@ -7044,12 +7387,21 @@ var syncRuntimeTrades = async ({
|
|
|
7044
7387
|
}
|
|
7045
7388
|
const syncedTrades = [];
|
|
7046
7389
|
for (const trade of trades) {
|
|
7390
|
+
if (!isRuntimeTradeInConnectorScope(trade, connector)) {
|
|
7391
|
+
syncedTrades.push(trade);
|
|
7392
|
+
continue;
|
|
7393
|
+
}
|
|
7047
7394
|
if (trade.status !== "active" && !isRuntimeTradeSyncFallbackClose(trade)) {
|
|
7048
7395
|
syncedTrades.push(trade);
|
|
7049
7396
|
continue;
|
|
7050
7397
|
}
|
|
7398
|
+
const activeTradeKey = import_redis5.redisKeys.runtimeActiveTrade(
|
|
7399
|
+
userName,
|
|
7400
|
+
trade.symbol,
|
|
7401
|
+
trade.deploymentId ?? trade.accountId
|
|
7402
|
+
);
|
|
7051
7403
|
const openPosition = openPositionsBySymbol.get(trade.symbol);
|
|
7052
|
-
const activeOrderId =
|
|
7404
|
+
const activeOrderId = activeOrderIdByKey.get(activeTradeKey);
|
|
7053
7405
|
const isCurrentActiveTrade = activeOrderId === trade.orderId;
|
|
7054
7406
|
if (trade.status === "active" && !openPositionsReliable) {
|
|
7055
7407
|
syncedTrades.push({
|
|
@@ -7067,15 +7419,15 @@ var syncRuntimeTrades = async ({
|
|
|
7067
7419
|
currentPnl: openPosition.unrealizedPnl,
|
|
7068
7420
|
lastSyncedAt: endTime
|
|
7069
7421
|
};
|
|
7070
|
-
await (0,
|
|
7071
|
-
|
|
7422
|
+
await (0, import_redis5.setData)(
|
|
7423
|
+
import_redis5.redisKeys.runtimeTrade(userName, trade.orderId),
|
|
7072
7424
|
nextTrade2,
|
|
7073
7425
|
{
|
|
7074
7426
|
expire: 0
|
|
7075
7427
|
}
|
|
7076
7428
|
);
|
|
7077
|
-
await (0,
|
|
7078
|
-
|
|
7429
|
+
await (0, import_redis5.setHashJsonField)(
|
|
7430
|
+
import_redis5.redisKeys.runtimeTradeBucket(
|
|
7079
7431
|
userName,
|
|
7080
7432
|
(0, import_time5.getRuntimeStorageDayKey)(trade.entryTimestamp)
|
|
7081
7433
|
),
|
|
@@ -7088,6 +7440,7 @@ var syncRuntimeTrades = async ({
|
|
|
7088
7440
|
}
|
|
7089
7441
|
const matchedClosedPnl = consumeClosedPnlMatch(closedPnlBuckets, trade);
|
|
7090
7442
|
if (!matchedClosedPnl) {
|
|
7443
|
+
syncedTrades.push(trade);
|
|
7091
7444
|
continue;
|
|
7092
7445
|
}
|
|
7093
7446
|
const nextTrade = {
|
|
@@ -7108,11 +7461,11 @@ var syncRuntimeTrades = async ({
|
|
|
7108
7461
|
lastSyncedAt: endTime
|
|
7109
7462
|
};
|
|
7110
7463
|
await Promise.all([
|
|
7111
|
-
(0,
|
|
7464
|
+
(0, import_redis5.setData)(import_redis5.redisKeys.runtimeTrade(userName, trade.orderId), nextTrade, {
|
|
7112
7465
|
expire: import_constants5.TTL_1M
|
|
7113
7466
|
}),
|
|
7114
|
-
(0,
|
|
7115
|
-
|
|
7467
|
+
(0, import_redis5.setHashJsonField)(
|
|
7468
|
+
import_redis5.redisKeys.runtimeTradeBucket(
|
|
7116
7469
|
userName,
|
|
7117
7470
|
(0, import_time5.getRuntimeStorageDayKey)(trade.entryTimestamp)
|
|
7118
7471
|
),
|
|
@@ -7120,7 +7473,7 @@ var syncRuntimeTrades = async ({
|
|
|
7120
7473
|
nextTrade,
|
|
7121
7474
|
{ expire: import_constants5.TTL_1M }
|
|
7122
7475
|
),
|
|
7123
|
-
...isCurrentActiveTrade ? [(0,
|
|
7476
|
+
...isCurrentActiveTrade ? [(0, import_redis5.delKey)(activeTradeKey)] : []
|
|
7124
7477
|
]);
|
|
7125
7478
|
syncedTrades.push(nextTrade);
|
|
7126
7479
|
}
|
|
@@ -7730,6 +8083,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
7730
8083
|
),
|
|
7731
8084
|
limit
|
|
7732
8085
|
),
|
|
8086
|
+
orderFailed: [],
|
|
7733
8087
|
runtimeOnly: cappedRuntimeOnlyDetails,
|
|
7734
8088
|
backtestOnly: cappedBacktestOnlyDetails,
|
|
7735
8089
|
nearestCandidates: cappedNearestCandidates,
|
|
@@ -7749,6 +8103,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
7749
8103
|
};
|
|
7750
8104
|
var buildReplayExchangeComparisonDetails = ({
|
|
7751
8105
|
matched,
|
|
8106
|
+
orderFailed = [],
|
|
7752
8107
|
exchangeOnly,
|
|
7753
8108
|
backtestOnly,
|
|
7754
8109
|
exchangeEntries,
|
|
@@ -7799,7 +8154,7 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
7799
8154
|
limit
|
|
7800
8155
|
);
|
|
7801
8156
|
return {
|
|
7802
|
-
capped: matched.length > limit || exchangeOnlyDetails.length > limit || backtestOnlyDetails.length > limit || nearestCandidates.length > limit,
|
|
8157
|
+
capped: matched.length > limit || orderFailed.length > limit || exchangeOnlyDetails.length > limit || backtestOnlyDetails.length > limit || nearestCandidates.length > limit,
|
|
7803
8158
|
limit,
|
|
7804
8159
|
matched: capReplayDetails(
|
|
7805
8160
|
matched.map(
|
|
@@ -7815,6 +8170,27 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
7815
8170
|
),
|
|
7816
8171
|
limit
|
|
7817
8172
|
),
|
|
8173
|
+
orderFailed: capReplayDetails(
|
|
8174
|
+
orderFailed.map((item) => {
|
|
8175
|
+
const detail = buildMatchedReplayDetail({
|
|
8176
|
+
runtime: toExchangeDetail(item.exchange),
|
|
8177
|
+
backtest: toBacktestParityDetail(
|
|
8178
|
+
item.backtest,
|
|
8179
|
+
backtestTimestampOffsetMs
|
|
8180
|
+
),
|
|
8181
|
+
timestampDiffMs: item.timestampDiffMs,
|
|
8182
|
+
priceDeltaPct: item.priceDeltaPct
|
|
8183
|
+
});
|
|
8184
|
+
return {
|
|
8185
|
+
runtime: detail.runtime,
|
|
8186
|
+
backtest: detail.backtest,
|
|
8187
|
+
timestampDiffMs: detail.timestampDiffMs,
|
|
8188
|
+
priceDeltaPct: detail.priceDeltaPct,
|
|
8189
|
+
reason: item.reason
|
|
8190
|
+
};
|
|
8191
|
+
}),
|
|
8192
|
+
limit
|
|
8193
|
+
),
|
|
7818
8194
|
runtimeOnly: cappedExchangeOnlyDetails,
|
|
7819
8195
|
backtestOnly: cappedBacktestOnlyDetails,
|
|
7820
8196
|
nearestCandidates: cappedNearestCandidates,
|
|
@@ -7850,11 +8226,147 @@ var buildReplaySignalEvaluations = (signals) => signals.map((signal) => ({
|
|
|
7850
8226
|
reason: signal.orderSkipReason || signal.orderStatus || "SIGNAL",
|
|
7851
8227
|
signalId: signal.signalId,
|
|
7852
8228
|
direction: signal.direction,
|
|
8229
|
+
runtimeLineage: signal.runtimeLineage,
|
|
7853
8230
|
orderStatus: signal.orderStatus,
|
|
7854
8231
|
orderSkipReason: signal.orderSkipReason,
|
|
7855
8232
|
...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
|
|
7856
8233
|
...signal.ml ? { ml: signal.ml } : {}
|
|
7857
8234
|
}));
|
|
8235
|
+
var lineageScopeKey = (strategy, symbol) => `${strategy}::${symbol}`;
|
|
8236
|
+
var buildExpectedLineageByScope = (replayLineages) => new Map(
|
|
8237
|
+
replayLineages.map((record) => [
|
|
8238
|
+
lineageScopeKey(record.strategy, record.symbol),
|
|
8239
|
+
record.lineage
|
|
8240
|
+
])
|
|
8241
|
+
);
|
|
8242
|
+
var hasExpectedLineage = ({
|
|
8243
|
+
strategy,
|
|
8244
|
+
symbol,
|
|
8245
|
+
lineage,
|
|
8246
|
+
expectedByScope
|
|
8247
|
+
}) => runtimeLineagesMatch(
|
|
8248
|
+
lineage,
|
|
8249
|
+
expectedByScope.get(lineageScopeKey(strategy, symbol))
|
|
8250
|
+
);
|
|
8251
|
+
var filterReplayComparisonByLineage = ({
|
|
8252
|
+
replayLineages,
|
|
8253
|
+
runtimeTrades,
|
|
8254
|
+
runtimeSignals,
|
|
8255
|
+
runtimeSignalEvaluations,
|
|
8256
|
+
runtimeLineageScopes,
|
|
8257
|
+
backtestEntries
|
|
8258
|
+
}) => {
|
|
8259
|
+
const expectedByScope = buildExpectedLineageByScope(replayLineages);
|
|
8260
|
+
const comparableRuntimeSignals = runtimeSignals.filter(
|
|
8261
|
+
(signal) => hasExpectedLineage({
|
|
8262
|
+
strategy: signal.strategy,
|
|
8263
|
+
symbol: signal.symbol,
|
|
8264
|
+
lineage: signal.runtimeLineage,
|
|
8265
|
+
expectedByScope
|
|
8266
|
+
})
|
|
8267
|
+
);
|
|
8268
|
+
const comparableRuntimeEvaluations = runtimeSignalEvaluations.filter(
|
|
8269
|
+
(evaluation) => hasExpectedLineage({
|
|
8270
|
+
strategy: evaluation.strategy,
|
|
8271
|
+
symbol: evaluation.symbol,
|
|
8272
|
+
lineage: evaluation.runtimeLineage,
|
|
8273
|
+
expectedByScope
|
|
8274
|
+
})
|
|
8275
|
+
);
|
|
8276
|
+
const comparableRuntimeLineageScopes = runtimeLineageScopes.filter(
|
|
8277
|
+
(scope) => hasExpectedLineage({
|
|
8278
|
+
strategy: scope.strategy,
|
|
8279
|
+
symbol: scope.symbol,
|
|
8280
|
+
lineage: scope.lineage,
|
|
8281
|
+
expectedByScope
|
|
8282
|
+
})
|
|
8283
|
+
);
|
|
8284
|
+
const deploymentWindows = /* @__PURE__ */ new Map();
|
|
8285
|
+
for (const scope of comparableRuntimeLineageScopes) {
|
|
8286
|
+
const key = lineageScopeKey(scope.strategy, scope.symbol);
|
|
8287
|
+
const existing = deploymentWindows.get(key);
|
|
8288
|
+
deploymentWindows.set(key, {
|
|
8289
|
+
firstTimestamp: existing == null ? scope.firstTimestamp : Math.min(existing.firstTimestamp, scope.firstTimestamp),
|
|
8290
|
+
lastTimestamp: existing == null ? scope.lastTimestamp : Math.max(existing.lastTimestamp, scope.lastTimestamp)
|
|
8291
|
+
});
|
|
8292
|
+
}
|
|
8293
|
+
const lineageBySignalId = /* @__PURE__ */ new Map();
|
|
8294
|
+
for (const signal of runtimeSignals) {
|
|
8295
|
+
if (signal.runtimeLineage) {
|
|
8296
|
+
lineageBySignalId.set(signal.signalId, signal.runtimeLineage);
|
|
8297
|
+
}
|
|
8298
|
+
}
|
|
8299
|
+
for (const evaluation of runtimeSignalEvaluations) {
|
|
8300
|
+
if (evaluation.signalId && evaluation.runtimeLineage) {
|
|
8301
|
+
lineageBySignalId.set(evaluation.signalId, evaluation.runtimeLineage);
|
|
8302
|
+
}
|
|
8303
|
+
}
|
|
8304
|
+
const comparableRuntimeTrades = runtimeTrades.filter((trade) => {
|
|
8305
|
+
const lineage = trade.signalId ? lineageBySignalId.get(trade.signalId) : void 0;
|
|
8306
|
+
if (lineage) {
|
|
8307
|
+
return hasExpectedLineage({
|
|
8308
|
+
strategy: trade.strategy,
|
|
8309
|
+
symbol: trade.symbol,
|
|
8310
|
+
lineage,
|
|
8311
|
+
expectedByScope
|
|
8312
|
+
});
|
|
8313
|
+
}
|
|
8314
|
+
const window2 = deploymentWindows.get(
|
|
8315
|
+
lineageScopeKey(trade.strategy, trade.symbol)
|
|
8316
|
+
);
|
|
8317
|
+
const signalTimestamp = trade.signalTimestamp ?? trade.entryTimestamp;
|
|
8318
|
+
return window2 != null && signalTimestamp >= window2.firstTimestamp && signalTimestamp <= window2.lastTimestamp;
|
|
8319
|
+
});
|
|
8320
|
+
for (const artifact of [
|
|
8321
|
+
...comparableRuntimeSignals,
|
|
8322
|
+
...comparableRuntimeEvaluations
|
|
8323
|
+
]) {
|
|
8324
|
+
const key = lineageScopeKey(artifact.strategy, artifact.symbol);
|
|
8325
|
+
const existing = deploymentWindows.get(key);
|
|
8326
|
+
deploymentWindows.set(key, {
|
|
8327
|
+
firstTimestamp: existing == null ? artifact.timestamp : Math.min(existing.firstTimestamp, artifact.timestamp),
|
|
8328
|
+
lastTimestamp: existing == null ? artifact.timestamp : Math.max(existing.lastTimestamp, artifact.timestamp)
|
|
8329
|
+
});
|
|
8330
|
+
}
|
|
8331
|
+
const comparableBacktestEntries = backtestEntries.filter((entry) => {
|
|
8332
|
+
const window2 = deploymentWindows.get(
|
|
8333
|
+
lineageScopeKey(entry.strategy, entry.symbol)
|
|
8334
|
+
);
|
|
8335
|
+
const signalTimestamp = entry.signalTimestamp ?? entry.timestamp;
|
|
8336
|
+
return window2 != null && signalTimestamp >= window2.firstTimestamp && signalTimestamp <= window2.lastTimestamp;
|
|
8337
|
+
});
|
|
8338
|
+
const comparableScopeKeys = new Set(deploymentWindows.keys());
|
|
8339
|
+
return {
|
|
8340
|
+
runtimeTrades: comparableRuntimeTrades,
|
|
8341
|
+
runtimeSignals: comparableRuntimeSignals,
|
|
8342
|
+
runtimeSignalEvaluations: comparableRuntimeEvaluations,
|
|
8343
|
+
backtestEntries: comparableBacktestEntries,
|
|
8344
|
+
lineage: {
|
|
8345
|
+
enforced: true,
|
|
8346
|
+
replayScopes: expectedByScope.size,
|
|
8347
|
+
comparableScopes: comparableScopeKeys.size,
|
|
8348
|
+
excludedRuntimeTrades: runtimeTrades.length - comparableRuntimeTrades.length,
|
|
8349
|
+
excludedRuntimeSignals: runtimeSignals.length - comparableRuntimeSignals.length,
|
|
8350
|
+
excludedRuntimeEvaluations: runtimeSignalEvaluations.length - comparableRuntimeEvaluations.length,
|
|
8351
|
+
excludedRuntimeLineageScopes: runtimeLineageScopes.length - comparableRuntimeLineageScopes.length,
|
|
8352
|
+
excludedExchangeEntries: 0,
|
|
8353
|
+
excludedBacktestEntries: backtestEntries.length - comparableBacktestEntries.length,
|
|
8354
|
+
reason: comparableScopeKeys.size > 0 ? null : "no_runtime_artifacts_with_matching_lineage",
|
|
8355
|
+
replay: [...expectedByScope.entries()].map(([scope, lineage]) => {
|
|
8356
|
+
const separator = scope.indexOf("::");
|
|
8357
|
+
return {
|
|
8358
|
+
strategy: scope.slice(0, separator),
|
|
8359
|
+
symbol: scope.slice(separator + 2),
|
|
8360
|
+
lineage
|
|
8361
|
+
};
|
|
8362
|
+
}).sort(
|
|
8363
|
+
(left, right) => left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || runtimeLineageKey(left.lineage).localeCompare(
|
|
8364
|
+
runtimeLineageKey(right.lineage)
|
|
8365
|
+
)
|
|
8366
|
+
)
|
|
8367
|
+
}
|
|
8368
|
+
};
|
|
8369
|
+
};
|
|
7858
8370
|
var formatDrilldownSummary = (summary) => Object.entries(summary).filter(([, count]) => count > 0).map(([classification, count]) => `${classification}=${count}`).join(", ");
|
|
7859
8371
|
var toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
7860
8372
|
var sumOptionalNumbers = (values) => {
|
|
@@ -7878,6 +8390,14 @@ var firstFiniteNumber = (values) => {
|
|
|
7878
8390
|
return null;
|
|
7879
8391
|
};
|
|
7880
8392
|
var firstString = (values) => values.find((value) => typeof value === "string" && value.trim());
|
|
8393
|
+
var summarizeBacktestPnlByStrategy = (entries) => {
|
|
8394
|
+
const totals = /* @__PURE__ */ new Map();
|
|
8395
|
+
for (const entry of entries) {
|
|
8396
|
+
const pnl = typeof entry.expectedPnl === "number" && Number.isFinite(entry.expectedPnl) ? entry.expectedPnl : 0;
|
|
8397
|
+
totals.set(entry.strategy, (totals.get(entry.strategy) ?? 0) + pnl);
|
|
8398
|
+
}
|
|
8399
|
+
return totals;
|
|
8400
|
+
};
|
|
7881
8401
|
var getExchangeExecutionMergeKey = (entry) => {
|
|
7882
8402
|
const orderId = firstString([entry.orderId, entry.orderLinkId]);
|
|
7883
8403
|
return orderId ? `${entry.symbol}::${entry.direction}::${orderId}` : null;
|
|
@@ -8144,16 +8664,89 @@ var compareExchangeEntriesToBacktest = ({
|
|
|
8144
8664
|
backtestOnly
|
|
8145
8665
|
};
|
|
8146
8666
|
};
|
|
8667
|
+
var findExchangeRuntimeOutcome = ({
|
|
8668
|
+
exchangeEntry,
|
|
8669
|
+
strategyName,
|
|
8670
|
+
runtimeSignals,
|
|
8671
|
+
runtimeSignalEvaluations,
|
|
8672
|
+
toleranceMs,
|
|
8673
|
+
signalTimestampOffsetMs
|
|
8674
|
+
}) => {
|
|
8675
|
+
const candidates = [];
|
|
8676
|
+
for (const signal of runtimeSignals) {
|
|
8677
|
+
if (signal.strategy === strategyName && signal.symbol === exchangeEntry.symbol && signal.direction === exchangeEntry.direction) {
|
|
8678
|
+
candidates.push({
|
|
8679
|
+
timestamp: signal.timestamp,
|
|
8680
|
+
orderStatus: signal.orderStatus,
|
|
8681
|
+
reason: signal.orderFailureReason || signal.orderSkipReason || signal.orderStatus
|
|
8682
|
+
});
|
|
8683
|
+
}
|
|
8684
|
+
}
|
|
8685
|
+
for (const evaluation of runtimeSignalEvaluations) {
|
|
8686
|
+
if (evaluation.strategy === strategyName && evaluation.symbol === exchangeEntry.symbol && (evaluation.direction == null || evaluation.direction === exchangeEntry.direction)) {
|
|
8687
|
+
candidates.push({
|
|
8688
|
+
timestamp: evaluation.timestamp,
|
|
8689
|
+
orderStatus: evaluation.orderStatus,
|
|
8690
|
+
reason: evaluation.orderSkipReason || evaluation.reason || evaluation.orderStatus
|
|
8691
|
+
});
|
|
8692
|
+
}
|
|
8693
|
+
}
|
|
8694
|
+
return candidates.map((candidate) => ({
|
|
8695
|
+
...candidate,
|
|
8696
|
+
timestampDiffMs: Math.abs(
|
|
8697
|
+
candidate.timestamp + signalTimestampOffsetMs - exchangeEntry.entryTimestamp
|
|
8698
|
+
)
|
|
8699
|
+
})).filter((candidate) => candidate.timestampDiffMs <= toleranceMs).sort(
|
|
8700
|
+
(left, right) => left.timestampDiffMs - right.timestampDiffMs || Number(right.orderStatus === "failed") - Number(left.orderStatus === "failed")
|
|
8701
|
+
)[0] ?? null;
|
|
8702
|
+
};
|
|
8703
|
+
var splitExchangeMatchesByRuntimeOrderStatus = ({
|
|
8704
|
+
matched,
|
|
8705
|
+
strategyNameByOrderLinkKey,
|
|
8706
|
+
runtimeSignals,
|
|
8707
|
+
runtimeSignalEvaluations,
|
|
8708
|
+
toleranceMs,
|
|
8709
|
+
signalTimestampOffsetMs
|
|
8710
|
+
}) => {
|
|
8711
|
+
const orderFailed = [];
|
|
8712
|
+
const completed = matched.filter((item) => {
|
|
8713
|
+
const strategyName = resolveReplayStrategyNameFromExchangeEntry({
|
|
8714
|
+
exchangeEntry: item.exchange,
|
|
8715
|
+
strategyNameByOrderLinkKey
|
|
8716
|
+
});
|
|
8717
|
+
if (!strategyName) {
|
|
8718
|
+
return true;
|
|
8719
|
+
}
|
|
8720
|
+
const outcome = findExchangeRuntimeOutcome({
|
|
8721
|
+
exchangeEntry: item.exchange,
|
|
8722
|
+
strategyName,
|
|
8723
|
+
runtimeSignals,
|
|
8724
|
+
runtimeSignalEvaluations,
|
|
8725
|
+
toleranceMs,
|
|
8726
|
+
signalTimestampOffsetMs
|
|
8727
|
+
});
|
|
8728
|
+
if (outcome?.orderStatus !== "failed") {
|
|
8729
|
+
return true;
|
|
8730
|
+
}
|
|
8731
|
+
orderFailed.push({
|
|
8732
|
+
...item,
|
|
8733
|
+
reason: outcome.reason || "orderStatus=failed"
|
|
8734
|
+
});
|
|
8735
|
+
return false;
|
|
8736
|
+
});
|
|
8737
|
+
return { completed, orderFailed };
|
|
8738
|
+
};
|
|
8147
8739
|
var saveAndPrintReplayExchangeComparison = async ({
|
|
8148
8740
|
liveStrategySummaries,
|
|
8149
8741
|
backtestEntries,
|
|
8150
8742
|
replaySignals,
|
|
8151
8743
|
replaySignalEvaluations,
|
|
8152
8744
|
runtimeSignals,
|
|
8153
|
-
runtimeSignalEvaluations
|
|
8745
|
+
runtimeSignalEvaluations,
|
|
8746
|
+
lineage
|
|
8154
8747
|
}) => {
|
|
8155
8748
|
const { connector, connectorName, window: window2 } = getRuntimeCompareContext();
|
|
8156
|
-
const
|
|
8749
|
+
const rawExchangeEntries = await loadExchangeEntriesForComparison({
|
|
8157
8750
|
connector,
|
|
8158
8751
|
startTime: window2.start,
|
|
8159
8752
|
endTime: window2.end
|
|
@@ -8161,11 +8754,36 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8161
8754
|
const strategyNameByOrderLinkKey = buildStrategyNameByOrderLinkKey(
|
|
8162
8755
|
liveStrategySummaries.map((summary) => summary.strategyName)
|
|
8163
8756
|
);
|
|
8757
|
+
const exchangeOutcomeByEntry = /* @__PURE__ */ new Map();
|
|
8758
|
+
const exchangeEntries = rawExchangeEntries.filter((entry) => {
|
|
8759
|
+
const strategyName = resolveReplayStrategyNameFromExchangeEntry({
|
|
8760
|
+
exchangeEntry: entry,
|
|
8761
|
+
strategyNameByOrderLinkKey
|
|
8762
|
+
});
|
|
8763
|
+
if (!strategyName) {
|
|
8764
|
+
return false;
|
|
8765
|
+
}
|
|
8766
|
+
const outcome = findExchangeRuntimeOutcome({
|
|
8767
|
+
exchangeEntry: entry,
|
|
8768
|
+
strategyName,
|
|
8769
|
+
runtimeSignals,
|
|
8770
|
+
runtimeSignalEvaluations,
|
|
8771
|
+
toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
|
|
8772
|
+
signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
|
|
8773
|
+
});
|
|
8774
|
+
exchangeOutcomeByEntry.set(entry, outcome);
|
|
8775
|
+
return outcome != null;
|
|
8776
|
+
});
|
|
8777
|
+
const comparisonLineage = {
|
|
8778
|
+
...lineage,
|
|
8779
|
+
excludedExchangeEntries: lineage.excludedExchangeEntries + rawExchangeEntries.length - exchangeEntries.length
|
|
8780
|
+
};
|
|
8781
|
+
const backtestPnlByStrategy = summarizeBacktestPnlByStrategy(backtestEntries);
|
|
8164
8782
|
if (!exchangeEntries.length) {
|
|
8165
8783
|
console.log("");
|
|
8166
8784
|
console.log(
|
|
8167
8785
|
import_chalk11.default.yellow(
|
|
8168
|
-
`SIGNALS REPLAY VS EXCHANGE: no exchange entry executions found for ${connectorName} in ${(0, import_time6.formatUnix)(
|
|
8786
|
+
`SIGNALS REPLAY VS EXCHANGE: no lineage-linked exchange entry executions found for ${connectorName} in ${(0, import_time6.formatUnix)(
|
|
8169
8787
|
window2.start
|
|
8170
8788
|
)} -> ${(0, import_time6.formatUnix)(window2.end)}`
|
|
8171
8789
|
)
|
|
@@ -8192,18 +8810,21 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8192
8810
|
runtimeEntriesCount: 0,
|
|
8193
8811
|
backtestEntriesCount: backtestEntries.length,
|
|
8194
8812
|
matchedCount: 0,
|
|
8813
|
+
orderFailedCount: 0,
|
|
8195
8814
|
runtimeOnlyCount: 0,
|
|
8196
8815
|
backtestOnlyCount: backtestEntries.length,
|
|
8197
8816
|
details: details2,
|
|
8817
|
+
lineage: comparisonLineage,
|
|
8198
8818
|
rows: liveStrategySummaries.map((summary) => ({
|
|
8199
8819
|
strategyName: summary.strategyName,
|
|
8200
8820
|
backtestEntries: backtestEntries.filter(
|
|
8201
8821
|
(entry) => entry.strategy === summary.strategyName
|
|
8202
8822
|
).length,
|
|
8203
|
-
backtestNetProfit: summary.
|
|
8823
|
+
backtestNetProfit: backtestPnlByStrategy.get(summary.strategyName) ?? 0,
|
|
8204
8824
|
runtimeTrades: 0,
|
|
8205
8825
|
runtimePnl: 0,
|
|
8206
8826
|
matched: 0,
|
|
8827
|
+
orderFailed: 0,
|
|
8207
8828
|
runtimeOnly: 0,
|
|
8208
8829
|
backtestOnly: backtestEntries.filter(
|
|
8209
8830
|
(entry) => entry.strategy === summary.strategyName
|
|
@@ -8217,6 +8838,14 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8217
8838
|
toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
|
|
8218
8839
|
backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
|
|
8219
8840
|
});
|
|
8841
|
+
const { completed: matched, orderFailed } = splitExchangeMatchesByRuntimeOrderStatus({
|
|
8842
|
+
matched: comparison.matched,
|
|
8843
|
+
strategyNameByOrderLinkKey,
|
|
8844
|
+
runtimeSignals,
|
|
8845
|
+
runtimeSignalEvaluations,
|
|
8846
|
+
toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
|
|
8847
|
+
signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
|
|
8848
|
+
});
|
|
8220
8849
|
const liveSummaryByStrategy = new Map(
|
|
8221
8850
|
liveStrategySummaries.map((summary) => [summary.strategyName, summary])
|
|
8222
8851
|
);
|
|
@@ -8229,10 +8858,11 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8229
8858
|
const next = {
|
|
8230
8859
|
strategyName,
|
|
8231
8860
|
backtestEntries: 0,
|
|
8232
|
-
backtestNetProfit:
|
|
8861
|
+
backtestNetProfit: backtestPnlByStrategy.get(strategyName) ?? 0,
|
|
8233
8862
|
runtimeTrades: 0,
|
|
8234
8863
|
runtimePnl: 0,
|
|
8235
8864
|
matched: 0,
|
|
8865
|
+
orderFailed: 0,
|
|
8236
8866
|
runtimeOnly: 0,
|
|
8237
8867
|
backtestOnly: 0
|
|
8238
8868
|
};
|
|
@@ -8245,7 +8875,7 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8245
8875
|
for (const entry of backtestEntries) {
|
|
8246
8876
|
ensureRow(entry.strategy).backtestEntries += 1;
|
|
8247
8877
|
}
|
|
8248
|
-
for (const item of
|
|
8878
|
+
for (const item of matched) {
|
|
8249
8879
|
const row = ensureRow(item.backtest.strategy);
|
|
8250
8880
|
row.runtimeTrades += 1;
|
|
8251
8881
|
row.matched += 1;
|
|
@@ -8253,6 +8883,11 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8253
8883
|
row.runtimePnl += item.exchange.closedPnl;
|
|
8254
8884
|
}
|
|
8255
8885
|
}
|
|
8886
|
+
for (const item of orderFailed) {
|
|
8887
|
+
const row = ensureRow(item.backtest.strategy);
|
|
8888
|
+
row.runtimeTrades += 1;
|
|
8889
|
+
row.orderFailed += 1;
|
|
8890
|
+
}
|
|
8256
8891
|
for (const entry of comparison.backtestOnly) {
|
|
8257
8892
|
ensureRow(entry.strategy).backtestOnly += 1;
|
|
8258
8893
|
}
|
|
@@ -8271,7 +8906,8 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8271
8906
|
}
|
|
8272
8907
|
}
|
|
8273
8908
|
const details = buildReplayExchangeComparisonDetails({
|
|
8274
|
-
matched
|
|
8909
|
+
matched,
|
|
8910
|
+
orderFailed,
|
|
8275
8911
|
exchangeOnly: comparison.exchangeOnly,
|
|
8276
8912
|
backtestOnly: comparison.backtestOnly,
|
|
8277
8913
|
exchangeEntries,
|
|
@@ -8298,8 +8934,10 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8298
8934
|
import_chalk11.default.yellow(String(row.runtimeTrades)),
|
|
8299
8935
|
rtPnlColor(`${row.runtimePnl.toFixed(2)}$`),
|
|
8300
8936
|
import_chalk11.default.green(String(row.matched)),
|
|
8937
|
+
import_chalk11.default.red(String(row.orderFailed)),
|
|
8301
8938
|
import_chalk11.default.yellow(String(row.runtimeOnly)),
|
|
8302
|
-
import_chalk11.default.magenta(String(row.backtestOnly))
|
|
8939
|
+
import_chalk11.default.magenta(String(row.backtestOnly)),
|
|
8940
|
+
import_chalk11.default.red(String(getReplayRuntimeUnmatchedCount(row)))
|
|
8303
8941
|
];
|
|
8304
8942
|
});
|
|
8305
8943
|
console.log("");
|
|
@@ -8327,17 +8965,20 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8327
8965
|
windowTradesCount: exchangeEntries.length,
|
|
8328
8966
|
runtimeEntriesCount: exchangeEntries.length,
|
|
8329
8967
|
backtestEntriesCount: backtestEntries.length,
|
|
8330
|
-
matchedCount:
|
|
8968
|
+
matchedCount: matched.length,
|
|
8969
|
+
orderFailedCount: orderFailed.length,
|
|
8331
8970
|
runtimeOnlyCount: comparison.exchangeOnly.length,
|
|
8332
8971
|
backtestOnlyCount: comparison.backtestOnly.length,
|
|
8333
8972
|
rows,
|
|
8334
|
-
details
|
|
8973
|
+
details,
|
|
8974
|
+
lineage: comparisonLineage
|
|
8335
8975
|
};
|
|
8336
8976
|
};
|
|
8337
8977
|
var saveAndPrintReplayRuntimeComparison = async ({
|
|
8338
8978
|
liveStrategySummaries,
|
|
8339
8979
|
backtestEntries,
|
|
8340
|
-
replaySignals
|
|
8980
|
+
replaySignals,
|
|
8981
|
+
replayLineages
|
|
8341
8982
|
}) => {
|
|
8342
8983
|
const { connector, connectorName, window: window2 } = getRuntimeCompareContext();
|
|
8343
8984
|
if (!connector || !window2) {
|
|
@@ -8347,8 +8988,16 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8347
8988
|
liveStrategySummaries.map((summary) => summary.strategyName)
|
|
8348
8989
|
);
|
|
8349
8990
|
const replaySignalEvaluations = buildReplaySignalEvaluations(replaySignals);
|
|
8350
|
-
const [
|
|
8351
|
-
|
|
8991
|
+
const [
|
|
8992
|
+
rawRuntimeTrades,
|
|
8993
|
+
runtimeSignals,
|
|
8994
|
+
runtimeSignalEvaluations,
|
|
8995
|
+
runtimeLineageScopes
|
|
8996
|
+
] = await Promise.all([
|
|
8997
|
+
loadRuntimeTrades(replayUserName, {
|
|
8998
|
+
startTime: window2.start,
|
|
8999
|
+
endTime: window2.end
|
|
9000
|
+
}),
|
|
8352
9001
|
loadRuntimeSignals(replayUserName, {
|
|
8353
9002
|
startTime: window2.start,
|
|
8354
9003
|
endTime: window2.end
|
|
@@ -8356,6 +9005,10 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8356
9005
|
loadRuntimeSignalEvaluations(replayUserName, {
|
|
8357
9006
|
startTime: window2.start,
|
|
8358
9007
|
endTime: window2.end
|
|
9008
|
+
}),
|
|
9009
|
+
loadRuntimeLineageScopes(replayUserName, {
|
|
9010
|
+
startTime: window2.start,
|
|
9011
|
+
endTime: window2.end
|
|
8359
9012
|
})
|
|
8360
9013
|
]);
|
|
8361
9014
|
const syncedRuntimeTrades = await syncRuntimeTrades({
|
|
@@ -8397,7 +9050,16 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8397
9050
|
}
|
|
8398
9051
|
}
|
|
8399
9052
|
});
|
|
8400
|
-
const
|
|
9053
|
+
const comparable = filterReplayComparisonByLineage({
|
|
9054
|
+
replayLineages,
|
|
9055
|
+
runtimeTrades: syncedRuntimeTrades,
|
|
9056
|
+
runtimeSignals,
|
|
9057
|
+
runtimeSignalEvaluations,
|
|
9058
|
+
runtimeLineageScopes,
|
|
9059
|
+
backtestEntries
|
|
9060
|
+
});
|
|
9061
|
+
const comparableBacktestEntries = comparable.backtestEntries;
|
|
9062
|
+
const windowRuntimeTrades = comparable.runtimeTrades.filter(
|
|
8401
9063
|
(trade) => trade.entryTimestamp >= window2.start && trade.entryTimestamp < window2.end && relevantStrategies.has(trade.strategy)
|
|
8402
9064
|
);
|
|
8403
9065
|
if (!windowRuntimeTrades.length) {
|
|
@@ -8406,17 +9068,18 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8406
9068
|
import_chalk11.default.yellow(
|
|
8407
9069
|
`SIGNALS REPLAY VS RUNTIME: no local runtime trades found for ${connectorName} in ${(0, import_time6.formatUnix)(
|
|
8408
9070
|
window2.start
|
|
8409
|
-
)} -> ${(0, import_time6.formatUnix)(window2.end)}
|
|
9071
|
+
)} -> ${(0, import_time6.formatUnix)(window2.end)} with matching lineage; checking lineage-linked exchange executions`
|
|
8410
9072
|
)
|
|
8411
9073
|
);
|
|
8412
9074
|
console.log("");
|
|
8413
9075
|
return saveAndPrintReplayExchangeComparison({
|
|
8414
9076
|
liveStrategySummaries,
|
|
8415
|
-
backtestEntries,
|
|
9077
|
+
backtestEntries: comparableBacktestEntries,
|
|
8416
9078
|
replaySignals,
|
|
8417
9079
|
replaySignalEvaluations,
|
|
8418
|
-
runtimeSignals,
|
|
8419
|
-
runtimeSignalEvaluations
|
|
9080
|
+
runtimeSignals: comparable.runtimeSignals,
|
|
9081
|
+
runtimeSignalEvaluations: comparable.runtimeSignalEvaluations,
|
|
9082
|
+
lineage: comparable.lineage
|
|
8420
9083
|
});
|
|
8421
9084
|
}
|
|
8422
9085
|
const runtimeSummaries = summarizeRuntimeTradesByStrategy(windowRuntimeTrades);
|
|
@@ -8427,7 +9090,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8427
9090
|
const runtimeDedupe = dedupeRuntimeParityEntries(rawRuntimeEntries);
|
|
8428
9091
|
const comparison = compareTradeParityEntries({
|
|
8429
9092
|
runtimeEntries: runtimeDedupe.entries,
|
|
8430
|
-
backtestEntries,
|
|
9093
|
+
backtestEntries: comparableBacktestEntries,
|
|
8431
9094
|
toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
|
|
8432
9095
|
backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
|
|
8433
9096
|
});
|
|
@@ -8436,18 +9099,18 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8436
9099
|
runtimeOnly: comparison.runtimeOnly,
|
|
8437
9100
|
backtestOnly: comparison.backtestOnly,
|
|
8438
9101
|
runtimeEntries: runtimeDedupe.entries,
|
|
8439
|
-
backtestEntries,
|
|
9102
|
+
backtestEntries: comparableBacktestEntries,
|
|
8440
9103
|
toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
|
|
8441
9104
|
backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs(),
|
|
8442
|
-
runtimeSignals,
|
|
8443
|
-
runtimeSignalEvaluations,
|
|
9105
|
+
runtimeSignals: comparable.runtimeSignals,
|
|
9106
|
+
runtimeSignalEvaluations: comparable.runtimeSignalEvaluations,
|
|
8444
9107
|
replaySignals,
|
|
8445
9108
|
replaySignalEvaluations
|
|
8446
9109
|
});
|
|
8447
9110
|
const parityRows = summarizeTradeParityByStrategy({
|
|
8448
9111
|
runtimeEntries: runtimeDedupe.entries,
|
|
8449
9112
|
runtimeDuplicateEntries: runtimeDedupe.duplicateEntries,
|
|
8450
|
-
backtestEntries,
|
|
9113
|
+
backtestEntries: comparableBacktestEntries,
|
|
8451
9114
|
matchedEntries: comparison.matched,
|
|
8452
9115
|
runtimeOnlyEntries: comparison.runtimeOnly,
|
|
8453
9116
|
backtestOnlyEntries: comparison.backtestOnly
|
|
@@ -8456,22 +9119,25 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8456
9119
|
const liveSummaryByStrategy = new Map(
|
|
8457
9120
|
liveStrategySummaries.map((summary) => [summary.strategyName, summary])
|
|
8458
9121
|
);
|
|
9122
|
+
const backtestPnlByStrategy = summarizeBacktestPnlByStrategy(
|
|
9123
|
+
comparableBacktestEntries
|
|
9124
|
+
);
|
|
8459
9125
|
const strategyNames = /* @__PURE__ */ new Set([
|
|
8460
9126
|
...liveSummaryByStrategy.keys(),
|
|
8461
9127
|
...runtimeSummaryByStrategy.keys(),
|
|
8462
9128
|
...parityByStrategy.keys()
|
|
8463
9129
|
]);
|
|
8464
9130
|
const rows = [...strategyNames].sort((left, right) => left.localeCompare(right)).map((strategyName) => {
|
|
8465
|
-
const liveSummary = liveSummaryByStrategy.get(strategyName);
|
|
8466
9131
|
const runtimeSummary = runtimeSummaryByStrategy.get(strategyName);
|
|
8467
9132
|
const parity = parityByStrategy.get(strategyName);
|
|
8468
9133
|
return {
|
|
8469
9134
|
strategyName,
|
|
8470
9135
|
backtestEntries: parity?.backtest ?? 0,
|
|
8471
|
-
backtestNetProfit:
|
|
9136
|
+
backtestNetProfit: backtestPnlByStrategy.get(strategyName) ?? 0,
|
|
8472
9137
|
runtimeTrades: runtimeSummary?.trades ?? 0,
|
|
8473
9138
|
runtimePnl: runtimeSummary?.totalPnl ?? 0,
|
|
8474
9139
|
matched: parity?.matched ?? 0,
|
|
9140
|
+
orderFailed: 0,
|
|
8475
9141
|
runtimeOnly: parity?.runtimeOnly ?? 0,
|
|
8476
9142
|
backtestOnly: parity?.backtestOnly ?? 0
|
|
8477
9143
|
};
|
|
@@ -8486,8 +9152,10 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8486
9152
|
import_chalk11.default.yellow(String(row.runtimeTrades)),
|
|
8487
9153
|
rtPnlColor(`${row.runtimePnl.toFixed(2)}$`),
|
|
8488
9154
|
import_chalk11.default.green(String(row.matched)),
|
|
9155
|
+
import_chalk11.default.red(String(row.orderFailed)),
|
|
8489
9156
|
import_chalk11.default.yellow(String(row.runtimeOnly)),
|
|
8490
|
-
import_chalk11.default.magenta(String(row.backtestOnly))
|
|
9157
|
+
import_chalk11.default.magenta(String(row.backtestOnly)),
|
|
9158
|
+
import_chalk11.default.red(String(getReplayRuntimeUnmatchedCount(row)))
|
|
8491
9159
|
];
|
|
8492
9160
|
});
|
|
8493
9161
|
console.log("");
|
|
@@ -8514,18 +9182,20 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8514
9182
|
syncedTradesCount: syncedRuntimeTrades.length,
|
|
8515
9183
|
windowTradesCount: windowRuntimeTrades.length,
|
|
8516
9184
|
runtimeEntriesCount: runtimeDedupe.entries.length,
|
|
8517
|
-
backtestEntriesCount:
|
|
9185
|
+
backtestEntriesCount: comparableBacktestEntries.length,
|
|
8518
9186
|
matchedCount: comparison.matched.length,
|
|
9187
|
+
orderFailedCount: 0,
|
|
8519
9188
|
runtimeOnlyCount: comparison.runtimeOnly.length,
|
|
8520
9189
|
backtestOnlyCount: comparison.backtestOnly.length,
|
|
8521
9190
|
rows,
|
|
8522
|
-
details
|
|
9191
|
+
details,
|
|
9192
|
+
lineage: comparable.lineage
|
|
8523
9193
|
};
|
|
8524
9194
|
};
|
|
8525
9195
|
|
|
8526
9196
|
// src/lib/replay/outputReport.ts
|
|
8527
|
-
var
|
|
8528
|
-
var
|
|
9197
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
9198
|
+
var import_node_path2 = __toESM(require("path"));
|
|
8529
9199
|
var import_data = require("@tradejs/core/data");
|
|
8530
9200
|
var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
|
|
8531
9201
|
timeZone: "Europe/Moscow",
|
|
@@ -8560,6 +9230,7 @@ var buildPerTradeAnalysis = (runtimeComparison) => {
|
|
|
8560
9230
|
if (!details) {
|
|
8561
9231
|
return {
|
|
8562
9232
|
matched: [],
|
|
9233
|
+
orderFailed: [],
|
|
8563
9234
|
runtimeOnly: [],
|
|
8564
9235
|
backtestOnly: []
|
|
8565
9236
|
};
|
|
@@ -8588,6 +9259,17 @@ var buildPerTradeAnalysis = (runtimeComparison) => {
|
|
|
8588
9259
|
slippage: item.slippage
|
|
8589
9260
|
}
|
|
8590
9261
|
})),
|
|
9262
|
+
orderFailed: details.orderFailed.map((item) => ({
|
|
9263
|
+
status: "orderFailed",
|
|
9264
|
+
strategy: entryStrategy(item.backtest),
|
|
9265
|
+
symbol: item.backtest.symbol,
|
|
9266
|
+
direction: item.backtest.direction,
|
|
9267
|
+
runtime: item.runtime,
|
|
9268
|
+
backtest: item.backtest,
|
|
9269
|
+
reason: item.reason,
|
|
9270
|
+
timestampDiffMs: item.timestampDiffMs,
|
|
9271
|
+
entryPriceDeltaPct: item.priceDeltaPct
|
|
9272
|
+
})),
|
|
8591
9273
|
runtimeOnly: details.runtimeOnly.map((entry) => ({
|
|
8592
9274
|
status: "runtimeOnly",
|
|
8593
9275
|
strategy: entryStrategy(entry),
|
|
@@ -8671,6 +9353,26 @@ var renderRuntimeOnlyRows = (perTrade) => perTrade.length ? createMarkdownTable(
|
|
|
8671
9353
|
];
|
|
8672
9354
|
})
|
|
8673
9355
|
) : "_No runtime-only trades._";
|
|
9356
|
+
var renderOrderFailedRows = (perTrade) => perTrade.length ? createMarkdownTable(
|
|
9357
|
+
[
|
|
9358
|
+
"Strategy",
|
|
9359
|
+
"Symbol",
|
|
9360
|
+
"Dir",
|
|
9361
|
+
"BT time",
|
|
9362
|
+
"RT time",
|
|
9363
|
+
"Entry delta",
|
|
9364
|
+
"Reason"
|
|
9365
|
+
],
|
|
9366
|
+
perTrade.map((item) => [
|
|
9367
|
+
item.strategy,
|
|
9368
|
+
item.symbol,
|
|
9369
|
+
item.direction,
|
|
9370
|
+
formatMsk(item.backtest.timestamp),
|
|
9371
|
+
formatMsk(item.runtime.timestamp),
|
|
9372
|
+
formatPct(item.entryPriceDeltaPct),
|
|
9373
|
+
item.reason
|
|
9374
|
+
])
|
|
9375
|
+
) : "_No failed runtime orders._";
|
|
8674
9376
|
var renderBacktestOnlyRows = (perTrade) => perTrade.length ? createMarkdownTable(
|
|
8675
9377
|
[
|
|
8676
9378
|
"Strategy",
|
|
@@ -8708,8 +9410,10 @@ var renderStrategyRows = (runtimeComparison) => runtimeComparison?.rows.length ?
|
|
|
8708
9410
|
"RT trades",
|
|
8709
9411
|
"RT PnL",
|
|
8710
9412
|
"Matched",
|
|
9413
|
+
"Order failed",
|
|
8711
9414
|
"RT only",
|
|
8712
|
-
"BT only"
|
|
9415
|
+
"BT only",
|
|
9416
|
+
"Unmatched"
|
|
8713
9417
|
],
|
|
8714
9418
|
runtimeComparison.rows.map((row) => [
|
|
8715
9419
|
row.strategyName,
|
|
@@ -8718,10 +9422,69 @@ var renderStrategyRows = (runtimeComparison) => runtimeComparison?.rows.length ?
|
|
|
8718
9422
|
row.runtimeTrades,
|
|
8719
9423
|
formatMoney(row.runtimePnl),
|
|
8720
9424
|
row.matched,
|
|
9425
|
+
row.orderFailed,
|
|
8721
9426
|
row.runtimeOnly,
|
|
8722
|
-
row.backtestOnly
|
|
9427
|
+
row.backtestOnly,
|
|
9428
|
+
getReplayRuntimeUnmatchedCount(row)
|
|
8723
9429
|
])
|
|
8724
9430
|
) : "_No runtime comparison rows._";
|
|
9431
|
+
var renderLineageRows = (runtimeComparison) => {
|
|
9432
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
9433
|
+
for (const record of runtimeComparison?.lineage.replay ?? []) {
|
|
9434
|
+
const existing = grouped.get(record.strategy) ?? {
|
|
9435
|
+
strategy: record.strategy,
|
|
9436
|
+
scopes: 0,
|
|
9437
|
+
gitShas: /* @__PURE__ */ new Set(),
|
|
9438
|
+
dirty: /* @__PURE__ */ new Set(),
|
|
9439
|
+
gates: /* @__PURE__ */ new Set(),
|
|
9440
|
+
configs: /* @__PURE__ */ new Set(),
|
|
9441
|
+
contexts: /* @__PURE__ */ new Set()
|
|
9442
|
+
};
|
|
9443
|
+
existing.scopes += 1;
|
|
9444
|
+
existing.gitShas.add(record.lineage.gitSha ?? "unknown");
|
|
9445
|
+
existing.dirty.add(
|
|
9446
|
+
record.lineage.gitDirty == null ? "unknown" : String(record.lineage.gitDirty)
|
|
9447
|
+
);
|
|
9448
|
+
existing.gates.add(record.lineage.gateFingerprint);
|
|
9449
|
+
existing.configs.add(record.lineage.configFingerprint);
|
|
9450
|
+
existing.contexts.add(record.lineage.contextFingerprint);
|
|
9451
|
+
grouped.set(record.strategy, existing);
|
|
9452
|
+
}
|
|
9453
|
+
const formatSet = (values) => {
|
|
9454
|
+
const sorted = [...values].sort();
|
|
9455
|
+
return sorted.length <= 3 ? sorted.join(", ") : `${sorted.slice(0, 3).join(", ")} \u2026 (+${sorted.length - 3})`;
|
|
9456
|
+
};
|
|
9457
|
+
return grouped.size ? createMarkdownTable(
|
|
9458
|
+
[
|
|
9459
|
+
"Strategy",
|
|
9460
|
+
"Scopes",
|
|
9461
|
+
"Git SHA",
|
|
9462
|
+
"Dirty",
|
|
9463
|
+
"Gate",
|
|
9464
|
+
"Config variants",
|
|
9465
|
+
"Context"
|
|
9466
|
+
],
|
|
9467
|
+
[...grouped.values()].sort((left, right) => left.strategy.localeCompare(right.strategy)).map(
|
|
9468
|
+
({
|
|
9469
|
+
strategy,
|
|
9470
|
+
scopes,
|
|
9471
|
+
gitShas,
|
|
9472
|
+
dirty,
|
|
9473
|
+
gates,
|
|
9474
|
+
configs,
|
|
9475
|
+
contexts
|
|
9476
|
+
}) => [
|
|
9477
|
+
strategy,
|
|
9478
|
+
scopes,
|
|
9479
|
+
formatSet(gitShas),
|
|
9480
|
+
formatSet(dirty),
|
|
9481
|
+
formatSet(gates),
|
|
9482
|
+
`${configs.size}: ${formatSet(configs)}`,
|
|
9483
|
+
formatSet(contexts)
|
|
9484
|
+
]
|
|
9485
|
+
)
|
|
9486
|
+
) : "_No replay lineage records._";
|
|
9487
|
+
};
|
|
8725
9488
|
var writeReplayOutputReport = async ({
|
|
8726
9489
|
projectRoot,
|
|
8727
9490
|
timestamp,
|
|
@@ -8736,8 +9499,8 @@ var writeReplayOutputReport = async ({
|
|
|
8736
9499
|
strategySnapshot,
|
|
8737
9500
|
runtimeComparison
|
|
8738
9501
|
}) => {
|
|
8739
|
-
const outputDir =
|
|
8740
|
-
await
|
|
9502
|
+
const outputDir = import_node_path2.default.resolve(projectRoot, "data/replay/output");
|
|
9503
|
+
await import_promises2.default.mkdir(outputDir, { recursive: true });
|
|
8741
9504
|
const perTradeAnalysis = buildPerTradeAnalysis(runtimeComparison);
|
|
8742
9505
|
const report = {
|
|
8743
9506
|
reportType: "replay-output",
|
|
@@ -8763,15 +9526,17 @@ var writeReplayOutputReport = async ({
|
|
|
8763
9526
|
cycleCount: replayResult.cycleCount,
|
|
8764
9527
|
abortedCycles: replayResult.abortedCycles,
|
|
8765
9528
|
matched: runtimeComparison?.matchedCount ?? 0,
|
|
9529
|
+
orderFailed: runtimeComparison?.orderFailedCount ?? 0,
|
|
8766
9530
|
runtimeOnly: runtimeComparison?.runtimeOnlyCount ?? 0,
|
|
8767
9531
|
backtestOnly: runtimeComparison?.backtestOnlyCount ?? 0
|
|
8768
9532
|
},
|
|
8769
9533
|
resultsByStrategies: strategySnapshot.summaries,
|
|
9534
|
+
replayLineage: replayResult.runtimeLineages,
|
|
8770
9535
|
runtimeComparison,
|
|
8771
9536
|
perTradeAnalysis
|
|
8772
9537
|
};
|
|
8773
|
-
const markdownPath =
|
|
8774
|
-
const jsonPath =
|
|
9538
|
+
const markdownPath = import_node_path2.default.join(outputDir, `${timestamp}-replay-report.md`);
|
|
9539
|
+
const jsonPath = import_node_path2.default.join(outputDir, `${timestamp}-replay-report.json`);
|
|
8775
9540
|
const lines = [
|
|
8776
9541
|
"# Replay Report",
|
|
8777
9542
|
"",
|
|
@@ -8791,13 +9556,26 @@ var writeReplayOutputReport = async ({
|
|
|
8791
9556
|
["Window", `${formatMsk(window2.start)} - ${formatMsk(window2.end)} MSK`],
|
|
8792
9557
|
["Runtime compare mode", runtimeComparison?.mode ?? "none"],
|
|
8793
9558
|
["Matched", runtimeComparison?.matchedCount ?? 0],
|
|
9559
|
+
["Order failed", runtimeComparison?.orderFailedCount ?? 0],
|
|
8794
9560
|
["Runtime-only", runtimeComparison?.runtimeOnlyCount ?? 0],
|
|
8795
9561
|
["Backtest-only", runtimeComparison?.backtestOnlyCount ?? 0],
|
|
9562
|
+
[
|
|
9563
|
+
"Comparable lineage scopes",
|
|
9564
|
+
runtimeComparison ? `${runtimeComparison.lineage.comparableScopes}/${runtimeComparison.lineage.replayScopes}` : "0/0"
|
|
9565
|
+
],
|
|
9566
|
+
[
|
|
9567
|
+
"Lineage exclusions",
|
|
9568
|
+
runtimeComparison ? `runtime=${runtimeComparison.lineage.excludedRuntimeTrades}, exchange=${runtimeComparison.lineage.excludedExchangeEntries}, backtest=${runtimeComparison.lineage.excludedBacktestEntries}` : ""
|
|
9569
|
+
],
|
|
8796
9570
|
["Command", `\`${process.argv.join(" ")}\``],
|
|
8797
9571
|
["JSON report", jsonPath]
|
|
8798
9572
|
]
|
|
8799
9573
|
),
|
|
8800
9574
|
"",
|
|
9575
|
+
"## Replay Lineage",
|
|
9576
|
+
"",
|
|
9577
|
+
renderLineageRows(runtimeComparison),
|
|
9578
|
+
"",
|
|
8801
9579
|
"## Strategy Comparison",
|
|
8802
9580
|
"",
|
|
8803
9581
|
renderStrategyRows(runtimeComparison),
|
|
@@ -8806,6 +9584,10 @@ var writeReplayOutputReport = async ({
|
|
|
8806
9584
|
"",
|
|
8807
9585
|
renderMatchedRows(perTradeAnalysis.matched),
|
|
8808
9586
|
"",
|
|
9587
|
+
"## Failed Runtime Orders",
|
|
9588
|
+
"",
|
|
9589
|
+
renderOrderFailedRows(perTradeAnalysis.orderFailed),
|
|
9590
|
+
"",
|
|
8809
9591
|
"## Runtime-Only Trades",
|
|
8810
9592
|
"",
|
|
8811
9593
|
renderRuntimeOnlyRows(perTradeAnalysis.runtimeOnly),
|
|
@@ -8820,8 +9602,8 @@ var writeReplayOutputReport = async ({
|
|
|
8820
9602
|
""
|
|
8821
9603
|
];
|
|
8822
9604
|
await Promise.all([
|
|
8823
|
-
|
|
8824
|
-
|
|
9605
|
+
import_promises2.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
|
|
9606
|
+
import_promises2.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
|
|
8825
9607
|
`, "utf8")
|
|
8826
9608
|
]);
|
|
8827
9609
|
return {
|
|
@@ -8860,7 +9642,8 @@ var finishReplay = async ({
|
|
|
8860
9642
|
const replayRuntimeComparison = await saveAndPrintReplayRuntimeComparison({
|
|
8861
9643
|
liveStrategySummaries: replayStrategySnapshot.summaries,
|
|
8862
9644
|
backtestEntries: replayStrategySnapshot.backtestEntries,
|
|
8863
|
-
replaySignals: replayResult.signals
|
|
9645
|
+
replaySignals: replayResult.signals,
|
|
9646
|
+
replayLineages: replayResult.runtimeLineages
|
|
8864
9647
|
});
|
|
8865
9648
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
8866
9649
|
const durationSeconds = Number(
|
|
@@ -8876,7 +9659,7 @@ var finishReplay = async ({
|
|
|
8876
9659
|
replayResult.orderLog[replayResult.orderLog.length - 1]?.timestamp ?? finishedAt.getTime()
|
|
8877
9660
|
)}`
|
|
8878
9661
|
}) : null;
|
|
8879
|
-
const replayKey =
|
|
9662
|
+
const replayKey = import_redis6.redisKeys.backtestResults(
|
|
8880
9663
|
replayUserName,
|
|
8881
9664
|
REPLAY_RESULTS_CONFIG,
|
|
8882
9665
|
timestamp
|
|
@@ -8897,7 +9680,7 @@ var finishReplay = async ({
|
|
|
8897
9680
|
});
|
|
8898
9681
|
console.log(import_chalk12.default.green(`Replay report: ${outputReport.markdownPath}`));
|
|
8899
9682
|
console.log(import_chalk12.default.green(`Replay report JSON: ${outputReport.jsonPath}`));
|
|
8900
|
-
await (0,
|
|
9683
|
+
await (0, import_redis6.setData)(
|
|
8901
9684
|
replayKey,
|
|
8902
9685
|
{
|
|
8903
9686
|
config: REPLAY_RESULTS_CONFIG,
|
|
@@ -8918,6 +9701,7 @@ var finishReplay = async ({
|
|
|
8918
9701
|
cycleCount: replayResult.cycleCount,
|
|
8919
9702
|
abortedCycles: replayResult.abortedCycles,
|
|
8920
9703
|
signalsCount: replayResult.signals.length,
|
|
9704
|
+
replayLineage: replayResult.runtimeLineages,
|
|
8921
9705
|
strategyCharts: replayChartSnapshot,
|
|
8922
9706
|
outputReport
|
|
8923
9707
|
},
|
|
@@ -8928,8 +9712,8 @@ var finishReplay = async ({
|
|
|
8928
9712
|
if (replayChartSnapshot) {
|
|
8929
9713
|
await Promise.all(
|
|
8930
9714
|
replayChartSnapshot.strategies.map(
|
|
8931
|
-
(card) => (0,
|
|
8932
|
-
|
|
9715
|
+
(card) => (0, import_redis6.setData)(
|
|
9716
|
+
import_redis6.redisKeys.strategyChartCard(replayUserName, "replay", card.cardId),
|
|
8933
9717
|
card,
|
|
8934
9718
|
{
|
|
8935
9719
|
expire: import_constants6.TTL_1M
|
|
@@ -8954,7 +9738,8 @@ var replayBacktest = async () => {
|
|
|
8954
9738
|
endTime: replayFlags.endTime,
|
|
8955
9739
|
cacheOnly: replayFlags.cacheOnly,
|
|
8956
9740
|
interval: replayInterval,
|
|
8957
|
-
projectRoot: replayProjectRoot
|
|
9741
|
+
projectRoot: replayProjectRoot,
|
|
9742
|
+
closedIntervalMs: (0, import_data2.intervalToMs)(replayInterval)
|
|
8958
9743
|
});
|
|
8959
9744
|
if (!preparedRun || isReplayUpdateOnlyRun) {
|
|
8960
9745
|
return;
|