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