@tradejs/cli 2.0.18 → 2.0.20
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 +14522 -15704
- package/dist/lib/aiExport.js +15 -1608
- 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/binanceMarketContextBackfill.js +7 -6
- package/dist/lib/coinMarketCapContextBackfill.js +19 -18
- package/dist/lib/coreResearch.js +2738 -0
- package/dist/lib/derivativesContextBackfill.js +10 -9
- package/dist/lib/executionCalibration.js +6 -3
- package/dist/lib/hyperliquidWhaleBackfill.js +8 -8
- package/dist/lib/hyperliquidWhaleStream.js +4 -4
- package/dist/lib/marketContextPrepare.js +83 -1686
- package/dist/lib/runEnvironment.js +14 -1666
- package/dist/lib/runFormatting.js +1 -1607
- package/dist/lib/runtimeDebugEvidence.js +2 -48
- package/dist/lib/runtimeLineage.js +81 -48
- package/dist/lib/runtimeRedis.js +6 -57
- package/dist/lib/runtimeScorecard.js +153 -10
- package/dist/lib/runtimeStrategyBacktest.js +2 -48
- package/dist/lib/strategyGateFingerprint.js +80 -0
- package/dist/lib/strategyRelease.js +1365 -0
- package/dist/scripts/aiExport.js +17 -1610
- package/dist/scripts/aiPocketSearch.js +1070 -1770
- package/dist/scripts/aiTrain.js +108 -1688
- package/dist/scripts/backtest.js +120 -1760
- package/dist/scripts/binanceMarketIngest.js +5 -4
- package/dist/scripts/candlesMigrateProvider.js +6 -5186
- package/dist/scripts/cleanupMarketContext.js +4 -3
- package/dist/scripts/continuity.js +4 -3
- package/dist/scripts/coreResearch.js +2830 -0
- package/dist/scripts/derivativesIngest.js +6 -4
- package/dist/scripts/derivativesIngestCoinalyzeAll.js +5 -4
- package/dist/scripts/doctor.js +2 -5184
- package/dist/scripts/executionCalibration.js +6 -3
- package/dist/scripts/hyperliquidWhaleBackfill.js +8 -8
- package/dist/scripts/hyperliquidWhaleIngest.js +4 -4
- package/dist/scripts/hyperliquidWhalesUpdate.js +1 -1
- package/dist/scripts/migration.js +3 -3
- package/dist/scripts/mlExport.js +1 -1607
- package/dist/scripts/replay.js +239 -1855
- package/dist/scripts/replayRunner.js +239 -1855
- package/dist/scripts/replayRuntimeEvidence.js +29 -62
- package/dist/scripts/researchAuto.js +3 -49
- package/dist/scripts/results.js +1 -1607
- package/dist/scripts/runtimeEvidence.js +18 -56
- package/dist/scripts/runtimeParity.js +121 -1746
- package/dist/scripts/runtimeScorecard.js +883 -29
- package/dist/scripts/signals.js +1178 -2724
- package/dist/scripts/signalsDaemon.js +1183 -2696
- package/dist/scripts/signalsSummary.js +4 -26
- package/dist/scripts/strategyRelease.js +1469 -0
- package/dist/workers/testerWorker.js +2 -2
- package/package.json +13 -9
|
@@ -34,17 +34,7 @@ var import_redis3 = require("@tradejs/infra/redis");
|
|
|
34
34
|
// src/lib/runtimeRedis.ts
|
|
35
35
|
var import_redis = require("@tradejs/infra/redis");
|
|
36
36
|
var import_time = require("@tradejs/core/time");
|
|
37
|
-
var
|
|
38
|
-
var RESERVED_CONFIG_IDS = /* @__PURE__ */ new Set(["results"]);
|
|
39
|
-
var resolveStrategyConfigIdentityByKey = (userName, key) => {
|
|
40
|
-
const parts = key.split(":");
|
|
41
|
-
if (parts.length !== 5) return null;
|
|
42
|
-
const [users, keyUserName, strategiesKey, strategyName, configId] = parts;
|
|
43
|
-
if (users !== "users" || keyUserName !== userName || strategiesKey !== "strategies" || !strategyName || !configId || RESERVED_STRATEGY_NAMES.has(strategyName) || RESERVED_CONFIG_IDS.has(configId) || !/^[a-zA-Z0-9_-]+$/.test(configId)) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
return { strategyName, configId };
|
|
47
|
-
};
|
|
37
|
+
var import_runtimeStrategyConfigs = require("@tradejs/infra/runtimeStrategyConfigs");
|
|
48
38
|
var isRuntimeTradeRecord = (value) => {
|
|
49
39
|
if (!value || typeof value !== "object") {
|
|
50
40
|
return false;
|
|
@@ -52,42 +42,6 @@ var isRuntimeTradeRecord = (value) => {
|
|
|
52
42
|
const record = value;
|
|
53
43
|
return typeof record.orderId === "string" && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.entryTimestamp === "number" && typeof record.entryPrice === "number" && typeof record.qty === "number" && (record.direction === "LONG" || record.direction === "SHORT");
|
|
54
44
|
};
|
|
55
|
-
var resolveStrategyNameByConfigKey = (userName, key) => {
|
|
56
|
-
return resolveStrategyConfigIdentityByKey(userName, key)?.strategyName ?? null;
|
|
57
|
-
};
|
|
58
|
-
var getRuntimeStrategyConfigKeys = async (userName) => {
|
|
59
|
-
const keys = await (0, import_redis.getKeys)(`${import_redis.redisKeys.strategies(userName)}:`);
|
|
60
|
-
return keys.filter((key) => resolveStrategyConfigIdentityByKey(userName, key)).sort((left, right) => left.localeCompare(right));
|
|
61
|
-
};
|
|
62
|
-
var loadRuntimeStrategyConfigs = async (userName, {
|
|
63
|
-
onInvalidConfig
|
|
64
|
-
} = {}) => {
|
|
65
|
-
const configKeys = await getRuntimeStrategyConfigKeys(userName);
|
|
66
|
-
const strategyConfigs = await Promise.all(
|
|
67
|
-
configKeys.map(async (key) => {
|
|
68
|
-
const strategyName = resolveStrategyNameByConfigKey(userName, key);
|
|
69
|
-
const identity = resolveStrategyConfigIdentityByKey(userName, key);
|
|
70
|
-
if (!strategyName || !identity) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
const strategyConfig = await (0, import_redis.getData)(
|
|
74
|
-
key,
|
|
75
|
-
null
|
|
76
|
-
);
|
|
77
|
-
if (!strategyConfig || typeof strategyConfig !== "object" || Array.isArray(strategyConfig)) {
|
|
78
|
-
onInvalidConfig?.(key);
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
key,
|
|
83
|
-
strategyName,
|
|
84
|
-
configId: identity.configId,
|
|
85
|
-
strategyConfig
|
|
86
|
-
};
|
|
87
|
-
})
|
|
88
|
-
);
|
|
89
|
-
return strategyConfigs.filter(Boolean);
|
|
90
|
-
};
|
|
91
45
|
var loadRuntimeTrades = async (userName, {
|
|
92
46
|
startTime,
|
|
93
47
|
endTime
|
|
@@ -356,7 +310,7 @@ var collectRuntimeDebugEvidence = async ({
|
|
|
356
310
|
loadRuntimeSignals(userName, { startTime, endTime }),
|
|
357
311
|
loadRuntimeSignalEvaluations(userName, { startTime, endTime }),
|
|
358
312
|
loadRuntimeSignalEvaluationStatsBuckets(userName),
|
|
359
|
-
loadRuntimeStrategyConfigs(userName)
|
|
313
|
+
(0, import_runtimeStrategyConfigs.loadRuntimeStrategyConfigs)(userName)
|
|
360
314
|
]);
|
|
361
315
|
const tradesByOrderId = new Map(
|
|
362
316
|
entryTrades.map((trade) => [trade.orderId, trade])
|
|
@@ -38,11 +38,56 @@ __export(runtimeLineage_exports, {
|
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(runtimeLineage_exports);
|
|
40
40
|
var import_node_child_process = require("child_process");
|
|
41
|
+
var import_node_crypto2 = require("crypto");
|
|
42
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
43
|
+
var import_node_path2 = __toESM(require("path"));
|
|
44
|
+
var import_types = require("@tradejs/types");
|
|
45
|
+
var import_strategies = require("@tradejs/node/strategies");
|
|
46
|
+
|
|
47
|
+
// src/lib/strategyGateFingerprint.ts
|
|
41
48
|
var import_node_crypto = require("crypto");
|
|
42
49
|
var import_promises = __toESM(require("fs/promises"));
|
|
43
50
|
var import_node_path = __toESM(require("path"));
|
|
44
|
-
var
|
|
45
|
-
|
|
51
|
+
var gateFingerprintRelativePaths = (strategyName) => [
|
|
52
|
+
`packages/strategies/src/${strategyName}/adapters/ai.ts`,
|
|
53
|
+
`packages/strategies/src/${strategyName}/guardrails.ts`,
|
|
54
|
+
`packages/strategies/src/${strategyName}/pockets.ts`,
|
|
55
|
+
`packages/strategies/src/${strategyName}/config.ts`,
|
|
56
|
+
"packages/strategies/src/shared/localAiGate.ts",
|
|
57
|
+
"packages/node/src/ai.ts",
|
|
58
|
+
"packages/core/src/utils/strategyHelpers/signalBuilders.ts"
|
|
59
|
+
];
|
|
60
|
+
var resolveStrategyGateFingerprint = async ({
|
|
61
|
+
projectRoot,
|
|
62
|
+
strategyName,
|
|
63
|
+
gitSha
|
|
64
|
+
}) => {
|
|
65
|
+
const candidates = await Promise.all(
|
|
66
|
+
gateFingerprintRelativePaths(strategyName).map(async (relativePath) => {
|
|
67
|
+
const content = await import_promises.default.readFile(import_node_path.default.join(projectRoot, relativePath)).catch(() => null);
|
|
68
|
+
return content == null ? null : { relativePath, content };
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
const sourceEntries = candidates.filter(
|
|
72
|
+
(entry) => entry != null
|
|
73
|
+
);
|
|
74
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
75
|
+
hash.update(strategyName);
|
|
76
|
+
if (sourceEntries.length) {
|
|
77
|
+
for (const entry of sourceEntries) {
|
|
78
|
+
hash.update(entry.relativePath);
|
|
79
|
+
hash.update(entry.content);
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
hash.update(gitSha ?? "unknown-git-sha");
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
gateFingerprint: hash.digest("hex").slice(0, 16),
|
|
86
|
+
gateFingerprintFiles: sourceEntries.map((entry) => entry.relativePath)
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/lib/runtimeLineage.ts
|
|
46
91
|
var RUNTIME_CONTEXT_ENV_KEYS = [
|
|
47
92
|
"AI_MODE",
|
|
48
93
|
"MIN_AI_QUALITY",
|
|
@@ -92,6 +137,17 @@ var normalizeForStableJson = (value) => {
|
|
|
92
137
|
}
|
|
93
138
|
return value;
|
|
94
139
|
};
|
|
140
|
+
var normalizeRuntimeLogicConfig = (value) => {
|
|
141
|
+
if (Array.isArray(value)) {
|
|
142
|
+
return value.map(normalizeRuntimeLogicConfig);
|
|
143
|
+
}
|
|
144
|
+
if (value && typeof value === "object") {
|
|
145
|
+
return Object.fromEntries(
|
|
146
|
+
Object.entries(value).filter(([key]) => key !== "MAX_LOSS_VALUE").sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeRuntimeLogicConfig(entry)])
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
};
|
|
95
151
|
var resolveRuntimeMaxLossValue = (config) => {
|
|
96
152
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
97
153
|
return null;
|
|
@@ -105,7 +161,7 @@ var resolveRuntimeMaxLossValue = (config) => {
|
|
|
105
161
|
);
|
|
106
162
|
return Number.isFinite(maxLossValue) ? maxLossValue : null;
|
|
107
163
|
};
|
|
108
|
-
var fingerprintRuntimeValue = (value) => (0,
|
|
164
|
+
var fingerprintRuntimeValue = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
|
|
109
165
|
var gitLineageCache = /* @__PURE__ */ new Map();
|
|
110
166
|
var gateFingerprintCache = /* @__PURE__ */ new Map();
|
|
111
167
|
var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
|
|
@@ -156,7 +212,7 @@ var resolveGitLineage = (projectRoot) => {
|
|
|
156
212
|
};
|
|
157
213
|
var readOptionalFile = async (filePath) => {
|
|
158
214
|
try {
|
|
159
|
-
return await
|
|
215
|
+
return await import_promises2.default.readFile(filePath);
|
|
160
216
|
} catch {
|
|
161
217
|
return null;
|
|
162
218
|
}
|
|
@@ -166,7 +222,7 @@ var resolveBinanceBreadthFingerprint = async (projectRoot) => {
|
|
|
166
222
|
return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
|
|
167
223
|
}
|
|
168
224
|
const content = await readOptionalFile(
|
|
169
|
-
|
|
225
|
+
import_node_path2.default.join(
|
|
170
226
|
projectRoot,
|
|
171
227
|
"packages/node/src/config/binanceBreadthUniverses.json"
|
|
172
228
|
)
|
|
@@ -188,7 +244,7 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
|
188
244
|
if (snapshotFingerprintCache.has(cacheKey)) {
|
|
189
245
|
return snapshotFingerprintCache.get(cacheKey) ?? null;
|
|
190
246
|
}
|
|
191
|
-
const content = await readOptionalFile(
|
|
247
|
+
const content = await readOptionalFile(import_node_path2.default.join(projectRoot, relativePath));
|
|
192
248
|
let result = null;
|
|
193
249
|
if (content) {
|
|
194
250
|
try {
|
|
@@ -201,55 +257,26 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
|
201
257
|
snapshotFingerprintCache.set(cacheKey, result);
|
|
202
258
|
return result;
|
|
203
259
|
};
|
|
204
|
-
var
|
|
260
|
+
var resolveRuntimeGateFingerprint = async ({
|
|
205
261
|
projectRoot,
|
|
206
262
|
strategyName,
|
|
207
263
|
gitSha
|
|
208
264
|
}) => {
|
|
209
265
|
const cacheKey = `${projectRoot}:${strategyName}:${gitSha ?? "unknown"}`;
|
|
210
266
|
const cached = gateFingerprintCache.get(cacheKey);
|
|
211
|
-
if (cached)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
"packages/node/src/ai.ts",
|
|
220
|
-
"packages/core/src/utils/strategyHelpers/signalBuilders.ts"
|
|
221
|
-
];
|
|
222
|
-
const optionalEntries = await Promise.all(
|
|
223
|
-
relativeCandidates.map(async (relativePath) => {
|
|
224
|
-
const content = await readOptionalFile(
|
|
225
|
-
import_node_path.default.join(projectRoot, relativePath)
|
|
226
|
-
);
|
|
227
|
-
return content == null ? null : { relativePath, content };
|
|
228
|
-
})
|
|
229
|
-
);
|
|
230
|
-
const sourceEntries = [];
|
|
231
|
-
for (const entry of optionalEntries) {
|
|
232
|
-
if (entry != null) {
|
|
233
|
-
sourceEntries.push(entry);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
237
|
-
hash.update(strategyName);
|
|
238
|
-
if (sourceEntries.length) {
|
|
239
|
-
for (const entry of sourceEntries) {
|
|
240
|
-
hash.update(entry.relativePath);
|
|
241
|
-
hash.update(entry.content);
|
|
242
|
-
}
|
|
243
|
-
} else {
|
|
244
|
-
hash.update(gitSha ?? "unknown-git-sha");
|
|
245
|
-
}
|
|
246
|
-
const fingerprint = hash.digest("hex").slice(0, 16);
|
|
247
|
-
gateFingerprintCache.set(cacheKey, fingerprint);
|
|
248
|
-
return fingerprint;
|
|
267
|
+
if (cached) return cached;
|
|
268
|
+
const result = await resolveStrategyGateFingerprint({
|
|
269
|
+
projectRoot,
|
|
270
|
+
strategyName,
|
|
271
|
+
gitSha
|
|
272
|
+
});
|
|
273
|
+
gateFingerprintCache.set(cacheKey, result);
|
|
274
|
+
return result;
|
|
249
275
|
};
|
|
250
276
|
var buildRuntimeLineage = async ({
|
|
251
277
|
projectRoot,
|
|
252
278
|
strategyName,
|
|
279
|
+
compositionId,
|
|
253
280
|
config,
|
|
254
281
|
runContext,
|
|
255
282
|
env = process.env
|
|
@@ -275,19 +302,25 @@ var buildRuntimeLineage = async ({
|
|
|
275
302
|
};
|
|
276
303
|
return {
|
|
277
304
|
schemaVersion: 1,
|
|
305
|
+
compositionId: compositionId?.trim() || null,
|
|
278
306
|
...git,
|
|
279
|
-
gateFingerprint: await
|
|
307
|
+
gateFingerprint: (await resolveRuntimeGateFingerprint({
|
|
280
308
|
projectRoot,
|
|
281
309
|
strategyName,
|
|
282
310
|
gitSha: git.gitSha
|
|
283
|
-
}),
|
|
284
|
-
|
|
311
|
+
})).gateFingerprint,
|
|
312
|
+
// Position risk is tracked separately so changing trade size does not
|
|
313
|
+
// masquerade as a change to the core + gate decision logic.
|
|
314
|
+
configFingerprint: fingerprintRuntimeValue(
|
|
315
|
+
normalizeRuntimeLogicConfig(config)
|
|
316
|
+
),
|
|
285
317
|
contextFingerprint: fingerprintRuntimeValue(context),
|
|
286
318
|
maxLossValue: resolveRuntimeMaxLossValue(config)
|
|
287
319
|
};
|
|
288
320
|
};
|
|
289
321
|
var runtimeLineageKey = (lineage) => [
|
|
290
322
|
lineage.schemaVersion,
|
|
323
|
+
lineage.compositionId ?? "unbound-composition",
|
|
291
324
|
lineage.gitSha ?? "unknown",
|
|
292
325
|
lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
|
|
293
326
|
lineage.gateFingerprint,
|
package/dist/lib/runtimeRedis.js
CHANGED
|
@@ -20,32 +20,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/lib/runtimeRedis.ts
|
|
21
21
|
var runtimeRedis_exports = {};
|
|
22
22
|
__export(runtimeRedis_exports, {
|
|
23
|
-
getRuntimeStrategyConfigKeys: () => getRuntimeStrategyConfigKeys,
|
|
23
|
+
getRuntimeStrategyConfigKeys: () => import_runtimeStrategyConfigs.getRuntimeStrategyConfigKeys,
|
|
24
24
|
isRuntimeStrategyEnabled: () => isRuntimeStrategyEnabled,
|
|
25
25
|
isRuntimeTradeRecord: () => isRuntimeTradeRecord,
|
|
26
26
|
loadRuntimeActiveTradeOrderIds: () => loadRuntimeActiveTradeOrderIds,
|
|
27
27
|
loadRuntimeClosedTrades: () => loadRuntimeClosedTrades,
|
|
28
|
-
loadRuntimeStrategyConfigs: () => loadRuntimeStrategyConfigs,
|
|
29
|
-
loadRuntimeStrategyNames: () => loadRuntimeStrategyNames,
|
|
28
|
+
loadRuntimeStrategyConfigs: () => import_runtimeStrategyConfigs.loadRuntimeStrategyConfigs,
|
|
29
|
+
loadRuntimeStrategyNames: () => import_runtimeStrategyConfigs.loadRuntimeStrategyNames,
|
|
30
30
|
loadRuntimeTrades: () => loadRuntimeTrades,
|
|
31
31
|
loadStrategyResultSymbols: () => loadStrategyResultSymbols,
|
|
32
|
-
resolveStrategyConfigIdentityByKey: () => resolveStrategyConfigIdentityByKey,
|
|
33
|
-
resolveStrategyNameByConfigKey: () => resolveStrategyNameByConfigKey
|
|
32
|
+
resolveStrategyConfigIdentityByKey: () => import_runtimeStrategyConfigs.resolveStrategyConfigIdentityByKey,
|
|
33
|
+
resolveStrategyNameByConfigKey: () => import_runtimeStrategyConfigs.resolveStrategyNameByConfigKey
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(runtimeRedis_exports);
|
|
36
36
|
var import_redis = require("@tradejs/infra/redis");
|
|
37
37
|
var import_time = require("@tradejs/core/time");
|
|
38
|
-
var
|
|
39
|
-
var RESERVED_CONFIG_IDS = /* @__PURE__ */ new Set(["results"]);
|
|
40
|
-
var resolveStrategyConfigIdentityByKey = (userName, key) => {
|
|
41
|
-
const parts = key.split(":");
|
|
42
|
-
if (parts.length !== 5) return null;
|
|
43
|
-
const [users, keyUserName, strategiesKey, strategyName, configId] = parts;
|
|
44
|
-
if (users !== "users" || keyUserName !== userName || strategiesKey !== "strategies" || !strategyName || !configId || RESERVED_STRATEGY_NAMES.has(strategyName) || RESERVED_CONFIG_IDS.has(configId) || !/^[a-zA-Z0-9_-]+$/.test(configId)) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
return { strategyName, configId };
|
|
48
|
-
};
|
|
38
|
+
var import_runtimeStrategyConfigs = require("@tradejs/infra/runtimeStrategyConfigs");
|
|
49
39
|
var isRuntimeStrategyEnabled = (strategyConfig) => {
|
|
50
40
|
const enabled = strategyConfig.ENABLE;
|
|
51
41
|
return enabled !== false;
|
|
@@ -57,47 +47,6 @@ var isRuntimeTradeRecord = (value) => {
|
|
|
57
47
|
const record = value;
|
|
58
48
|
return typeof record.orderId === "string" && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.entryTimestamp === "number" && typeof record.entryPrice === "number" && typeof record.qty === "number" && (record.direction === "LONG" || record.direction === "SHORT");
|
|
59
49
|
};
|
|
60
|
-
var resolveStrategyNameByConfigKey = (userName, key) => {
|
|
61
|
-
return resolveStrategyConfigIdentityByKey(userName, key)?.strategyName ?? null;
|
|
62
|
-
};
|
|
63
|
-
var getRuntimeStrategyConfigKeys = async (userName) => {
|
|
64
|
-
const keys = await (0, import_redis.getKeys)(`${import_redis.redisKeys.strategies(userName)}:`);
|
|
65
|
-
return keys.filter((key) => resolveStrategyConfigIdentityByKey(userName, key)).sort((left, right) => left.localeCompare(right));
|
|
66
|
-
};
|
|
67
|
-
var loadRuntimeStrategyNames = async (userName) => [
|
|
68
|
-
...new Set(
|
|
69
|
-
(await getRuntimeStrategyConfigKeys(userName)).map((key) => resolveStrategyNameByConfigKey(userName, key)).filter((value) => Boolean(value))
|
|
70
|
-
)
|
|
71
|
-
].sort((left, right) => left.localeCompare(right));
|
|
72
|
-
var loadRuntimeStrategyConfigs = async (userName, {
|
|
73
|
-
onInvalidConfig
|
|
74
|
-
} = {}) => {
|
|
75
|
-
const configKeys = await getRuntimeStrategyConfigKeys(userName);
|
|
76
|
-
const strategyConfigs = await Promise.all(
|
|
77
|
-
configKeys.map(async (key) => {
|
|
78
|
-
const strategyName = resolveStrategyNameByConfigKey(userName, key);
|
|
79
|
-
const identity = resolveStrategyConfigIdentityByKey(userName, key);
|
|
80
|
-
if (!strategyName || !identity) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
const strategyConfig = await (0, import_redis.getData)(
|
|
84
|
-
key,
|
|
85
|
-
null
|
|
86
|
-
);
|
|
87
|
-
if (!strategyConfig || typeof strategyConfig !== "object" || Array.isArray(strategyConfig)) {
|
|
88
|
-
onInvalidConfig?.(key);
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
key,
|
|
93
|
-
strategyName,
|
|
94
|
-
configId: identity.configId,
|
|
95
|
-
strategyConfig
|
|
96
|
-
};
|
|
97
|
-
})
|
|
98
|
-
);
|
|
99
|
-
return strategyConfigs.filter(Boolean);
|
|
100
|
-
};
|
|
101
50
|
var loadRuntimeTrades = async (userName, {
|
|
102
51
|
startTime,
|
|
103
52
|
endTime
|
|
@@ -47,6 +47,71 @@ var extractRuntimeRows = (artifact) => {
|
|
|
47
47
|
statsBuckets: asArray(runtime.evaluationStatsBuckets).map(asRecord).filter((item) => item != null)
|
|
48
48
|
};
|
|
49
49
|
};
|
|
50
|
+
var buildRuntimeLineageSummary = (rows) => {
|
|
51
|
+
const lineageRows = [...rows.evaluations, ...rows.signals, ...rows.trades];
|
|
52
|
+
const lineages = lineageRows.map((row) => asRecord(row.runtimeLineage)).filter((lineage) => lineage != null);
|
|
53
|
+
const identities = /* @__PURE__ */ new Map();
|
|
54
|
+
for (const lineage of lineages) {
|
|
55
|
+
const identity2 = {
|
|
56
|
+
compositionId: finiteString(lineage.compositionId),
|
|
57
|
+
gitSha: finiteString(lineage.gitSha),
|
|
58
|
+
gitDirty: typeof lineage.gitDirty === "boolean" ? lineage.gitDirty : null,
|
|
59
|
+
gateFingerprint: finiteString(lineage.gateFingerprint),
|
|
60
|
+
configFingerprint: finiteString(lineage.configFingerprint),
|
|
61
|
+
contextFingerprint: finiteString(lineage.contextFingerprint),
|
|
62
|
+
maxLossValue: finiteNumber(lineage.maxLossValue)
|
|
63
|
+
};
|
|
64
|
+
identities.set(JSON.stringify(identity2), identity2);
|
|
65
|
+
}
|
|
66
|
+
const identity = identities.size === 1 ? [...identities.values()][0] : null;
|
|
67
|
+
const complete = lineageRows.length > 0 && lineages.length === lineageRows.length && identity != null && identity.compositionId != null && identity.gitSha != null && identity.gitDirty === false && identity.gateFingerprint != null && identity.configFingerprint != null && identity.contextFingerprint != null && identity.maxLossValue != null;
|
|
68
|
+
return {
|
|
69
|
+
complete,
|
|
70
|
+
conflicts: identities.size > 1,
|
|
71
|
+
rows: lineageRows.length,
|
|
72
|
+
rowsWithLineage: lineages.length,
|
|
73
|
+
compositionId: finiteString(identity?.compositionId),
|
|
74
|
+
gitSha: finiteString(identity?.gitSha),
|
|
75
|
+
gitDirty: typeof identity?.gitDirty === "boolean" ? identity.gitDirty : null,
|
|
76
|
+
gateFingerprint: finiteString(identity?.gateFingerprint),
|
|
77
|
+
configFingerprint: finiteString(identity?.configFingerprint),
|
|
78
|
+
contextFingerprint: finiteString(identity?.contextFingerprint),
|
|
79
|
+
maxLossValue: finiteNumber(identity?.maxLossValue)
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
var belongsToStrategy = (row, strategy) => strategy == null || finiteString(row.strategy) === strategy;
|
|
83
|
+
var filterArtifactByStrategy = (artifact, strategy) => {
|
|
84
|
+
if (!strategy) return artifact;
|
|
85
|
+
const root = asRecord(artifact) ?? {};
|
|
86
|
+
const runtime = runtimePayload(artifact);
|
|
87
|
+
return {
|
|
88
|
+
...root,
|
|
89
|
+
runtime: {
|
|
90
|
+
...runtime,
|
|
91
|
+
evaluations: asArray(runtime.evaluations).filter(
|
|
92
|
+
(item) => belongsToStrategy(
|
|
93
|
+
asRecord(asRecord(item)?.evaluation) ?? asRecord(item) ?? {},
|
|
94
|
+
strategy
|
|
95
|
+
)
|
|
96
|
+
),
|
|
97
|
+
signals: asArray(runtime.signals).filter(
|
|
98
|
+
(item) => belongsToStrategy(
|
|
99
|
+
asRecord(asRecord(item)?.signal) ?? asRecord(item) ?? {},
|
|
100
|
+
strategy
|
|
101
|
+
)
|
|
102
|
+
),
|
|
103
|
+
trades: asArray(runtime.trades).filter(
|
|
104
|
+
(item) => belongsToStrategy(
|
|
105
|
+
asRecord(asRecord(item)?.trade) ?? asRecord(item) ?? {},
|
|
106
|
+
strategy
|
|
107
|
+
)
|
|
108
|
+
),
|
|
109
|
+
evaluationStatsBuckets: asArray(runtime.evaluationStatsBuckets).filter(
|
|
110
|
+
(item) => belongsToStrategy(asRecord(item) ?? {}, strategy)
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
};
|
|
50
115
|
var countBy = (items, getter) => {
|
|
51
116
|
const counts = {};
|
|
52
117
|
for (const item of items) {
|
|
@@ -165,14 +230,43 @@ var getReplayComparison = (artifact) => {
|
|
|
165
230
|
const root = asRecord(artifact) ?? {};
|
|
166
231
|
return asRecord(asRecord(root.replay)?.runtimeComparison) ?? null;
|
|
167
232
|
};
|
|
233
|
+
var replayCountsForStrategy = (replayComparison, strategy) => {
|
|
234
|
+
if (strategy) {
|
|
235
|
+
const byStrategy = asRecord(replayComparison?.byStrategy);
|
|
236
|
+
const strategyCounts = asRecord(byStrategy?.[strategy]);
|
|
237
|
+
if (strategyCounts) {
|
|
238
|
+
return {
|
|
239
|
+
matched: finiteNumber(strategyCounts.matched) ?? 0,
|
|
240
|
+
backtestOnly: finiteNumber(strategyCounts.backtestOnly) ?? 0,
|
|
241
|
+
runtimeOnly: finiteNumber(strategyCounts.runtimeOnly) ?? 0
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const counts = asRecord(replayComparison?.counts);
|
|
246
|
+
return {
|
|
247
|
+
matched: finiteNumber(counts?.matched) ?? 0,
|
|
248
|
+
backtestOnly: finiteNumber(counts?.backtestOnly) ?? 0,
|
|
249
|
+
runtimeOnly: finiteNumber(counts?.runtimeOnly) ?? 0
|
|
250
|
+
};
|
|
251
|
+
};
|
|
168
252
|
var getCalibrationSummary = (artifact) => {
|
|
169
253
|
const root = asRecord(artifact) ?? {};
|
|
170
254
|
return asRecord(asRecord(root.summary)?.all) ?? null;
|
|
171
255
|
};
|
|
256
|
+
var getProspectiveSummary = (artifact) => {
|
|
257
|
+
const root = asRecord(artifact);
|
|
258
|
+
if (!root || root.reportType !== "strategy-prospective-evidence") return null;
|
|
259
|
+
return {
|
|
260
|
+
rawCoreExpectancy: finiteNumber(root.rawCoreExpectancy),
|
|
261
|
+
aiGateExpectancy: finiteNumber(root.aiGateExpectancy),
|
|
262
|
+
regimeCoverage: finiteNumber(root.regimeCoverage)
|
|
263
|
+
};
|
|
264
|
+
};
|
|
172
265
|
var buildRuntimeScorecard = ({
|
|
173
266
|
runtimeArtifact,
|
|
174
267
|
replayEvidenceArtifact,
|
|
175
268
|
calibrationArtifact,
|
|
269
|
+
prospectiveEvidenceArtifact,
|
|
176
270
|
historyRuntimeArtifacts = [],
|
|
177
271
|
thresholds = {
|
|
178
272
|
minimumParityRatio: 0.95,
|
|
@@ -180,9 +274,19 @@ var buildRuntimeScorecard = ({
|
|
|
180
274
|
minimumClosedTrades: 20,
|
|
181
275
|
minimumExpectancy: 0
|
|
182
276
|
},
|
|
183
|
-
generatedAt = Date.now()
|
|
277
|
+
generatedAt = Date.now(),
|
|
278
|
+
strategy = null,
|
|
279
|
+
llmComparatorPolicy = "ai_approved_only"
|
|
184
280
|
}) => {
|
|
185
|
-
const
|
|
281
|
+
const scopedRuntimeArtifact = filterArtifactByStrategy(
|
|
282
|
+
runtimeArtifact,
|
|
283
|
+
strategy
|
|
284
|
+
);
|
|
285
|
+
const scopedHistoryArtifacts = historyRuntimeArtifacts.map(
|
|
286
|
+
(artifact) => filterArtifactByStrategy(artifact, strategy)
|
|
287
|
+
);
|
|
288
|
+
const rows = extractRuntimeRows(scopedRuntimeArtifact);
|
|
289
|
+
const runtimeLineage = buildRuntimeLineageSummary(rows);
|
|
186
290
|
const window = runtimeWindow(runtimeArtifact);
|
|
187
291
|
const startTime = finiteNumber(window.startTime) ?? generatedAt - 864e5;
|
|
188
292
|
const endTime = finiteNumber(window.endTime) ?? generatedAt;
|
|
@@ -197,6 +301,20 @@ var buildRuntimeScorecard = ({
|
|
|
197
301
|
})).filter(
|
|
198
302
|
({ decision }) => decision === "approved" || decision === "rejected"
|
|
199
303
|
);
|
|
304
|
+
const llmEligibleEvaluations = llmComparatorPolicy === "disabled" ? [] : llmComparatorPolicy === "ai_approved_only" ? signalEvaluations.filter(
|
|
305
|
+
(evaluation) => finiteString(asRecord(evaluation.aiAnalysis)?.gateDecision) === "approved"
|
|
306
|
+
) : signalEvaluations;
|
|
307
|
+
const gateComparisons = llmEligibleEvaluations.map((evaluation) => {
|
|
308
|
+
const analysis = asRecord(evaluation.aiAnalysis);
|
|
309
|
+
const gateDecision = finiteString(analysis?.gateDecision);
|
|
310
|
+
const llmDecision = finiteString(analysis?.llmDecision);
|
|
311
|
+
if (gateDecision !== "approved" && gateDecision !== "rejected" || llmDecision !== "approved" && llmDecision !== "rejected") {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
return { gateDecision, llmDecision };
|
|
315
|
+
}).filter(
|
|
316
|
+
(comparison) => Boolean(comparison)
|
|
317
|
+
);
|
|
200
318
|
const allocatorDecisions = signalEvaluations.map((evaluation) => asRecord(evaluation.allocatorDecision)).filter((item) => item != null);
|
|
201
319
|
const riskDecisions = signalEvaluations.map((evaluation) => asRecord(evaluation.riskDecision)).filter((item) => item != null);
|
|
202
320
|
const orderAttempts = rows.signals.filter(
|
|
@@ -207,27 +325,30 @@ var buildRuntimeScorecard = ({
|
|
|
207
325
|
return trade.status === "closed" && exitTimestamp != null && exitTimestamp >= startTime && exitTimestamp < endTime;
|
|
208
326
|
});
|
|
209
327
|
const replayComparison = getReplayComparison(replayEvidenceArtifact);
|
|
210
|
-
const replayCounts =
|
|
211
|
-
const matched =
|
|
212
|
-
const backtestOnly =
|
|
213
|
-
const runtimeOnly =
|
|
328
|
+
const replayCounts = replayCountsForStrategy(replayComparison, strategy);
|
|
329
|
+
const matched = replayCounts.matched;
|
|
330
|
+
const backtestOnly = replayCounts.backtestOnly;
|
|
331
|
+
const runtimeOnly = replayCounts.runtimeOnly;
|
|
214
332
|
const comparisonTotal = matched + backtestOnly + runtimeOnly;
|
|
215
333
|
const parityRatio = comparisonTotal > 0 ? matched / comparisonTotal : null;
|
|
216
334
|
const lineage = asRecord(replayComparison?.lineage);
|
|
217
335
|
const lineageReason = finiteString(lineage?.reason);
|
|
218
|
-
const
|
|
336
|
+
const calibrationRoot = asRecord(calibrationArtifact) ?? {};
|
|
337
|
+
const calibrationSummary = asRecord(calibrationRoot.summary);
|
|
338
|
+
const calibration = strategy ? asRecord(asRecord(calibrationSummary?.byStrategy)?.[strategy]) : getCalibrationSummary(calibrationArtifact);
|
|
219
339
|
const actualSlippageBps = finiteNumber(
|
|
220
340
|
asRecord(calibration?.signalToFillAdverseBps)?.avg
|
|
221
341
|
);
|
|
222
342
|
const residualVsModelBps = finiteNumber(
|
|
223
343
|
asRecord(calibration?.residualVsCurrentModelBps)?.avg
|
|
224
344
|
);
|
|
345
|
+
const prospective = getProspectiveSummary(prospectiveEvidenceArtifact);
|
|
225
346
|
const historyTrades = dedupeTrades([
|
|
226
|
-
...
|
|
227
|
-
|
|
347
|
+
...scopedHistoryArtifacts,
|
|
348
|
+
scopedRuntimeArtifact
|
|
228
349
|
]);
|
|
229
350
|
const currentDistributions = buildDistributions(rows);
|
|
230
|
-
const previousArtifact =
|
|
351
|
+
const previousArtifact = scopedHistoryArtifacts.map((artifact) => ({ artifact, window: runtimeWindow(artifact) })).filter(({ window: candidateWindow }) => {
|
|
231
352
|
const candidateEndTime = finiteNumber(candidateWindow.endTime);
|
|
232
353
|
return candidateEndTime != null && candidateEndTime <= startTime;
|
|
233
354
|
}).sort(
|
|
@@ -286,6 +407,8 @@ var buildRuntimeScorecard = ({
|
|
|
286
407
|
schemaVersion: 1,
|
|
287
408
|
generatedAt,
|
|
288
409
|
window: { startTime, endTime },
|
|
410
|
+
strategy,
|
|
411
|
+
lineage: runtimeLineage,
|
|
289
412
|
promotionStatus,
|
|
290
413
|
thresholds,
|
|
291
414
|
funnel: {
|
|
@@ -342,6 +465,24 @@ var buildRuntimeScorecard = ({
|
|
|
342
465
|
currentClosedTrades.map((trade) => finiteNumber(trade.fundingFee))
|
|
343
466
|
)
|
|
344
467
|
},
|
|
468
|
+
gateComparison: {
|
|
469
|
+
policy: llmComparatorPolicy,
|
|
470
|
+
eligible: llmEligibleEvaluations.length,
|
|
471
|
+
compared: gateComparisons.length,
|
|
472
|
+
coverage: llmEligibleEvaluations.length ? round(gateComparisons.length / llmEligibleEvaluations.length) : null,
|
|
473
|
+
agreements: gateComparisons.filter(
|
|
474
|
+
(comparison) => comparison.gateDecision === comparison.llmDecision
|
|
475
|
+
).length,
|
|
476
|
+
disagreements: gateComparisons.filter(
|
|
477
|
+
(comparison) => comparison.gateDecision !== comparison.llmDecision
|
|
478
|
+
).length,
|
|
479
|
+
gateApprovedLlmRejected: gateComparisons.filter(
|
|
480
|
+
(comparison) => comparison.gateDecision === "approved" && comparison.llmDecision === "rejected"
|
|
481
|
+
).length,
|
|
482
|
+
gateRejectedLlmApproved: gateComparisons.filter(
|
|
483
|
+
(comparison) => comparison.gateDecision === "rejected" && comparison.llmDecision === "approved"
|
|
484
|
+
).length
|
|
485
|
+
},
|
|
345
486
|
parity: {
|
|
346
487
|
available: comparisonTotal > 0,
|
|
347
488
|
matched,
|
|
@@ -355,6 +496,7 @@ var buildRuntimeScorecard = ({
|
|
|
355
496
|
actualSignalToFillSlippageBps: actualSlippageBps,
|
|
356
497
|
residualVsCurrentModelBps: residualVsModelBps
|
|
357
498
|
},
|
|
499
|
+
prospective,
|
|
358
500
|
rolling,
|
|
359
501
|
distributions: currentDistributions,
|
|
360
502
|
distributionChanges: {
|
|
@@ -407,6 +549,7 @@ Window: ${new Date(scorecard.window.startTime).toISOString()} \u2014 ${new Date(
|
|
|
407
549
|
- Evaluations: ${scorecard.funnel.evaluations}
|
|
408
550
|
- Core candidates: ${scorecard.funnel.coreCandidates}
|
|
409
551
|
- Gate approvals/rejects: ${scorecard.funnel.gate.approved}/${scorecard.funnel.gate.rejected}
|
|
552
|
+
- AI / LLM disagreement: ${scorecard.gateComparison.disagreements}/${scorecard.gateComparison.compared}
|
|
410
553
|
- Allocator approvals/rejects: ${scorecard.funnel.allocator.available ? `${scorecard.funnel.allocator.approved}/${scorecard.funnel.allocator.rejected}` : "unavailable"}
|
|
411
554
|
- Risk approvals/rejects: ${scorecard.funnel.risk.available ? `${scorecard.funnel.risk.approved}/${scorecard.funnel.risk.rejected}` : "unavailable"}
|
|
412
555
|
- Order attempts/failures: ${scorecard.funnel.orderAttempts}/${scorecard.funnel.orderFailures}
|
|
@@ -40,57 +40,11 @@ var import_chalk = __toESM(require("chalk"));
|
|
|
40
40
|
// src/lib/runtimeRedis.ts
|
|
41
41
|
var import_redis = require("@tradejs/infra/redis");
|
|
42
42
|
var import_time = require("@tradejs/core/time");
|
|
43
|
-
var
|
|
44
|
-
var RESERVED_CONFIG_IDS = /* @__PURE__ */ new Set(["results"]);
|
|
45
|
-
var resolveStrategyConfigIdentityByKey = (userName, key) => {
|
|
46
|
-
const parts = key.split(":");
|
|
47
|
-
if (parts.length !== 5) return null;
|
|
48
|
-
const [users, keyUserName, strategiesKey, strategyName, configId] = parts;
|
|
49
|
-
if (users !== "users" || keyUserName !== userName || strategiesKey !== "strategies" || !strategyName || !configId || RESERVED_STRATEGY_NAMES.has(strategyName) || RESERVED_CONFIG_IDS.has(configId) || !/^[a-zA-Z0-9_-]+$/.test(configId)) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
return { strategyName, configId };
|
|
53
|
-
};
|
|
43
|
+
var import_runtimeStrategyConfigs = require("@tradejs/infra/runtimeStrategyConfigs");
|
|
54
44
|
var isRuntimeStrategyEnabled = (strategyConfig) => {
|
|
55
45
|
const enabled = strategyConfig.ENABLE;
|
|
56
46
|
return enabled !== false;
|
|
57
47
|
};
|
|
58
|
-
var resolveStrategyNameByConfigKey = (userName, key) => {
|
|
59
|
-
return resolveStrategyConfigIdentityByKey(userName, key)?.strategyName ?? null;
|
|
60
|
-
};
|
|
61
|
-
var getRuntimeStrategyConfigKeys = async (userName) => {
|
|
62
|
-
const keys = await (0, import_redis.getKeys)(`${import_redis.redisKeys.strategies(userName)}:`);
|
|
63
|
-
return keys.filter((key) => resolveStrategyConfigIdentityByKey(userName, key)).sort((left, right) => left.localeCompare(right));
|
|
64
|
-
};
|
|
65
|
-
var loadRuntimeStrategyConfigs = async (userName, {
|
|
66
|
-
onInvalidConfig
|
|
67
|
-
} = {}) => {
|
|
68
|
-
const configKeys = await getRuntimeStrategyConfigKeys(userName);
|
|
69
|
-
const strategyConfigs = await Promise.all(
|
|
70
|
-
configKeys.map(async (key) => {
|
|
71
|
-
const strategyName = resolveStrategyNameByConfigKey(userName, key);
|
|
72
|
-
const identity = resolveStrategyConfigIdentityByKey(userName, key);
|
|
73
|
-
if (!strategyName || !identity) {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
const strategyConfig = await (0, import_redis.getData)(
|
|
77
|
-
key,
|
|
78
|
-
null
|
|
79
|
-
);
|
|
80
|
-
if (!strategyConfig || typeof strategyConfig !== "object" || Array.isArray(strategyConfig)) {
|
|
81
|
-
onInvalidConfig?.(key);
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
return {
|
|
85
|
-
key,
|
|
86
|
-
strategyName,
|
|
87
|
-
configId: identity.configId,
|
|
88
|
-
strategyConfig
|
|
89
|
-
};
|
|
90
|
-
})
|
|
91
|
-
);
|
|
92
|
-
return strategyConfigs.filter(Boolean);
|
|
93
|
-
};
|
|
94
48
|
|
|
95
49
|
// src/lib/runtimeStrategyBacktest.ts
|
|
96
50
|
var BACKTEST_CLI_RUNTIME_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -105,7 +59,7 @@ var toStrategyConfigGrid = (strategyConfig) => Object.fromEntries(
|
|
|
105
59
|
Object.entries(strategyConfig).filter(([key]) => !BACKTEST_CLI_RUNTIME_CONFIG_KEYS.has(key)).map(([key, value]) => [key, [value]])
|
|
106
60
|
);
|
|
107
61
|
var loadRuntimeStrategyBacktestConfigs = async (userName) => {
|
|
108
|
-
const configs = await loadRuntimeStrategyConfigs(userName, {
|
|
62
|
+
const configs = await (0, import_runtimeStrategyConfigs.loadRuntimeStrategyConfigs)(userName, {
|
|
109
63
|
onInvalidConfig: (key) => {
|
|
110
64
|
console.log(import_chalk.default.yellow(`Skip invalid runtime strategy config: ${key}`));
|
|
111
65
|
}
|