@tradejs/cli 3.1.8 → 3.1.9-beta.214
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 +856 -334
- package/dist/lib/runEnvironment.js +88 -48
- package/dist/lib/runtimeDebugEvidence.js +48 -12
- package/dist/lib/runtimeEvidenceDeployment.js +162 -0
- package/dist/lib/runtimeLineage.js +9 -1
- package/dist/lib/runtimeScorecard.js +119 -8
- package/dist/lib/runtimeSignalsLoader.js +8 -4
- package/dist/scripts/backtest.js +50 -49
- package/dist/scripts/replay.js +921 -648
- package/dist/scripts/replayRunner.js +921 -648
- package/dist/scripts/replayRuntimeEvidence.js +50 -13
- package/dist/scripts/runtimeEvidence.js +136 -18
- package/dist/scripts/runtimeScorecard.js +143 -44
- package/dist/scripts/signals.js +47 -8
- package/dist/scripts/signalsDaemon.js +47 -8
- package/dist/scripts/signalsSummary.js +12 -5
- package/package.json +7 -7
package/dist/scripts/replay.js
CHANGED
|
@@ -72,6 +72,57 @@ var import_connectors3 = require("@tradejs/node/connectors");
|
|
|
72
72
|
var import_cli3 = require("@tradejs/node/cli");
|
|
73
73
|
var import_constants = require("@tradejs/core/constants");
|
|
74
74
|
var import_time2 = require("@tradejs/core/time");
|
|
75
|
+
var import_runtimeStrategies = require("@tradejs/node/runtimeStrategies");
|
|
76
|
+
|
|
77
|
+
// src/lib/runtimeStrategyBacktest.ts
|
|
78
|
+
var import_chalk = __toESM(require("chalk"));
|
|
79
|
+
|
|
80
|
+
// src/lib/runtimeRedis.ts
|
|
81
|
+
var import_redis = require("@tradejs/infra/redis");
|
|
82
|
+
var import_time = require("@tradejs/core/time");
|
|
83
|
+
var import_runtimeStrategyConfigs = require("@tradejs/infra/runtimeStrategyConfigs");
|
|
84
|
+
var isRuntimeTradeRecord = (value) => {
|
|
85
|
+
if (!value || typeof value !== "object") {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const record = value;
|
|
89
|
+
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");
|
|
90
|
+
};
|
|
91
|
+
var loadRuntimeTrades = async (userName, {
|
|
92
|
+
startTime,
|
|
93
|
+
endTime
|
|
94
|
+
} = {}) => {
|
|
95
|
+
const hasWindow = Number.isFinite(startTime) && Number.isFinite(endTime) && startTime != null && endTime != null;
|
|
96
|
+
const filterByWindow = (trade) => !hasWindow || trade.entryTimestamp >= startTime && trade.entryTimestamp <= endTime;
|
|
97
|
+
if (hasWindow) {
|
|
98
|
+
const dayKeys = (0, import_time.getRuntimeStorageDayKeys)(startTime, endTime);
|
|
99
|
+
const bucketTrades = (await Promise.all(
|
|
100
|
+
dayKeys.map(
|
|
101
|
+
(dayKey) => (0, import_redis.getHashJsonValues)(
|
|
102
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey)
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
)).flat();
|
|
106
|
+
const dedupedBucketTrades = /* @__PURE__ */ new Map();
|
|
107
|
+
for (const trade of bucketTrades) {
|
|
108
|
+
if (!isRuntimeTradeRecord(trade)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
dedupedBucketTrades.set(trade.orderId, trade);
|
|
112
|
+
}
|
|
113
|
+
const windowedTrades = [...dedupedBucketTrades.values()].filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
|
|
114
|
+
if (windowedTrades.length > 0 || dayKeys.length === 0) {
|
|
115
|
+
return windowedTrades;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const tradePrefix = import_redis.redisKeys.runtimeTrades(userName);
|
|
119
|
+
const bucketPrefix = import_redis.redisKeys.runtimeTradeBuckets(userName);
|
|
120
|
+
const keys = (await (0, import_redis.getKeys)(tradePrefix)).filter(
|
|
121
|
+
(key) => !key.startsWith(bucketPrefix)
|
|
122
|
+
);
|
|
123
|
+
const trades = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
|
|
124
|
+
return trades.filter(isRuntimeTradeRecord).filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
|
|
125
|
+
};
|
|
75
126
|
|
|
76
127
|
// src/lib/timeWindow.ts
|
|
77
128
|
var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -175,95 +226,6 @@ var resolveTimeWindow = ({
|
|
|
175
226
|
);
|
|
176
227
|
};
|
|
177
228
|
|
|
178
|
-
// src/lib/runtimeStrategyBacktest.ts
|
|
179
|
-
var import_chalk = __toESM(require("chalk"));
|
|
180
|
-
|
|
181
|
-
// src/lib/runtimeRedis.ts
|
|
182
|
-
var import_redis = require("@tradejs/infra/redis");
|
|
183
|
-
var import_time = require("@tradejs/core/time");
|
|
184
|
-
var import_runtimeStrategyConfigs = require("@tradejs/infra/runtimeStrategyConfigs");
|
|
185
|
-
var isRuntimeStrategyEnabled = (strategyConfig) => {
|
|
186
|
-
const enabled = strategyConfig.ENABLE;
|
|
187
|
-
return enabled !== false;
|
|
188
|
-
};
|
|
189
|
-
var isRuntimeTradeRecord = (value) => {
|
|
190
|
-
if (!value || typeof value !== "object") {
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
const record = value;
|
|
194
|
-
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");
|
|
195
|
-
};
|
|
196
|
-
var loadRuntimeTrades = async (userName, {
|
|
197
|
-
startTime,
|
|
198
|
-
endTime
|
|
199
|
-
} = {}) => {
|
|
200
|
-
const hasWindow = Number.isFinite(startTime) && Number.isFinite(endTime) && startTime != null && endTime != null;
|
|
201
|
-
const filterByWindow = (trade) => !hasWindow || trade.entryTimestamp >= startTime && trade.entryTimestamp <= endTime;
|
|
202
|
-
if (hasWindow) {
|
|
203
|
-
const dayKeys = (0, import_time.getRuntimeStorageDayKeys)(startTime, endTime);
|
|
204
|
-
const bucketTrades = (await Promise.all(
|
|
205
|
-
dayKeys.map(
|
|
206
|
-
(dayKey) => (0, import_redis.getHashJsonValues)(
|
|
207
|
-
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey)
|
|
208
|
-
)
|
|
209
|
-
)
|
|
210
|
-
)).flat();
|
|
211
|
-
const dedupedBucketTrades = /* @__PURE__ */ new Map();
|
|
212
|
-
for (const trade of bucketTrades) {
|
|
213
|
-
if (!isRuntimeTradeRecord(trade)) {
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
dedupedBucketTrades.set(trade.orderId, trade);
|
|
217
|
-
}
|
|
218
|
-
const windowedTrades = [...dedupedBucketTrades.values()].filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
|
|
219
|
-
if (windowedTrades.length > 0 || dayKeys.length === 0) {
|
|
220
|
-
return windowedTrades;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const tradePrefix = import_redis.redisKeys.runtimeTrades(userName);
|
|
224
|
-
const bucketPrefix = import_redis.redisKeys.runtimeTradeBuckets(userName);
|
|
225
|
-
const keys = (await (0, import_redis.getKeys)(tradePrefix)).filter(
|
|
226
|
-
(key) => !key.startsWith(bucketPrefix)
|
|
227
|
-
);
|
|
228
|
-
const trades = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
|
|
229
|
-
return trades.filter(isRuntimeTradeRecord).filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// src/lib/runtimeStrategyBacktest.ts
|
|
233
|
-
var BACKTEST_CLI_RUNTIME_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
234
|
-
"ENV",
|
|
235
|
-
"INTERVAL",
|
|
236
|
-
"MAKE_ORDERS",
|
|
237
|
-
"CLOSE_OPPOSITE_POSITIONS",
|
|
238
|
-
"BACKTEST_PRICE_MODE",
|
|
239
|
-
"BACKTEST_ENTRY_DELAY_BARS"
|
|
240
|
-
]);
|
|
241
|
-
var toStrategyConfigGrid = (strategyConfig) => Object.fromEntries(
|
|
242
|
-
Object.entries(strategyConfig).filter(([key]) => !BACKTEST_CLI_RUNTIME_CONFIG_KEYS.has(key)).map(([key, value]) => [key, [value]])
|
|
243
|
-
);
|
|
244
|
-
var loadRuntimeStrategyBacktestConfigs = async (userName) => {
|
|
245
|
-
const configs = await (0, import_runtimeStrategyConfigs.loadRuntimeStrategyConfigs)(userName, {
|
|
246
|
-
onInvalidConfig: (key) => {
|
|
247
|
-
console.log(import_chalk.default.yellow(`Skip invalid runtime strategy config: ${key}`));
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
return configs.filter(({ configId }) => configId === "config").filter(({ strategyName, strategyConfig }) => {
|
|
251
|
-
const enabled = isRuntimeStrategyEnabled(strategyConfig);
|
|
252
|
-
if (!enabled) {
|
|
253
|
-
console.log(
|
|
254
|
-
import_chalk.default.yellow(
|
|
255
|
-
`Skip inactive runtime strategy config by ENABLE=false: ${strategyName}`
|
|
256
|
-
)
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
return enabled;
|
|
260
|
-
}).map(({ strategyName, strategyConfig }) => ({
|
|
261
|
-
strategyName,
|
|
262
|
-
strategyConfig,
|
|
263
|
-
backtestConfig: toStrategyConfigGrid(strategyConfig)
|
|
264
|
-
}));
|
|
265
|
-
};
|
|
266
|
-
|
|
267
229
|
// src/lib/marketData/historyPrepare.ts
|
|
268
230
|
var import_chalk2 = __toESM(require("chalk"));
|
|
269
231
|
var import_connectors = require("@tradejs/connectors");
|
|
@@ -537,17 +499,43 @@ var resolveRunConnectorName = async ({
|
|
|
537
499
|
);
|
|
538
500
|
return import_connectors3.DEFAULT_CONNECTOR_NAME;
|
|
539
501
|
};
|
|
540
|
-
var
|
|
541
|
-
|
|
542
|
-
|
|
502
|
+
var loadDeploymentReplayStrategies = async ({
|
|
503
|
+
userName,
|
|
504
|
+
projectRoot,
|
|
505
|
+
deploymentId
|
|
506
|
+
}) => {
|
|
507
|
+
const [deployment, runtimeStrategies] = await Promise.all([
|
|
508
|
+
(0, import_runtimeStrategies.getRuntimeDeployment)({ userName, projectRoot, deploymentId }),
|
|
509
|
+
(0, import_runtimeStrategies.loadResolvedRuntimeStrategies)({ userName, projectRoot, deploymentId })
|
|
510
|
+
]);
|
|
511
|
+
if (!deployment) {
|
|
512
|
+
throw new Error(`Runtime deployment not found: ${deploymentId}`);
|
|
513
|
+
}
|
|
514
|
+
const strategies = deployment.enabled ? runtimeStrategies.filter((strategy) => strategy.enabled).map(
|
|
515
|
+
({
|
|
516
|
+
strategyName,
|
|
517
|
+
version,
|
|
518
|
+
strategyPackage,
|
|
519
|
+
strategyPackageVersion,
|
|
520
|
+
runtimePackageVersion,
|
|
521
|
+
sourceStrategyConfig
|
|
522
|
+
}) => ({
|
|
523
|
+
strategyName,
|
|
524
|
+
version,
|
|
525
|
+
strategyPackage,
|
|
526
|
+
strategyPackageVersion,
|
|
527
|
+
runtimePackageVersion,
|
|
528
|
+
strategyConfig: sourceStrategyConfig
|
|
529
|
+
})
|
|
530
|
+
) : [];
|
|
531
|
+
if (!strategies.length) {
|
|
543
532
|
console.log(
|
|
544
533
|
import_chalk3.default.yellow(
|
|
545
|
-
`No
|
|
534
|
+
`No enabled strategies found in runtime deployment ${deploymentId}`
|
|
546
535
|
)
|
|
547
536
|
);
|
|
548
|
-
return [];
|
|
549
537
|
}
|
|
550
|
-
return
|
|
538
|
+
return { deployment, strategies };
|
|
551
539
|
};
|
|
552
540
|
var prepareRunEnvironment = async ({
|
|
553
541
|
connector,
|
|
@@ -4304,6 +4292,11 @@ import_args.default.option(
|
|
|
4304
4292
|
"runtimeEvidence",
|
|
4305
4293
|
"Immutable runtime evidence JSON used for offline runtime comparison"
|
|
4306
4294
|
);
|
|
4295
|
+
import_args.default.option(
|
|
4296
|
+
"deployment",
|
|
4297
|
+
"Git-owned runtime deployment used when runtime evidence is omitted",
|
|
4298
|
+
"production"
|
|
4299
|
+
);
|
|
4307
4300
|
import_args.default.option(["u", "updateOnly"], "Only update tickers history", false);
|
|
4308
4301
|
import_args.default.option(["C", "cacheOnly"], "Do not update tickers history", false);
|
|
4309
4302
|
import_args.default.option(
|
|
@@ -4330,97 +4323,673 @@ var replayInterval = String(replayFlags.timeframe ?? 15);
|
|
|
4330
4323
|
var replayUserName = String(replayFlags.user ?? "root");
|
|
4331
4324
|
var replayProjectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
|
|
4332
4325
|
var replayRuntimeEvidencePath = typeof replayFlags.runtimeEvidence === "string" && replayFlags.runtimeEvidence.trim() ? replayFlags.runtimeEvidence.trim() : null;
|
|
4326
|
+
var replayDeploymentId = String(
|
|
4327
|
+
replayFlags.deployment ?? "production"
|
|
4328
|
+
).trim();
|
|
4333
4329
|
var isReplayUpdateOnlyRun = Boolean(replayFlags.updateOnly);
|
|
4334
4330
|
|
|
4335
|
-
// src/lib/replay/
|
|
4336
|
-
var
|
|
4331
|
+
// src/lib/replay/runtimeEvidenceSource.ts
|
|
4332
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
4333
|
+
var import_node_path3 = __toESM(require("path"));
|
|
4337
4334
|
|
|
4338
|
-
// src/lib/
|
|
4339
|
-
var
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
}) => ({
|
|
4347
|
-
...strategyConfig,
|
|
4348
|
-
ENV: env,
|
|
4349
|
-
MAKE_ORDERS: makeOrders,
|
|
4350
|
-
INTERVAL: strategyConfig.INTERVAL ?? interval,
|
|
4351
|
-
...typeof recordRuntimeTrades === "boolean" ? { RECORD_RUNTIME_TRADES: recordRuntimeTrades } : {},
|
|
4352
|
-
...aiReplayAnalyses?.length ? { AI_REPLAY_ANALYSES: aiReplayAnalyses } : {}
|
|
4353
|
-
});
|
|
4335
|
+
// src/lib/runtimeLineage.ts
|
|
4336
|
+
var import_node_child_process = require("child_process");
|
|
4337
|
+
var import_node_crypto2 = require("crypto");
|
|
4338
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
4339
|
+
var import_node_path2 = __toESM(require("path"));
|
|
4340
|
+
var import_types2 = require("@tradejs/types");
|
|
4341
|
+
var import_strategies3 = require("@tradejs/node/strategies");
|
|
4342
|
+
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
4354
4343
|
|
|
4355
|
-
// src/lib/
|
|
4356
|
-
var
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
import_chalk8.default.cyan("BT ENTRIES"),
|
|
4368
|
-
import_chalk8.default.cyan("BT PNL"),
|
|
4369
|
-
import_chalk8.default.yellow("RT TRADES"),
|
|
4370
|
-
import_chalk8.default.yellow("RT PNL"),
|
|
4371
|
-
import_chalk8.default.green("MATCHED"),
|
|
4372
|
-
import_chalk8.default.red("ORDER FAILED"),
|
|
4373
|
-
import_chalk8.default.yellow("RT ONLY"),
|
|
4374
|
-
import_chalk8.default.magenta("BT ONLY"),
|
|
4375
|
-
import_chalk8.default.red("UNMATCHED")
|
|
4344
|
+
// src/lib/strategyGateFingerprint.ts
|
|
4345
|
+
var import_node_crypto = require("crypto");
|
|
4346
|
+
var import_node_module = require("module");
|
|
4347
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
4348
|
+
var import_node_path = __toESM(require("path"));
|
|
4349
|
+
var toPackageToken = (strategyName) => strategyName.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
|
|
4350
|
+
var getStrategyPackageName = (strategyName) => strategyName === "TrendLine" || strategyName === "ReverseTrendLine" ? "@tradejs/strategy-trend-line" : `@tradejs/strategy-${toPackageToken(strategyName)}`;
|
|
4351
|
+
var gateFingerprintSpecifiers = (strategyName) => [
|
|
4352
|
+
getStrategyPackageName(strategyName),
|
|
4353
|
+
"@tradejs/strategy-kit/ai-gate",
|
|
4354
|
+
"@tradejs/node/ai",
|
|
4355
|
+
"@tradejs/core/strategies"
|
|
4376
4356
|
];
|
|
4377
|
-
var
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
var toRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4393
|
-
var compactReplayAdditionalIndicators = (additionalIndicators) => {
|
|
4394
|
-
const additional = toRecord(additionalIndicators);
|
|
4395
|
-
if (!additional) {
|
|
4396
|
-
return void 0;
|
|
4397
|
-
}
|
|
4398
|
-
const compact = {};
|
|
4399
|
-
if (additional.executionSlippage != null) {
|
|
4400
|
-
compact.executionSlippage = additional.executionSlippage;
|
|
4401
|
-
}
|
|
4402
|
-
if (additional.marketContext != null) {
|
|
4403
|
-
compact.marketContext = additional.marketContext;
|
|
4404
|
-
}
|
|
4405
|
-
const baseContext = toRecord(additional.baseContext);
|
|
4406
|
-
const relative = toRecord(baseContext?.relative);
|
|
4407
|
-
const execution = toRecord(relative?.execution);
|
|
4408
|
-
if (execution) {
|
|
4409
|
-
compact.baseContext = {
|
|
4410
|
-
relative: {
|
|
4411
|
-
execution
|
|
4357
|
+
var resolveStrategyGateFingerprint = async ({
|
|
4358
|
+
projectRoot,
|
|
4359
|
+
strategyName,
|
|
4360
|
+
gitSha
|
|
4361
|
+
}) => {
|
|
4362
|
+
const projectManifestPath = import_node_path.default.join(projectRoot, "package.json");
|
|
4363
|
+
const projectExists = await import_promises.default.access(projectManifestPath).then(() => true).catch(() => false);
|
|
4364
|
+
const requireFromProject = projectExists ? (0, import_node_module.createRequire)(projectManifestPath) : null;
|
|
4365
|
+
const candidates = requireFromProject ? await Promise.all(
|
|
4366
|
+
gateFingerprintSpecifiers(strategyName).map(async (specifier) => {
|
|
4367
|
+
let resolvedPath;
|
|
4368
|
+
try {
|
|
4369
|
+
resolvedPath = requireFromProject.resolve(specifier);
|
|
4370
|
+
} catch {
|
|
4371
|
+
return null;
|
|
4412
4372
|
}
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4373
|
+
const content = await import_promises.default.readFile(resolvedPath).catch(() => null);
|
|
4374
|
+
return content == null ? null : { specifier, content };
|
|
4375
|
+
})
|
|
4376
|
+
) : [];
|
|
4377
|
+
const sourceEntries = candidates.filter(
|
|
4378
|
+
(entry) => entry != null
|
|
4379
|
+
);
|
|
4380
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
4381
|
+
hash.update(strategyName);
|
|
4382
|
+
if (sourceEntries.length) {
|
|
4383
|
+
for (const entry of sourceEntries) {
|
|
4384
|
+
hash.update(entry.specifier);
|
|
4385
|
+
hash.update(entry.content);
|
|
4386
|
+
}
|
|
4387
|
+
} else {
|
|
4388
|
+
hash.update(gitSha ?? "unknown-git-sha");
|
|
4389
|
+
}
|
|
4390
|
+
return {
|
|
4391
|
+
gateFingerprint: hash.digest("hex").slice(0, 16),
|
|
4392
|
+
gateFingerprintFiles: sourceEntries.map((entry) => entry.specifier)
|
|
4393
|
+
};
|
|
4394
|
+
};
|
|
4395
|
+
|
|
4396
|
+
// src/lib/runtimeLineage.ts
|
|
4397
|
+
var RUNTIME_CONTEXT_ENV_KEYS = [
|
|
4398
|
+
"AI_MODE",
|
|
4399
|
+
"MIN_AI_QUALITY",
|
|
4400
|
+
"INTERVAL",
|
|
4401
|
+
"DERIVATIVES_CONTEXT_ENABLED",
|
|
4402
|
+
"DERIVATIVES_CONTEXT_TARGET_ENABLED",
|
|
4403
|
+
"DERIVATIVES_CONTEXT_LOOKBACK_HOURS",
|
|
4404
|
+
"DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS",
|
|
4405
|
+
"DERIVATIVES_CONTEXT_EXCHANGE_PRIORITY",
|
|
4406
|
+
"COINMARKETCAP_CONTEXT_ENABLED",
|
|
4407
|
+
"COINMARKETCAP_CONTEXT_MAX_AGE_MS",
|
|
4408
|
+
"COINMARKETCAP_CONTEXT_EXCHANGE_LIQUIDITY_ENABLED",
|
|
4409
|
+
"COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS",
|
|
4410
|
+
"COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED",
|
|
4411
|
+
"COINMARKETCAP_CONTEXT_HISTORICAL_ACCESS_MONTHS",
|
|
4412
|
+
"COINMARKETCAP_CONTEXT_BACKFILL_ENABLED",
|
|
4413
|
+
"COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
|
|
4414
|
+
"COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS",
|
|
4415
|
+
"HYPERLIQUID_WHALE_CONTEXT_ENABLED",
|
|
4416
|
+
"HYPERLIQUID_WHALE_BACKFILL_ENABLED",
|
|
4417
|
+
"HYPERLIQUID_WHALE_MIN_COVERAGE_PCT",
|
|
4418
|
+
"HYPERLIQUID_WHALE_CONCURRENCY",
|
|
4419
|
+
"HYPERLIQUID_WHALE_RATE_LIMIT_WEIGHT"
|
|
4420
|
+
];
|
|
4421
|
+
var RUNTIME_CONTEXT_DATA_MODEL = {
|
|
4422
|
+
derivativesSourceIntervals: "15m",
|
|
4423
|
+
derivativesDerivedIntervals: "1h",
|
|
4424
|
+
derivativesHourlyFallback: "stored-1h",
|
|
4425
|
+
derivativesDataModelVersion: 2,
|
|
4426
|
+
coinMarketCapAvailabilityModel: "closed-interval-all-daily",
|
|
4427
|
+
coinMarketCapDataModelVersion: 3,
|
|
4428
|
+
binanceBreadthUniverseModel: "fixed-versioned-top5-top10-top30-top50-top100",
|
|
4429
|
+
binanceBreadthDataModelVersion: 2,
|
|
4430
|
+
hyperliquidWhaleCanonicalInterval: "1m",
|
|
4431
|
+
hyperliquidWhaleGateMinNotionalUsd: 5e4,
|
|
4432
|
+
hyperliquidWhaleMinCoveragePct: 0.8,
|
|
4433
|
+
hyperliquidWhaleDataModelVersion: import_types2.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
4434
|
+
};
|
|
4435
|
+
var normalizeForStableJson = (value) => {
|
|
4436
|
+
if (Array.isArray(value)) {
|
|
4437
|
+
return value.map(normalizeForStableJson);
|
|
4438
|
+
}
|
|
4439
|
+
if (value && typeof value === "object") {
|
|
4440
|
+
return Object.fromEntries(
|
|
4441
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeForStableJson(entry)])
|
|
4442
|
+
);
|
|
4443
|
+
}
|
|
4444
|
+
return value;
|
|
4445
|
+
};
|
|
4446
|
+
var resolveRuntimeMaxLossValue = (config) => {
|
|
4447
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
4448
|
+
return null;
|
|
4449
|
+
}
|
|
4450
|
+
const strategyConfig = config.strategyConfig;
|
|
4451
|
+
if (!strategyConfig || typeof strategyConfig !== "object" || Array.isArray(strategyConfig)) {
|
|
4452
|
+
return null;
|
|
4453
|
+
}
|
|
4454
|
+
const maxLossValue = Number(
|
|
4455
|
+
strategyConfig.MAX_LOSS_VALUE
|
|
4456
|
+
);
|
|
4457
|
+
return Number.isFinite(maxLossValue) ? maxLossValue : null;
|
|
4458
|
+
};
|
|
4459
|
+
var fingerprintRuntimeValue = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
|
|
4460
|
+
var gitLineageCache = /* @__PURE__ */ new Map();
|
|
4461
|
+
var gateFingerprintCache = /* @__PURE__ */ new Map();
|
|
4462
|
+
var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
|
|
4463
|
+
var snapshotFingerprintCache = /* @__PURE__ */ new Map();
|
|
4464
|
+
var resolveGitLineage = (projectRoot) => {
|
|
4465
|
+
const cached = gitLineageCache.get(projectRoot);
|
|
4466
|
+
if (cached) {
|
|
4467
|
+
return cached;
|
|
4468
|
+
}
|
|
4469
|
+
const envSha = String(process.env.TRADEJS_GIT_SHA ?? "").trim();
|
|
4470
|
+
if (envSha && envSha !== "unknown") {
|
|
4471
|
+
const result = {
|
|
4472
|
+
gitSha: envSha,
|
|
4473
|
+
gitDirty: false
|
|
4474
|
+
};
|
|
4475
|
+
gitLineageCache.set(projectRoot, result);
|
|
4476
|
+
return result;
|
|
4477
|
+
}
|
|
4478
|
+
try {
|
|
4479
|
+
const gitSha = (0, import_node_child_process.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
4480
|
+
cwd: projectRoot,
|
|
4481
|
+
encoding: "utf8",
|
|
4482
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
4483
|
+
}).trim();
|
|
4484
|
+
const status = (0, import_node_child_process.execFileSync)(
|
|
4485
|
+
"git",
|
|
4486
|
+
["status", "--porcelain", "--untracked-files=normal"],
|
|
4487
|
+
{
|
|
4488
|
+
cwd: projectRoot,
|
|
4489
|
+
encoding: "utf8",
|
|
4490
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
4491
|
+
}
|
|
4492
|
+
).trim();
|
|
4493
|
+
const result = {
|
|
4494
|
+
gitSha: gitSha || null,
|
|
4495
|
+
gitDirty: status.length > 0
|
|
4496
|
+
};
|
|
4497
|
+
gitLineageCache.set(projectRoot, result);
|
|
4498
|
+
return result;
|
|
4499
|
+
} catch {
|
|
4500
|
+
const result = {
|
|
4501
|
+
gitSha: null,
|
|
4502
|
+
gitDirty: null
|
|
4503
|
+
};
|
|
4504
|
+
gitLineageCache.set(projectRoot, result);
|
|
4505
|
+
return result;
|
|
4506
|
+
}
|
|
4507
|
+
};
|
|
4508
|
+
var readOptionalFile = async (filePath) => {
|
|
4509
|
+
try {
|
|
4510
|
+
return await import_promises2.default.readFile(filePath);
|
|
4511
|
+
} catch {
|
|
4512
|
+
return null;
|
|
4513
|
+
}
|
|
4514
|
+
};
|
|
4515
|
+
var resolveBinanceBreadthFingerprint = async (projectRoot) => {
|
|
4516
|
+
if (binanceBreadthFingerprintCache.has(projectRoot)) {
|
|
4517
|
+
return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
|
|
4518
|
+
}
|
|
4519
|
+
const content = await readOptionalFile(
|
|
4520
|
+
import_node_path2.default.join(
|
|
4521
|
+
projectRoot,
|
|
4522
|
+
"packages/node/src/config/binanceBreadthUniverses.json"
|
|
4523
|
+
)
|
|
4524
|
+
);
|
|
4525
|
+
let result = null;
|
|
4526
|
+
if (content) {
|
|
4527
|
+
try {
|
|
4528
|
+
const parsed = JSON.parse(content.toString("utf8"));
|
|
4529
|
+
result = typeof parsed.fingerprint === "string" && parsed.fingerprint.trim() ? parsed.fingerprint.trim() : null;
|
|
4530
|
+
} catch {
|
|
4531
|
+
result = null;
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
binanceBreadthFingerprintCache.set(projectRoot, result);
|
|
4535
|
+
return result;
|
|
4536
|
+
};
|
|
4537
|
+
var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
4538
|
+
const cacheKey = `${projectRoot}:${relativePath}`;
|
|
4539
|
+
if (snapshotFingerprintCache.has(cacheKey)) {
|
|
4540
|
+
return snapshotFingerprintCache.get(cacheKey) ?? null;
|
|
4541
|
+
}
|
|
4542
|
+
const content = await readOptionalFile(import_node_path2.default.join(projectRoot, relativePath));
|
|
4543
|
+
let result = null;
|
|
4544
|
+
if (content) {
|
|
4545
|
+
try {
|
|
4546
|
+
const parsed = JSON.parse(content.toString("utf8"));
|
|
4547
|
+
result = typeof parsed.fingerprint === "string" && parsed.fingerprint.trim() ? parsed.fingerprint.trim() : null;
|
|
4548
|
+
} catch {
|
|
4549
|
+
result = null;
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
snapshotFingerprintCache.set(cacheKey, result);
|
|
4553
|
+
return result;
|
|
4554
|
+
};
|
|
4555
|
+
var resolveRuntimeGateFingerprint = async ({
|
|
4556
|
+
projectRoot,
|
|
4557
|
+
strategyName,
|
|
4558
|
+
gitSha
|
|
4559
|
+
}) => {
|
|
4560
|
+
const cacheKey = `${projectRoot}:${strategyName}:${gitSha ?? "unknown"}`;
|
|
4561
|
+
const cached = gateFingerprintCache.get(cacheKey);
|
|
4562
|
+
if (cached) return cached;
|
|
4563
|
+
const result = await resolveStrategyGateFingerprint({
|
|
4564
|
+
projectRoot,
|
|
4565
|
+
strategyName,
|
|
4566
|
+
gitSha
|
|
4567
|
+
});
|
|
4568
|
+
gateFingerprintCache.set(cacheKey, result);
|
|
4569
|
+
return result;
|
|
4570
|
+
};
|
|
4571
|
+
async function buildRuntimeLineage({
|
|
4572
|
+
projectRoot,
|
|
4573
|
+
strategyName,
|
|
4574
|
+
compositionId,
|
|
4575
|
+
version,
|
|
4576
|
+
strategyPackageVersion,
|
|
4577
|
+
runtimePackageVersion,
|
|
4578
|
+
config,
|
|
4579
|
+
runContext,
|
|
4580
|
+
env = process.env
|
|
4581
|
+
}) {
|
|
4582
|
+
if (version != null) {
|
|
4583
|
+
if (!Number.isSafeInteger(version) || version <= 0) {
|
|
4584
|
+
throw new Error(`Invalid runtime strategy version: ${version}`);
|
|
4585
|
+
}
|
|
4586
|
+
return {
|
|
4587
|
+
schemaVersion: 2,
|
|
4588
|
+
version,
|
|
4589
|
+
strategyPackageVersion,
|
|
4590
|
+
runtimePackageVersion,
|
|
4591
|
+
maxLossValue: resolveRuntimeMaxLossValue(config)
|
|
4592
|
+
};
|
|
4593
|
+
}
|
|
4594
|
+
const git = resolveGitLineage(projectRoot);
|
|
4595
|
+
const hyperliquidPerpUniverseFingerprint = typeof import_strategies3.getHyperliquidPerpUniverseSnapshot === "function" ? (0, import_strategies3.getHyperliquidPerpUniverseSnapshot)().fingerprint : await resolveSnapshotFingerprint(
|
|
4596
|
+
projectRoot,
|
|
4597
|
+
"packages/node/src/config/hyperliquidPerpUniverse.json"
|
|
4598
|
+
);
|
|
4599
|
+
const hyperliquidWhaleRegistryFingerprint = typeof import_strategies3.getHyperliquidWhaleRegistrySnapshot === "function" ? (0, import_strategies3.getHyperliquidWhaleRegistrySnapshot)().fingerprint : await resolveSnapshotFingerprint(
|
|
4600
|
+
projectRoot,
|
|
4601
|
+
"packages/node/src/config/hyperliquidWhales.json"
|
|
4602
|
+
);
|
|
4603
|
+
const context = {
|
|
4604
|
+
...Object.fromEntries(
|
|
4605
|
+
RUNTIME_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
|
|
4606
|
+
),
|
|
4607
|
+
...RUNTIME_CONTEXT_DATA_MODEL,
|
|
4608
|
+
binanceBreadthUniverseFingerprint: await resolveBinanceBreadthFingerprint(projectRoot),
|
|
4609
|
+
hyperliquidPerpUniverseFingerprint,
|
|
4610
|
+
hyperliquidWhaleRegistryFingerprint,
|
|
4611
|
+
...runContext
|
|
4612
|
+
};
|
|
4613
|
+
return {
|
|
4614
|
+
schemaVersion: 1,
|
|
4615
|
+
compositionId: compositionId?.trim() || null,
|
|
4616
|
+
...git,
|
|
4617
|
+
gateFingerprint: (await resolveRuntimeGateFingerprint({
|
|
4618
|
+
projectRoot,
|
|
4619
|
+
strategyName,
|
|
4620
|
+
gitSha: git.gitSha
|
|
4621
|
+
})).gateFingerprint,
|
|
4622
|
+
// Position risk is tracked separately so changing trade size does not
|
|
4623
|
+
// masquerade as a change to the core + gate decision logic.
|
|
4624
|
+
configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config),
|
|
4625
|
+
contextFingerprint: fingerprintRuntimeValue(context),
|
|
4626
|
+
maxLossValue: resolveRuntimeMaxLossValue(config)
|
|
4627
|
+
};
|
|
4628
|
+
}
|
|
4629
|
+
var runtimeLineageKey = (lineage) => lineage.schemaVersion === 2 ? [
|
|
4630
|
+
"v2",
|
|
4631
|
+
lineage.version,
|
|
4632
|
+
lineage.strategyPackageVersion ?? "strategy:unknown",
|
|
4633
|
+
lineage.runtimePackageVersion ?? "runtime:unknown"
|
|
4634
|
+
].join(":") : [
|
|
4635
|
+
lineage.schemaVersion,
|
|
4636
|
+
lineage.compositionId ?? "unbound-composition",
|
|
4637
|
+
lineage.gitSha ?? "unknown",
|
|
4638
|
+
lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
|
|
4639
|
+
lineage.gateFingerprint,
|
|
4640
|
+
lineage.configFingerprint,
|
|
4641
|
+
lineage.contextFingerprint
|
|
4642
|
+
].join(":");
|
|
4643
|
+
var runtimeLineagesMatch = (left, right) => left != null && right != null && runtimeLineageKey(left) === runtimeLineageKey(right);
|
|
4644
|
+
var runtimeLineagesComparable = (left, right) => runtimeLineagesMatch(left, right) && typeof left?.maxLossValue === "number" && Number.isFinite(left.maxLossValue) && typeof right?.maxLossValue === "number" && Number.isFinite(right.maxLossValue) && left.maxLossValue === right.maxLossValue;
|
|
4645
|
+
|
|
4646
|
+
// src/lib/runtimeEvidenceDeployment.ts
|
|
4647
|
+
var import_runtimeStrategies2 = require("@tradejs/node/runtimeStrategies");
|
|
4648
|
+
var isRecord = (value) => value != null && typeof value === "object" && !Array.isArray(value);
|
|
4649
|
+
var isNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0;
|
|
4650
|
+
var INTERVALS = /* @__PURE__ */ new Set([
|
|
4651
|
+
"1",
|
|
4652
|
+
"3",
|
|
4653
|
+
"5",
|
|
4654
|
+
"15",
|
|
4655
|
+
"30",
|
|
4656
|
+
"60",
|
|
4657
|
+
"120",
|
|
4658
|
+
"240",
|
|
4659
|
+
"360",
|
|
4660
|
+
"720",
|
|
4661
|
+
"D",
|
|
4662
|
+
"W",
|
|
4663
|
+
"M"
|
|
4664
|
+
]);
|
|
4665
|
+
var UNIVERSES = /* @__PURE__ */ new Set(["crypto", "tradfi"]);
|
|
4666
|
+
var parseRuntimeEvidenceDeploymentSnapshot = (value) => {
|
|
4667
|
+
if (!isRecord(value) || value.schemaVersion !== 1 || !isNonEmptyString(value.id) || !isNonEmptyString(value.label) || !isNonEmptyString(value.connectorName) || !isNonEmptyString(value.provider) || !isNonEmptyString(value.accountId) || typeof value.enabled !== "boolean" || !Array.isArray(value.strategies)) {
|
|
4668
|
+
throw new Error(
|
|
4669
|
+
"Runtime evidence deployment snapshot is missing or invalid"
|
|
4670
|
+
);
|
|
4671
|
+
}
|
|
4672
|
+
const strategies = value.strategies.map((candidate) => {
|
|
4673
|
+
if (!isRecord(candidate) || !isNonEmptyString(candidate.strategyName) || !Number.isSafeInteger(candidate.version) || Number(candidate.version) <= 0 || typeof candidate.enabled !== "boolean" || !["active", "entries_paused"].includes(String(candidate.controlState)) || !INTERVALS.has(String(candidate.interval)) || !UNIVERSES.has(String(candidate.universe)) || !isNonEmptyString(candidate.strategyPackage) || !isNonEmptyString(candidate.strategyPackageVersion) || !isNonEmptyString(candidate.runtimePackageVersion) || !isRecord(candidate.strategyConfig)) {
|
|
4674
|
+
throw new Error(
|
|
4675
|
+
"Runtime evidence deployment strategy snapshot is invalid"
|
|
4676
|
+
);
|
|
4677
|
+
}
|
|
4678
|
+
if (String(candidate.strategyConfig.INTERVAL) !== String(candidate.interval) || String(candidate.strategyConfig.UNIVERSE) !== String(candidate.universe)) {
|
|
4679
|
+
throw new Error(
|
|
4680
|
+
`Runtime evidence strategy config does not match its scope: ${candidate.strategyName}`
|
|
4681
|
+
);
|
|
4682
|
+
}
|
|
4683
|
+
if (candidate.accountId != null && (!isNonEmptyString(candidate.accountId) || candidate.accountId !== value.accountId)) {
|
|
4684
|
+
throw new Error(
|
|
4685
|
+
`Runtime evidence strategy account does not match deployment: ${candidate.strategyName}`
|
|
4686
|
+
);
|
|
4687
|
+
}
|
|
4688
|
+
return candidate;
|
|
4689
|
+
});
|
|
4690
|
+
const identities = /* @__PURE__ */ new Set();
|
|
4691
|
+
for (const strategy of strategies) {
|
|
4692
|
+
if (identities.has(strategy.strategyName)) {
|
|
4693
|
+
throw new Error(
|
|
4694
|
+
`Duplicate runtime evidence strategy: ${strategy.strategyName}`
|
|
4695
|
+
);
|
|
4696
|
+
}
|
|
4697
|
+
identities.add(strategy.strategyName);
|
|
4698
|
+
}
|
|
4699
|
+
return {
|
|
4700
|
+
...value,
|
|
4701
|
+
strategies
|
|
4702
|
+
};
|
|
4703
|
+
};
|
|
4704
|
+
var activeRuntimeEvidenceStrategies = (deployment) => deployment.enabled ? deployment.strategies.filter((strategy) => strategy.enabled) : [];
|
|
4705
|
+
var runtimeDeploymentFromEvidence = (deployment) => ({
|
|
4706
|
+
id: deployment.id,
|
|
4707
|
+
label: deployment.label,
|
|
4708
|
+
connectorName: deployment.connectorName,
|
|
4709
|
+
provider: deployment.provider,
|
|
4710
|
+
accountId: deployment.accountId,
|
|
4711
|
+
enabled: deployment.enabled,
|
|
4712
|
+
strategies: deployment.strategies.map(
|
|
4713
|
+
({ strategyName, version, enabled, controlState }) => ({
|
|
4714
|
+
strategyName,
|
|
4715
|
+
version,
|
|
4716
|
+
enabled,
|
|
4717
|
+
controlState
|
|
4718
|
+
})
|
|
4719
|
+
),
|
|
4720
|
+
...deployment.assetClasses ? { assetClasses: [...deployment.assetClasses] } : {},
|
|
4721
|
+
...deployment.tickers ? { tickers: [...deployment.tickers] } : {}
|
|
4722
|
+
});
|
|
4723
|
+
|
|
4724
|
+
// src/lib/replay/runtimeEvidenceSource.ts
|
|
4725
|
+
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4726
|
+
var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
4727
|
+
var unwrapRows = (value, field) => (Array.isArray(value) ? value : []).map((item) => {
|
|
4728
|
+
const row = asRecord(item);
|
|
4729
|
+
return asRecord(row?.[field]) ?? row;
|
|
4730
|
+
}).filter((item) => item != null);
|
|
4731
|
+
var assertRuntimeRowsMatchDeployment = ({
|
|
4732
|
+
label,
|
|
4733
|
+
rows,
|
|
4734
|
+
deployment
|
|
4735
|
+
}) => {
|
|
4736
|
+
const activeStrategies = new Set(
|
|
4737
|
+
activeRuntimeEvidenceStrategies(deployment).map(
|
|
4738
|
+
({ strategyName }) => strategyName
|
|
4739
|
+
)
|
|
4740
|
+
);
|
|
4741
|
+
for (const row of rows) {
|
|
4742
|
+
if (row.deploymentId !== deployment.id || row.accountId !== deployment.accountId || !row.strategy || !activeStrategies.has(row.strategy)) {
|
|
4743
|
+
throw new Error(
|
|
4744
|
+
`Runtime evidence ${label} row is outside embedded deployment composition`
|
|
4745
|
+
);
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
};
|
|
4749
|
+
var readRuntimeEvidenceArtifact = async ({
|
|
4750
|
+
filePath,
|
|
4751
|
+
projectRoot
|
|
4752
|
+
}) => {
|
|
4753
|
+
const resolvedPath = import_node_path3.default.resolve(projectRoot, filePath);
|
|
4754
|
+
const root = asRecord(
|
|
4755
|
+
JSON.parse(await import_promises3.default.readFile(resolvedPath, "utf8"))
|
|
4756
|
+
);
|
|
4757
|
+
const runtime = asRecord(root?.runtime) ?? root;
|
|
4758
|
+
if (!root || !runtime) {
|
|
4759
|
+
throw new Error(`Invalid runtime evidence JSON: ${resolvedPath}`);
|
|
4760
|
+
}
|
|
4761
|
+
const rootWindow = asRecord(root.window);
|
|
4762
|
+
const runtimeWindow = asRecord(runtime.window);
|
|
4763
|
+
return {
|
|
4764
|
+
resolvedPath,
|
|
4765
|
+
root,
|
|
4766
|
+
runtime,
|
|
4767
|
+
userName: String(root.userName ?? runtime.userName ?? "").trim(),
|
|
4768
|
+
startTime: asFiniteNumber(rootWindow?.startTime) ?? asFiniteNumber(runtimeWindow?.startTime),
|
|
4769
|
+
endTime: asFiniteNumber(rootWindow?.endTime) ?? asFiniteNumber(runtimeWindow?.endTime),
|
|
4770
|
+
deployment: parseRuntimeEvidenceDeploymentSnapshot(root.deployment)
|
|
4771
|
+
};
|
|
4772
|
+
};
|
|
4773
|
+
var loadReplayRuntimeEvidenceMetadata = async (params) => {
|
|
4774
|
+
const artifact = await readRuntimeEvidenceArtifact(params);
|
|
4775
|
+
return {
|
|
4776
|
+
path: artifact.resolvedPath,
|
|
4777
|
+
userName: artifact.userName,
|
|
4778
|
+
startTime: artifact.startTime,
|
|
4779
|
+
endTime: artifact.endTime,
|
|
4780
|
+
deployment: artifact.deployment
|
|
4781
|
+
};
|
|
4782
|
+
};
|
|
4783
|
+
var buildRuntimeEvidenceLineageScopes = ({
|
|
4784
|
+
signals,
|
|
4785
|
+
evaluations
|
|
4786
|
+
}) => {
|
|
4787
|
+
const scopes = /* @__PURE__ */ new Map();
|
|
4788
|
+
const add = ({
|
|
4789
|
+
strategy,
|
|
4790
|
+
symbol,
|
|
4791
|
+
deploymentId,
|
|
4792
|
+
accountId,
|
|
4793
|
+
runtimeConfigId,
|
|
4794
|
+
runtimeVersion,
|
|
4795
|
+
timestamp,
|
|
4796
|
+
lineage
|
|
4797
|
+
}) => {
|
|
4798
|
+
if (!lineage || !Number.isFinite(timestamp)) {
|
|
4799
|
+
return;
|
|
4800
|
+
}
|
|
4801
|
+
const key = `${deploymentId ?? "default"}::${accountId ?? "default"}::${strategy}::${symbol}::${runtimeLineageKey(lineage)}`;
|
|
4802
|
+
const existing = scopes.get(key);
|
|
4803
|
+
scopes.set(key, {
|
|
4804
|
+
strategy,
|
|
4805
|
+
symbol,
|
|
4806
|
+
...deploymentId ? { deploymentId } : {},
|
|
4807
|
+
...accountId ? { accountId } : {},
|
|
4808
|
+
...runtimeConfigId ? { runtimeConfigId } : {},
|
|
4809
|
+
...runtimeVersion ? { runtimeVersion } : {},
|
|
4810
|
+
lineage,
|
|
4811
|
+
firstTimestamp: existing ? Math.min(existing.firstTimestamp, timestamp) : timestamp,
|
|
4812
|
+
lastTimestamp: existing ? Math.max(existing.lastTimestamp, timestamp) : timestamp
|
|
4813
|
+
});
|
|
4814
|
+
};
|
|
4815
|
+
for (const signal of signals) {
|
|
4816
|
+
add({
|
|
4817
|
+
strategy: signal.strategy,
|
|
4818
|
+
symbol: signal.symbol,
|
|
4819
|
+
deploymentId: signal.deploymentId,
|
|
4820
|
+
accountId: signal.accountId,
|
|
4821
|
+
runtimeConfigId: signal.runtimeConfigId,
|
|
4822
|
+
runtimeVersion: signal.runtimeVersion,
|
|
4823
|
+
timestamp: signal.timestamp,
|
|
4824
|
+
lineage: signal.runtimeLineage
|
|
4825
|
+
});
|
|
4826
|
+
}
|
|
4827
|
+
for (const evaluation of evaluations) {
|
|
4828
|
+
add({
|
|
4829
|
+
strategy: evaluation.strategy,
|
|
4830
|
+
symbol: evaluation.symbol,
|
|
4831
|
+
deploymentId: evaluation.deploymentId,
|
|
4832
|
+
accountId: evaluation.accountId,
|
|
4833
|
+
runtimeConfigId: evaluation.runtimeConfigId,
|
|
4834
|
+
runtimeVersion: evaluation.runtimeVersion,
|
|
4835
|
+
timestamp: evaluation.timestamp,
|
|
4836
|
+
lineage: evaluation.runtimeLineage
|
|
4837
|
+
});
|
|
4838
|
+
}
|
|
4839
|
+
return [...scopes.values()].sort(
|
|
4840
|
+
(left, right) => left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || left.firstTimestamp - right.firstTimestamp
|
|
4841
|
+
);
|
|
4842
|
+
};
|
|
4843
|
+
var loadReplayRuntimeEvidenceSource = async ({
|
|
4844
|
+
filePath,
|
|
4845
|
+
projectRoot,
|
|
4846
|
+
expectedUserName,
|
|
4847
|
+
expectedWindow
|
|
4848
|
+
}) => {
|
|
4849
|
+
const { resolvedPath, runtime, userName, startTime, endTime, deployment } = await readRuntimeEvidenceArtifact({ filePath, projectRoot });
|
|
4850
|
+
if (!userName || userName !== expectedUserName) {
|
|
4851
|
+
throw new Error(
|
|
4852
|
+
`Runtime evidence user mismatch: expected=${expectedUserName}, actual=${userName || "missing"}`
|
|
4853
|
+
);
|
|
4854
|
+
}
|
|
4855
|
+
const endTimeMatches = endTime === expectedWindow.end || endTime === expectedWindow.end + 1;
|
|
4856
|
+
if (startTime !== expectedWindow.start || !endTimeMatches) {
|
|
4857
|
+
throw new Error(
|
|
4858
|
+
`Runtime evidence window mismatch: expected=${expectedWindow.start}..${expectedWindow.end}, actual=${startTime ?? "missing"}..${endTime ?? "missing"}`
|
|
4859
|
+
);
|
|
4860
|
+
}
|
|
4861
|
+
const trades = unwrapRows(runtime.trades, "trade");
|
|
4862
|
+
const signals = unwrapRows(runtime.signals, "signal");
|
|
4863
|
+
const evaluations = unwrapRows(
|
|
4864
|
+
runtime.evaluations,
|
|
4865
|
+
"evaluation"
|
|
4866
|
+
);
|
|
4867
|
+
const storedLineageScopes = unwrapRows(
|
|
4868
|
+
runtime.lineageScopes,
|
|
4869
|
+
"lineageScope"
|
|
4870
|
+
);
|
|
4871
|
+
assertRuntimeRowsMatchDeployment({
|
|
4872
|
+
label: "trade",
|
|
4873
|
+
rows: trades,
|
|
4874
|
+
deployment
|
|
4875
|
+
});
|
|
4876
|
+
assertRuntimeRowsMatchDeployment({
|
|
4877
|
+
label: "signal",
|
|
4878
|
+
rows: signals,
|
|
4879
|
+
deployment
|
|
4880
|
+
});
|
|
4881
|
+
assertRuntimeRowsMatchDeployment({
|
|
4882
|
+
label: "evaluation",
|
|
4883
|
+
rows: evaluations,
|
|
4884
|
+
deployment
|
|
4885
|
+
});
|
|
4886
|
+
assertRuntimeRowsMatchDeployment({
|
|
4887
|
+
label: "lineage scope",
|
|
4888
|
+
rows: storedLineageScopes,
|
|
4889
|
+
deployment
|
|
4890
|
+
});
|
|
4891
|
+
return {
|
|
4892
|
+
path: resolvedPath,
|
|
4893
|
+
userName,
|
|
4894
|
+
startTime,
|
|
4895
|
+
endTime,
|
|
4896
|
+
trades,
|
|
4897
|
+
signals,
|
|
4898
|
+
evaluations,
|
|
4899
|
+
lineageScopes: storedLineageScopes.length ? storedLineageScopes : buildRuntimeEvidenceLineageScopes({ signals, evaluations }),
|
|
4900
|
+
deployment
|
|
4901
|
+
};
|
|
4902
|
+
};
|
|
4903
|
+
|
|
4904
|
+
// src/lib/replay/support.ts
|
|
4905
|
+
var import_chalk8 = __toESM(require("chalk"));
|
|
4906
|
+
|
|
4907
|
+
// src/lib/runtimeModeConfig.ts
|
|
4908
|
+
var buildRuntimeModeStrategyConfig = ({
|
|
4909
|
+
strategyConfig,
|
|
4910
|
+
env,
|
|
4911
|
+
interval,
|
|
4912
|
+
makeOrders,
|
|
4913
|
+
recordRuntimeTrades,
|
|
4914
|
+
aiReplayAnalyses
|
|
4915
|
+
}) => ({
|
|
4916
|
+
...strategyConfig,
|
|
4917
|
+
ENV: env,
|
|
4918
|
+
MAKE_ORDERS: makeOrders,
|
|
4919
|
+
INTERVAL: strategyConfig.INTERVAL ?? interval,
|
|
4920
|
+
...typeof recordRuntimeTrades === "boolean" ? { RECORD_RUNTIME_TRADES: recordRuntimeTrades } : {},
|
|
4921
|
+
...aiReplayAnalyses?.length ? { AI_REPLAY_ANALYSES: aiReplayAnalyses } : {}
|
|
4922
|
+
});
|
|
4923
|
+
|
|
4924
|
+
// src/lib/replay/support.ts
|
|
4925
|
+
var REPLAY_RESULTS_BY_STRATEGY_HEADERS = [
|
|
4926
|
+
import_chalk8.default.blue("STRATEGY"),
|
|
4927
|
+
import_chalk8.default.yellow("TICKERS"),
|
|
4928
|
+
import_chalk8.default.yellow("TRADE TICKERS"),
|
|
4929
|
+
import_chalk8.default.cyan("ORDERS"),
|
|
4930
|
+
import_chalk8.default.cyan("WIN/LOSS (%)"),
|
|
4931
|
+
import_chalk8.default.cyan("PROFIT"),
|
|
4932
|
+
import_chalk8.default.cyan("AVG TRADE")
|
|
4933
|
+
];
|
|
4934
|
+
var REPLAY_RUNTIME_COMPARISON_HEADERS = [
|
|
4935
|
+
import_chalk8.default.blue("STRATEGY"),
|
|
4936
|
+
import_chalk8.default.cyan("BT ENTRIES"),
|
|
4937
|
+
import_chalk8.default.cyan("BT PNL"),
|
|
4938
|
+
import_chalk8.default.yellow("RT TRADES"),
|
|
4939
|
+
import_chalk8.default.yellow("RT PNL"),
|
|
4940
|
+
import_chalk8.default.green("MATCHED"),
|
|
4941
|
+
import_chalk8.default.red("ORDER FAILED"),
|
|
4942
|
+
import_chalk8.default.yellow("RT ONLY"),
|
|
4943
|
+
import_chalk8.default.magenta("BT ONLY"),
|
|
4944
|
+
import_chalk8.default.red("UNMATCHED")
|
|
4945
|
+
];
|
|
4946
|
+
var REPLAY_RESULTS_CONFIG = "replay";
|
|
4947
|
+
var REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS = 1;
|
|
4948
|
+
var REPLAY_RUNTIME_COMPARE_TOLERANCE_MS = REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS * 15 * 60 * 1e3;
|
|
4949
|
+
var formatReplayRuntimeCompareTolerance = () => `${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS} ${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS === 1 ? "bar" : "bars"}`;
|
|
4950
|
+
var getReplayRuntimeUnmatchedCount = (row) => row.orderFailed + row.runtimeOnly + row.backtestOnly;
|
|
4951
|
+
var buildReplayStrategyConfig = ({
|
|
4952
|
+
strategyConfig,
|
|
4953
|
+
interval
|
|
4954
|
+
}) => buildRuntimeModeStrategyConfig({
|
|
4955
|
+
strategyConfig,
|
|
4956
|
+
env: "PARITY",
|
|
4957
|
+
interval,
|
|
4958
|
+
makeOrders: true,
|
|
4959
|
+
recordRuntimeTrades: false
|
|
4960
|
+
});
|
|
4961
|
+
var toRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4962
|
+
var compactReplayAdditionalIndicators = (additionalIndicators) => {
|
|
4963
|
+
const additional = toRecord(additionalIndicators);
|
|
4964
|
+
if (!additional) {
|
|
4965
|
+
return void 0;
|
|
4966
|
+
}
|
|
4967
|
+
const compact = {};
|
|
4968
|
+
if (additional.executionSlippage != null) {
|
|
4969
|
+
compact.executionSlippage = additional.executionSlippage;
|
|
4970
|
+
}
|
|
4971
|
+
if (additional.marketContext != null) {
|
|
4972
|
+
compact.marketContext = additional.marketContext;
|
|
4973
|
+
}
|
|
4974
|
+
const baseContext = toRecord(additional.baseContext);
|
|
4975
|
+
const relative = toRecord(baseContext?.relative);
|
|
4976
|
+
const execution = toRecord(relative?.execution);
|
|
4977
|
+
if (execution) {
|
|
4978
|
+
compact.baseContext = {
|
|
4979
|
+
relative: {
|
|
4980
|
+
execution
|
|
4981
|
+
}
|
|
4982
|
+
};
|
|
4983
|
+
}
|
|
4984
|
+
return Object.keys(compact).length ? compact : void 0;
|
|
4985
|
+
};
|
|
4986
|
+
var compactReplaySignal = (signal) => {
|
|
4987
|
+
if (!signal) {
|
|
4988
|
+
return void 0;
|
|
4989
|
+
}
|
|
4990
|
+
return {
|
|
4991
|
+
signalId: signal.signalId,
|
|
4992
|
+
strategy: signal.strategy,
|
|
4424
4993
|
symbol: signal.symbol,
|
|
4425
4994
|
interval: signal.interval,
|
|
4426
4995
|
direction: signal.direction,
|
|
@@ -4450,6 +5019,7 @@ var import_redis3 = require("@tradejs/infra/redis");
|
|
|
4450
5019
|
var import_connectors6 = require("@tradejs/node/connectors");
|
|
4451
5020
|
var import_cli4 = require("@tradejs/node/cli");
|
|
4452
5021
|
var import_strategies5 = require("@tradejs/node/strategies");
|
|
5022
|
+
var import_runtimeStrategies3 = require("@tradejs/node/runtimeStrategies");
|
|
4453
5023
|
|
|
4454
5024
|
// src/lib/replay/portfolioReplayConnector.ts
|
|
4455
5025
|
var import_constants3 = require("@tradejs/core/constants");
|
|
@@ -4869,345 +5439,48 @@ var splitCandlesForReplayWindow = (candles, start, preloadStart) => {
|
|
|
4869
5439
|
}
|
|
4870
5440
|
replayData.push(candle);
|
|
4871
5441
|
}
|
|
4872
|
-
return { prevData, replayData };
|
|
4873
|
-
};
|
|
4874
|
-
var alignSymbolWithBtcReference = (symbolCandles, btcCandles) => (0, import_indicators2.alignSortedCandlesByTimestamp)(symbolCandles, btcCandles);
|
|
4875
|
-
|
|
4876
|
-
// src/lib/signals/hooks.ts
|
|
4877
|
-
var normalizeHookList = (value) => {
|
|
4878
|
-
if (Array.isArray(value)) {
|
|
4879
|
-
return value;
|
|
4880
|
-
}
|
|
4881
|
-
return value ? [value] : [];
|
|
4882
|
-
};
|
|
4883
|
-
var invokeBeforeSignalsHooks = async (hooks, params) => {
|
|
4884
|
-
for (const hook of normalizeHookList(
|
|
4885
|
-
hooks?.beforeSignals
|
|
4886
|
-
)) {
|
|
4887
|
-
const result = await hook(params);
|
|
4888
|
-
if (result?.abort === true) {
|
|
4889
|
-
return result;
|
|
4890
|
-
}
|
|
4891
|
-
}
|
|
4892
|
-
return void 0;
|
|
4893
|
-
};
|
|
4894
|
-
var invokeAfterSignalsHooks = async (hooks, params) => {
|
|
4895
|
-
for (const hook of normalizeHookList(
|
|
4896
|
-
hooks?.afterSignals
|
|
4897
|
-
)) {
|
|
4898
|
-
await hook(params);
|
|
4899
|
-
}
|
|
4900
|
-
};
|
|
4901
|
-
|
|
4902
|
-
// src/lib/runtimeLineage.ts
|
|
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
|
|
4912
|
-
var import_node_crypto = require("crypto");
|
|
4913
|
-
var import_node_module = require("module");
|
|
4914
|
-
var import_promises = __toESM(require("fs/promises"));
|
|
4915
|
-
var import_node_path = __toESM(require("path"));
|
|
4916
|
-
var toPackageToken = (strategyName) => strategyName.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
|
|
4917
|
-
var getStrategyPackageName = (strategyName) => strategyName === "TrendLine" || strategyName === "ReverseTrendLine" ? "@tradejs/strategy-trend-line" : `@tradejs/strategy-${toPackageToken(strategyName)}`;
|
|
4918
|
-
var gateFingerprintSpecifiers = (strategyName) => [
|
|
4919
|
-
getStrategyPackageName(strategyName),
|
|
4920
|
-
"@tradejs/strategy-kit/ai-gate",
|
|
4921
|
-
"@tradejs/node/ai",
|
|
4922
|
-
"@tradejs/core/strategies"
|
|
4923
|
-
];
|
|
4924
|
-
var resolveStrategyGateFingerprint = async ({
|
|
4925
|
-
projectRoot,
|
|
4926
|
-
strategyName,
|
|
4927
|
-
gitSha
|
|
4928
|
-
}) => {
|
|
4929
|
-
const projectManifestPath = import_node_path.default.join(projectRoot, "package.json");
|
|
4930
|
-
const projectExists = await import_promises.default.access(projectManifestPath).then(() => true).catch(() => false);
|
|
4931
|
-
const requireFromProject = projectExists ? (0, import_node_module.createRequire)(projectManifestPath) : null;
|
|
4932
|
-
const candidates = requireFromProject ? await Promise.all(
|
|
4933
|
-
gateFingerprintSpecifiers(strategyName).map(async (specifier) => {
|
|
4934
|
-
let resolvedPath;
|
|
4935
|
-
try {
|
|
4936
|
-
resolvedPath = requireFromProject.resolve(specifier);
|
|
4937
|
-
} catch {
|
|
4938
|
-
return null;
|
|
4939
|
-
}
|
|
4940
|
-
const content = await import_promises.default.readFile(resolvedPath).catch(() => null);
|
|
4941
|
-
return content == null ? null : { specifier, content };
|
|
4942
|
-
})
|
|
4943
|
-
) : [];
|
|
4944
|
-
const sourceEntries = candidates.filter(
|
|
4945
|
-
(entry) => entry != null
|
|
4946
|
-
);
|
|
4947
|
-
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
4948
|
-
hash.update(strategyName);
|
|
4949
|
-
if (sourceEntries.length) {
|
|
4950
|
-
for (const entry of sourceEntries) {
|
|
4951
|
-
hash.update(entry.specifier);
|
|
4952
|
-
hash.update(entry.content);
|
|
4953
|
-
}
|
|
4954
|
-
} else {
|
|
4955
|
-
hash.update(gitSha ?? "unknown-git-sha");
|
|
4956
|
-
}
|
|
4957
|
-
return {
|
|
4958
|
-
gateFingerprint: hash.digest("hex").slice(0, 16),
|
|
4959
|
-
gateFingerprintFiles: sourceEntries.map((entry) => entry.specifier)
|
|
4960
|
-
};
|
|
4961
|
-
};
|
|
4962
|
-
|
|
4963
|
-
// src/lib/runtimeLineage.ts
|
|
4964
|
-
var RUNTIME_CONTEXT_ENV_KEYS = [
|
|
4965
|
-
"AI_MODE",
|
|
4966
|
-
"MIN_AI_QUALITY",
|
|
4967
|
-
"INTERVAL",
|
|
4968
|
-
"DERIVATIVES_CONTEXT_ENABLED",
|
|
4969
|
-
"DERIVATIVES_CONTEXT_TARGET_ENABLED",
|
|
4970
|
-
"DERIVATIVES_CONTEXT_LOOKBACK_HOURS",
|
|
4971
|
-
"DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS",
|
|
4972
|
-
"DERIVATIVES_CONTEXT_EXCHANGE_PRIORITY",
|
|
4973
|
-
"COINMARKETCAP_CONTEXT_ENABLED",
|
|
4974
|
-
"COINMARKETCAP_CONTEXT_MAX_AGE_MS",
|
|
4975
|
-
"COINMARKETCAP_CONTEXT_EXCHANGE_LIQUIDITY_ENABLED",
|
|
4976
|
-
"COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS",
|
|
4977
|
-
"COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED",
|
|
4978
|
-
"COINMARKETCAP_CONTEXT_HISTORICAL_ACCESS_MONTHS",
|
|
4979
|
-
"COINMARKETCAP_CONTEXT_BACKFILL_ENABLED",
|
|
4980
|
-
"COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
|
|
4981
|
-
"COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS",
|
|
4982
|
-
"HYPERLIQUID_WHALE_CONTEXT_ENABLED",
|
|
4983
|
-
"HYPERLIQUID_WHALE_BACKFILL_ENABLED",
|
|
4984
|
-
"HYPERLIQUID_WHALE_MIN_COVERAGE_PCT",
|
|
4985
|
-
"HYPERLIQUID_WHALE_CONCURRENCY",
|
|
4986
|
-
"HYPERLIQUID_WHALE_RATE_LIMIT_WEIGHT"
|
|
4987
|
-
];
|
|
4988
|
-
var RUNTIME_CONTEXT_DATA_MODEL = {
|
|
4989
|
-
derivativesSourceIntervals: "15m",
|
|
4990
|
-
derivativesDerivedIntervals: "1h",
|
|
4991
|
-
derivativesHourlyFallback: "stored-1h",
|
|
4992
|
-
derivativesDataModelVersion: 2,
|
|
4993
|
-
coinMarketCapAvailabilityModel: "closed-interval-all-daily",
|
|
4994
|
-
coinMarketCapDataModelVersion: 3,
|
|
4995
|
-
binanceBreadthUniverseModel: "fixed-versioned-top5-top10-top30-top50-top100",
|
|
4996
|
-
binanceBreadthDataModelVersion: 2,
|
|
4997
|
-
hyperliquidWhaleCanonicalInterval: "1m",
|
|
4998
|
-
hyperliquidWhaleGateMinNotionalUsd: 5e4,
|
|
4999
|
-
hyperliquidWhaleMinCoveragePct: 0.8,
|
|
5000
|
-
hyperliquidWhaleDataModelVersion: import_types2.HYPERLIQUID_WHALE_DATA_MODEL_VERSION
|
|
5001
|
-
};
|
|
5002
|
-
var normalizeForStableJson = (value) => {
|
|
5003
|
-
if (Array.isArray(value)) {
|
|
5004
|
-
return value.map(normalizeForStableJson);
|
|
5005
|
-
}
|
|
5006
|
-
if (value && typeof value === "object") {
|
|
5007
|
-
return Object.fromEntries(
|
|
5008
|
-
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeForStableJson(entry)])
|
|
5009
|
-
);
|
|
5010
|
-
}
|
|
5011
|
-
return value;
|
|
5012
|
-
};
|
|
5013
|
-
var resolveRuntimeMaxLossValue = (config) => {
|
|
5014
|
-
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
5015
|
-
return null;
|
|
5016
|
-
}
|
|
5017
|
-
const strategyConfig = config.strategyConfig;
|
|
5018
|
-
if (!strategyConfig || typeof strategyConfig !== "object" || Array.isArray(strategyConfig)) {
|
|
5019
|
-
return null;
|
|
5020
|
-
}
|
|
5021
|
-
const maxLossValue = Number(
|
|
5022
|
-
strategyConfig.MAX_LOSS_VALUE
|
|
5023
|
-
);
|
|
5024
|
-
return Number.isFinite(maxLossValue) ? maxLossValue : null;
|
|
5025
|
-
};
|
|
5026
|
-
var fingerprintRuntimeValue = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
|
|
5027
|
-
var gitLineageCache = /* @__PURE__ */ new Map();
|
|
5028
|
-
var gateFingerprintCache = /* @__PURE__ */ new Map();
|
|
5029
|
-
var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
|
|
5030
|
-
var snapshotFingerprintCache = /* @__PURE__ */ new Map();
|
|
5031
|
-
var resolveGitLineage = (projectRoot) => {
|
|
5032
|
-
const cached = gitLineageCache.get(projectRoot);
|
|
5033
|
-
if (cached) {
|
|
5034
|
-
return cached;
|
|
5035
|
-
}
|
|
5036
|
-
const envSha = String(process.env.TRADEJS_GIT_SHA ?? "").trim();
|
|
5037
|
-
if (envSha && envSha !== "unknown") {
|
|
5038
|
-
const result = {
|
|
5039
|
-
gitSha: envSha,
|
|
5040
|
-
gitDirty: false
|
|
5041
|
-
};
|
|
5042
|
-
gitLineageCache.set(projectRoot, result);
|
|
5043
|
-
return result;
|
|
5044
|
-
}
|
|
5045
|
-
try {
|
|
5046
|
-
const gitSha = (0, import_node_child_process.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
5047
|
-
cwd: projectRoot,
|
|
5048
|
-
encoding: "utf8",
|
|
5049
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
5050
|
-
}).trim();
|
|
5051
|
-
const status = (0, import_node_child_process.execFileSync)(
|
|
5052
|
-
"git",
|
|
5053
|
-
["status", "--porcelain", "--untracked-files=normal"],
|
|
5054
|
-
{
|
|
5055
|
-
cwd: projectRoot,
|
|
5056
|
-
encoding: "utf8",
|
|
5057
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
5058
|
-
}
|
|
5059
|
-
).trim();
|
|
5060
|
-
const result = {
|
|
5061
|
-
gitSha: gitSha || null,
|
|
5062
|
-
gitDirty: status.length > 0
|
|
5063
|
-
};
|
|
5064
|
-
gitLineageCache.set(projectRoot, result);
|
|
5065
|
-
return result;
|
|
5066
|
-
} catch {
|
|
5067
|
-
const result = {
|
|
5068
|
-
gitSha: null,
|
|
5069
|
-
gitDirty: null
|
|
5070
|
-
};
|
|
5071
|
-
gitLineageCache.set(projectRoot, result);
|
|
5072
|
-
return result;
|
|
5073
|
-
}
|
|
5074
|
-
};
|
|
5075
|
-
var readOptionalFile = async (filePath) => {
|
|
5076
|
-
try {
|
|
5077
|
-
return await import_promises2.default.readFile(filePath);
|
|
5078
|
-
} catch {
|
|
5079
|
-
return null;
|
|
5080
|
-
}
|
|
5081
|
-
};
|
|
5082
|
-
var resolveBinanceBreadthFingerprint = async (projectRoot) => {
|
|
5083
|
-
if (binanceBreadthFingerprintCache.has(projectRoot)) {
|
|
5084
|
-
return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
|
|
5085
|
-
}
|
|
5086
|
-
const content = await readOptionalFile(
|
|
5087
|
-
import_node_path2.default.join(
|
|
5088
|
-
projectRoot,
|
|
5089
|
-
"packages/node/src/config/binanceBreadthUniverses.json"
|
|
5090
|
-
)
|
|
5091
|
-
);
|
|
5092
|
-
let result = null;
|
|
5093
|
-
if (content) {
|
|
5094
|
-
try {
|
|
5095
|
-
const parsed = JSON.parse(content.toString("utf8"));
|
|
5096
|
-
result = typeof parsed.fingerprint === "string" && parsed.fingerprint.trim() ? parsed.fingerprint.trim() : null;
|
|
5097
|
-
} catch {
|
|
5098
|
-
result = null;
|
|
5099
|
-
}
|
|
5100
|
-
}
|
|
5101
|
-
binanceBreadthFingerprintCache.set(projectRoot, result);
|
|
5102
|
-
return result;
|
|
5103
|
-
};
|
|
5104
|
-
var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
5105
|
-
const cacheKey = `${projectRoot}:${relativePath}`;
|
|
5106
|
-
if (snapshotFingerprintCache.has(cacheKey)) {
|
|
5107
|
-
return snapshotFingerprintCache.get(cacheKey) ?? null;
|
|
5108
|
-
}
|
|
5109
|
-
const content = await readOptionalFile(import_node_path2.default.join(projectRoot, relativePath));
|
|
5110
|
-
let result = null;
|
|
5111
|
-
if (content) {
|
|
5112
|
-
try {
|
|
5113
|
-
const parsed = JSON.parse(content.toString("utf8"));
|
|
5114
|
-
result = typeof parsed.fingerprint === "string" && parsed.fingerprint.trim() ? parsed.fingerprint.trim() : null;
|
|
5115
|
-
} catch {
|
|
5116
|
-
result = null;
|
|
5117
|
-
}
|
|
5118
|
-
}
|
|
5119
|
-
snapshotFingerprintCache.set(cacheKey, result);
|
|
5120
|
-
return result;
|
|
5442
|
+
return { prevData, replayData };
|
|
5121
5443
|
};
|
|
5122
|
-
var
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
const result = await resolveStrategyGateFingerprint({
|
|
5131
|
-
projectRoot,
|
|
5132
|
-
strategyName,
|
|
5133
|
-
gitSha
|
|
5134
|
-
});
|
|
5135
|
-
gateFingerprintCache.set(cacheKey, result);
|
|
5136
|
-
return result;
|
|
5444
|
+
var alignSymbolWithBtcReference = (symbolCandles, btcCandles) => (0, import_indicators2.alignSortedCandlesByTimestamp)(symbolCandles, btcCandles);
|
|
5445
|
+
|
|
5446
|
+
// src/lib/signals/hooks.ts
|
|
5447
|
+
var normalizeHookList = (value) => {
|
|
5448
|
+
if (Array.isArray(value)) {
|
|
5449
|
+
return value;
|
|
5450
|
+
}
|
|
5451
|
+
return value ? [value] : [];
|
|
5137
5452
|
};
|
|
5138
|
-
async
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
config,
|
|
5146
|
-
runContext,
|
|
5147
|
-
env = process.env
|
|
5148
|
-
}) {
|
|
5149
|
-
if (version != null) {
|
|
5150
|
-
if (!Number.isSafeInteger(version) || version <= 0) {
|
|
5151
|
-
throw new Error(`Invalid runtime strategy version: ${version}`);
|
|
5453
|
+
var invokeBeforeSignalsHooks = async (hooks, params) => {
|
|
5454
|
+
for (const hook of normalizeHookList(
|
|
5455
|
+
hooks?.beforeSignals
|
|
5456
|
+
)) {
|
|
5457
|
+
const result = await hook(params);
|
|
5458
|
+
if (result?.abort === true) {
|
|
5459
|
+
return result;
|
|
5152
5460
|
}
|
|
5153
|
-
return {
|
|
5154
|
-
schemaVersion: 2,
|
|
5155
|
-
version,
|
|
5156
|
-
strategyPackageVersion,
|
|
5157
|
-
runtimePackageVersion,
|
|
5158
|
-
maxLossValue: resolveRuntimeMaxLossValue(config)
|
|
5159
|
-
};
|
|
5160
5461
|
}
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
const context = {
|
|
5171
|
-
...Object.fromEntries(
|
|
5172
|
-
RUNTIME_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
|
|
5173
|
-
),
|
|
5174
|
-
...RUNTIME_CONTEXT_DATA_MODEL,
|
|
5175
|
-
binanceBreadthUniverseFingerprint: await resolveBinanceBreadthFingerprint(projectRoot),
|
|
5176
|
-
hyperliquidPerpUniverseFingerprint,
|
|
5177
|
-
hyperliquidWhaleRegistryFingerprint,
|
|
5178
|
-
...runContext
|
|
5179
|
-
};
|
|
5180
|
-
return {
|
|
5181
|
-
schemaVersion: 1,
|
|
5182
|
-
compositionId: compositionId?.trim() || null,
|
|
5183
|
-
...git,
|
|
5184
|
-
gateFingerprint: (await resolveRuntimeGateFingerprint({
|
|
5185
|
-
projectRoot,
|
|
5186
|
-
strategyName,
|
|
5187
|
-
gitSha: git.gitSha
|
|
5188
|
-
})).gateFingerprint,
|
|
5189
|
-
// Position risk is tracked separately so changing trade size does not
|
|
5190
|
-
// masquerade as a change to the core + gate decision logic.
|
|
5191
|
-
configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config),
|
|
5192
|
-
contextFingerprint: fingerprintRuntimeValue(context),
|
|
5193
|
-
maxLossValue: resolveRuntimeMaxLossValue(config)
|
|
5194
|
-
};
|
|
5195
|
-
}
|
|
5196
|
-
var runtimeLineageKey = (lineage) => lineage.schemaVersion === 2 ? ["v2", lineage.version].join(":") : [
|
|
5197
|
-
lineage.schemaVersion,
|
|
5198
|
-
lineage.compositionId ?? "unbound-composition",
|
|
5199
|
-
lineage.gitSha ?? "unknown",
|
|
5200
|
-
lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
|
|
5201
|
-
lineage.gateFingerprint,
|
|
5202
|
-
lineage.configFingerprint,
|
|
5203
|
-
lineage.contextFingerprint
|
|
5204
|
-
].join(":");
|
|
5205
|
-
var runtimeLineagesMatch = (left, right) => left != null && right != null && runtimeLineageKey(left) === runtimeLineageKey(right);
|
|
5462
|
+
return void 0;
|
|
5463
|
+
};
|
|
5464
|
+
var invokeAfterSignalsHooks = async (hooks, params) => {
|
|
5465
|
+
for (const hook of normalizeHookList(
|
|
5466
|
+
hooks?.afterSignals
|
|
5467
|
+
)) {
|
|
5468
|
+
await hook(params);
|
|
5469
|
+
}
|
|
5470
|
+
};
|
|
5206
5471
|
|
|
5207
5472
|
// src/lib/replay/historicalSignalsReplay.ts
|
|
5208
5473
|
var loadRuntimeStrategies = async (runtimeStrategies) => {
|
|
5209
5474
|
const strategies = await Promise.all(
|
|
5210
|
-
runtimeStrategies.map(async (
|
|
5475
|
+
runtimeStrategies.map(async (runtimeStrategy) => {
|
|
5476
|
+
const {
|
|
5477
|
+
strategyName,
|
|
5478
|
+
version,
|
|
5479
|
+
strategyPackage,
|
|
5480
|
+
strategyPackageVersion,
|
|
5481
|
+
runtimePackageVersion,
|
|
5482
|
+
strategyConfig
|
|
5483
|
+
} = runtimeStrategy;
|
|
5211
5484
|
const strategyCreator = await (0, import_strategies5.getStrategyCreator)(
|
|
5212
5485
|
strategyName,
|
|
5213
5486
|
replayProjectRoot
|
|
@@ -5215,12 +5488,25 @@ var loadRuntimeStrategies = async (runtimeStrategies) => {
|
|
|
5215
5488
|
if (!strategyCreator) {
|
|
5216
5489
|
throw new Error(`Unknown strategy: ${strategyName}`);
|
|
5217
5490
|
}
|
|
5491
|
+
const installed = await (0, import_runtimeStrategies3.getRuntimeStrategyPackageMetadata)({
|
|
5492
|
+
strategyName,
|
|
5493
|
+
projectRoot: replayProjectRoot
|
|
5494
|
+
});
|
|
5495
|
+
if (installed.strategyPackage !== strategyPackage || installed.strategyPackageVersion !== strategyPackageVersion || installed.runtimePackageVersion !== runtimePackageVersion) {
|
|
5496
|
+
throw new Error(
|
|
5497
|
+
`Runtime evidence package mismatch for ${strategyName}: expected=${strategyPackage}@${strategyPackageVersion}/@tradejs/node@${runtimePackageVersion}, installed=${installed.strategyPackage ?? "missing"}@${installed.strategyPackageVersion ?? "missing"}/@tradejs/node@${installed.runtimePackageVersion ?? "missing"}`
|
|
5498
|
+
);
|
|
5499
|
+
}
|
|
5218
5500
|
const strategyResults = await (0, import_redis3.getData)(
|
|
5219
5501
|
import_redis3.redisKeys.strategyResults(replayUserName, strategyName),
|
|
5220
5502
|
{}
|
|
5221
5503
|
);
|
|
5222
5504
|
return {
|
|
5223
5505
|
strategyName,
|
|
5506
|
+
version,
|
|
5507
|
+
strategyPackage,
|
|
5508
|
+
strategyPackageVersion,
|
|
5509
|
+
runtimePackageVersion,
|
|
5224
5510
|
strategyCreator,
|
|
5225
5511
|
strategyConfig,
|
|
5226
5512
|
strategyResults
|
|
@@ -5414,6 +5700,9 @@ var runHistoricalSignalsReplay = async ({
|
|
|
5414
5700
|
loadedStrategies.map(
|
|
5415
5701
|
async ({
|
|
5416
5702
|
strategyName,
|
|
5703
|
+
version,
|
|
5704
|
+
strategyPackageVersion,
|
|
5705
|
+
runtimePackageVersion,
|
|
5417
5706
|
strategyCreator,
|
|
5418
5707
|
strategyConfig,
|
|
5419
5708
|
strategyResults
|
|
@@ -5421,8 +5710,11 @@ var runHistoricalSignalsReplay = async ({
|
|
|
5421
5710
|
const runtimeLineage = await buildRuntimeLineage({
|
|
5422
5711
|
projectRoot: replayProjectRoot,
|
|
5423
5712
|
strategyName,
|
|
5713
|
+
version,
|
|
5714
|
+
strategyPackageVersion,
|
|
5715
|
+
runtimePackageVersion,
|
|
5424
5716
|
config: {
|
|
5425
|
-
configId:
|
|
5717
|
+
configId: `v${version}`,
|
|
5426
5718
|
strategyConfig,
|
|
5427
5719
|
symbolResultConfig: strategyResults?.[symbol]?.config ?? null
|
|
5428
5720
|
},
|
|
@@ -5435,6 +5727,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
5435
5727
|
runtimeLineages.push({
|
|
5436
5728
|
strategy: strategyName,
|
|
5437
5729
|
symbol,
|
|
5730
|
+
...preparedRun.deploymentId ? { deploymentId: preparedRun.deploymentId } : {},
|
|
5731
|
+
...preparedRun.accountId ? { accountId: preparedRun.accountId } : {},
|
|
5438
5732
|
lineage: runtimeLineage
|
|
5439
5733
|
});
|
|
5440
5734
|
return {
|
|
@@ -5444,8 +5738,11 @@ var runHistoricalSignalsReplay = async ({
|
|
|
5444
5738
|
run: await strategyCreator({
|
|
5445
5739
|
userName: replayUserName,
|
|
5446
5740
|
connectorName,
|
|
5447
|
-
runtimeConfigId:
|
|
5741
|
+
runtimeConfigId: `v${version}`,
|
|
5448
5742
|
runtimeLineage,
|
|
5743
|
+
universe: preparedRun.universe,
|
|
5744
|
+
accountId: preparedRun.accountId,
|
|
5745
|
+
deploymentId: preparedRun.deploymentId,
|
|
5449
5746
|
config: buildReplayStrategyConfig({
|
|
5450
5747
|
strategyConfig,
|
|
5451
5748
|
interval
|
|
@@ -7208,118 +7505,6 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
7208
7505
|
};
|
|
7209
7506
|
};
|
|
7210
7507
|
|
|
7211
|
-
// src/lib/replay/runtimeEvidenceSource.ts
|
|
7212
|
-
var import_promises3 = __toESM(require("fs/promises"));
|
|
7213
|
-
var import_node_path3 = __toESM(require("path"));
|
|
7214
|
-
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
7215
|
-
var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
7216
|
-
var unwrapRows = (value, field) => (Array.isArray(value) ? value : []).map((item) => {
|
|
7217
|
-
const row = asRecord(item);
|
|
7218
|
-
return asRecord(row?.[field]) ?? row;
|
|
7219
|
-
}).filter((item) => item != null);
|
|
7220
|
-
var buildRuntimeEvidenceLineageScopes = ({
|
|
7221
|
-
signals,
|
|
7222
|
-
evaluations
|
|
7223
|
-
}) => {
|
|
7224
|
-
const scopes = /* @__PURE__ */ new Map();
|
|
7225
|
-
const add = ({
|
|
7226
|
-
strategy,
|
|
7227
|
-
symbol,
|
|
7228
|
-
runtimeConfigId,
|
|
7229
|
-
runtimeVersion,
|
|
7230
|
-
timestamp,
|
|
7231
|
-
lineage
|
|
7232
|
-
}) => {
|
|
7233
|
-
if (!lineage || !Number.isFinite(timestamp)) {
|
|
7234
|
-
return;
|
|
7235
|
-
}
|
|
7236
|
-
const key = `${strategy}::${symbol}::${runtimeLineageKey(lineage)}`;
|
|
7237
|
-
const existing = scopes.get(key);
|
|
7238
|
-
scopes.set(key, {
|
|
7239
|
-
strategy,
|
|
7240
|
-
symbol,
|
|
7241
|
-
...runtimeConfigId ? { runtimeConfigId } : {},
|
|
7242
|
-
...runtimeVersion ? { runtimeVersion } : {},
|
|
7243
|
-
lineage,
|
|
7244
|
-
firstTimestamp: existing ? Math.min(existing.firstTimestamp, timestamp) : timestamp,
|
|
7245
|
-
lastTimestamp: existing ? Math.max(existing.lastTimestamp, timestamp) : timestamp
|
|
7246
|
-
});
|
|
7247
|
-
};
|
|
7248
|
-
for (const signal of signals) {
|
|
7249
|
-
add({
|
|
7250
|
-
strategy: signal.strategy,
|
|
7251
|
-
symbol: signal.symbol,
|
|
7252
|
-
runtimeConfigId: signal.runtimeConfigId,
|
|
7253
|
-
runtimeVersion: signal.runtimeVersion,
|
|
7254
|
-
timestamp: signal.timestamp,
|
|
7255
|
-
lineage: signal.runtimeLineage
|
|
7256
|
-
});
|
|
7257
|
-
}
|
|
7258
|
-
for (const evaluation of evaluations) {
|
|
7259
|
-
add({
|
|
7260
|
-
strategy: evaluation.strategy,
|
|
7261
|
-
symbol: evaluation.symbol,
|
|
7262
|
-
runtimeConfigId: evaluation.runtimeConfigId,
|
|
7263
|
-
runtimeVersion: evaluation.runtimeVersion,
|
|
7264
|
-
timestamp: evaluation.timestamp,
|
|
7265
|
-
lineage: evaluation.runtimeLineage
|
|
7266
|
-
});
|
|
7267
|
-
}
|
|
7268
|
-
return [...scopes.values()].sort(
|
|
7269
|
-
(left, right) => left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || left.firstTimestamp - right.firstTimestamp
|
|
7270
|
-
);
|
|
7271
|
-
};
|
|
7272
|
-
var loadReplayRuntimeEvidenceSource = async ({
|
|
7273
|
-
filePath,
|
|
7274
|
-
projectRoot,
|
|
7275
|
-
expectedUserName,
|
|
7276
|
-
expectedWindow
|
|
7277
|
-
}) => {
|
|
7278
|
-
const resolvedPath = import_node_path3.default.resolve(projectRoot, filePath);
|
|
7279
|
-
const root = asRecord(
|
|
7280
|
-
JSON.parse(await import_promises3.default.readFile(resolvedPath, "utf8"))
|
|
7281
|
-
);
|
|
7282
|
-
const runtime = asRecord(root?.runtime) ?? root;
|
|
7283
|
-
if (!root || !runtime) {
|
|
7284
|
-
throw new Error(`Invalid runtime evidence JSON: ${resolvedPath}`);
|
|
7285
|
-
}
|
|
7286
|
-
const rootWindow = asRecord(root.window);
|
|
7287
|
-
const runtimeWindow = asRecord(runtime.window);
|
|
7288
|
-
const startTime = asFiniteNumber(rootWindow?.startTime) ?? asFiniteNumber(runtimeWindow?.startTime);
|
|
7289
|
-
const endTime = asFiniteNumber(rootWindow?.endTime) ?? asFiniteNumber(runtimeWindow?.endTime);
|
|
7290
|
-
const userName = String(root.userName ?? runtime.userName ?? "").trim();
|
|
7291
|
-
if (!userName || userName !== expectedUserName) {
|
|
7292
|
-
throw new Error(
|
|
7293
|
-
`Runtime evidence user mismatch: expected=${expectedUserName}, actual=${userName || "missing"}`
|
|
7294
|
-
);
|
|
7295
|
-
}
|
|
7296
|
-
const endTimeMatches = endTime === expectedWindow.end || endTime === expectedWindow.end + 1;
|
|
7297
|
-
if (startTime !== expectedWindow.start || !endTimeMatches) {
|
|
7298
|
-
throw new Error(
|
|
7299
|
-
`Runtime evidence window mismatch: expected=${expectedWindow.start}..${expectedWindow.end}, actual=${startTime ?? "missing"}..${endTime ?? "missing"}`
|
|
7300
|
-
);
|
|
7301
|
-
}
|
|
7302
|
-
const trades = unwrapRows(runtime.trades, "trade");
|
|
7303
|
-
const signals = unwrapRows(runtime.signals, "signal");
|
|
7304
|
-
const evaluations = unwrapRows(
|
|
7305
|
-
runtime.evaluations,
|
|
7306
|
-
"evaluation"
|
|
7307
|
-
);
|
|
7308
|
-
return {
|
|
7309
|
-
path: resolvedPath,
|
|
7310
|
-
userName,
|
|
7311
|
-
startTime,
|
|
7312
|
-
endTime,
|
|
7313
|
-
trades,
|
|
7314
|
-
signals,
|
|
7315
|
-
evaluations,
|
|
7316
|
-
lineageScopes: buildRuntimeEvidenceLineageScopes({
|
|
7317
|
-
signals,
|
|
7318
|
-
evaluations
|
|
7319
|
-
})
|
|
7320
|
-
};
|
|
7321
|
-
};
|
|
7322
|
-
|
|
7323
7508
|
// src/lib/replay/runtimeComparison.ts
|
|
7324
7509
|
var getReplayEntryTimestampCompareOffsetMs = () => {
|
|
7325
7510
|
const intervalMinutes = Number(replayInterval);
|
|
@@ -7337,27 +7522,40 @@ var buildReplaySignalEvaluations = (signals) => signals.map((signal) => ({
|
|
|
7337
7522
|
reason: signal.orderSkipReason || signal.orderStatus || "SIGNAL",
|
|
7338
7523
|
signalId: signal.signalId,
|
|
7339
7524
|
direction: signal.direction,
|
|
7525
|
+
accountId: signal.accountId,
|
|
7526
|
+
deploymentId: signal.deploymentId,
|
|
7340
7527
|
runtimeLineage: signal.runtimeLineage,
|
|
7341
7528
|
orderStatus: signal.orderStatus,
|
|
7342
7529
|
orderSkipReason: signal.orderSkipReason,
|
|
7343
7530
|
...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
|
|
7344
7531
|
...signal.ml ? { ml: signal.ml } : {}
|
|
7345
7532
|
}));
|
|
7346
|
-
var lineageScopeKey = (
|
|
7533
|
+
var lineageScopeKey = ({
|
|
7534
|
+
strategy,
|
|
7535
|
+
symbol,
|
|
7536
|
+
deploymentId,
|
|
7537
|
+
accountId
|
|
7538
|
+
}) => `${deploymentId ?? "default-deployment"}::${accountId ?? "default-account"}::${strategy}::${symbol}`;
|
|
7347
7539
|
var buildExpectedLineageByScope = (replayLineages) => new Map(
|
|
7348
|
-
replayLineages.map((record) => [
|
|
7349
|
-
lineageScopeKey(record.strategy, record.symbol),
|
|
7350
|
-
record.lineage
|
|
7351
|
-
])
|
|
7540
|
+
replayLineages.map((record) => [lineageScopeKey(record), record.lineage])
|
|
7352
7541
|
);
|
|
7353
7542
|
var hasExpectedLineage = ({
|
|
7354
7543
|
strategy,
|
|
7355
7544
|
symbol,
|
|
7545
|
+
deploymentId,
|
|
7546
|
+
accountId,
|
|
7356
7547
|
lineage,
|
|
7357
7548
|
expectedByScope
|
|
7358
|
-
}) =>
|
|
7549
|
+
}) => runtimeLineagesComparable(
|
|
7359
7550
|
lineage,
|
|
7360
|
-
expectedByScope.get(
|
|
7551
|
+
expectedByScope.get(
|
|
7552
|
+
lineageScopeKey({
|
|
7553
|
+
strategy,
|
|
7554
|
+
symbol,
|
|
7555
|
+
deploymentId,
|
|
7556
|
+
accountId
|
|
7557
|
+
})
|
|
7558
|
+
)
|
|
7361
7559
|
);
|
|
7362
7560
|
var filterReplayComparisonByLineage = ({
|
|
7363
7561
|
replayLineages,
|
|
@@ -7372,6 +7570,8 @@ var filterReplayComparisonByLineage = ({
|
|
|
7372
7570
|
(signal) => hasExpectedLineage({
|
|
7373
7571
|
strategy: signal.strategy,
|
|
7374
7572
|
symbol: signal.symbol,
|
|
7573
|
+
deploymentId: signal.deploymentId,
|
|
7574
|
+
accountId: signal.accountId,
|
|
7375
7575
|
lineage: signal.runtimeLineage,
|
|
7376
7576
|
expectedByScope
|
|
7377
7577
|
})
|
|
@@ -7380,6 +7580,8 @@ var filterReplayComparisonByLineage = ({
|
|
|
7380
7580
|
(evaluation) => hasExpectedLineage({
|
|
7381
7581
|
strategy: evaluation.strategy,
|
|
7382
7582
|
symbol: evaluation.symbol,
|
|
7583
|
+
deploymentId: evaluation.deploymentId,
|
|
7584
|
+
accountId: evaluation.accountId,
|
|
7383
7585
|
lineage: evaluation.runtimeLineage,
|
|
7384
7586
|
expectedByScope
|
|
7385
7587
|
})
|
|
@@ -7388,13 +7590,15 @@ var filterReplayComparisonByLineage = ({
|
|
|
7388
7590
|
(scope) => hasExpectedLineage({
|
|
7389
7591
|
strategy: scope.strategy,
|
|
7390
7592
|
symbol: scope.symbol,
|
|
7593
|
+
deploymentId: scope.deploymentId,
|
|
7594
|
+
accountId: scope.accountId,
|
|
7391
7595
|
lineage: scope.lineage,
|
|
7392
7596
|
expectedByScope
|
|
7393
7597
|
})
|
|
7394
7598
|
);
|
|
7395
7599
|
const deploymentWindows = /* @__PURE__ */ new Map();
|
|
7396
7600
|
for (const scope of comparableRuntimeLineageScopes) {
|
|
7397
|
-
const key = lineageScopeKey(scope
|
|
7601
|
+
const key = lineageScopeKey(scope);
|
|
7398
7602
|
const existing = deploymentWindows.get(key);
|
|
7399
7603
|
deploymentWindows.set(key, {
|
|
7400
7604
|
firstTimestamp: existing == null ? scope.firstTimestamp : Math.min(existing.firstTimestamp, scope.firstTimestamp),
|
|
@@ -7418,13 +7622,13 @@ var filterReplayComparisonByLineage = ({
|
|
|
7418
7622
|
return hasExpectedLineage({
|
|
7419
7623
|
strategy: trade.strategy,
|
|
7420
7624
|
symbol: trade.symbol,
|
|
7625
|
+
deploymentId: trade.deploymentId,
|
|
7626
|
+
accountId: trade.accountId,
|
|
7421
7627
|
lineage,
|
|
7422
7628
|
expectedByScope
|
|
7423
7629
|
});
|
|
7424
7630
|
}
|
|
7425
|
-
const window = deploymentWindows.get(
|
|
7426
|
-
lineageScopeKey(trade.strategy, trade.symbol)
|
|
7427
|
-
);
|
|
7631
|
+
const window = deploymentWindows.get(lineageScopeKey(trade));
|
|
7428
7632
|
const signalTimestamp = trade.signalTimestamp ?? trade.entryTimestamp;
|
|
7429
7633
|
return window != null && signalTimestamp >= window.firstTimestamp && signalTimestamp <= window.lastTimestamp;
|
|
7430
7634
|
});
|
|
@@ -7432,17 +7636,24 @@ var filterReplayComparisonByLineage = ({
|
|
|
7432
7636
|
...comparableRuntimeSignals,
|
|
7433
7637
|
...comparableRuntimeEvaluations
|
|
7434
7638
|
]) {
|
|
7435
|
-
const key = lineageScopeKey(artifact
|
|
7639
|
+
const key = lineageScopeKey(artifact);
|
|
7436
7640
|
const existing = deploymentWindows.get(key);
|
|
7437
7641
|
deploymentWindows.set(key, {
|
|
7438
7642
|
firstTimestamp: existing == null ? artifact.timestamp : Math.min(existing.firstTimestamp, artifact.timestamp),
|
|
7439
7643
|
lastTimestamp: existing == null ? artifact.timestamp : Math.max(existing.lastTimestamp, artifact.timestamp)
|
|
7440
7644
|
});
|
|
7441
7645
|
}
|
|
7646
|
+
const replayScopeByStrategySymbol = new Map(
|
|
7647
|
+
replayLineages.map((record) => [
|
|
7648
|
+
`${record.strategy}::${record.symbol}`,
|
|
7649
|
+
lineageScopeKey(record)
|
|
7650
|
+
])
|
|
7651
|
+
);
|
|
7442
7652
|
const comparableBacktestEntries = backtestEntries.filter((entry) => {
|
|
7443
|
-
const
|
|
7444
|
-
|
|
7653
|
+
const replayScope = replayScopeByStrategySymbol.get(
|
|
7654
|
+
`${entry.strategy}::${entry.symbol}`
|
|
7445
7655
|
);
|
|
7656
|
+
const window = replayScope ? deploymentWindows.get(replayScope) : null;
|
|
7446
7657
|
const signalTimestamp = entry.signalTimestamp ?? entry.timestamp;
|
|
7447
7658
|
return window != null && signalTimestamp >= window.firstTimestamp && signalTimestamp <= window.lastTimestamp;
|
|
7448
7659
|
});
|
|
@@ -7464,10 +7675,12 @@ var filterReplayComparisonByLineage = ({
|
|
|
7464
7675
|
excludedBacktestEntries: backtestEntries.length - comparableBacktestEntries.length,
|
|
7465
7676
|
reason: comparableScopeKeys.size > 0 ? null : "no_runtime_artifacts_with_matching_lineage",
|
|
7466
7677
|
replay: [...expectedByScope.entries()].map(([scope, lineage]) => {
|
|
7467
|
-
const
|
|
7678
|
+
const [deploymentId, accountId, strategy, symbol] = scope.split("::");
|
|
7468
7679
|
return {
|
|
7469
|
-
|
|
7470
|
-
|
|
7680
|
+
deploymentId,
|
|
7681
|
+
accountId,
|
|
7682
|
+
strategy,
|
|
7683
|
+
symbol,
|
|
7471
7684
|
lineage
|
|
7472
7685
|
};
|
|
7473
7686
|
}).sort(
|
|
@@ -8871,10 +9084,69 @@ var finishReplay = async ({
|
|
|
8871
9084
|
};
|
|
8872
9085
|
var replayBacktest = async () => {
|
|
8873
9086
|
resetRunState();
|
|
9087
|
+
const evidenceMetadata = replayRuntimeEvidencePath ? await loadReplayRuntimeEvidenceMetadata({
|
|
9088
|
+
filePath: replayRuntimeEvidencePath,
|
|
9089
|
+
projectRoot: replayProjectRoot
|
|
9090
|
+
}) : null;
|
|
9091
|
+
if (evidenceMetadata && evidenceMetadata.userName !== replayUserName) {
|
|
9092
|
+
throw new Error(
|
|
9093
|
+
`Runtime evidence user mismatch: expected=${replayUserName}, actual=${evidenceMetadata.userName || "missing"}`
|
|
9094
|
+
);
|
|
9095
|
+
}
|
|
9096
|
+
const replayComposition = evidenceMetadata ? {
|
|
9097
|
+
deployment: runtimeDeploymentFromEvidence(evidenceMetadata.deployment),
|
|
9098
|
+
strategies: activeRuntimeEvidenceStrategies(
|
|
9099
|
+
evidenceMetadata.deployment
|
|
9100
|
+
).map(
|
|
9101
|
+
({
|
|
9102
|
+
strategyName,
|
|
9103
|
+
version,
|
|
9104
|
+
strategyPackage,
|
|
9105
|
+
strategyPackageVersion,
|
|
9106
|
+
runtimePackageVersion,
|
|
9107
|
+
strategyConfig
|
|
9108
|
+
}) => ({
|
|
9109
|
+
strategyName,
|
|
9110
|
+
version,
|
|
9111
|
+
strategyPackage,
|
|
9112
|
+
strategyPackageVersion,
|
|
9113
|
+
runtimePackageVersion,
|
|
9114
|
+
strategyConfig
|
|
9115
|
+
})
|
|
9116
|
+
)
|
|
9117
|
+
} : await loadDeploymentReplayStrategies({
|
|
9118
|
+
userName: replayUserName,
|
|
9119
|
+
projectRoot: replayProjectRoot,
|
|
9120
|
+
deploymentId: replayDeploymentId
|
|
9121
|
+
});
|
|
9122
|
+
const { deployment, strategies: replayStrategies } = replayComposition;
|
|
9123
|
+
if (!replayStrategies.length) {
|
|
9124
|
+
throw new Error(`No enabled strategies in deployment ${deployment.id}`);
|
|
9125
|
+
}
|
|
9126
|
+
const strategyIntervals = new Set(
|
|
9127
|
+
replayStrategies.map(
|
|
9128
|
+
({ strategyConfig }) => String(strategyConfig.INTERVAL)
|
|
9129
|
+
)
|
|
9130
|
+
);
|
|
9131
|
+
if (strategyIntervals.size !== 1 || !strategyIntervals.has(replayInterval)) {
|
|
9132
|
+
throw new Error(
|
|
9133
|
+
`Replay interval does not match deployment ${deployment.id}: replay=${replayInterval}, strategies=${[...strategyIntervals].join(",")}`
|
|
9134
|
+
);
|
|
9135
|
+
}
|
|
9136
|
+
const strategyUniverses = new Set(
|
|
9137
|
+
replayStrategies.map(
|
|
9138
|
+
({ strategyConfig }) => String(strategyConfig.UNIVERSE)
|
|
9139
|
+
)
|
|
9140
|
+
);
|
|
9141
|
+
if (strategyUniverses.size !== 1) {
|
|
9142
|
+
throw new Error(
|
|
9143
|
+
`Replay requires one deployment universe: ${[...strategyUniverses].join(",")}`
|
|
9144
|
+
);
|
|
9145
|
+
}
|
|
8874
9146
|
const preparedRun = await prepareRunEnvironment({
|
|
8875
|
-
connector:
|
|
9147
|
+
connector: deployment.connectorName,
|
|
8876
9148
|
userName: replayUserName,
|
|
8877
|
-
tickers: replayFlags.tickers,
|
|
9149
|
+
tickers: replayFlags.tickers ?? deployment.tickers,
|
|
8878
9150
|
exclude: replayFlags.exclude,
|
|
8879
9151
|
tickersLimit: replayFlags.tickersLimit,
|
|
8880
9152
|
showTickersList: replayFlags.showTickersList,
|
|
@@ -8884,6 +9156,11 @@ var replayBacktest = async () => {
|
|
|
8884
9156
|
cacheOnly: replayFlags.cacheOnly,
|
|
8885
9157
|
interval: replayInterval,
|
|
8886
9158
|
projectRoot: replayProjectRoot,
|
|
9159
|
+
universe: [...strategyUniverses][0],
|
|
9160
|
+
accountId: deployment.accountId,
|
|
9161
|
+
deploymentId: deployment.id,
|
|
9162
|
+
assetClasses: deployment.assetClasses,
|
|
9163
|
+
deployment,
|
|
8887
9164
|
closedIntervalMs: (0, import_data2.intervalToMs)(replayInterval)
|
|
8888
9165
|
});
|
|
8889
9166
|
if (!preparedRun || isReplayUpdateOnlyRun) {
|
|
@@ -8897,10 +9174,6 @@ var replayBacktest = async () => {
|
|
|
8897
9174
|
end: preparedRun.window.end
|
|
8898
9175
|
}
|
|
8899
9176
|
});
|
|
8900
|
-
const replayStrategies = await loadReplayStrategies(replayUserName);
|
|
8901
|
-
if (!replayStrategies.length) {
|
|
8902
|
-
return;
|
|
8903
|
-
}
|
|
8904
9177
|
await prepareReplayBinanceMarketContext({
|
|
8905
9178
|
...preparedRun,
|
|
8906
9179
|
aiEnabled: replayStrategies.some(
|