@tradejs/cli 3.1.29-beta.282 → 3.1.29-beta.283
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 +2716 -2375
- package/dist/lib/mcpReplayProcess.js +1 -1
- package/dist/lib/runtimeFeedbackProcess.js +1700 -0
- package/dist/scripts/mcpWorker.js +43 -9
- package/dist/scripts/replay.js +173 -47
- package/dist/scripts/replayRunner.js +173 -47
- package/dist/scripts/replayRuntimeEvidence.js +109 -6
- package/dist/scripts/runtimeFeedbackProcess.js +1736 -0
- package/package.json +7 -7
|
@@ -41,7 +41,7 @@ __export(replayRunner_exports, {
|
|
|
41
41
|
module.exports = __toCommonJS(replayRunner_exports);
|
|
42
42
|
var import_chalk13 = __toESM(require("chalk"));
|
|
43
43
|
var import_constants7 = require("@tradejs/core/constants");
|
|
44
|
-
var
|
|
44
|
+
var import_data3 = require("@tradejs/core/data");
|
|
45
45
|
var import_time7 = require("@tradejs/core/time");
|
|
46
46
|
var import_redis6 = require("@tradejs/infra/redis");
|
|
47
47
|
var import_candles = require("@tradejs/infra/timescale/candles");
|
|
@@ -2711,27 +2711,27 @@ var createCoinMarketCapFetchStats = () => ({
|
|
|
2711
2711
|
totalCredits: 0,
|
|
2712
2712
|
byPath: /* @__PURE__ */ new Map()
|
|
2713
2713
|
});
|
|
2714
|
-
var getCoinMarketCapPathLabel = (
|
|
2715
|
-
if (
|
|
2716
|
-
if (
|
|
2717
|
-
if (
|
|
2718
|
-
if (
|
|
2719
|
-
if (
|
|
2720
|
-
if (
|
|
2721
|
-
return
|
|
2722
|
-
};
|
|
2723
|
-
var recordCoinMarketCapFetchStats = (stats,
|
|
2714
|
+
var getCoinMarketCapPathLabel = (path7) => {
|
|
2715
|
+
if (path7.includes("global-metrics")) return "global";
|
|
2716
|
+
if (path7.includes("cryptocurrency/quotes")) return "reference";
|
|
2717
|
+
if (path7.includes("exchange/quotes")) return "exchangeLiquidity";
|
|
2718
|
+
if (path7.includes("fear-and-greed")) return "fearGreed";
|
|
2719
|
+
if (path7.includes("cmc100")) return "cmc100";
|
|
2720
|
+
if (path7.includes("cmc20")) return "cmc20";
|
|
2721
|
+
return path7;
|
|
2722
|
+
};
|
|
2723
|
+
var recordCoinMarketCapFetchStats = (stats, path7, creditCount) => {
|
|
2724
2724
|
if (!stats) return;
|
|
2725
|
-
const existing = stats.byPath.get(
|
|
2725
|
+
const existing = stats.byPath.get(path7) ?? { requests: 0, credits: 0 };
|
|
2726
2726
|
existing.requests += 1;
|
|
2727
2727
|
existing.credits += creditCount ?? 0;
|
|
2728
2728
|
stats.requests += 1;
|
|
2729
2729
|
stats.totalCredits += creditCount ?? 0;
|
|
2730
|
-
stats.byPath.set(
|
|
2730
|
+
stats.byPath.set(path7, existing);
|
|
2731
2731
|
};
|
|
2732
2732
|
var formatCoinMarketCapFetchStats = (stats) => {
|
|
2733
2733
|
const byPath = [...stats.byPath.entries()].map(
|
|
2734
|
-
([
|
|
2734
|
+
([path7, item]) => `${getCoinMarketCapPathLabel(path7)}=${item.requests}req/${item.credits}cr`
|
|
2735
2735
|
).join(" ");
|
|
2736
2736
|
return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
|
|
2737
2737
|
};
|
|
@@ -4751,10 +4751,32 @@ var compactReplaySignal = (signal) => {
|
|
|
4751
4751
|
};
|
|
4752
4752
|
};
|
|
4753
4753
|
|
|
4754
|
+
// src/lib/replay/memoryProfile.ts
|
|
4755
|
+
var import_node_fs = __toESM(require("fs"));
|
|
4756
|
+
var import_node_path2 = __toESM(require("path"));
|
|
4757
|
+
var createReplayMemoryProfile = (file = process.env.TRADEJS_REPLAY_MEMORY_PROFILE) => {
|
|
4758
|
+
if (!file) return void 0;
|
|
4759
|
+
import_node_fs.default.mkdirSync(import_node_path2.default.dirname(file), { recursive: true });
|
|
4760
|
+
return (phase, counts = {}) => {
|
|
4761
|
+
import_node_fs.default.appendFileSync(
|
|
4762
|
+
file,
|
|
4763
|
+
JSON.stringify({
|
|
4764
|
+
schemaVersion: 1,
|
|
4765
|
+
observedAt: Date.now(),
|
|
4766
|
+
pid: process.pid,
|
|
4767
|
+
phase,
|
|
4768
|
+
counts,
|
|
4769
|
+
...process.memoryUsage(),
|
|
4770
|
+
maxRssKiB: process.resourceUsage().maxRSS
|
|
4771
|
+
}) + "\n"
|
|
4772
|
+
);
|
|
4773
|
+
};
|
|
4774
|
+
};
|
|
4775
|
+
|
|
4754
4776
|
// src/lib/replay/historicalSignalsReplay.ts
|
|
4755
4777
|
var import_chalk9 = __toESM(require("chalk"));
|
|
4756
4778
|
var import_progress4 = __toESM(require("progress"));
|
|
4757
|
-
var
|
|
4779
|
+
var import_node_path3 = __toESM(require("path"));
|
|
4758
4780
|
var import_node_module = require("module");
|
|
4759
4781
|
var import_strategies3 = require("@tradejs/core/strategies");
|
|
4760
4782
|
var import_time3 = require("@tradejs/core/time");
|
|
@@ -5303,6 +5325,11 @@ var prepareHistoricalReplay = async (context, adapters) => {
|
|
|
5303
5325
|
cacheOnly: true,
|
|
5304
5326
|
interval
|
|
5305
5327
|
});
|
|
5328
|
+
if (symbolRuntimes.length % 50 === 0)
|
|
5329
|
+
adapters.sampleMemory?.("history:loaded", {
|
|
5330
|
+
symbols: symbolRuntimes.length + 1,
|
|
5331
|
+
candles: data.length
|
|
5332
|
+
});
|
|
5306
5333
|
const preparedData = buildPreparedData({
|
|
5307
5334
|
data,
|
|
5308
5335
|
btcData: references.btcMarketData,
|
|
@@ -5399,6 +5426,11 @@ var prepareHistoricalReplay = async (context, adapters) => {
|
|
|
5399
5426
|
strategies: strategiesForSymbol
|
|
5400
5427
|
};
|
|
5401
5428
|
symbolRuntimes.push(symbolRuntime);
|
|
5429
|
+
if (symbolRuntimes.length % 50 === 0 || symbol === preparedRun.tickers.at(-1))
|
|
5430
|
+
adapters.sampleMemory?.("strategies:initializing", {
|
|
5431
|
+
symbols: symbolRuntimes.length,
|
|
5432
|
+
strategiesPerSymbol: strategiesForSymbol.length
|
|
5433
|
+
});
|
|
5402
5434
|
adapters.progress.tick({
|
|
5403
5435
|
skipped: adapters.display.skipped(skippedSymbols),
|
|
5404
5436
|
symbol: adapters.display.symbol(symbol)
|
|
@@ -5416,6 +5448,10 @@ var prepareHistoricalReplay = async (context, adapters) => {
|
|
|
5416
5448
|
runtimeLineages
|
|
5417
5449
|
};
|
|
5418
5450
|
};
|
|
5451
|
+
var releaseHistoricalReplayPlan = (plan) => {
|
|
5452
|
+
plan.symbolRuntimes.length = 0;
|
|
5453
|
+
plan.sharedReplayKeyPrefixes.length = 0;
|
|
5454
|
+
};
|
|
5419
5455
|
|
|
5420
5456
|
// src/lib/replay/historicalSignalsReplaySkipEvidence.ts
|
|
5421
5457
|
var import_data = require("@tradejs/core/data");
|
|
@@ -5715,7 +5751,7 @@ var getProjectReplayCacheReleasers = () => {
|
|
|
5715
5751
|
}
|
|
5716
5752
|
try {
|
|
5717
5753
|
const requireFromProject = (0, import_node_module.createRequire)(
|
|
5718
|
-
|
|
5754
|
+
import_node_path3.default.join(replayProjectRoot, "package.json")
|
|
5719
5755
|
);
|
|
5720
5756
|
projectReplayCacheReleasers = requireFromProject(
|
|
5721
5757
|
"@tradejs/core/strategies"
|
|
@@ -5856,6 +5892,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
5856
5892
|
collectSkipEvidence = true,
|
|
5857
5893
|
collectSignals = true
|
|
5858
5894
|
}) => {
|
|
5895
|
+
const memory = createReplayMemoryProfile();
|
|
5896
|
+
memory?.("prepare:start");
|
|
5859
5897
|
const startedAt = Date.now();
|
|
5860
5898
|
const projectConfig = await (0, import_cli4.loadTradejsConfig)(replayProjectRoot);
|
|
5861
5899
|
const projectHooks = projectConfig.hooks;
|
|
@@ -5872,6 +5910,7 @@ var runHistoricalSignalsReplay = async ({
|
|
|
5872
5910
|
width: 30
|
|
5873
5911
|
}
|
|
5874
5912
|
);
|
|
5913
|
+
memory?.("references:loaded");
|
|
5875
5914
|
const plan = await prepareHistoricalReplay(
|
|
5876
5915
|
{
|
|
5877
5916
|
userName: replayUserName,
|
|
@@ -5896,9 +5935,11 @@ var runHistoricalSignalsReplay = async ({
|
|
|
5896
5935
|
skipped: (value) => import_chalk9.default.yellow(value),
|
|
5897
5936
|
symbol: (value) => import_chalk9.default.gray(value)
|
|
5898
5937
|
},
|
|
5899
|
-
buildLineage: buildRuntimeLineage
|
|
5938
|
+
buildLineage: buildRuntimeLineage,
|
|
5939
|
+
sampleMemory: memory
|
|
5900
5940
|
}
|
|
5901
5941
|
);
|
|
5942
|
+
memory?.("strategies:prepared", { symbols: plan.symbolRuntimes.length });
|
|
5902
5943
|
const hookContext = buildAfterSignalsContext({
|
|
5903
5944
|
connector: replayConnector,
|
|
5904
5945
|
connectorName: preparedRun.connectorName,
|
|
@@ -5948,13 +5989,16 @@ var runHistoricalSignalsReplay = async ({
|
|
|
5948
5989
|
)
|
|
5949
5990
|
}
|
|
5950
5991
|
);
|
|
5992
|
+
memory?.("execution:complete", { signals: execution.signals.length });
|
|
5993
|
+
releaseHistoricalReplayPlan(plan);
|
|
5994
|
+
memory?.("execution:released");
|
|
5951
5995
|
const artifacts = replayConnector.getReplayArtifacts();
|
|
5952
5996
|
import_logger.logger.info(
|
|
5953
5997
|
import_chalk9.default.gray(
|
|
5954
5998
|
`signals replay historical cycles: ${plan.orderedTimestamps.length} (aborted=${execution.abortedCycles}) done in ${((Date.now() - startedAt) / 1e3).toFixed(1)}s`
|
|
5955
5999
|
)
|
|
5956
6000
|
);
|
|
5957
|
-
|
|
6001
|
+
const result = collectHistoricalReplayResult({
|
|
5958
6002
|
strategies: loadedStrategies,
|
|
5959
6003
|
artifacts,
|
|
5960
6004
|
signals: execution.signals,
|
|
@@ -5963,6 +6007,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
5963
6007
|
runtimeLineages: plan.runtimeLineages,
|
|
5964
6008
|
replayLineageScopes: execution.replayLineageScopes
|
|
5965
6009
|
});
|
|
6010
|
+
memory?.("result:collected");
|
|
6011
|
+
return result;
|
|
5966
6012
|
};
|
|
5967
6013
|
|
|
5968
6014
|
// src/lib/replay/chartSnapshot.ts
|
|
@@ -8797,9 +8843,87 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8797
8843
|
};
|
|
8798
8844
|
|
|
8799
8845
|
// src/lib/replay/outputReport.ts
|
|
8846
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
8847
|
+
var import_node_path5 = __toESM(require("path"));
|
|
8848
|
+
|
|
8849
|
+
// src/lib/replay/jsonFile.ts
|
|
8850
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
8800
8851
|
var import_promises2 = __toESM(require("fs/promises"));
|
|
8801
|
-
var
|
|
8802
|
-
var
|
|
8852
|
+
var import_node_path4 = __toESM(require("path"));
|
|
8853
|
+
var import_node_events = require("events");
|
|
8854
|
+
var import_node_crypto2 = require("crypto");
|
|
8855
|
+
function* jsonParts(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
8856
|
+
if (value === null || typeof value !== "object") {
|
|
8857
|
+
yield JSON.stringify(value) ?? "null";
|
|
8858
|
+
return;
|
|
8859
|
+
}
|
|
8860
|
+
if (ancestors.has(value)) throw new TypeError("Circular JSON evidence");
|
|
8861
|
+
ancestors.add(value);
|
|
8862
|
+
try {
|
|
8863
|
+
if (Array.isArray(value)) {
|
|
8864
|
+
yield "[";
|
|
8865
|
+
for (let index = 0; index < value.length; index++) {
|
|
8866
|
+
if (index) yield ",";
|
|
8867
|
+
yield* jsonParts(value[index], ancestors);
|
|
8868
|
+
}
|
|
8869
|
+
yield "]";
|
|
8870
|
+
} else {
|
|
8871
|
+
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
|
|
8872
|
+
throw new TypeError("JSON evidence requires plain records");
|
|
8873
|
+
if ("toJSON" in value && typeof value.toJSON === "function")
|
|
8874
|
+
throw new TypeError("JSON evidence cannot contain toJSON hooks");
|
|
8875
|
+
yield "{";
|
|
8876
|
+
let first = true;
|
|
8877
|
+
for (const key of Object.keys(value)) {
|
|
8878
|
+
const field = Reflect.get(value, key);
|
|
8879
|
+
if (field === void 0 || typeof field === "function" || typeof field === "symbol")
|
|
8880
|
+
continue;
|
|
8881
|
+
if (!first) yield ",";
|
|
8882
|
+
first = false;
|
|
8883
|
+
yield JSON.stringify(key) + ":";
|
|
8884
|
+
yield* jsonParts(field, ancestors);
|
|
8885
|
+
}
|
|
8886
|
+
yield "}";
|
|
8887
|
+
}
|
|
8888
|
+
} finally {
|
|
8889
|
+
ancestors.delete(value);
|
|
8890
|
+
}
|
|
8891
|
+
}
|
|
8892
|
+
var writeReplayJsonFile = async (file, value) => {
|
|
8893
|
+
await import_promises2.default.mkdir(import_node_path4.default.dirname(file), { recursive: true });
|
|
8894
|
+
const temporary = file + ".tmp-" + (0, import_node_crypto2.randomUUID)();
|
|
8895
|
+
const stream = import_node_fs2.default.createWriteStream(temporary, {
|
|
8896
|
+
flags: "wx",
|
|
8897
|
+
highWaterMark: 65536
|
|
8898
|
+
});
|
|
8899
|
+
let failure;
|
|
8900
|
+
stream.on("error", (error) => {
|
|
8901
|
+
failure = error;
|
|
8902
|
+
});
|
|
8903
|
+
try {
|
|
8904
|
+
let buffer = "";
|
|
8905
|
+
for (const part of jsonParts(value)) {
|
|
8906
|
+
buffer += part;
|
|
8907
|
+
if (buffer.length >= 65536) {
|
|
8908
|
+
if (failure) throw failure;
|
|
8909
|
+
if (!stream.write(buffer)) await (0, import_node_events.once)(stream, "drain");
|
|
8910
|
+
buffer = "";
|
|
8911
|
+
}
|
|
8912
|
+
}
|
|
8913
|
+
if (failure) throw failure;
|
|
8914
|
+
stream.end(buffer + "\n");
|
|
8915
|
+
await (0, import_node_events.once)(stream, "close");
|
|
8916
|
+
if (failure) throw failure;
|
|
8917
|
+
await import_promises2.default.rename(temporary, file);
|
|
8918
|
+
} catch (error) {
|
|
8919
|
+
stream.destroy();
|
|
8920
|
+
if (!stream.closed) await (0, import_node_events.once)(stream, "close").catch(() => void 0);
|
|
8921
|
+
await import_promises2.default.rm(temporary, { force: true });
|
|
8922
|
+
throw error;
|
|
8923
|
+
}
|
|
8924
|
+
};
|
|
8925
|
+
|
|
8926
|
+
// src/lib/replay/outputReport.ts
|
|
8803
8927
|
var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
|
|
8804
8928
|
timeZone: "Europe/Moscow",
|
|
8805
8929
|
dateStyle: "short",
|
|
@@ -9102,8 +9226,8 @@ var writeReplayOutputReport = async ({
|
|
|
9102
9226
|
strategySnapshot,
|
|
9103
9227
|
runtimeComparison
|
|
9104
9228
|
}) => {
|
|
9105
|
-
const outputDir =
|
|
9106
|
-
await
|
|
9229
|
+
const outputDir = import_node_path5.default.resolve(projectRoot, "data/replay/output");
|
|
9230
|
+
await import_promises3.default.mkdir(outputDir, { recursive: true });
|
|
9107
9231
|
const perTradeAnalysis = buildPerTradeAnalysis(runtimeComparison);
|
|
9108
9232
|
const report = {
|
|
9109
9233
|
reportType: "replay-output",
|
|
@@ -9138,8 +9262,8 @@ var writeReplayOutputReport = async ({
|
|
|
9138
9262
|
runtimeComparison,
|
|
9139
9263
|
perTradeAnalysis
|
|
9140
9264
|
};
|
|
9141
|
-
const markdownPath =
|
|
9142
|
-
const jsonPath =
|
|
9265
|
+
const markdownPath = import_node_path5.default.join(outputDir, `${timestamp}-replay-report.md`);
|
|
9266
|
+
const jsonPath = import_node_path5.default.join(outputDir, `${timestamp}-replay-report.json`);
|
|
9143
9267
|
const lines = [
|
|
9144
9268
|
"# Replay Report",
|
|
9145
9269
|
"",
|
|
@@ -9204,11 +9328,13 @@ var writeReplayOutputReport = async ({
|
|
|
9204
9328
|
"Full machine-readable details are stored in the JSON sidecar.",
|
|
9205
9329
|
""
|
|
9206
9330
|
];
|
|
9331
|
+
const memory = createReplayMemoryProfile();
|
|
9332
|
+
memory?.("report:write:start");
|
|
9207
9333
|
await Promise.all([
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
`, "utf8")
|
|
9334
|
+
import_promises3.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
|
|
9335
|
+
writeReplayJsonFile(jsonPath, report)
|
|
9211
9336
|
]);
|
|
9337
|
+
memory?.("report:write:complete");
|
|
9212
9338
|
return {
|
|
9213
9339
|
markdownPath,
|
|
9214
9340
|
jsonPath
|
|
@@ -9216,9 +9342,9 @@ var writeReplayOutputReport = async ({
|
|
|
9216
9342
|
};
|
|
9217
9343
|
|
|
9218
9344
|
// src/lib/replay/portfolioReport.ts
|
|
9219
|
-
var
|
|
9220
|
-
var
|
|
9221
|
-
var
|
|
9345
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
9346
|
+
var import_node_path6 = __toESM(require("path"));
|
|
9347
|
+
var import_node_crypto3 = require("crypto");
|
|
9222
9348
|
var DAY_MS4 = 864e5;
|
|
9223
9349
|
var WINDOWS = [365, 180, 90, 30, 7];
|
|
9224
9350
|
var COLORS = [
|
|
@@ -9396,8 +9522,8 @@ var writePortfolioReport = async ({
|
|
|
9396
9522
|
lineage,
|
|
9397
9523
|
command
|
|
9398
9524
|
}) => {
|
|
9399
|
-
const outputDir =
|
|
9400
|
-
await
|
|
9525
|
+
const outputDir = import_node_path6.default.resolve(projectRoot, "data/replay/output");
|
|
9526
|
+
await import_promises4.default.mkdir(outputDir, { recursive: true });
|
|
9401
9527
|
const trades = replayResult.strategies.flatMap(
|
|
9402
9528
|
(strategy) => tradesFromPositionLog(strategy.strategyName, strategy.positionLog)
|
|
9403
9529
|
);
|
|
@@ -9469,11 +9595,11 @@ var writePortfolioReport = async ({
|
|
|
9469
9595
|
});
|
|
9470
9596
|
const prefix = `${timestamp}-portfolio-backtest`;
|
|
9471
9597
|
const paths = {
|
|
9472
|
-
markdown:
|
|
9473
|
-
json:
|
|
9474
|
-
equitySvg:
|
|
9475
|
-
drawdownSvg:
|
|
9476
|
-
html:
|
|
9598
|
+
markdown: import_node_path6.default.join(outputDir, `${prefix}.md`),
|
|
9599
|
+
json: import_node_path6.default.join(outputDir, `${prefix}.json`),
|
|
9600
|
+
equitySvg: import_node_path6.default.join(outputDir, `${prefix}-equity.svg`),
|
|
9601
|
+
drawdownSvg: import_node_path6.default.join(outputDir, `${prefix}-drawdown.svg`),
|
|
9602
|
+
html: import_node_path6.default.join(outputDir, `${prefix}.html`)
|
|
9477
9603
|
};
|
|
9478
9604
|
const report = {
|
|
9479
9605
|
schema: "tradejs-portfolio-backtest/v1",
|
|
@@ -9499,7 +9625,7 @@ var writePortfolioReport = async ({
|
|
|
9499
9625
|
},
|
|
9500
9626
|
trades
|
|
9501
9627
|
};
|
|
9502
|
-
const checksum = (0,
|
|
9628
|
+
const checksum = (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(report)).digest("hex");
|
|
9503
9629
|
const semantics = "Production strategy order, at most one open position per symbol, and fixed-risk symbol batches merged by canonical realized PnL.";
|
|
9504
9630
|
const markdown = `# Portfolio Backtest
|
|
9505
9631
|
|
|
@@ -9511,9 +9637,9 @@ var writePortfolioReport = async ({
|
|
|
9511
9637
|
- Report checksum: \`${checksum}\`
|
|
9512
9638
|
- Command: \`${command}\`
|
|
9513
9639
|
|
|
9514
|
-
})
|
|
9515
9641
|
|
|
9516
|
-
})
|
|
9517
9643
|
|
|
9518
9644
|
## Portfolio metrics
|
|
9519
9645
|
|
|
@@ -9525,16 +9651,16 @@ ${markdownTable(strategyRows)}
|
|
|
9525
9651
|
`;
|
|
9526
9652
|
const html = `<!doctype html><html><head><meta charset="utf-8"><title>Portfolio Backtest</title><style>body{font-family:Inter,system-ui,sans-serif;margin:32px;color:#0f172a}h1,h2{margin-top:32px}.meta{color:#475569}table{border-collapse:collapse;width:100%;font-size:13px}th,td{padding:8px;border:1px solid #e2e8f0;text-align:right}th:nth-child(-n+3),td:nth-child(-n+3){text-align:left}.chart{overflow:auto;border:1px solid #e2e8f0;margin:16px 0}</style></head><body><h1>Portfolio Backtest</h1><p class="meta">${escapeHtml(report.window.startIso)} .. ${escapeHtml(report.window.endIso)} \xB7 ${report.counts.strategies} strategies \xB7 ${trades.length} completed trades \xB7 MAX_LOSS_VALUE=${escapeHtml(String(lineage.maxLossValue))}</p><p class="meta">${escapeHtml(semantics)}</p><div class="chart">${equitySvg}</div><div class="chart">${drawdownSvg}</div><h2>Portfolio metrics</h2>${metricTable(portfolioRows)}<h2>Strategy metrics</h2>${metricTable(strategyRows)}<h2>Lineage</h2><pre>${escapeHtml(JSON.stringify(lineage, null, 2))}</pre></body></html>`;
|
|
9527
9653
|
await Promise.all([
|
|
9528
|
-
|
|
9529
|
-
|
|
9654
|
+
import_promises4.default.writeFile(paths.markdown, markdown, "utf8"),
|
|
9655
|
+
import_promises4.default.writeFile(
|
|
9530
9656
|
paths.json,
|
|
9531
9657
|
`${JSON.stringify({ ...report, checksum }, null, 2)}
|
|
9532
9658
|
`,
|
|
9533
9659
|
"utf8"
|
|
9534
9660
|
),
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9661
|
+
import_promises4.default.writeFile(paths.equitySvg, equitySvg, "utf8"),
|
|
9662
|
+
import_promises4.default.writeFile(paths.drawdownSvg, drawdownSvg, "utf8"),
|
|
9663
|
+
import_promises4.default.writeFile(paths.html, html, "utf8")
|
|
9538
9664
|
]);
|
|
9539
9665
|
return { ...paths, checksum };
|
|
9540
9666
|
};
|
|
@@ -9848,13 +9974,13 @@ var replayBacktest = async () => {
|
|
|
9848
9974
|
deploymentId: deployment.id,
|
|
9849
9975
|
assetClasses: deployment.assetClasses,
|
|
9850
9976
|
deployment,
|
|
9851
|
-
closedIntervalMs: (0,
|
|
9977
|
+
closedIntervalMs: (0, import_data3.intervalToMs)(replayInterval)
|
|
9852
9978
|
});
|
|
9853
9979
|
if (!preparedRun || isReplayUpdateOnlyRun) {
|
|
9854
9980
|
return;
|
|
9855
9981
|
}
|
|
9856
9982
|
if (useAllData) {
|
|
9857
|
-
const intervalMs = (0,
|
|
9983
|
+
const intervalMs = (0, import_data3.intervalToMs)(replayInterval);
|
|
9858
9984
|
const edges = await (0, import_candles.getDataEdgesForSymbols)(
|
|
9859
9985
|
deployment.provider,
|
|
9860
9986
|
preparedRun.tickers,
|
|
@@ -35,8 +35,109 @@ __export(replayRuntimeEvidence_exports, {
|
|
|
35
35
|
summarizeReplayComparison: () => summarizeReplayComparison
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(replayRuntimeEvidence_exports);
|
|
38
|
+
|
|
39
|
+
// src/lib/replay/jsonFile.ts
|
|
40
|
+
var import_node_fs = __toESM(require("fs"));
|
|
38
41
|
var import_promises = __toESM(require("fs/promises"));
|
|
39
42
|
var import_node_path = __toESM(require("path"));
|
|
43
|
+
var import_node_events = require("events");
|
|
44
|
+
var import_node_crypto = require("crypto");
|
|
45
|
+
function* jsonParts(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
46
|
+
if (value === null || typeof value !== "object") {
|
|
47
|
+
yield JSON.stringify(value) ?? "null";
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (ancestors.has(value)) throw new TypeError("Circular JSON evidence");
|
|
51
|
+
ancestors.add(value);
|
|
52
|
+
try {
|
|
53
|
+
if (Array.isArray(value)) {
|
|
54
|
+
yield "[";
|
|
55
|
+
for (let index = 0; index < value.length; index++) {
|
|
56
|
+
if (index) yield ",";
|
|
57
|
+
yield* jsonParts(value[index], ancestors);
|
|
58
|
+
}
|
|
59
|
+
yield "]";
|
|
60
|
+
} else {
|
|
61
|
+
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
|
|
62
|
+
throw new TypeError("JSON evidence requires plain records");
|
|
63
|
+
if ("toJSON" in value && typeof value.toJSON === "function")
|
|
64
|
+
throw new TypeError("JSON evidence cannot contain toJSON hooks");
|
|
65
|
+
yield "{";
|
|
66
|
+
let first = true;
|
|
67
|
+
for (const key of Object.keys(value)) {
|
|
68
|
+
const field = Reflect.get(value, key);
|
|
69
|
+
if (field === void 0 || typeof field === "function" || typeof field === "symbol")
|
|
70
|
+
continue;
|
|
71
|
+
if (!first) yield ",";
|
|
72
|
+
first = false;
|
|
73
|
+
yield JSON.stringify(key) + ":";
|
|
74
|
+
yield* jsonParts(field, ancestors);
|
|
75
|
+
}
|
|
76
|
+
yield "}";
|
|
77
|
+
}
|
|
78
|
+
} finally {
|
|
79
|
+
ancestors.delete(value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
var writeReplayJsonFile = async (file, value) => {
|
|
83
|
+
await import_promises.default.mkdir(import_node_path.default.dirname(file), { recursive: true });
|
|
84
|
+
const temporary = file + ".tmp-" + (0, import_node_crypto.randomUUID)();
|
|
85
|
+
const stream = import_node_fs.default.createWriteStream(temporary, {
|
|
86
|
+
flags: "wx",
|
|
87
|
+
highWaterMark: 65536
|
|
88
|
+
});
|
|
89
|
+
let failure;
|
|
90
|
+
stream.on("error", (error) => {
|
|
91
|
+
failure = error;
|
|
92
|
+
});
|
|
93
|
+
try {
|
|
94
|
+
let buffer = "";
|
|
95
|
+
for (const part of jsonParts(value)) {
|
|
96
|
+
buffer += part;
|
|
97
|
+
if (buffer.length >= 65536) {
|
|
98
|
+
if (failure) throw failure;
|
|
99
|
+
if (!stream.write(buffer)) await (0, import_node_events.once)(stream, "drain");
|
|
100
|
+
buffer = "";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (failure) throw failure;
|
|
104
|
+
stream.end(buffer + "\n");
|
|
105
|
+
await (0, import_node_events.once)(stream, "close");
|
|
106
|
+
if (failure) throw failure;
|
|
107
|
+
await import_promises.default.rename(temporary, file);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
stream.destroy();
|
|
110
|
+
if (!stream.closed) await (0, import_node_events.once)(stream, "close").catch(() => void 0);
|
|
111
|
+
await import_promises.default.rm(temporary, { force: true });
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/lib/replay/memoryProfile.ts
|
|
117
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
118
|
+
var import_node_path2 = __toESM(require("path"));
|
|
119
|
+
var createReplayMemoryProfile = (file = process.env.TRADEJS_REPLAY_MEMORY_PROFILE) => {
|
|
120
|
+
if (!file) return void 0;
|
|
121
|
+
import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(file), { recursive: true });
|
|
122
|
+
return (phase, counts = {}) => {
|
|
123
|
+
import_node_fs2.default.appendFileSync(
|
|
124
|
+
file,
|
|
125
|
+
JSON.stringify({
|
|
126
|
+
schemaVersion: 1,
|
|
127
|
+
observedAt: Date.now(),
|
|
128
|
+
pid: process.pid,
|
|
129
|
+
phase,
|
|
130
|
+
counts,
|
|
131
|
+
...process.memoryUsage(),
|
|
132
|
+
maxRssKiB: process.resourceUsage().maxRSS
|
|
133
|
+
}) + "\n"
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/scripts/replayRuntimeEvidence.ts
|
|
139
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
140
|
+
var import_node_path3 = __toESM(require("path"));
|
|
40
141
|
var import_args = __toESM(require("args"));
|
|
41
142
|
var import_chalk2 = __toESM(require("chalk"));
|
|
42
143
|
var import_redis4 = require("@tradejs/infra/redis");
|
|
@@ -659,10 +760,10 @@ var loadRuntimePayload = async ({
|
|
|
659
760
|
runtimeEvidenceFile,
|
|
660
761
|
projectRoot
|
|
661
762
|
}) => {
|
|
662
|
-
const runtimeEvidencePath = typeof runtimeEvidenceFile === "string" && runtimeEvidenceFile.trim() ?
|
|
763
|
+
const runtimeEvidencePath = typeof runtimeEvidenceFile === "string" && runtimeEvidenceFile.trim() ? import_node_path3.default.resolve(projectRoot, runtimeEvidenceFile.trim()) : null;
|
|
663
764
|
if (runtimeEvidencePath) {
|
|
664
765
|
const artifact = JSON.parse(
|
|
665
|
-
await
|
|
766
|
+
await import_promises2.default.readFile(runtimeEvidencePath, "utf8")
|
|
666
767
|
);
|
|
667
768
|
return {
|
|
668
769
|
source: "file",
|
|
@@ -748,10 +849,12 @@ var replayRuntimeEvidence = async () => {
|
|
|
748
849
|
},
|
|
749
850
|
runtime: runtime.payload
|
|
750
851
|
};
|
|
751
|
-
const outPath =
|
|
752
|
-
await
|
|
753
|
-
|
|
754
|
-
|
|
852
|
+
const outPath = import_node_path3.default.resolve(projectRoot, String(flags.out));
|
|
853
|
+
await import_promises2.default.mkdir(import_node_path3.default.dirname(outPath), { recursive: true });
|
|
854
|
+
const memory = createReplayMemoryProfile();
|
|
855
|
+
memory?.("evidence:write:start");
|
|
856
|
+
await writeReplayJsonFile(outPath, artifact);
|
|
857
|
+
memory?.("evidence:write:complete");
|
|
755
858
|
console.log(import_chalk2.default.green(`Wrote ${outPath}`));
|
|
756
859
|
console.log(
|
|
757
860
|
JSON.stringify(
|