@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.
Files changed (56) hide show
  1. package/dist/cli.js +14522 -15704
  2. package/dist/lib/aiExport.js +15 -1608
  3. package/dist/lib/aiPocketSearch.js +499 -67
  4. package/dist/lib/aiPocketSearchCli.js +113 -0
  5. package/dist/lib/aiTrainCharts.js +14 -1
  6. package/dist/lib/aiTrainMetrics.js +4 -1
  7. package/dist/lib/aiTrainResearch.js +88 -54
  8. package/dist/lib/binanceMarketContextBackfill.js +7 -6
  9. package/dist/lib/coinMarketCapContextBackfill.js +19 -18
  10. package/dist/lib/coreResearch.js +2738 -0
  11. package/dist/lib/derivativesContextBackfill.js +10 -9
  12. package/dist/lib/executionCalibration.js +6 -3
  13. package/dist/lib/hyperliquidWhaleBackfill.js +8 -8
  14. package/dist/lib/hyperliquidWhaleStream.js +4 -4
  15. package/dist/lib/marketContextPrepare.js +83 -1686
  16. package/dist/lib/runEnvironment.js +14 -1666
  17. package/dist/lib/runFormatting.js +1 -1607
  18. package/dist/lib/runtimeDebugEvidence.js +2 -48
  19. package/dist/lib/runtimeLineage.js +81 -48
  20. package/dist/lib/runtimeRedis.js +6 -57
  21. package/dist/lib/runtimeScorecard.js +153 -10
  22. package/dist/lib/runtimeStrategyBacktest.js +2 -48
  23. package/dist/lib/strategyGateFingerprint.js +80 -0
  24. package/dist/lib/strategyRelease.js +1365 -0
  25. package/dist/scripts/aiExport.js +17 -1610
  26. package/dist/scripts/aiPocketSearch.js +1070 -1770
  27. package/dist/scripts/aiTrain.js +108 -1688
  28. package/dist/scripts/backtest.js +120 -1760
  29. package/dist/scripts/binanceMarketIngest.js +5 -4
  30. package/dist/scripts/candlesMigrateProvider.js +6 -5186
  31. package/dist/scripts/cleanupMarketContext.js +4 -3
  32. package/dist/scripts/continuity.js +4 -3
  33. package/dist/scripts/coreResearch.js +2830 -0
  34. package/dist/scripts/derivativesIngest.js +6 -4
  35. package/dist/scripts/derivativesIngestCoinalyzeAll.js +5 -4
  36. package/dist/scripts/doctor.js +2 -5184
  37. package/dist/scripts/executionCalibration.js +6 -3
  38. package/dist/scripts/hyperliquidWhaleBackfill.js +8 -8
  39. package/dist/scripts/hyperliquidWhaleIngest.js +4 -4
  40. package/dist/scripts/hyperliquidWhalesUpdate.js +1 -1
  41. package/dist/scripts/migration.js +3 -3
  42. package/dist/scripts/mlExport.js +1 -1607
  43. package/dist/scripts/replay.js +239 -1855
  44. package/dist/scripts/replayRunner.js +239 -1855
  45. package/dist/scripts/replayRuntimeEvidence.js +29 -62
  46. package/dist/scripts/researchAuto.js +3 -49
  47. package/dist/scripts/results.js +1 -1607
  48. package/dist/scripts/runtimeEvidence.js +18 -56
  49. package/dist/scripts/runtimeParity.js +121 -1746
  50. package/dist/scripts/runtimeScorecard.js +883 -29
  51. package/dist/scripts/signals.js +1178 -2724
  52. package/dist/scripts/signalsDaemon.js +1183 -2696
  53. package/dist/scripts/signalsSummary.js +4 -26
  54. package/dist/scripts/strategyRelease.js +1469 -0
  55. package/dist/workers/testerWorker.js +2 -2
  56. 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 RESERVED_STRATEGY_NAMES = /* @__PURE__ */ new Set(["charts"]);
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 import_types = require("@tradejs/types");
45
- var import_strategies = require("@tradejs/node/strategies");
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, import_node_crypto.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
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 import_promises.default.readFile(filePath);
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
- import_node_path.default.join(
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(import_node_path.default.join(projectRoot, relativePath));
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 resolveGateFingerprint = async ({
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
- return cached;
213
- }
214
- const relativeCandidates = [
215
- `packages/strategies/src/${strategyName}/adapters/ai.ts`,
216
- `packages/strategies/src/${strategyName}/guardrails.ts`,
217
- `packages/strategies/src/${strategyName}/pockets.ts`,
218
- `packages/strategies/src/${strategyName}/config.ts`,
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 resolveGateFingerprint({
307
+ gateFingerprint: (await resolveRuntimeGateFingerprint({
280
308
  projectRoot,
281
309
  strategyName,
282
310
  gitSha: git.gitSha
283
- }),
284
- configFingerprint: fingerprintRuntimeValue(config),
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,
@@ -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 RESERVED_STRATEGY_NAMES = /* @__PURE__ */ new Set(["charts"]);
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 rows = extractRuntimeRows(runtimeArtifact);
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 = asRecord(replayComparison?.counts);
211
- const matched = finiteNumber(replayCounts?.matched) ?? 0;
212
- const backtestOnly = finiteNumber(replayCounts?.backtestOnly) ?? 0;
213
- const runtimeOnly = finiteNumber(replayCounts?.runtimeOnly) ?? 0;
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 calibration = getCalibrationSummary(calibrationArtifact);
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
- ...historyRuntimeArtifacts,
227
- runtimeArtifact
347
+ ...scopedHistoryArtifacts,
348
+ scopedRuntimeArtifact
228
349
  ]);
229
350
  const currentDistributions = buildDistributions(rows);
230
- const previousArtifact = historyRuntimeArtifacts.map((artifact) => ({ artifact, window: runtimeWindow(artifact) })).filter(({ window: candidateWindow }) => {
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 RESERVED_STRATEGY_NAMES = /* @__PURE__ */ new Set(["charts"]);
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
  }