@tradejs/cli 2.0.19 → 2.0.21
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 +8783 -3214
- package/dist/lib/aiExport.js +14 -1
- package/dist/lib/aiPocketSearch.js +499 -67
- package/dist/lib/aiPocketSearchCli.js +113 -0
- package/dist/lib/aiTrainCharts.js +14 -1
- package/dist/lib/aiTrainMetrics.js +4 -1
- package/dist/lib/aiTrainResearch.js +88 -54
- package/dist/lib/coreResearch.js +2738 -0
- package/dist/lib/executionCalibration.js +6 -3
- package/dist/lib/runtimeLineage.js +69 -48
- package/dist/lib/runtimeScorecard.js +153 -10
- package/dist/lib/strategyGateFingerprint.js +80 -0
- package/dist/lib/strategyRelease.js +1373 -0
- package/dist/scripts/aiExport.js +16 -3
- package/dist/scripts/aiPocketSearch.js +1069 -163
- package/dist/scripts/aiTrain.js +91 -65
- package/dist/scripts/backtest.js +10 -1
- package/dist/scripts/coreResearch.js +2830 -0
- package/dist/scripts/executionCalibration.js +6 -3
- package/dist/scripts/replay.js +94 -73
- package/dist/scripts/replayRunner.js +94 -73
- package/dist/scripts/replayRuntimeEvidence.js +27 -14
- package/dist/scripts/runtimeEvidence.js +17 -8
- package/dist/scripts/runtimeScorecard.js +883 -29
- package/dist/scripts/signals.js +121 -89
- package/dist/scripts/signalsDaemon.js +121 -89
- package/dist/scripts/strategyRelease.js +1477 -0
- package/package.json +11 -9
|
@@ -169,7 +169,8 @@ var extractRuntimeTradeRows = (runtimePayload) => {
|
|
|
169
169
|
const signal = asRecord(redisValues?.signal) ?? (signalId ? signalsById.get(signalId) ?? null : null) ?? (shapeKey ? signalsByShape.get(shapeKey) ?? null : null);
|
|
170
170
|
return {
|
|
171
171
|
trade,
|
|
172
|
-
signal
|
|
172
|
+
signal,
|
|
173
|
+
runtimeLineage: asRecord(trade.runtimeLineage) ?? null
|
|
173
174
|
};
|
|
174
175
|
}).filter((row) => row != null);
|
|
175
176
|
};
|
|
@@ -471,7 +472,8 @@ var buildSample = ({
|
|
|
471
472
|
direction: direction ?? match?.direction ?? null,
|
|
472
473
|
expectedPrice: match?.backtestPrice ?? null,
|
|
473
474
|
actualPrice: match?.runtimePrice ?? null
|
|
474
|
-
})
|
|
475
|
+
}),
|
|
476
|
+
runtimeLineage: row.runtimeLineage
|
|
475
477
|
};
|
|
476
478
|
};
|
|
477
479
|
var buildReplayOnlySample = (match) => ({
|
|
@@ -518,7 +520,8 @@ var buildReplayOnlySample = (match) => ({
|
|
|
518
520
|
direction: match.direction,
|
|
519
521
|
expectedPrice: match.backtestPrice,
|
|
520
522
|
actualPrice: match.runtimePrice
|
|
521
|
-
})
|
|
523
|
+
}),
|
|
524
|
+
runtimeLineage: null
|
|
522
525
|
});
|
|
523
526
|
var hasTelemetry = (sample) => sample.signalClosePrice != null || sample.arrivalMid != null || sample.orderSubmitTime != null || sample.fillAvgPrice != null || sample.fillTime != null || sample.fee != null;
|
|
524
527
|
var hasFullTelemetry = (sample) => sample.signalClosePrice != null && sample.arrivalMid != null && sample.orderSubmitTime != null && sample.orderAckTime != null && sample.fillAvgPrice != null && sample.fillTime != null;
|
package/dist/scripts/replay.js
CHANGED
|
@@ -2700,27 +2700,27 @@ var createCoinMarketCapFetchStats = () => ({
|
|
|
2700
2700
|
totalCredits: 0,
|
|
2701
2701
|
byPath: /* @__PURE__ */ new Map()
|
|
2702
2702
|
});
|
|
2703
|
-
var getCoinMarketCapPathLabel = (
|
|
2704
|
-
if (
|
|
2705
|
-
if (
|
|
2706
|
-
if (
|
|
2707
|
-
if (
|
|
2708
|
-
if (
|
|
2709
|
-
if (
|
|
2710
|
-
return
|
|
2711
|
-
};
|
|
2712
|
-
var recordCoinMarketCapFetchStats = (stats,
|
|
2703
|
+
var getCoinMarketCapPathLabel = (path5) => {
|
|
2704
|
+
if (path5.includes("global-metrics")) return "global";
|
|
2705
|
+
if (path5.includes("cryptocurrency/quotes")) return "reference";
|
|
2706
|
+
if (path5.includes("exchange/quotes")) return "exchangeLiquidity";
|
|
2707
|
+
if (path5.includes("fear-and-greed")) return "fearGreed";
|
|
2708
|
+
if (path5.includes("cmc100")) return "cmc100";
|
|
2709
|
+
if (path5.includes("cmc20")) return "cmc20";
|
|
2710
|
+
return path5;
|
|
2711
|
+
};
|
|
2712
|
+
var recordCoinMarketCapFetchStats = (stats, path5, creditCount) => {
|
|
2713
2713
|
if (!stats) return;
|
|
2714
|
-
const existing = stats.byPath.get(
|
|
2714
|
+
const existing = stats.byPath.get(path5) ?? { requests: 0, credits: 0 };
|
|
2715
2715
|
existing.requests += 1;
|
|
2716
2716
|
existing.credits += creditCount ?? 0;
|
|
2717
2717
|
stats.requests += 1;
|
|
2718
2718
|
stats.totalCredits += creditCount ?? 0;
|
|
2719
|
-
stats.byPath.set(
|
|
2719
|
+
stats.byPath.set(path5, existing);
|
|
2720
2720
|
};
|
|
2721
2721
|
var formatCoinMarketCapFetchStats = (stats) => {
|
|
2722
2722
|
const byPath = [...stats.byPath.entries()].map(
|
|
2723
|
-
([
|
|
2723
|
+
([path5, item]) => `${getCoinMarketCapPathLabel(path5)}=${item.requests}req/${item.credits}cr`
|
|
2724
2724
|
).join(" ");
|
|
2725
2725
|
return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
|
|
2726
2726
|
};
|
|
@@ -4901,11 +4901,57 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
|
|
|
4901
4901
|
|
|
4902
4902
|
// src/lib/runtimeLineage.ts
|
|
4903
4903
|
var import_node_child_process = require("child_process");
|
|
4904
|
+
var import_node_crypto2 = require("crypto");
|
|
4905
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
4906
|
+
var import_node_path2 = __toESM(require("path"));
|
|
4907
|
+
var import_types2 = require("@tradejs/types");
|
|
4908
|
+
var import_strategies3 = require("@tradejs/node/strategies");
|
|
4909
|
+
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
4910
|
+
|
|
4911
|
+
// src/lib/strategyGateFingerprint.ts
|
|
4904
4912
|
var import_node_crypto = require("crypto");
|
|
4905
4913
|
var import_promises = __toESM(require("fs/promises"));
|
|
4906
4914
|
var import_node_path = __toESM(require("path"));
|
|
4907
|
-
var
|
|
4908
|
-
|
|
4915
|
+
var gateFingerprintRelativePaths = (strategyName) => [
|
|
4916
|
+
`packages/strategies/src/${strategyName}/adapters/ai.ts`,
|
|
4917
|
+
`packages/strategies/src/${strategyName}/guardrails.ts`,
|
|
4918
|
+
`packages/strategies/src/${strategyName}/pockets.ts`,
|
|
4919
|
+
`packages/strategies/src/${strategyName}/config.ts`,
|
|
4920
|
+
"packages/strategies/src/shared/localAiGate.ts",
|
|
4921
|
+
"packages/node/src/ai.ts",
|
|
4922
|
+
"packages/core/src/utils/strategyHelpers/signalBuilders.ts"
|
|
4923
|
+
];
|
|
4924
|
+
var resolveStrategyGateFingerprint = async ({
|
|
4925
|
+
projectRoot,
|
|
4926
|
+
strategyName,
|
|
4927
|
+
gitSha
|
|
4928
|
+
}) => {
|
|
4929
|
+
const candidates = await Promise.all(
|
|
4930
|
+
gateFingerprintRelativePaths(strategyName).map(async (relativePath) => {
|
|
4931
|
+
const content = await import_promises.default.readFile(import_node_path.default.join(projectRoot, relativePath)).catch(() => null);
|
|
4932
|
+
return content == null ? null : { relativePath, content };
|
|
4933
|
+
})
|
|
4934
|
+
);
|
|
4935
|
+
const sourceEntries = candidates.filter(
|
|
4936
|
+
(entry) => entry != null
|
|
4937
|
+
);
|
|
4938
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
4939
|
+
hash.update(strategyName);
|
|
4940
|
+
if (sourceEntries.length) {
|
|
4941
|
+
for (const entry of sourceEntries) {
|
|
4942
|
+
hash.update(entry.relativePath);
|
|
4943
|
+
hash.update(entry.content);
|
|
4944
|
+
}
|
|
4945
|
+
} else {
|
|
4946
|
+
hash.update(gitSha ?? "unknown-git-sha");
|
|
4947
|
+
}
|
|
4948
|
+
return {
|
|
4949
|
+
gateFingerprint: hash.digest("hex").slice(0, 16),
|
|
4950
|
+
gateFingerprintFiles: sourceEntries.map((entry) => entry.relativePath)
|
|
4951
|
+
};
|
|
4952
|
+
};
|
|
4953
|
+
|
|
4954
|
+
// src/lib/runtimeLineage.ts
|
|
4909
4955
|
var RUNTIME_CONTEXT_ENV_KEYS = [
|
|
4910
4956
|
"AI_MODE",
|
|
4911
4957
|
"MIN_AI_QUALITY",
|
|
@@ -4968,7 +5014,7 @@ var resolveRuntimeMaxLossValue = (config) => {
|
|
|
4968
5014
|
);
|
|
4969
5015
|
return Number.isFinite(maxLossValue) ? maxLossValue : null;
|
|
4970
5016
|
};
|
|
4971
|
-
var fingerprintRuntimeValue = (value) => (0,
|
|
5017
|
+
var fingerprintRuntimeValue = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
|
|
4972
5018
|
var gitLineageCache = /* @__PURE__ */ new Map();
|
|
4973
5019
|
var gateFingerprintCache = /* @__PURE__ */ new Map();
|
|
4974
5020
|
var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
|
|
@@ -5019,7 +5065,7 @@ var resolveGitLineage = (projectRoot) => {
|
|
|
5019
5065
|
};
|
|
5020
5066
|
var readOptionalFile = async (filePath) => {
|
|
5021
5067
|
try {
|
|
5022
|
-
return await
|
|
5068
|
+
return await import_promises2.default.readFile(filePath);
|
|
5023
5069
|
} catch {
|
|
5024
5070
|
return null;
|
|
5025
5071
|
}
|
|
@@ -5029,7 +5075,7 @@ var resolveBinanceBreadthFingerprint = async (projectRoot) => {
|
|
|
5029
5075
|
return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
|
|
5030
5076
|
}
|
|
5031
5077
|
const content = await readOptionalFile(
|
|
5032
|
-
|
|
5078
|
+
import_node_path2.default.join(
|
|
5033
5079
|
projectRoot,
|
|
5034
5080
|
"packages/node/src/config/binanceBreadthUniverses.json"
|
|
5035
5081
|
)
|
|
@@ -5051,7 +5097,7 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
|
5051
5097
|
if (snapshotFingerprintCache.has(cacheKey)) {
|
|
5052
5098
|
return snapshotFingerprintCache.get(cacheKey) ?? null;
|
|
5053
5099
|
}
|
|
5054
|
-
const content = await readOptionalFile(
|
|
5100
|
+
const content = await readOptionalFile(import_node_path2.default.join(projectRoot, relativePath));
|
|
5055
5101
|
let result = null;
|
|
5056
5102
|
if (content) {
|
|
5057
5103
|
try {
|
|
@@ -5064,55 +5110,26 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
|
5064
5110
|
snapshotFingerprintCache.set(cacheKey, result);
|
|
5065
5111
|
return result;
|
|
5066
5112
|
};
|
|
5067
|
-
var
|
|
5113
|
+
var resolveRuntimeGateFingerprint = async ({
|
|
5068
5114
|
projectRoot,
|
|
5069
5115
|
strategyName,
|
|
5070
5116
|
gitSha
|
|
5071
5117
|
}) => {
|
|
5072
5118
|
const cacheKey = `${projectRoot}:${strategyName}:${gitSha ?? "unknown"}`;
|
|
5073
5119
|
const cached = gateFingerprintCache.get(cacheKey);
|
|
5074
|
-
if (cached)
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
"packages/node/src/ai.ts",
|
|
5083
|
-
"packages/core/src/utils/strategyHelpers/signalBuilders.ts"
|
|
5084
|
-
];
|
|
5085
|
-
const optionalEntries = await Promise.all(
|
|
5086
|
-
relativeCandidates.map(async (relativePath) => {
|
|
5087
|
-
const content = await readOptionalFile(
|
|
5088
|
-
import_node_path.default.join(projectRoot, relativePath)
|
|
5089
|
-
);
|
|
5090
|
-
return content == null ? null : { relativePath, content };
|
|
5091
|
-
})
|
|
5092
|
-
);
|
|
5093
|
-
const sourceEntries = [];
|
|
5094
|
-
for (const entry of optionalEntries) {
|
|
5095
|
-
if (entry != null) {
|
|
5096
|
-
sourceEntries.push(entry);
|
|
5097
|
-
}
|
|
5098
|
-
}
|
|
5099
|
-
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
5100
|
-
hash.update(strategyName);
|
|
5101
|
-
if (sourceEntries.length) {
|
|
5102
|
-
for (const entry of sourceEntries) {
|
|
5103
|
-
hash.update(entry.relativePath);
|
|
5104
|
-
hash.update(entry.content);
|
|
5105
|
-
}
|
|
5106
|
-
} else {
|
|
5107
|
-
hash.update(gitSha ?? "unknown-git-sha");
|
|
5108
|
-
}
|
|
5109
|
-
const fingerprint = hash.digest("hex").slice(0, 16);
|
|
5110
|
-
gateFingerprintCache.set(cacheKey, fingerprint);
|
|
5111
|
-
return fingerprint;
|
|
5120
|
+
if (cached) return cached;
|
|
5121
|
+
const result = await resolveStrategyGateFingerprint({
|
|
5122
|
+
projectRoot,
|
|
5123
|
+
strategyName,
|
|
5124
|
+
gitSha
|
|
5125
|
+
});
|
|
5126
|
+
gateFingerprintCache.set(cacheKey, result);
|
|
5127
|
+
return result;
|
|
5112
5128
|
};
|
|
5113
5129
|
var buildRuntimeLineage = async ({
|
|
5114
5130
|
projectRoot,
|
|
5115
5131
|
strategyName,
|
|
5132
|
+
compositionId,
|
|
5116
5133
|
config,
|
|
5117
5134
|
runContext,
|
|
5118
5135
|
env = process.env
|
|
@@ -5138,19 +5155,23 @@ var buildRuntimeLineage = async ({
|
|
|
5138
5155
|
};
|
|
5139
5156
|
return {
|
|
5140
5157
|
schemaVersion: 1,
|
|
5158
|
+
compositionId: compositionId?.trim() || null,
|
|
5141
5159
|
...git,
|
|
5142
|
-
gateFingerprint: await
|
|
5160
|
+
gateFingerprint: (await resolveRuntimeGateFingerprint({
|
|
5143
5161
|
projectRoot,
|
|
5144
5162
|
strategyName,
|
|
5145
5163
|
gitSha: git.gitSha
|
|
5146
|
-
}),
|
|
5147
|
-
|
|
5164
|
+
})).gateFingerprint,
|
|
5165
|
+
// Position risk is tracked separately so changing trade size does not
|
|
5166
|
+
// masquerade as a change to the core + gate decision logic.
|
|
5167
|
+
configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config),
|
|
5148
5168
|
contextFingerprint: fingerprintRuntimeValue(context),
|
|
5149
5169
|
maxLossValue: resolveRuntimeMaxLossValue(config)
|
|
5150
5170
|
};
|
|
5151
5171
|
};
|
|
5152
5172
|
var runtimeLineageKey = (lineage) => [
|
|
5153
5173
|
lineage.schemaVersion,
|
|
5174
|
+
lineage.compositionId ?? "unbound-composition",
|
|
5154
5175
|
lineage.gitSha ?? "unknown",
|
|
5155
5176
|
lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
|
|
5156
5177
|
lineage.gateFingerprint,
|
|
@@ -7163,8 +7184,8 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
7163
7184
|
};
|
|
7164
7185
|
|
|
7165
7186
|
// src/lib/replay/runtimeEvidenceSource.ts
|
|
7166
|
-
var
|
|
7167
|
-
var
|
|
7187
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
7188
|
+
var import_node_path3 = __toESM(require("path"));
|
|
7168
7189
|
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
7169
7190
|
var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
7170
7191
|
var unwrapRows = (value, field) => (Array.isArray(value) ? value : []).map((item) => {
|
|
@@ -7225,9 +7246,9 @@ var loadReplayRuntimeEvidenceSource = async ({
|
|
|
7225
7246
|
expectedUserName,
|
|
7226
7247
|
expectedWindow
|
|
7227
7248
|
}) => {
|
|
7228
|
-
const resolvedPath =
|
|
7249
|
+
const resolvedPath = import_node_path3.default.resolve(projectRoot, filePath);
|
|
7229
7250
|
const root = asRecord(
|
|
7230
|
-
JSON.parse(await
|
|
7251
|
+
JSON.parse(await import_promises3.default.readFile(resolvedPath, "utf8"))
|
|
7231
7252
|
);
|
|
7232
7253
|
const runtime = asRecord(root?.runtime) ?? root;
|
|
7233
7254
|
if (!root || !runtime) {
|
|
@@ -8274,8 +8295,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8274
8295
|
};
|
|
8275
8296
|
|
|
8276
8297
|
// src/lib/replay/outputReport.ts
|
|
8277
|
-
var
|
|
8278
|
-
var
|
|
8298
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
8299
|
+
var import_node_path4 = __toESM(require("path"));
|
|
8279
8300
|
var import_data = require("@tradejs/core/data");
|
|
8280
8301
|
var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
|
|
8281
8302
|
timeZone: "Europe/Moscow",
|
|
@@ -8579,8 +8600,8 @@ var writeReplayOutputReport = async ({
|
|
|
8579
8600
|
strategySnapshot,
|
|
8580
8601
|
runtimeComparison
|
|
8581
8602
|
}) => {
|
|
8582
|
-
const outputDir =
|
|
8583
|
-
await
|
|
8603
|
+
const outputDir = import_node_path4.default.resolve(projectRoot, "data/replay/output");
|
|
8604
|
+
await import_promises4.default.mkdir(outputDir, { recursive: true });
|
|
8584
8605
|
const perTradeAnalysis = buildPerTradeAnalysis(runtimeComparison);
|
|
8585
8606
|
const report = {
|
|
8586
8607
|
reportType: "replay-output",
|
|
@@ -8615,8 +8636,8 @@ var writeReplayOutputReport = async ({
|
|
|
8615
8636
|
runtimeComparison,
|
|
8616
8637
|
perTradeAnalysis
|
|
8617
8638
|
};
|
|
8618
|
-
const markdownPath =
|
|
8619
|
-
const jsonPath =
|
|
8639
|
+
const markdownPath = import_node_path4.default.join(outputDir, `${timestamp}-replay-report.md`);
|
|
8640
|
+
const jsonPath = import_node_path4.default.join(outputDir, `${timestamp}-replay-report.json`);
|
|
8620
8641
|
const lines = [
|
|
8621
8642
|
"# Replay Report",
|
|
8622
8643
|
"",
|
|
@@ -8682,8 +8703,8 @@ var writeReplayOutputReport = async ({
|
|
|
8682
8703
|
""
|
|
8683
8704
|
];
|
|
8684
8705
|
await Promise.all([
|
|
8685
|
-
|
|
8686
|
-
|
|
8706
|
+
import_promises4.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
|
|
8707
|
+
import_promises4.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
|
|
8687
8708
|
`, "utf8")
|
|
8688
8709
|
]);
|
|
8689
8710
|
return {
|
|
@@ -2703,27 +2703,27 @@ var createCoinMarketCapFetchStats = () => ({
|
|
|
2703
2703
|
totalCredits: 0,
|
|
2704
2704
|
byPath: /* @__PURE__ */ new Map()
|
|
2705
2705
|
});
|
|
2706
|
-
var getCoinMarketCapPathLabel = (
|
|
2707
|
-
if (
|
|
2708
|
-
if (
|
|
2709
|
-
if (
|
|
2710
|
-
if (
|
|
2711
|
-
if (
|
|
2712
|
-
if (
|
|
2713
|
-
return
|
|
2714
|
-
};
|
|
2715
|
-
var recordCoinMarketCapFetchStats = (stats,
|
|
2706
|
+
var getCoinMarketCapPathLabel = (path5) => {
|
|
2707
|
+
if (path5.includes("global-metrics")) return "global";
|
|
2708
|
+
if (path5.includes("cryptocurrency/quotes")) return "reference";
|
|
2709
|
+
if (path5.includes("exchange/quotes")) return "exchangeLiquidity";
|
|
2710
|
+
if (path5.includes("fear-and-greed")) return "fearGreed";
|
|
2711
|
+
if (path5.includes("cmc100")) return "cmc100";
|
|
2712
|
+
if (path5.includes("cmc20")) return "cmc20";
|
|
2713
|
+
return path5;
|
|
2714
|
+
};
|
|
2715
|
+
var recordCoinMarketCapFetchStats = (stats, path5, creditCount) => {
|
|
2716
2716
|
if (!stats) return;
|
|
2717
|
-
const existing = stats.byPath.get(
|
|
2717
|
+
const existing = stats.byPath.get(path5) ?? { requests: 0, credits: 0 };
|
|
2718
2718
|
existing.requests += 1;
|
|
2719
2719
|
existing.credits += creditCount ?? 0;
|
|
2720
2720
|
stats.requests += 1;
|
|
2721
2721
|
stats.totalCredits += creditCount ?? 0;
|
|
2722
|
-
stats.byPath.set(
|
|
2722
|
+
stats.byPath.set(path5, existing);
|
|
2723
2723
|
};
|
|
2724
2724
|
var formatCoinMarketCapFetchStats = (stats) => {
|
|
2725
2725
|
const byPath = [...stats.byPath.entries()].map(
|
|
2726
|
-
([
|
|
2726
|
+
([path5, item]) => `${getCoinMarketCapPathLabel(path5)}=${item.requests}req/${item.credits}cr`
|
|
2727
2727
|
).join(" ");
|
|
2728
2728
|
return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
|
|
2729
2729
|
};
|
|
@@ -4904,11 +4904,57 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
|
|
|
4904
4904
|
|
|
4905
4905
|
// src/lib/runtimeLineage.ts
|
|
4906
4906
|
var import_node_child_process = require("child_process");
|
|
4907
|
+
var import_node_crypto2 = require("crypto");
|
|
4908
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
4909
|
+
var import_node_path2 = __toESM(require("path"));
|
|
4910
|
+
var import_types2 = require("@tradejs/types");
|
|
4911
|
+
var import_strategies3 = require("@tradejs/node/strategies");
|
|
4912
|
+
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
4913
|
+
|
|
4914
|
+
// src/lib/strategyGateFingerprint.ts
|
|
4907
4915
|
var import_node_crypto = require("crypto");
|
|
4908
4916
|
var import_promises = __toESM(require("fs/promises"));
|
|
4909
4917
|
var import_node_path = __toESM(require("path"));
|
|
4910
|
-
var
|
|
4911
|
-
|
|
4918
|
+
var gateFingerprintRelativePaths = (strategyName) => [
|
|
4919
|
+
`packages/strategies/src/${strategyName}/adapters/ai.ts`,
|
|
4920
|
+
`packages/strategies/src/${strategyName}/guardrails.ts`,
|
|
4921
|
+
`packages/strategies/src/${strategyName}/pockets.ts`,
|
|
4922
|
+
`packages/strategies/src/${strategyName}/config.ts`,
|
|
4923
|
+
"packages/strategies/src/shared/localAiGate.ts",
|
|
4924
|
+
"packages/node/src/ai.ts",
|
|
4925
|
+
"packages/core/src/utils/strategyHelpers/signalBuilders.ts"
|
|
4926
|
+
];
|
|
4927
|
+
var resolveStrategyGateFingerprint = async ({
|
|
4928
|
+
projectRoot,
|
|
4929
|
+
strategyName,
|
|
4930
|
+
gitSha
|
|
4931
|
+
}) => {
|
|
4932
|
+
const candidates = await Promise.all(
|
|
4933
|
+
gateFingerprintRelativePaths(strategyName).map(async (relativePath) => {
|
|
4934
|
+
const content = await import_promises.default.readFile(import_node_path.default.join(projectRoot, relativePath)).catch(() => null);
|
|
4935
|
+
return content == null ? null : { relativePath, content };
|
|
4936
|
+
})
|
|
4937
|
+
);
|
|
4938
|
+
const sourceEntries = candidates.filter(
|
|
4939
|
+
(entry) => entry != null
|
|
4940
|
+
);
|
|
4941
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
4942
|
+
hash.update(strategyName);
|
|
4943
|
+
if (sourceEntries.length) {
|
|
4944
|
+
for (const entry of sourceEntries) {
|
|
4945
|
+
hash.update(entry.relativePath);
|
|
4946
|
+
hash.update(entry.content);
|
|
4947
|
+
}
|
|
4948
|
+
} else {
|
|
4949
|
+
hash.update(gitSha ?? "unknown-git-sha");
|
|
4950
|
+
}
|
|
4951
|
+
return {
|
|
4952
|
+
gateFingerprint: hash.digest("hex").slice(0, 16),
|
|
4953
|
+
gateFingerprintFiles: sourceEntries.map((entry) => entry.relativePath)
|
|
4954
|
+
};
|
|
4955
|
+
};
|
|
4956
|
+
|
|
4957
|
+
// src/lib/runtimeLineage.ts
|
|
4912
4958
|
var RUNTIME_CONTEXT_ENV_KEYS = [
|
|
4913
4959
|
"AI_MODE",
|
|
4914
4960
|
"MIN_AI_QUALITY",
|
|
@@ -4971,7 +5017,7 @@ var resolveRuntimeMaxLossValue = (config) => {
|
|
|
4971
5017
|
);
|
|
4972
5018
|
return Number.isFinite(maxLossValue) ? maxLossValue : null;
|
|
4973
5019
|
};
|
|
4974
|
-
var fingerprintRuntimeValue = (value) => (0,
|
|
5020
|
+
var fingerprintRuntimeValue = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
|
|
4975
5021
|
var gitLineageCache = /* @__PURE__ */ new Map();
|
|
4976
5022
|
var gateFingerprintCache = /* @__PURE__ */ new Map();
|
|
4977
5023
|
var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
|
|
@@ -5022,7 +5068,7 @@ var resolveGitLineage = (projectRoot) => {
|
|
|
5022
5068
|
};
|
|
5023
5069
|
var readOptionalFile = async (filePath) => {
|
|
5024
5070
|
try {
|
|
5025
|
-
return await
|
|
5071
|
+
return await import_promises2.default.readFile(filePath);
|
|
5026
5072
|
} catch {
|
|
5027
5073
|
return null;
|
|
5028
5074
|
}
|
|
@@ -5032,7 +5078,7 @@ var resolveBinanceBreadthFingerprint = async (projectRoot) => {
|
|
|
5032
5078
|
return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
|
|
5033
5079
|
}
|
|
5034
5080
|
const content = await readOptionalFile(
|
|
5035
|
-
|
|
5081
|
+
import_node_path2.default.join(
|
|
5036
5082
|
projectRoot,
|
|
5037
5083
|
"packages/node/src/config/binanceBreadthUniverses.json"
|
|
5038
5084
|
)
|
|
@@ -5054,7 +5100,7 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
|
5054
5100
|
if (snapshotFingerprintCache.has(cacheKey)) {
|
|
5055
5101
|
return snapshotFingerprintCache.get(cacheKey) ?? null;
|
|
5056
5102
|
}
|
|
5057
|
-
const content = await readOptionalFile(
|
|
5103
|
+
const content = await readOptionalFile(import_node_path2.default.join(projectRoot, relativePath));
|
|
5058
5104
|
let result = null;
|
|
5059
5105
|
if (content) {
|
|
5060
5106
|
try {
|
|
@@ -5067,55 +5113,26 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
|
5067
5113
|
snapshotFingerprintCache.set(cacheKey, result);
|
|
5068
5114
|
return result;
|
|
5069
5115
|
};
|
|
5070
|
-
var
|
|
5116
|
+
var resolveRuntimeGateFingerprint = async ({
|
|
5071
5117
|
projectRoot,
|
|
5072
5118
|
strategyName,
|
|
5073
5119
|
gitSha
|
|
5074
5120
|
}) => {
|
|
5075
5121
|
const cacheKey = `${projectRoot}:${strategyName}:${gitSha ?? "unknown"}`;
|
|
5076
5122
|
const cached = gateFingerprintCache.get(cacheKey);
|
|
5077
|
-
if (cached)
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
"packages/node/src/ai.ts",
|
|
5086
|
-
"packages/core/src/utils/strategyHelpers/signalBuilders.ts"
|
|
5087
|
-
];
|
|
5088
|
-
const optionalEntries = await Promise.all(
|
|
5089
|
-
relativeCandidates.map(async (relativePath) => {
|
|
5090
|
-
const content = await readOptionalFile(
|
|
5091
|
-
import_node_path.default.join(projectRoot, relativePath)
|
|
5092
|
-
);
|
|
5093
|
-
return content == null ? null : { relativePath, content };
|
|
5094
|
-
})
|
|
5095
|
-
);
|
|
5096
|
-
const sourceEntries = [];
|
|
5097
|
-
for (const entry of optionalEntries) {
|
|
5098
|
-
if (entry != null) {
|
|
5099
|
-
sourceEntries.push(entry);
|
|
5100
|
-
}
|
|
5101
|
-
}
|
|
5102
|
-
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
5103
|
-
hash.update(strategyName);
|
|
5104
|
-
if (sourceEntries.length) {
|
|
5105
|
-
for (const entry of sourceEntries) {
|
|
5106
|
-
hash.update(entry.relativePath);
|
|
5107
|
-
hash.update(entry.content);
|
|
5108
|
-
}
|
|
5109
|
-
} else {
|
|
5110
|
-
hash.update(gitSha ?? "unknown-git-sha");
|
|
5111
|
-
}
|
|
5112
|
-
const fingerprint = hash.digest("hex").slice(0, 16);
|
|
5113
|
-
gateFingerprintCache.set(cacheKey, fingerprint);
|
|
5114
|
-
return fingerprint;
|
|
5123
|
+
if (cached) return cached;
|
|
5124
|
+
const result = await resolveStrategyGateFingerprint({
|
|
5125
|
+
projectRoot,
|
|
5126
|
+
strategyName,
|
|
5127
|
+
gitSha
|
|
5128
|
+
});
|
|
5129
|
+
gateFingerprintCache.set(cacheKey, result);
|
|
5130
|
+
return result;
|
|
5115
5131
|
};
|
|
5116
5132
|
var buildRuntimeLineage = async ({
|
|
5117
5133
|
projectRoot,
|
|
5118
5134
|
strategyName,
|
|
5135
|
+
compositionId,
|
|
5119
5136
|
config,
|
|
5120
5137
|
runContext,
|
|
5121
5138
|
env = process.env
|
|
@@ -5141,19 +5158,23 @@ var buildRuntimeLineage = async ({
|
|
|
5141
5158
|
};
|
|
5142
5159
|
return {
|
|
5143
5160
|
schemaVersion: 1,
|
|
5161
|
+
compositionId: compositionId?.trim() || null,
|
|
5144
5162
|
...git,
|
|
5145
|
-
gateFingerprint: await
|
|
5163
|
+
gateFingerprint: (await resolveRuntimeGateFingerprint({
|
|
5146
5164
|
projectRoot,
|
|
5147
5165
|
strategyName,
|
|
5148
5166
|
gitSha: git.gitSha
|
|
5149
|
-
}),
|
|
5150
|
-
|
|
5167
|
+
})).gateFingerprint,
|
|
5168
|
+
// Position risk is tracked separately so changing trade size does not
|
|
5169
|
+
// masquerade as a change to the core + gate decision logic.
|
|
5170
|
+
configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config),
|
|
5151
5171
|
contextFingerprint: fingerprintRuntimeValue(context),
|
|
5152
5172
|
maxLossValue: resolveRuntimeMaxLossValue(config)
|
|
5153
5173
|
};
|
|
5154
5174
|
};
|
|
5155
5175
|
var runtimeLineageKey = (lineage) => [
|
|
5156
5176
|
lineage.schemaVersion,
|
|
5177
|
+
lineage.compositionId ?? "unbound-composition",
|
|
5157
5178
|
lineage.gitSha ?? "unknown",
|
|
5158
5179
|
lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
|
|
5159
5180
|
lineage.gateFingerprint,
|
|
@@ -7166,8 +7187,8 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
7166
7187
|
};
|
|
7167
7188
|
|
|
7168
7189
|
// src/lib/replay/runtimeEvidenceSource.ts
|
|
7169
|
-
var
|
|
7170
|
-
var
|
|
7190
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
7191
|
+
var import_node_path3 = __toESM(require("path"));
|
|
7171
7192
|
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
7172
7193
|
var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
7173
7194
|
var unwrapRows = (value, field) => (Array.isArray(value) ? value : []).map((item) => {
|
|
@@ -7228,9 +7249,9 @@ var loadReplayRuntimeEvidenceSource = async ({
|
|
|
7228
7249
|
expectedUserName,
|
|
7229
7250
|
expectedWindow
|
|
7230
7251
|
}) => {
|
|
7231
|
-
const resolvedPath =
|
|
7252
|
+
const resolvedPath = import_node_path3.default.resolve(projectRoot, filePath);
|
|
7232
7253
|
const root = asRecord(
|
|
7233
|
-
JSON.parse(await
|
|
7254
|
+
JSON.parse(await import_promises3.default.readFile(resolvedPath, "utf8"))
|
|
7234
7255
|
);
|
|
7235
7256
|
const runtime = asRecord(root?.runtime) ?? root;
|
|
7236
7257
|
if (!root || !runtime) {
|
|
@@ -8277,8 +8298,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8277
8298
|
};
|
|
8278
8299
|
|
|
8279
8300
|
// src/lib/replay/outputReport.ts
|
|
8280
|
-
var
|
|
8281
|
-
var
|
|
8301
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
8302
|
+
var import_node_path4 = __toESM(require("path"));
|
|
8282
8303
|
var import_data = require("@tradejs/core/data");
|
|
8283
8304
|
var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
|
|
8284
8305
|
timeZone: "Europe/Moscow",
|
|
@@ -8582,8 +8603,8 @@ var writeReplayOutputReport = async ({
|
|
|
8582
8603
|
strategySnapshot,
|
|
8583
8604
|
runtimeComparison
|
|
8584
8605
|
}) => {
|
|
8585
|
-
const outputDir =
|
|
8586
|
-
await
|
|
8606
|
+
const outputDir = import_node_path4.default.resolve(projectRoot, "data/replay/output");
|
|
8607
|
+
await import_promises4.default.mkdir(outputDir, { recursive: true });
|
|
8587
8608
|
const perTradeAnalysis = buildPerTradeAnalysis(runtimeComparison);
|
|
8588
8609
|
const report = {
|
|
8589
8610
|
reportType: "replay-output",
|
|
@@ -8618,8 +8639,8 @@ var writeReplayOutputReport = async ({
|
|
|
8618
8639
|
runtimeComparison,
|
|
8619
8640
|
perTradeAnalysis
|
|
8620
8641
|
};
|
|
8621
|
-
const markdownPath =
|
|
8622
|
-
const jsonPath =
|
|
8642
|
+
const markdownPath = import_node_path4.default.join(outputDir, `${timestamp}-replay-report.md`);
|
|
8643
|
+
const jsonPath = import_node_path4.default.join(outputDir, `${timestamp}-replay-report.json`);
|
|
8623
8644
|
const lines = [
|
|
8624
8645
|
"# Replay Report",
|
|
8625
8646
|
"",
|
|
@@ -8685,8 +8706,8 @@ var writeReplayOutputReport = async ({
|
|
|
8685
8706
|
""
|
|
8686
8707
|
];
|
|
8687
8708
|
await Promise.all([
|
|
8688
|
-
|
|
8689
|
-
|
|
8709
|
+
import_promises4.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
|
|
8710
|
+
import_promises4.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
|
|
8690
8711
|
`, "utf8")
|
|
8691
8712
|
]);
|
|
8692
8713
|
return {
|