@tradejs/cli 3.1.29-beta.286 → 3.1.29-beta.288

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.
@@ -35,10 +35,481 @@ __export(replay_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(replay_exports);
37
37
 
38
+ // src/lib/replay/historicalSignalsReplayPreparation.ts
39
+ var import_data = require("@tradejs/core/data");
40
+
41
+ // src/lib/replay/support.ts
42
+ var import_chalk = __toESM(require("chalk"));
43
+
44
+ // src/lib/runtimeModeConfig.ts
45
+ var buildRuntimeModeStrategyConfig = ({
46
+ strategyConfig,
47
+ env,
48
+ interval,
49
+ makeOrders,
50
+ recordRuntimeTrades,
51
+ aiReplayAnalyses
52
+ }) => ({
53
+ ...strategyConfig,
54
+ ENV: env,
55
+ MAKE_ORDERS: makeOrders,
56
+ INTERVAL: strategyConfig.INTERVAL ?? interval,
57
+ ...typeof recordRuntimeTrades === "boolean" ? { RECORD_RUNTIME_TRADES: recordRuntimeTrades } : {},
58
+ ...aiReplayAnalyses?.length ? { AI_REPLAY_ANALYSES: aiReplayAnalyses } : {}
59
+ });
60
+
61
+ // src/lib/replay/support.ts
62
+ var REPLAY_RESULTS_BY_STRATEGY_HEADERS = [
63
+ import_chalk.default.blue("STRATEGY"),
64
+ import_chalk.default.yellow("TICKERS"),
65
+ import_chalk.default.yellow("TRADE TICKERS"),
66
+ import_chalk.default.cyan("ORDERS"),
67
+ import_chalk.default.cyan("WIN/LOSS (%)"),
68
+ import_chalk.default.cyan("PROFIT"),
69
+ import_chalk.default.cyan("AVG TRADE")
70
+ ];
71
+ var REPLAY_RUNTIME_COMPARISON_HEADERS = [
72
+ import_chalk.default.blue("STRATEGY"),
73
+ import_chalk.default.cyan("BT ENTRIES"),
74
+ import_chalk.default.cyan("BT PNL"),
75
+ import_chalk.default.yellow("RT TRADES"),
76
+ import_chalk.default.yellow("RT PNL"),
77
+ import_chalk.default.green("MATCHED"),
78
+ import_chalk.default.red("ORDER FAILED"),
79
+ import_chalk.default.yellow("RT ONLY"),
80
+ import_chalk.default.magenta("BT ONLY"),
81
+ import_chalk.default.red("UNMATCHED")
82
+ ];
83
+ var REPLAY_RESULTS_CONFIG = "replay";
84
+ var REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS = 1;
85
+ var REPLAY_RUNTIME_COMPARE_TOLERANCE_MS = REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS * 15 * 60 * 1e3;
86
+ var formatReplayRuntimeCompareTolerance = () => `${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS} ${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS === 1 ? "bar" : "bars"}`;
87
+ var getReplayRuntimeUnmatchedCount = (row) => row.orderFailed + row.runtimeOnly + row.backtestOnly;
88
+ var buildReplayStrategyConfig = ({
89
+ strategyConfig,
90
+ interval
91
+ }) => ({
92
+ ...buildRuntimeModeStrategyConfig({
93
+ strategyConfig,
94
+ env: "PARITY",
95
+ interval,
96
+ makeOrders: false,
97
+ recordRuntimeTrades: false
98
+ }),
99
+ SIMULATE_ORDERS: true
100
+ });
101
+ var toRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
102
+ var compactReplayAdditionalIndicators = (additionalIndicators) => {
103
+ const additional = toRecord(additionalIndicators);
104
+ if (!additional) {
105
+ return void 0;
106
+ }
107
+ const compact = {};
108
+ if (additional.executionSlippage != null) {
109
+ compact.executionSlippage = additional.executionSlippage;
110
+ }
111
+ if (additional.marketContext != null) {
112
+ compact.marketContext = additional.marketContext;
113
+ }
114
+ const baseContext = toRecord(additional.baseContext);
115
+ const relative = toRecord(baseContext?.relative);
116
+ const execution = toRecord(relative?.execution);
117
+ if (execution) {
118
+ compact.baseContext = {
119
+ relative: {
120
+ execution
121
+ }
122
+ };
123
+ }
124
+ return Object.keys(compact).length ? compact : void 0;
125
+ };
126
+ var compactReplaySignal = (signal) => {
127
+ if (!signal) {
128
+ return void 0;
129
+ }
130
+ return {
131
+ signalId: signal.signalId,
132
+ strategy: signal.strategy,
133
+ symbol: signal.symbol,
134
+ interval: signal.interval,
135
+ direction: signal.direction,
136
+ timestamp: signal.timestamp,
137
+ prices: signal.prices,
138
+ indicators: {},
139
+ figures: {},
140
+ additionalIndicators: compactReplayAdditionalIndicators(
141
+ signal.additionalIndicators
142
+ ),
143
+ isConfigFromBacktest: signal.isConfigFromBacktest,
144
+ ...signal.orderStatus ? { orderStatus: signal.orderStatus } : {},
145
+ ...signal.orderSkipReason ? { orderSkipReason: signal.orderSkipReason } : {},
146
+ ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
147
+ ...signal.ml ? { ml: signal.ml } : {}
148
+ };
149
+ };
150
+
151
+ // src/lib/marketData/windows.ts
152
+ var import_indicators = require("@tradejs/core/indicators");
153
+ var splitCandlesForReplayWindow = (candles, start, preloadStart) => {
154
+ const prevData = [];
155
+ const replayData = [];
156
+ for (const candle of candles) {
157
+ if (candle.timestamp < preloadStart) {
158
+ continue;
159
+ }
160
+ if (candle.timestamp < start) {
161
+ prevData.push(candle);
162
+ continue;
163
+ }
164
+ replayData.push(candle);
165
+ }
166
+ return { prevData, replayData };
167
+ };
168
+ var alignSymbolWithBtcReference = (symbolCandles, btcCandles) => (0, import_indicators.alignSortedCandlesByTimestamp)(symbolCandles, btcCandles);
169
+
170
+ // src/lib/replay/historicalSignalsReplayPreparation.ts
171
+ var buildPreparedData = ({
172
+ data,
173
+ btcData,
174
+ ethData,
175
+ btcBinanceData,
176
+ btcCoinbaseData,
177
+ start,
178
+ preloadStart
179
+ }) => {
180
+ const { prevData: prevDataRaw, replayData: replayDataRaw } = splitCandlesForReplayWindow(data, start, preloadStart);
181
+ const { prevData: btcPrevDataRaw, replayData: btcReplayDataRaw } = splitCandlesForReplayWindow(btcData, start, preloadStart);
182
+ const { prevData: ethPrevDataRaw, replayData: ethReplayDataRaw } = splitCandlesForReplayWindow(ethData, start, preloadStart);
183
+ const { prevData: btcBinancePrevDataRaw } = splitCandlesForReplayWindow(
184
+ btcBinanceData,
185
+ start,
186
+ preloadStart
187
+ );
188
+ const { prevData: btcCoinbasePrevDataRaw } = splitCandlesForReplayWindow(
189
+ btcCoinbaseData,
190
+ start,
191
+ preloadStart
192
+ );
193
+ const { alignedCoinCandles: prevData, alignedBtcCandles: btcPrevData } = alignSymbolWithBtcReference(prevDataRaw, btcPrevDataRaw);
194
+ const { alignedCoinCandles: replayData, alignedBtcCandles: btcReplayData } = alignSymbolWithBtcReference(replayDataRaw, btcReplayDataRaw);
195
+ const { alignedBtcCandles: ethPrevData } = alignSymbolWithBtcReference(
196
+ prevData,
197
+ ethPrevDataRaw
198
+ );
199
+ const { alignedBtcCandles: ethReplayData } = alignSymbolWithBtcReference(
200
+ replayData,
201
+ ethReplayDataRaw
202
+ );
203
+ const { alignedBtcCandles: btcBinancePrevData } = alignSymbolWithBtcReference(
204
+ prevDataRaw,
205
+ btcBinancePrevDataRaw
206
+ );
207
+ const { alignedBtcCandles: btcCoinbasePrevData } = alignSymbolWithBtcReference(prevDataRaw, btcCoinbasePrevDataRaw);
208
+ return {
209
+ prevData,
210
+ btcPrevData,
211
+ ethPrevData,
212
+ replayData,
213
+ btcReplayData,
214
+ ethReplayData,
215
+ btcBinancePrevData,
216
+ btcCoinbasePrevData
217
+ };
218
+ };
219
+ var strategyIncludesSymbol = (strategy, symbol) => {
220
+ const tickers = strategy.selection?.tickers;
221
+ if (!tickers) return true;
222
+ const normalizedSymbol = symbol.trim().toUpperCase();
223
+ return tickers.some(
224
+ (ticker) => ticker.trim().toUpperCase() === normalizedSymbol
225
+ );
226
+ };
227
+ var buildSymbolSharedReplayKey = ({
228
+ userName,
229
+ connectorName,
230
+ interval,
231
+ symbol,
232
+ start,
233
+ end
234
+ }) => ["replay", userName, connectorName, symbol, interval, start, end].join(":");
235
+ var resolveHistoricalReplayIntervals = (baseInterval, strategyIntervals) => {
236
+ const intervals = [
237
+ ...new Set(
238
+ strategyIntervals.map((value) => {
239
+ if (!value || !Number.isFinite((0, import_data.intervalToMs)(value)) || (0, import_data.intervalToMs)(value) <= 0)
240
+ throw new Error(`Invalid replay strategy interval: ${value}`);
241
+ return value;
242
+ })
243
+ )
244
+ ].sort((a, b) => (0, import_data.intervalToMs)(a) - (0, import_data.intervalToMs)(b));
245
+ if (intervals.length > 1 && intervals.includes("M")) {
246
+ throw new Error("Mixed replay does not support calendar intervals");
247
+ }
248
+ const baseMs = (0, import_data.intervalToMs)(baseInterval);
249
+ if (intervals.some(
250
+ (value) => (0, import_data.intervalToMs)(value) < baseMs || (0, import_data.intervalToMs)(value) % baseMs !== 0
251
+ )) {
252
+ throw new Error(
253
+ "Replay strategy intervals must be multiples of the base timeframe"
254
+ );
255
+ }
256
+ if (!intervals.length || !intervals.includes(baseInterval)) {
257
+ throw new Error(
258
+ "Replay base timeframe must match an active strategy interval"
259
+ );
260
+ }
261
+ return intervals;
262
+ };
263
+ var prepareMultiIntervalHistoricalReplay = async (context, adapters, loadReferences) => {
264
+ const intervals = resolveHistoricalReplayIntervals(
265
+ context.interval,
266
+ context.strategies.map((s) => s.strategyConfig.INTERVAL)
267
+ );
268
+ const baseMs = (0, import_data.intervalToMs)(context.interval);
269
+ const plan = {
270
+ symbolRuntimes: [],
271
+ orderedTimestamps: [],
272
+ sharedReplayKeyPrefixes: [],
273
+ runtimeLineages: []
274
+ };
275
+ const timestamps = /* @__PURE__ */ new Set();
276
+ const baseRuntimes = /* @__PURE__ */ new Map();
277
+ const validatedSymbols = /* @__PURE__ */ new Set();
278
+ try {
279
+ for (const interval of intervals) {
280
+ const offset = (0, import_data.intervalToMs)(interval) - baseMs;
281
+ const nativeRun = {
282
+ ...context.preparedRun,
283
+ preloadStart: context.preparedRun.preloadStart - offset,
284
+ window: {
285
+ ...context.preparedRun.window,
286
+ start: context.preparedRun.window.start - offset,
287
+ end: context.preparedRun.window.end - offset
288
+ }
289
+ };
290
+ const part = await prepareHistoricalReplay(
291
+ {
292
+ ...context,
293
+ interval,
294
+ preparedRun: nativeRun,
295
+ strategies: context.strategies.filter(
296
+ (strategy) => strategy.strategyConfig.INTERVAL === interval
297
+ ),
298
+ references: interval === context.interval ? context.references : await loadReferences(interval, nativeRun)
299
+ },
300
+ adapters
301
+ );
302
+ plan.sharedReplayKeyPrefixes.push(...part.sharedReplayKeyPrefixes);
303
+ plan.runtimeLineages.push(...part.runtimeLineages);
304
+ for (const runtime of part.symbolRuntimes) {
305
+ if (interval === context.interval) {
306
+ baseRuntimes.set(runtime.symbol, runtime);
307
+ } else if (runtime.strategies.length && !validatedSymbols.has(runtime.symbol)) {
308
+ assertBaseReplayCoverage(
309
+ baseRuntimes.get(runtime.symbol),
310
+ runtime.symbol,
311
+ context.preparedRun.window,
312
+ baseMs
313
+ );
314
+ validatedSymbols.add(runtime.symbol);
315
+ }
316
+ runtime.timestampOffsetMs = offset;
317
+ runtime.advanceMarket = interval === context.interval;
318
+ plan.symbolRuntimes.push(runtime);
319
+ }
320
+ for (const timestamp of part.orderedTimestamps) {
321
+ const scheduled = timestamp + offset;
322
+ if (scheduled >= context.preparedRun.window.start && scheduled <= context.preparedRun.window.end)
323
+ timestamps.add(scheduled);
324
+ }
325
+ }
326
+ plan.orderedTimestamps = [...timestamps].sort((a, b) => a - b);
327
+ return plan;
328
+ } catch (error) {
329
+ releaseHistoricalReplayCaches(plan, adapters);
330
+ throw error;
331
+ }
332
+ };
333
+ var assertBaseReplayCoverage = (runtime, symbol, window, baseMs) => {
334
+ let index = 0;
335
+ for (let timestamp = Math.ceil(window.start / baseMs) * baseMs; timestamp <= window.end; timestamp += baseMs) {
336
+ if (runtime?.replayData[index]?.timestamp !== timestamp) {
337
+ throw new Error(`Missing base replay candle: ${symbol} at ${timestamp}`);
338
+ }
339
+ index += 1;
340
+ }
341
+ };
342
+ var prepareHistoricalReplay = async (context, adapters) => {
343
+ const {
344
+ userName,
345
+ projectRoot,
346
+ preparedRun,
347
+ interval,
348
+ connectorName,
349
+ replayConnector,
350
+ strategies,
351
+ references
352
+ } = context;
353
+ const symbolRuntimes = [];
354
+ const orderedTimestamps = /* @__PURE__ */ new Set();
355
+ const sharedReplayKeyPrefixes = [];
356
+ const runtimeLineages = [];
357
+ let skippedSymbols = 0;
358
+ try {
359
+ for (const symbol of preparedRun.tickers) {
360
+ const data = await preparedRun.marketConnector.kline({
361
+ symbol,
362
+ start: preparedRun.preloadStart,
363
+ end: preparedRun.window.end,
364
+ cacheOnly: true,
365
+ interval
366
+ });
367
+ if (symbolRuntimes.length % 50 === 0)
368
+ adapters.sampleMemory?.("history:loaded", {
369
+ symbols: symbolRuntimes.length + 1,
370
+ candles: data.length
371
+ });
372
+ const preparedData = buildPreparedData({
373
+ data,
374
+ btcData: references.btcMarketData,
375
+ ethData: references.ethMarketData,
376
+ btcBinanceData: references.btcBinanceData,
377
+ btcCoinbaseData: references.btcCoinbaseData,
378
+ start: preparedRun.window.start,
379
+ preloadStart: preparedRun.preloadStart
380
+ });
381
+ if (!preparedData.replayData.length || !preparedData.btcReplayData.length) {
382
+ skippedSymbols += 1;
383
+ adapters.progress.tick({
384
+ skipped: adapters.display.skipped(skippedSymbols),
385
+ symbol: adapters.display.symbol(symbol)
386
+ });
387
+ continue;
388
+ }
389
+ const sharedIndicatorsReplayKey = buildSymbolSharedReplayKey({
390
+ userName,
391
+ connectorName,
392
+ interval,
393
+ symbol,
394
+ start: preparedRun.window.start,
395
+ end: preparedRun.window.end
396
+ });
397
+ sharedReplayKeyPrefixes.push(sharedIndicatorsReplayKey);
398
+ const initializedStrategies = await Promise.allSettled(
399
+ strategies.filter((strategy) => strategyIncludesSymbol(strategy, symbol)).map(async (strategy) => {
400
+ const replayStrategyConfig = buildReplayStrategyConfig({
401
+ strategyConfig: strategy.strategyConfig,
402
+ interval
403
+ });
404
+ const runtimeLineage = await adapters.buildLineage({
405
+ projectRoot,
406
+ strategyName: strategy.strategyName,
407
+ strategyRevision: strategy.strategyRevision,
408
+ deploymentCompositionId: strategy.deploymentCompositionId,
409
+ strategyPackageVersion: strategy.strategyPackageVersion,
410
+ strategyDependencyVersions: strategy.strategyDependencyVersions,
411
+ runtimePackageVersion: strategy.runtimePackageVersion,
412
+ config: {
413
+ configId: strategy.strategyRevision,
414
+ strategyConfig: strategy.strategyConfig,
415
+ symbolResultConfig: strategy.strategyResults?.[symbol]?.config ?? null
416
+ },
417
+ runContext: {
418
+ connectorName: connectorName.toLowerCase(),
419
+ interval: String(interval),
420
+ universe: preparedRun.universe ?? null
421
+ }
422
+ });
423
+ runtimeLineages.push({
424
+ strategy: strategy.strategyName,
425
+ symbol,
426
+ ...preparedRun.deploymentId ? { deploymentId: preparedRun.deploymentId } : {},
427
+ ...preparedRun.accountId ? { accountId: preparedRun.accountId } : {},
428
+ lineage: runtimeLineage
429
+ });
430
+ return {
431
+ strategyName: strategy.strategyName,
432
+ strategyConfig: strategy.strategyConfig,
433
+ runtimeLineage,
434
+ run: await strategy.strategyCreator({
435
+ userName,
436
+ connectorName,
437
+ runtimeConfigId: strategy.strategyRevision,
438
+ strategyRevision: strategy.strategyRevision,
439
+ runtimeLineage,
440
+ universe: preparedRun.universe,
441
+ accountId: preparedRun.accountId,
442
+ deploymentId: preparedRun.deploymentId,
443
+ runtimeConfigSnapshot: {
444
+ userConfig: replayStrategyConfig
445
+ },
446
+ config: replayStrategyConfig,
447
+ symbol,
448
+ data: preparedData.prevData,
449
+ btcData: preparedData.btcPrevData,
450
+ ethData: preparedData.ethPrevData,
451
+ btcBinanceData: preparedData.btcBinancePrevData,
452
+ btcCoinbaseData: preparedData.btcCoinbasePrevData,
453
+ connector: replayConnector,
454
+ sharedIndicatorsReplayKey
455
+ })
456
+ };
457
+ })
458
+ );
459
+ const strategiesForSymbol = initializedStrategies.map((result) => {
460
+ if (result.status === "rejected") throw result.reason;
461
+ return result.value;
462
+ });
463
+ const symbolRuntime = {
464
+ symbol,
465
+ replayData: preparedData.replayData,
466
+ btcReplayData: preparedData.btcReplayData,
467
+ ethReplayData: preparedData.ethReplayData,
468
+ currentIndex: 0,
469
+ strategies: strategiesForSymbol
470
+ };
471
+ symbolRuntimes.push(symbolRuntime);
472
+ if (symbolRuntimes.length % 50 === 0 || symbol === preparedRun.tickers.at(-1))
473
+ adapters.sampleMemory?.("strategies:initializing", {
474
+ symbols: symbolRuntimes.length,
475
+ strategiesPerSymbol: strategiesForSymbol.length
476
+ });
477
+ adapters.progress.tick({
478
+ skipped: adapters.display.skipped(skippedSymbols),
479
+ symbol: adapters.display.symbol(symbol)
480
+ });
481
+ for (const candle of preparedData.replayData) {
482
+ orderedTimestamps.add(candle.timestamp);
483
+ }
484
+ }
485
+ return {
486
+ symbolRuntimes,
487
+ orderedTimestamps: [...orderedTimestamps].sort(
488
+ (left, right) => left - right
489
+ ),
490
+ sharedReplayKeyPrefixes,
491
+ runtimeLineages
492
+ };
493
+ } catch (error) {
494
+ releaseHistoricalReplayCaches({ sharedReplayKeyPrefixes }, adapters);
495
+ throw error;
496
+ }
497
+ };
498
+ var releaseHistoricalReplayPlan = (plan) => {
499
+ plan.symbolRuntimes.length = 0;
500
+ plan.sharedReplayKeyPrefixes.length = 0;
501
+ };
502
+ var releaseHistoricalReplayCaches = (plan, adapters) => {
503
+ for (const keyPrefix of plan.sharedReplayKeyPrefixes) {
504
+ adapters.releaseIndicatorsCache(keyPrefix);
505
+ adapters.releaseReplayCache(keyPrefix);
506
+ }
507
+ };
508
+
38
509
  // src/scripts/replayRunner.ts
39
510
  var import_chalk13 = __toESM(require("chalk"));
40
511
  var import_constants7 = require("@tradejs/core/constants");
41
- var import_data3 = require("@tradejs/core/data");
512
+ var import_data5 = require("@tradejs/core/data");
42
513
  var import_time7 = require("@tradejs/core/time");
43
514
  var import_redis6 = require("@tradejs/infra/redis");
44
515
  var import_candles = require("@tradejs/infra/timescale/candles");
@@ -71,7 +542,7 @@ var timeOperation = async (label, operation, log = console.log) => {
71
542
  };
72
543
 
73
544
  // src/lib/runEnvironment.ts
74
- var import_chalk3 = __toESM(require("chalk"));
545
+ var import_chalk4 = __toESM(require("chalk"));
75
546
  var import_connectors3 = require("@tradejs/node/connectors");
76
547
  var import_cli3 = require("@tradejs/node/cli");
77
548
  var import_constants = require("@tradejs/core/constants");
@@ -79,7 +550,7 @@ var import_time2 = require("@tradejs/core/time");
79
550
  var import_runtimeStrategies = require("@tradejs/node/runtimeStrategies");
80
551
 
81
552
  // src/lib/runtimeStrategyBacktest.ts
82
- var import_chalk = __toESM(require("chalk"));
553
+ var import_chalk2 = __toESM(require("chalk"));
83
554
 
84
555
  // src/lib/runtimeRedis.ts
85
556
  var import_redis = require("@tradejs/infra/redis");
@@ -229,7 +700,7 @@ var resolveTimeWindow = ({
229
700
  };
230
701
 
231
702
  // src/lib/marketData/historyPrepare.ts
232
- var import_chalk2 = __toESM(require("chalk"));
703
+ var import_chalk3 = __toESM(require("chalk"));
233
704
  var import_connectors = require("@tradejs/connectors");
234
705
  var import_cli = require("@tradejs/node/cli");
235
706
  var import_connectors2 = require("@tradejs/node/connectors");
@@ -320,7 +791,7 @@ var updatePrimaryMarketHistory = async ({
320
791
  const timeOperation3 = (label, operation) => timeOperation(
321
792
  label,
322
793
  operation,
323
- log ?? ((message) => console.log(import_chalk2.default.gray(message)))
794
+ log ?? ((message) => console.log(import_chalk3.default.gray(message)))
324
795
  );
325
796
  const updateOptions = preloadStart != null || preloadEnd != null ? {
326
797
  connectorLabel: connectorName,
@@ -352,7 +823,7 @@ var updateBtcReferenceHistory = async ({
352
823
  const timeOperation3 = (label, operation) => timeOperation(
353
824
  label,
354
825
  operation,
355
- log ?? ((message) => console.log(import_chalk2.default.gray(message)))
826
+ log ?? ((message) => console.log(import_chalk3.default.gray(message)))
356
827
  );
357
828
  const updateReference = async ({
358
829
  connector,
@@ -474,7 +945,7 @@ var loadRunTickers = async ({
474
945
  var timeOperation2 = async (label, operation) => timeOperation(
475
946
  label,
476
947
  operation,
477
- (message) => console.log(import_chalk3.default.gray(message))
948
+ (message) => console.log(import_chalk4.default.gray(message))
478
949
  );
479
950
  var resolveBacktestExecutionPreloadInterval = (interval) => {
480
951
  const normalized = String(interval);
@@ -495,7 +966,7 @@ var resolveRunConnectorName = async ({
495
966
  return connectorName;
496
967
  }
497
968
  console.log(
498
- import_chalk3.default.yellow(
969
+ import_chalk4.default.yellow(
499
970
  `Unknown connector "${String(value || "").trim() || String(value)}". Fallback to ${import_connectors3.DEFAULT_CONNECTOR_NAME}.`
500
971
  )
501
972
  );
@@ -550,7 +1021,7 @@ var loadDeploymentReplayStrategies = async ({
550
1021
  ) : [];
551
1022
  if (!strategies.length) {
552
1023
  console.log(
553
- import_chalk3.default.yellow(
1024
+ import_chalk4.default.yellow(
554
1025
  `No enabled strategies found in runtime deployment ${deploymentId}`
555
1026
  )
556
1027
  );
@@ -616,7 +1087,7 @@ var prepareRunEnvironment = async ({
616
1087
  );
617
1088
  }
618
1089
  if (showTickersList) {
619
- console.log(import_chalk3.default.gray(JSON.stringify(loadedTickers.sort(), null, 2)));
1090
+ console.log(import_chalk4.default.gray(JSON.stringify(loadedTickers.sort(), null, 2)));
620
1091
  return null;
621
1092
  }
622
1093
  const instruments = !cacheOnly && typeof marketConnector.listInstruments === "function" ? await timeOperation2(
@@ -653,7 +1124,7 @@ var prepareRunEnvironment = async ({
653
1124
  projectRoot,
654
1125
  shouldUseDedicatedReferences: universe === "crypto",
655
1126
  requireDedicatedReferences: universe === "crypto",
656
- warn: (message) => console.log(import_chalk3.default.yellow(message))
1127
+ warn: (message) => console.log(import_chalk4.default.yellow(message))
657
1128
  });
658
1129
  await updateMarketHistoryWithBtcReferences({
659
1130
  marketConnector,
@@ -664,7 +1135,7 @@ var prepareRunEnvironment = async ({
664
1135
  preloadStart,
665
1136
  preloadEnd: window.end,
666
1137
  universe,
667
- log: (message) => console.log(import_chalk3.default.gray(message))
1138
+ log: (message) => console.log(import_chalk4.default.gray(message))
668
1139
  });
669
1140
  const backtestExecutionInterval = resolveBacktestExecutionPreloadInterval(interval);
670
1141
  if (import_constants.BACKTEST_LOWER_TIMEFRAME_EXECUTION_ENABLED && backtestExecutionInterval && String(backtestExecutionInterval) !== String(interval)) {
@@ -701,13 +1172,13 @@ var prepareRunEnvironment = async ({
701
1172
  };
702
1173
 
703
1174
  // src/lib/marketContextPrepare.ts
704
- var import_chalk7 = __toESM(require("chalk"));
1175
+ var import_chalk8 = __toESM(require("chalk"));
705
1176
  var import_marketContext3 = require("@tradejs/infra/timescale/marketContext");
706
1177
  var import_registry = require("@tradejs/node/registry");
707
1178
 
708
1179
  // src/lib/binanceMarketContextBackfill.ts
709
1180
  var import_progress = __toESM(require("progress"));
710
- var import_chalk4 = __toESM(require("chalk"));
1181
+ var import_chalk5 = __toESM(require("chalk"));
711
1182
  var import_connectors4 = require("@tradejs/connectors");
712
1183
  var import_connectors5 = require("@tradejs/node/connectors");
713
1184
  var import_strategies = require("@tradejs/node/strategies");
@@ -1261,7 +1732,7 @@ var backfillBinanceMarketContext = async (params, enabled) => {
1261
1732
  let tradeFlowRows = 0;
1262
1733
  let breadthRows = 0;
1263
1734
  console.log(
1264
- import_chalk4.default.cyan(
1735
+ import_chalk5.default.cyan(
1265
1736
  `binance market context backfill: requestedSymbols=${symbols.length}, referenceSymbols=${referenceSymbols.length}, interval=${interval}, tradeFlowWindow=${new Date(tradeFlowStartMs).toISOString()}..${new Date(endMs).toISOString()}, breadthWindow=${new Date(breadthStartMs).toISOString()}..${new Date(endMs).toISOString()}`
1266
1737
  )
1267
1738
  );
@@ -1397,7 +1868,7 @@ var backfillBinanceMarketContext = async (params, enabled) => {
1397
1868
  }) : [];
1398
1869
  if (!missingChunks2.length) {
1399
1870
  console.log(
1400
- import_chalk4.default.gray(`breadth cached: universe=${definition.universe}`)
1871
+ import_chalk5.default.gray(`breadth cached: universe=${definition.universe}`)
1401
1872
  );
1402
1873
  }
1403
1874
  return {
@@ -1485,7 +1956,7 @@ var backfillBinanceMarketContext = async (params, enabled) => {
1485
1956
  }
1486
1957
  }
1487
1958
  console.log(
1488
- import_chalk4.default.green(
1959
+ import_chalk5.default.green(
1489
1960
  `binance market context backfill done: tradeFlowRows=${tradeFlowRows}, depthRows=0, breadthRows=${breadthRows}, skippedSymbols=${skippedSymbols}`
1490
1961
  )
1491
1962
  );
@@ -1502,11 +1973,11 @@ var backfillBinanceMarketContextForSignals = (params) => backfillBinanceMarketCo
1502
1973
  var backfillBinanceMarketContextForReplay = (params) => backfillBinanceMarketContext(params, true);
1503
1974
 
1504
1975
  // src/lib/derivativesContextBackfill.ts
1505
- var import_chalk5 = __toESM(require("chalk"));
1976
+ var import_chalk6 = __toESM(require("chalk"));
1506
1977
  var import_progress2 = __toESM(require("progress"));
1507
1978
  var import_async = require("@tradejs/core/async");
1508
1979
  var import_constants2 = require("@tradejs/core/constants");
1509
- var import_indicators = require("@tradejs/core/indicators");
1980
+ var import_indicators2 = require("@tradejs/core/indicators");
1510
1981
  var import_derivatives = require("@tradejs/infra/timescale/derivatives");
1511
1982
  var import_client2 = require("@tradejs/infra/timescale/client");
1512
1983
  var import_userSettings = require("@tradejs/infra/userSettings");
@@ -1609,7 +2080,7 @@ var resolveDerivativesContextBackfillWindow = (params) => {
1609
2080
  };
1610
2081
  var resolveDerivativesContextIntervalBackfillWindow = (params) => {
1611
2082
  const intervalMs = derivativesIntervalMs(params.interval);
1612
- const alignedToMs = params.closedOnly ? (0, import_indicators.getLastClosedDerivativesBarStartMs)(params.toMs, params.interval) : Math.floor(params.toMs / intervalMs) * intervalMs;
2083
+ const alignedToMs = params.closedOnly ? (0, import_indicators2.getLastClosedDerivativesBarStartMs)(params.toMs, params.interval) : Math.floor(params.toMs / intervalMs) * intervalMs;
1613
2084
  return {
1614
2085
  fromMs: Math.floor(params.fromMs / intervalMs) * intervalMs,
1615
2086
  toMs: alignedToMs,
@@ -1646,7 +2117,7 @@ var resolveDerivativesContextRequiredFetchFromMs = (params) => {
1646
2117
  refreshClosedTail: params.mode === "signals"
1647
2118
  });
1648
2119
  const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
1649
- const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
2120
+ const confirmedLiquidationWindow = (0, import_indicators2.resolveCoinalyzeConfirmedIntradayCoverage)({
1650
2121
  interval: params.interval,
1651
2122
  fromMs: params.fromMs,
1652
2123
  toMs: params.toMs,
@@ -2037,7 +2508,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2037
2508
  const coverageNowMs = Date.now();
2038
2509
  await Promise.all(
2039
2510
  intervalWindows.map(async (window) => {
2040
- const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
2511
+ const confirmedLiquidationWindow = (0, import_indicators2.resolveCoinalyzeConfirmedIntradayCoverage)({
2041
2512
  interval: window.interval,
2042
2513
  fromMs: window.fromMs,
2043
2514
  toMs: window.toMs,
@@ -2133,7 +2604,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2133
2604
  toMs: backfillWindow.toMs
2134
2605
  })
2135
2606
  );
2136
- const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
2607
+ const confirmedLiquidationWindow = (0, import_indicators2.resolveCoinalyzeConfirmedIntradayCoverage)({
2137
2608
  interval: window.interval,
2138
2609
  fromMs: backfillWindow.fromMs,
2139
2610
  toMs: backfillWindow.toMs,
@@ -2151,7 +2622,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2151
2622
  });
2152
2623
  if (allBackfillWindowsCached) {
2153
2624
  console.log(
2154
- import_chalk5.default.gray(
2625
+ import_chalk6.default.gray(
2155
2626
  `derivatives context backfill: cached symbols=${symbols.length}, requestedSymbols=${requestedSymbols.length}, intervals=${intervals.join(",")}, window=${new Date(fromMs).toISOString()}..${new Date(safeEndMs).toISOString()}`
2156
2627
  )
2157
2628
  );
@@ -2216,7 +2687,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2216
2687
  let failedWindows = 0;
2217
2688
  let skippedWindows = 0;
2218
2689
  console.log(
2219
- import_chalk5.default.cyan(
2690
+ import_chalk6.default.cyan(
2220
2691
  `derivatives context backfill: symbols=${matches.length}, requestedSymbols=${requestedSymbols.length}, unmatched=${unmatched.length}, intervals=${intervals.join(",")}, window=${new Date(fromMs).toISOString()}..${new Date(safeEndMs).toISOString()}, testStart=${new Date(safeStartMs).toISOString()}`
2221
2692
  )
2222
2693
  );
@@ -2321,13 +2792,13 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2321
2792
  });
2322
2793
  rows = missingBatch.flatMap((item) => {
2323
2794
  const marketSymbol = item.marketSymbol.toUpperCase();
2324
- const points = (0, import_indicators.mergeCoinalyzeMetrics)({
2795
+ const points = (0, import_indicators2.mergeCoinalyzeMetrics)({
2325
2796
  symbol: item.symbol,
2326
2797
  oiRaw: oiMap.get(marketSymbol) ?? [],
2327
2798
  fundingRaw: fundingMap.get(marketSymbol) ?? [],
2328
2799
  liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
2329
2800
  });
2330
- return (0, import_indicators.coinalyzePointsToRows)(points, interval, "coinalyze");
2801
+ return (0, import_indicators2.coinalyzePointsToRows)(points, interval, "coinalyze");
2331
2802
  });
2332
2803
  missingClosedSymbols = mode === "signals" ? getMissingClosedDerivativesSymbols({
2333
2804
  symbols: missingBatch.map((item) => item.symbol),
@@ -2348,7 +2819,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2348
2819
  await (0, import_derivatives.upsertDerivatives)(rows);
2349
2820
  totalRows += rows.length;
2350
2821
  }
2351
- const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
2822
+ const confirmedLiquidationWindow = (0, import_indicators2.resolveCoinalyzeConfirmedIntradayCoverage)({
2352
2823
  interval,
2353
2824
  fromMs: group.fromMs,
2354
2825
  toMs,
@@ -2358,7 +2829,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2358
2829
  const metricCoverageRows = missingBatch.map((item) => {
2359
2830
  const marketSymbol = item.marketSymbol.toUpperCase();
2360
2831
  const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
2361
- const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
2832
+ const timestamp = (0, import_indicators2.toCoinalyzeTimestampMs)(
2362
2833
  point.t ?? point.ts ?? point.time ?? point.timestamp
2363
2834
  );
2364
2835
  return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
@@ -2430,13 +2901,13 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2430
2901
  } catch (error) {
2431
2902
  failedWindows += 1;
2432
2903
  console.error(
2433
- import_chalk5.default.red(
2904
+ import_chalk6.default.red(
2434
2905
  `derivatives context backfill window failed batch=${batchIdx + 1}/${symbolBatches.length} interval=${interval} ${new Date(cursor).toISOString()}..${new Date(toMs).toISOString()}: ${error}`
2435
2906
  )
2436
2907
  );
2437
2908
  } finally {
2438
2909
  bar.tick(1, {
2439
- batch: import_chalk5.default.gray(
2910
+ batch: import_chalk6.default.gray(
2440
2911
  `${batchIdx + 1}/${symbolBatches.length} ${interval}`
2441
2912
  ),
2442
2913
  rows: totalRows,
@@ -2450,7 +2921,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2450
2921
  }
2451
2922
  console.log("");
2452
2923
  console.log(
2453
- import_chalk5.default.green(
2924
+ import_chalk6.default.green(
2454
2925
  `derivatives context backfill done: rows=${totalRows}, failed_windows=${failedWindows}, skipped_windows=${skippedWindows}`
2455
2926
  )
2456
2927
  );
@@ -2480,7 +2951,7 @@ var backfillDerivativesContextForSignals = async (params) => backfillDerivatives
2480
2951
  );
2481
2952
 
2482
2953
  // src/lib/coinMarketCapContextBackfill.ts
2483
- var import_chalk6 = __toESM(require("chalk"));
2954
+ var import_chalk7 = __toESM(require("chalk"));
2484
2955
  var import_async2 = require("@tradejs/core/async");
2485
2956
  var import_marketContext2 = require("@tradejs/infra/timescale/marketContext");
2486
2957
  var import_client3 = require("@tradejs/infra/timescale/client");
@@ -2742,7 +3213,7 @@ var formatRowTimeRange = (rows) => {
2742
3213
  };
2743
3214
  var logBackfillSourceDone = (params) => {
2744
3215
  console.log(
2745
- import_chalk6.default.gray(
3216
+ import_chalk7.default.gray(
2746
3217
  `coinmarketcap context source done: ${params.name}=${params.status} rows=${params.rows}${params.details ? ` ${params.details}` : ""}`
2747
3218
  )
2748
3219
  );
@@ -2776,7 +3247,7 @@ var coinMarketCapFetch = async (params) => {
2776
3247
  recordCoinMarketCapFetchStats(params.stats, params.path, creditCount);
2777
3248
  if (creditCount != null && isVerboseRequestLoggingEnabled()) {
2778
3249
  console.log(
2779
- import_chalk6.default.gray(`coinmarketcap ${params.path}: credits=${creditCount}`)
3250
+ import_chalk7.default.gray(`coinmarketcap ${params.path}: credits=${creditCount}`)
2780
3251
  );
2781
3252
  }
2782
3253
  return payload;
@@ -2821,7 +3292,7 @@ var fetchCoinMarketCapIndexRows = async (params) => {
2821
3292
  );
2822
3293
  if (chunk === 1 || chunk === totalChunks || chunk % 25 === 0) {
2823
3294
  console.log(
2824
- import_chalk6.default.gray(
3295
+ import_chalk7.default.gray(
2825
3296
  `coinmarketcap index backfill progress: ${params.index.slug} chunk=${chunk}/${totalChunks} rows=${rows.length} chunkRange=${new Date(cursorMs).toISOString()}..${new Date(chunkEndMs).toISOString()}`
2826
3297
  )
2827
3298
  );
@@ -3260,7 +3731,7 @@ var backfillCoinMarketCapContext = async (params) => {
3260
3731
  covered: indexesBackfillCovered
3261
3732
  });
3262
3733
  console.log(
3263
- import_chalk6.default.gray(
3734
+ import_chalk7.default.gray(
3264
3735
  [
3265
3736
  "coinmarketcap context status:",
3266
3737
  formatSourceStatus({
@@ -3295,7 +3766,7 @@ var backfillCoinMarketCapContext = async (params) => {
3295
3766
  );
3296
3767
  if (globalDailyReady && referencesDailyReady && exchangeLiquidityReady && fearGreedReady && indexesReady) {
3297
3768
  console.log(
3298
- import_chalk6.default.gray(
3769
+ import_chalk7.default.gray(
3299
3770
  `coinmarketcap context cached: ${new Date(fromMs).toISOString()}..${new Date(toMs).toISOString()}`
3300
3771
  )
3301
3772
  );
@@ -3322,7 +3793,7 @@ var backfillCoinMarketCapContext = async (params) => {
3322
3793
  !indexesReady ? "indexes" : null
3323
3794
  ].filter((source) => source != null);
3324
3795
  console.log(
3325
- import_chalk6.default.cyan(
3796
+ import_chalk7.default.cyan(
3326
3797
  `coinmarketcap context backfill: window=${new Date(fromMs).toISOString()}..${new Date(toMs).toISOString()} days=${Math.floor((toMs - fromMs) / DAY_MS3) + 1} fetch=${pendingSources.join(",")}`
3327
3798
  )
3328
3799
  );
@@ -3514,7 +3985,7 @@ var backfillCoinMarketCapContext = async (params) => {
3514
3985
  });
3515
3986
  }
3516
3987
  console.log(
3517
- import_chalk6.default.green(
3988
+ import_chalk7.default.green(
3518
3989
  [
3519
3990
  "coinmarketcap context backfill done:",
3520
3991
  `global=${globalStatus} rows=${globalRows}`,
@@ -4126,7 +4597,7 @@ var buildDerivativesBackfillParams = (params) => ({
4126
4597
  });
4127
4598
  var prepareMarketContextForRun = async (params) => {
4128
4599
  const log = params.log ?? ((message) => {
4129
- console.log(import_chalk7.default.gray(message));
4600
+ console.log(import_chalk8.default.gray(message));
4130
4601
  });
4131
4602
  const timeOperation3 = (label, operation) => timeOperation(label, operation, log);
4132
4603
  const requirements = await resolveMarketContextRunRequirements(params);
@@ -4577,174 +5048,64 @@ var buildRuntimeEvidenceLineageScopes = ({
4577
5048
  (left, right) => left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || left.firstTimestamp - right.firstTimestamp
4578
5049
  );
4579
5050
  };
4580
- var loadReplayRuntimeEvidenceSource = async ({
4581
- filePath,
4582
- projectRoot,
4583
- expectedUserName,
4584
- expectedWindow
4585
- }) => {
4586
- const { resolvedPath, runtime, userName, startTime, endTime, deployment } = await readRuntimeEvidenceArtifact({ filePath, projectRoot });
4587
- if (!userName || userName !== expectedUserName) {
4588
- throw new Error(
4589
- `Runtime evidence user mismatch: expected=${expectedUserName}, actual=${userName || "missing"}`
4590
- );
4591
- }
4592
- const endTimeMatches = endTime === expectedWindow.end || endTime === expectedWindow.end + 1;
4593
- if (startTime !== expectedWindow.start || !endTimeMatches) {
4594
- throw new Error(
4595
- `Runtime evidence window mismatch: expected=${expectedWindow.start}..${expectedWindow.end}, actual=${startTime ?? "missing"}..${endTime ?? "missing"}`
4596
- );
4597
- }
4598
- const trades = unwrapRows(runtime.trades, "trade");
4599
- const signals = unwrapRows(runtime.signals, "signal");
4600
- const evaluations = unwrapRows(
4601
- runtime.evaluations,
4602
- "evaluation"
4603
- );
4604
- const storedLineageScopes = unwrapRows(
4605
- runtime.lineageScopes,
4606
- "lineageScope"
4607
- );
4608
- assertRuntimeRowsMatchDeployment({
4609
- label: "trade",
4610
- rows: trades,
4611
- deployment
4612
- });
4613
- assertRuntimeRowsMatchDeployment({
4614
- label: "signal",
4615
- rows: signals,
4616
- deployment
4617
- });
4618
- assertRuntimeRowsMatchDeployment({
4619
- label: "evaluation",
4620
- rows: evaluations,
4621
- deployment
4622
- });
4623
- assertRuntimeRowsMatchDeployment({
4624
- label: "lineage scope",
4625
- rows: storedLineageScopes,
4626
- deployment
4627
- });
4628
- return {
4629
- path: resolvedPath,
4630
- userName,
4631
- startTime,
4632
- endTime,
4633
- trades,
4634
- signals,
4635
- evaluations,
4636
- lineageScopes: storedLineageScopes.length ? storedLineageScopes : buildRuntimeEvidenceLineageScopes({ signals, evaluations }),
4637
- deployment
4638
- };
4639
- };
4640
-
4641
- // src/lib/replay/support.ts
4642
- var import_chalk8 = __toESM(require("chalk"));
4643
-
4644
- // src/lib/runtimeModeConfig.ts
4645
- var buildRuntimeModeStrategyConfig = ({
4646
- strategyConfig,
4647
- env,
4648
- interval,
4649
- makeOrders,
4650
- recordRuntimeTrades,
4651
- aiReplayAnalyses
4652
- }) => ({
4653
- ...strategyConfig,
4654
- ENV: env,
4655
- MAKE_ORDERS: makeOrders,
4656
- INTERVAL: strategyConfig.INTERVAL ?? interval,
4657
- ...typeof recordRuntimeTrades === "boolean" ? { RECORD_RUNTIME_TRADES: recordRuntimeTrades } : {},
4658
- ...aiReplayAnalyses?.length ? { AI_REPLAY_ANALYSES: aiReplayAnalyses } : {}
4659
- });
4660
-
4661
- // src/lib/replay/support.ts
4662
- var REPLAY_RESULTS_BY_STRATEGY_HEADERS = [
4663
- import_chalk8.default.blue("STRATEGY"),
4664
- import_chalk8.default.yellow("TICKERS"),
4665
- import_chalk8.default.yellow("TRADE TICKERS"),
4666
- import_chalk8.default.cyan("ORDERS"),
4667
- import_chalk8.default.cyan("WIN/LOSS (%)"),
4668
- import_chalk8.default.cyan("PROFIT"),
4669
- import_chalk8.default.cyan("AVG TRADE")
4670
- ];
4671
- var REPLAY_RUNTIME_COMPARISON_HEADERS = [
4672
- import_chalk8.default.blue("STRATEGY"),
4673
- import_chalk8.default.cyan("BT ENTRIES"),
4674
- import_chalk8.default.cyan("BT PNL"),
4675
- import_chalk8.default.yellow("RT TRADES"),
4676
- import_chalk8.default.yellow("RT PNL"),
4677
- import_chalk8.default.green("MATCHED"),
4678
- import_chalk8.default.red("ORDER FAILED"),
4679
- import_chalk8.default.yellow("RT ONLY"),
4680
- import_chalk8.default.magenta("BT ONLY"),
4681
- import_chalk8.default.red("UNMATCHED")
4682
- ];
4683
- var REPLAY_RESULTS_CONFIG = "replay";
4684
- var REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS = 1;
4685
- var REPLAY_RUNTIME_COMPARE_TOLERANCE_MS = REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS * 15 * 60 * 1e3;
4686
- var formatReplayRuntimeCompareTolerance = () => `${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS} ${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS === 1 ? "bar" : "bars"}`;
4687
- var getReplayRuntimeUnmatchedCount = (row) => row.orderFailed + row.runtimeOnly + row.backtestOnly;
4688
- var buildReplayStrategyConfig = ({
4689
- strategyConfig,
4690
- interval
4691
- }) => ({
4692
- ...buildRuntimeModeStrategyConfig({
4693
- strategyConfig,
4694
- env: "PARITY",
4695
- interval,
4696
- makeOrders: false,
4697
- recordRuntimeTrades: false
4698
- }),
4699
- SIMULATE_ORDERS: true
4700
- });
4701
- var toRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4702
- var compactReplayAdditionalIndicators = (additionalIndicators) => {
4703
- const additional = toRecord(additionalIndicators);
4704
- if (!additional) {
4705
- return void 0;
4706
- }
4707
- const compact = {};
4708
- if (additional.executionSlippage != null) {
4709
- compact.executionSlippage = additional.executionSlippage;
4710
- }
4711
- if (additional.marketContext != null) {
4712
- compact.marketContext = additional.marketContext;
4713
- }
4714
- const baseContext = toRecord(additional.baseContext);
4715
- const relative = toRecord(baseContext?.relative);
4716
- const execution = toRecord(relative?.execution);
4717
- if (execution) {
4718
- compact.baseContext = {
4719
- relative: {
4720
- execution
4721
- }
4722
- };
4723
- }
4724
- return Object.keys(compact).length ? compact : void 0;
4725
- };
4726
- var compactReplaySignal = (signal) => {
4727
- if (!signal) {
4728
- return void 0;
5051
+ var loadReplayRuntimeEvidenceSource = async ({
5052
+ filePath,
5053
+ projectRoot,
5054
+ expectedUserName,
5055
+ expectedWindow
5056
+ }) => {
5057
+ const { resolvedPath, runtime, userName, startTime, endTime, deployment } = await readRuntimeEvidenceArtifact({ filePath, projectRoot });
5058
+ if (!userName || userName !== expectedUserName) {
5059
+ throw new Error(
5060
+ `Runtime evidence user mismatch: expected=${expectedUserName}, actual=${userName || "missing"}`
5061
+ );
5062
+ }
5063
+ const endTimeMatches = endTime === expectedWindow.end || endTime === expectedWindow.end + 1;
5064
+ if (startTime !== expectedWindow.start || !endTimeMatches) {
5065
+ throw new Error(
5066
+ `Runtime evidence window mismatch: expected=${expectedWindow.start}..${expectedWindow.end}, actual=${startTime ?? "missing"}..${endTime ?? "missing"}`
5067
+ );
4729
5068
  }
5069
+ const trades = unwrapRows(runtime.trades, "trade");
5070
+ const signals = unwrapRows(runtime.signals, "signal");
5071
+ const evaluations = unwrapRows(
5072
+ runtime.evaluations,
5073
+ "evaluation"
5074
+ );
5075
+ const storedLineageScopes = unwrapRows(
5076
+ runtime.lineageScopes,
5077
+ "lineageScope"
5078
+ );
5079
+ assertRuntimeRowsMatchDeployment({
5080
+ label: "trade",
5081
+ rows: trades,
5082
+ deployment
5083
+ });
5084
+ assertRuntimeRowsMatchDeployment({
5085
+ label: "signal",
5086
+ rows: signals,
5087
+ deployment
5088
+ });
5089
+ assertRuntimeRowsMatchDeployment({
5090
+ label: "evaluation",
5091
+ rows: evaluations,
5092
+ deployment
5093
+ });
5094
+ assertRuntimeRowsMatchDeployment({
5095
+ label: "lineage scope",
5096
+ rows: storedLineageScopes,
5097
+ deployment
5098
+ });
4730
5099
  return {
4731
- signalId: signal.signalId,
4732
- strategy: signal.strategy,
4733
- symbol: signal.symbol,
4734
- interval: signal.interval,
4735
- direction: signal.direction,
4736
- timestamp: signal.timestamp,
4737
- prices: signal.prices,
4738
- indicators: {},
4739
- figures: {},
4740
- additionalIndicators: compactReplayAdditionalIndicators(
4741
- signal.additionalIndicators
4742
- ),
4743
- isConfigFromBacktest: signal.isConfigFromBacktest,
4744
- ...signal.orderStatus ? { orderStatus: signal.orderStatus } : {},
4745
- ...signal.orderSkipReason ? { orderSkipReason: signal.orderSkipReason } : {},
4746
- ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
4747
- ...signal.ml ? { ml: signal.ml } : {}
5100
+ path: resolvedPath,
5101
+ userName,
5102
+ startTime,
5103
+ endTime,
5104
+ trades,
5105
+ signals,
5106
+ evaluations,
5107
+ lineageScopes: storedLineageScopes.length ? storedLineageScopes : buildRuntimeEvidenceLineageScopes({ signals, evaluations }),
5108
+ deployment
4748
5109
  };
4749
5110
  };
4750
5111
 
@@ -4813,6 +5174,7 @@ var getPositionPnlSnapshot = ({
4813
5174
  var createPortfolioReplayConnector = (connector) => {
4814
5175
  let state = {};
4815
5176
  let amount = import_constants3.INITIAL_BACKTEST_AMOUNT;
5177
+ let executionTimestamp = null;
4816
5178
  const positionsBySymbol = /* @__PURE__ */ new Map();
4817
5179
  const currentPriceBySymbol = /* @__PURE__ */ new Map();
4818
5180
  const orderLog = [];
@@ -5074,6 +5436,7 @@ var createPortfolioReplayConnector = (connector) => {
5074
5436
  });
5075
5437
  const nextPosition = {
5076
5438
  ...order,
5439
+ timestamp: executionTimestamp ?? order.timestamp,
5077
5440
  price: entryPrice,
5078
5441
  signal: compactReplaySignal(order.signal),
5079
5442
  amount,
@@ -5098,6 +5461,7 @@ var createPortfolioReplayConnector = (connector) => {
5098
5461
  positionState,
5099
5462
  data: {
5100
5463
  ...order,
5464
+ timestamp: executionTimestamp ?? order.timestamp,
5101
5465
  price: entryPrice,
5102
5466
  profit,
5103
5467
  fee,
@@ -5161,6 +5525,7 @@ var createPortfolioReplayConnector = (connector) => {
5161
5525
  positionState,
5162
5526
  data: {
5163
5527
  ...order,
5528
+ timestamp: executionTimestamp ?? order.timestamp,
5164
5529
  price: executionPrice,
5165
5530
  qty: position.qty,
5166
5531
  profit,
@@ -5169,11 +5534,12 @@ var createPortfolioReplayConnector = (connector) => {
5169
5534
  ...getExecutionSlippageLogData(slippageBreakdown, "exit")
5170
5535
  }
5171
5536
  });
5172
- clearPosition(order.symbol, order.timestamp);
5537
+ clearPosition(order.symbol, executionTimestamp ?? order.timestamp);
5173
5538
  return true;
5174
5539
  },
5175
5540
  getTickers: connector.getTickers,
5176
5541
  advanceMarket: async ({ symbol, candle }) => {
5542
+ executionTimestamp = candle.timestamp;
5177
5543
  currentPriceBySymbol.set(symbol, candle.close);
5178
5544
  await checkSl({ symbol, candle });
5179
5545
  await checkTp({ symbol, candle });
@@ -5214,244 +5580,8 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
5214
5580
  }
5215
5581
  };
5216
5582
 
5217
- // src/lib/marketData/windows.ts
5218
- var import_indicators2 = require("@tradejs/core/indicators");
5219
- var splitCandlesForReplayWindow = (candles, start, preloadStart) => {
5220
- const prevData = [];
5221
- const replayData = [];
5222
- for (const candle of candles) {
5223
- if (candle.timestamp < preloadStart) {
5224
- continue;
5225
- }
5226
- if (candle.timestamp < start) {
5227
- prevData.push(candle);
5228
- continue;
5229
- }
5230
- replayData.push(candle);
5231
- }
5232
- return { prevData, replayData };
5233
- };
5234
- var alignSymbolWithBtcReference = (symbolCandles, btcCandles) => (0, import_indicators2.alignSortedCandlesByTimestamp)(symbolCandles, btcCandles);
5235
-
5236
- // src/lib/replay/historicalSignalsReplayPreparation.ts
5237
- var buildPreparedData = ({
5238
- data,
5239
- btcData,
5240
- ethData,
5241
- btcBinanceData,
5242
- btcCoinbaseData,
5243
- start,
5244
- preloadStart
5245
- }) => {
5246
- const { prevData: prevDataRaw, replayData: replayDataRaw } = splitCandlesForReplayWindow(data, start, preloadStart);
5247
- const { prevData: btcPrevDataRaw, replayData: btcReplayDataRaw } = splitCandlesForReplayWindow(btcData, start, preloadStart);
5248
- const { prevData: ethPrevDataRaw, replayData: ethReplayDataRaw } = splitCandlesForReplayWindow(ethData, start, preloadStart);
5249
- const { prevData: btcBinancePrevDataRaw } = splitCandlesForReplayWindow(
5250
- btcBinanceData,
5251
- start,
5252
- preloadStart
5253
- );
5254
- const { prevData: btcCoinbasePrevDataRaw } = splitCandlesForReplayWindow(
5255
- btcCoinbaseData,
5256
- start,
5257
- preloadStart
5258
- );
5259
- const { alignedCoinCandles: prevData, alignedBtcCandles: btcPrevData } = alignSymbolWithBtcReference(prevDataRaw, btcPrevDataRaw);
5260
- const { alignedCoinCandles: replayData, alignedBtcCandles: btcReplayData } = alignSymbolWithBtcReference(replayDataRaw, btcReplayDataRaw);
5261
- const { alignedBtcCandles: ethPrevData } = alignSymbolWithBtcReference(
5262
- prevData,
5263
- ethPrevDataRaw
5264
- );
5265
- const { alignedBtcCandles: ethReplayData } = alignSymbolWithBtcReference(
5266
- replayData,
5267
- ethReplayDataRaw
5268
- );
5269
- const { alignedBtcCandles: btcBinancePrevData } = alignSymbolWithBtcReference(
5270
- prevDataRaw,
5271
- btcBinancePrevDataRaw
5272
- );
5273
- const { alignedBtcCandles: btcCoinbasePrevData } = alignSymbolWithBtcReference(prevDataRaw, btcCoinbasePrevDataRaw);
5274
- return {
5275
- prevData,
5276
- btcPrevData,
5277
- ethPrevData,
5278
- replayData,
5279
- btcReplayData,
5280
- ethReplayData,
5281
- btcBinancePrevData,
5282
- btcCoinbasePrevData
5283
- };
5284
- };
5285
- var strategyIncludesSymbol = (strategy, symbol) => {
5286
- const tickers = strategy.selection?.tickers;
5287
- if (!tickers) return true;
5288
- const normalizedSymbol = symbol.trim().toUpperCase();
5289
- return tickers.some(
5290
- (ticker) => ticker.trim().toUpperCase() === normalizedSymbol
5291
- );
5292
- };
5293
- var buildSymbolSharedReplayKey = ({
5294
- userName,
5295
- connectorName,
5296
- interval,
5297
- symbol,
5298
- start,
5299
- end
5300
- }) => ["replay", userName, connectorName, symbol, interval, start, end].join(":");
5301
- var prepareHistoricalReplay = async (context, adapters) => {
5302
- const {
5303
- userName,
5304
- projectRoot,
5305
- preparedRun,
5306
- interval,
5307
- connectorName,
5308
- replayConnector,
5309
- strategies,
5310
- references
5311
- } = context;
5312
- const symbolRuntimes = [];
5313
- const orderedTimestamps = /* @__PURE__ */ new Set();
5314
- const sharedReplayKeyPrefixes = [];
5315
- const runtimeLineages = [];
5316
- let skippedSymbols = 0;
5317
- for (const symbol of preparedRun.tickers) {
5318
- const data = await preparedRun.marketConnector.kline({
5319
- symbol,
5320
- start: preparedRun.preloadStart,
5321
- end: preparedRun.window.end,
5322
- cacheOnly: true,
5323
- interval
5324
- });
5325
- if (symbolRuntimes.length % 50 === 0)
5326
- adapters.sampleMemory?.("history:loaded", {
5327
- symbols: symbolRuntimes.length + 1,
5328
- candles: data.length
5329
- });
5330
- const preparedData = buildPreparedData({
5331
- data,
5332
- btcData: references.btcMarketData,
5333
- ethData: references.ethMarketData,
5334
- btcBinanceData: references.btcBinanceData,
5335
- btcCoinbaseData: references.btcCoinbaseData,
5336
- start: preparedRun.window.start,
5337
- preloadStart: preparedRun.preloadStart
5338
- });
5339
- if (!preparedData.replayData.length || !preparedData.btcReplayData.length) {
5340
- skippedSymbols += 1;
5341
- adapters.progress.tick({
5342
- skipped: adapters.display.skipped(skippedSymbols),
5343
- symbol: adapters.display.symbol(symbol)
5344
- });
5345
- continue;
5346
- }
5347
- const sharedIndicatorsReplayKey = buildSymbolSharedReplayKey({
5348
- userName,
5349
- connectorName,
5350
- interval,
5351
- symbol,
5352
- start: preparedRun.window.start,
5353
- end: preparedRun.window.end
5354
- });
5355
- sharedReplayKeyPrefixes.push(sharedIndicatorsReplayKey);
5356
- const strategiesForSymbol = await Promise.all(
5357
- strategies.filter((strategy) => strategyIncludesSymbol(strategy, symbol)).map(async (strategy) => {
5358
- const replayStrategyConfig = buildReplayStrategyConfig({
5359
- strategyConfig: strategy.strategyConfig,
5360
- interval
5361
- });
5362
- const runtimeLineage = await adapters.buildLineage({
5363
- projectRoot,
5364
- strategyName: strategy.strategyName,
5365
- strategyRevision: strategy.strategyRevision,
5366
- deploymentCompositionId: strategy.deploymentCompositionId,
5367
- strategyPackageVersion: strategy.strategyPackageVersion,
5368
- strategyDependencyVersions: strategy.strategyDependencyVersions,
5369
- runtimePackageVersion: strategy.runtimePackageVersion,
5370
- config: {
5371
- configId: strategy.strategyRevision,
5372
- strategyConfig: strategy.strategyConfig,
5373
- symbolResultConfig: strategy.strategyResults?.[symbol]?.config ?? null
5374
- },
5375
- runContext: {
5376
- connectorName: connectorName.toLowerCase(),
5377
- interval: String(interval),
5378
- universe: preparedRun.universe ?? null
5379
- }
5380
- });
5381
- runtimeLineages.push({
5382
- strategy: strategy.strategyName,
5383
- symbol,
5384
- ...preparedRun.deploymentId ? { deploymentId: preparedRun.deploymentId } : {},
5385
- ...preparedRun.accountId ? { accountId: preparedRun.accountId } : {},
5386
- lineage: runtimeLineage
5387
- });
5388
- return {
5389
- strategyName: strategy.strategyName,
5390
- strategyConfig: strategy.strategyConfig,
5391
- runtimeLineage,
5392
- run: await strategy.strategyCreator({
5393
- userName,
5394
- connectorName,
5395
- runtimeConfigId: strategy.strategyRevision,
5396
- strategyRevision: strategy.strategyRevision,
5397
- runtimeLineage,
5398
- universe: preparedRun.universe,
5399
- accountId: preparedRun.accountId,
5400
- deploymentId: preparedRun.deploymentId,
5401
- runtimeConfigSnapshot: {
5402
- userConfig: replayStrategyConfig
5403
- },
5404
- config: replayStrategyConfig,
5405
- symbol,
5406
- data: preparedData.prevData,
5407
- btcData: preparedData.btcPrevData,
5408
- ethData: preparedData.ethPrevData,
5409
- btcBinanceData: preparedData.btcBinancePrevData,
5410
- btcCoinbaseData: preparedData.btcCoinbasePrevData,
5411
- connector: replayConnector,
5412
- sharedIndicatorsReplayKey
5413
- })
5414
- };
5415
- })
5416
- );
5417
- const symbolRuntime = {
5418
- symbol,
5419
- replayData: preparedData.replayData,
5420
- btcReplayData: preparedData.btcReplayData,
5421
- ethReplayData: preparedData.ethReplayData,
5422
- currentIndex: 0,
5423
- strategies: strategiesForSymbol
5424
- };
5425
- symbolRuntimes.push(symbolRuntime);
5426
- if (symbolRuntimes.length % 50 === 0 || symbol === preparedRun.tickers.at(-1))
5427
- adapters.sampleMemory?.("strategies:initializing", {
5428
- symbols: symbolRuntimes.length,
5429
- strategiesPerSymbol: strategiesForSymbol.length
5430
- });
5431
- adapters.progress.tick({
5432
- skipped: adapters.display.skipped(skippedSymbols),
5433
- symbol: adapters.display.symbol(symbol)
5434
- });
5435
- for (const candle of preparedData.replayData) {
5436
- orderedTimestamps.add(candle.timestamp);
5437
- }
5438
- }
5439
- return {
5440
- symbolRuntimes,
5441
- orderedTimestamps: [...orderedTimestamps].sort(
5442
- (left, right) => left - right
5443
- ),
5444
- sharedReplayKeyPrefixes,
5445
- runtimeLineages
5446
- };
5447
- };
5448
- var releaseHistoricalReplayPlan = (plan) => {
5449
- plan.symbolRuntimes.length = 0;
5450
- plan.sharedReplayKeyPrefixes.length = 0;
5451
- };
5452
-
5453
5583
  // src/lib/replay/historicalSignalsReplaySkipEvidence.ts
5454
- var import_data = require("@tradejs/core/data");
5584
+ var import_data2 = require("@tradejs/core/data");
5455
5585
  var replayScopeKey = (strategy, symbol) => `${strategy}::${symbol}`;
5456
5586
  var appendReplaySignalEvaluationRun = ({
5457
5587
  runs = [],
@@ -5459,7 +5589,7 @@ var appendReplaySignalEvaluationRun = ({
5459
5589
  timestamp,
5460
5590
  interval
5461
5591
  }) => {
5462
- const stepMs = (0, import_data.intervalToMs)(interval);
5592
+ const stepMs = (0, import_data2.intervalToMs)(interval);
5463
5593
  const last = runs.at(-1);
5464
5594
  if (last?.reason === reason && last.stepMs === stepMs && timestamp === last.lastTimestamp + stepMs) {
5465
5595
  return [...runs.slice(0, -1), { ...last, lastTimestamp: timestamp }];
@@ -5531,11 +5661,12 @@ var createHistoricalReplaySkipEvidence = (runtimeLineages, enabled = true) => {
5531
5661
  // src/lib/replay/historicalSignalsReplayExecution.ts
5532
5662
  var advanceCycleMarket = async (plan, connector, timestamp) => {
5533
5663
  const cycleSymbols = plan.symbolRuntimes.filter(
5534
- (symbolRuntime) => symbolRuntime.replayData[symbolRuntime.currentIndex]?.timestamp === timestamp
5664
+ (symbolRuntime) => (symbolRuntime.replayData[symbolRuntime.currentIndex]?.timestamp ?? NaN) + (symbolRuntime.timestampOffsetMs ?? 0) === timestamp
5535
5665
  );
5536
5666
  for (const symbolRuntime of cycleSymbols) {
5537
5667
  const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
5538
- if (candle?.timestamp === timestamp) {
5668
+ if (candle && candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) === timestamp) {
5669
+ if (symbolRuntime.advanceMarket === false) continue;
5539
5670
  await connector.advanceMarket({
5540
5671
  symbol: symbolRuntime.symbol,
5541
5672
  candle
@@ -5547,7 +5678,7 @@ var advanceCycleMarket = async (plan, connector, timestamp) => {
5547
5678
  var advanceAbortedCycle = (cycleSymbols, timestamp) => {
5548
5679
  for (const symbolRuntime of cycleSymbols ?? []) {
5549
5680
  const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
5550
- if (candle?.timestamp === timestamp) {
5681
+ if (candle && candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) === timestamp) {
5551
5682
  symbolRuntime.currentIndex += 1;
5552
5683
  }
5553
5684
  }
@@ -5596,21 +5727,21 @@ var executeHistoricalReplay = async (context, adapters) => {
5596
5727
  const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
5597
5728
  const btcCandle = symbolRuntime.btcReplayData[symbolRuntime.currentIndex];
5598
5729
  const ethCandle = symbolRuntime.ethReplayData[symbolRuntime.currentIndex];
5599
- if (!candle || !btcCandle || candle.timestamp !== timestamp || btcCandle.timestamp !== timestamp) {
5730
+ if (!candle || !btcCandle || candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) !== timestamp || btcCandle.timestamp !== candle.timestamp) {
5600
5731
  continue;
5601
5732
  }
5602
5733
  for (const strategyRuntime of symbolRuntime.strategies) {
5603
5734
  const result = await strategyRuntime.run(
5604
5735
  candle,
5605
5736
  btcCandle,
5606
- ethCandle?.timestamp === timestamp ? ethCandle : void 0
5737
+ ethCandle?.timestamp === candle.timestamp ? ethCandle : void 0
5607
5738
  );
5608
5739
  skipEvidence.record({
5609
5740
  strategyName: strategyRuntime.strategyName,
5610
5741
  symbol: symbolRuntime.symbol,
5611
5742
  strategyConfig: strategyRuntime.strategyConfig,
5612
5743
  runtimeLineage: strategyRuntime.runtimeLineage,
5613
- timestamp,
5744
+ timestamp: candle.timestamp,
5614
5745
  result
5615
5746
  });
5616
5747
  if (result && typeof result !== "string") {
@@ -5631,10 +5762,7 @@ var executeHistoricalReplay = async (context, adapters) => {
5631
5762
  reportProgress(timestamp);
5632
5763
  }
5633
5764
  } finally {
5634
- for (const keyPrefix of context.plan.sharedReplayKeyPrefixes) {
5635
- adapters.releaseIndicatorsCache(keyPrefix);
5636
- adapters.releaseReplayCache(keyPrefix);
5637
- }
5765
+ releaseHistoricalReplayCaches(context.plan, adapters);
5638
5766
  }
5639
5767
  return {
5640
5768
  signals,
@@ -5908,7 +6036,19 @@ var runHistoricalSignalsReplay = async ({
5908
6036
  }
5909
6037
  );
5910
6038
  memory?.("references:loaded");
5911
- const plan = await prepareHistoricalReplay(
6039
+ const cacheAdapters = {
6040
+ releaseIndicatorsCache: (keyPrefix) => releaseReplayCacheCopies(
6041
+ keyPrefix,
6042
+ import_strategies3.releaseStrategyIndicatorsReplayCache,
6043
+ getProjectReplayCacheReleasers()?.releaseStrategyIndicatorsReplayCache
6044
+ ),
6045
+ releaseReplayCache: (keyPrefix) => releaseReplayCacheCopies(
6046
+ keyPrefix,
6047
+ import_strategies3.releaseStrategyReplayCache,
6048
+ getProjectReplayCacheReleasers()?.releaseStrategyReplayCache
6049
+ )
6050
+ };
6051
+ const plan = await prepareMultiIntervalHistoricalReplay(
5912
6052
  {
5913
6053
  userName: replayUserName,
5914
6054
  projectRoot: replayProjectRoot,
@@ -5925,6 +6065,7 @@ var runHistoricalSignalsReplay = async ({
5925
6065
  }
5926
6066
  },
5927
6067
  {
6068
+ ...cacheAdapters,
5928
6069
  progress: {
5929
6070
  tick: (tokens) => prepareBar.tick(1, tokens)
5930
6071
  },
@@ -5934,7 +6075,11 @@ var runHistoricalSignalsReplay = async ({
5934
6075
  },
5935
6076
  buildLineage: buildRuntimeLineage,
5936
6077
  sampleMemory: memory
5937
- }
6078
+ },
6079
+ (nativeInterval, nativeRun) => loadHistoricalReplayReferences({
6080
+ preparedRun: nativeRun,
6081
+ interval: nativeInterval
6082
+ })
5938
6083
  );
5939
6084
  memory?.("strategies:prepared", { symbols: plan.symbolRuntimes.length });
5940
6085
  const hookContext = buildAfterSignalsContext({
@@ -5961,6 +6106,7 @@ var runHistoricalSignalsReplay = async ({
5961
6106
  hookContext
5962
6107
  },
5963
6108
  {
6109
+ ...cacheAdapters,
5964
6110
  clock: { now: Date.now },
5965
6111
  progress: {
5966
6112
  enabled: showProgress,
@@ -5973,17 +6119,7 @@ var runHistoricalSignalsReplay = async ({
5973
6119
  },
5974
6120
  invokeBeforeSignals: invokeBeforeSignalsHooks,
5975
6121
  invokeAfterSignals: invokeAfterSignalsHooks,
5976
- enrichSignal: (signal) => (0, import_strategies4.enrichSignalWithBinanceMarketContext)({ signal, env: "PARITY" }),
5977
- releaseIndicatorsCache: (keyPrefix) => releaseReplayCacheCopies(
5978
- keyPrefix,
5979
- import_strategies3.releaseStrategyIndicatorsReplayCache,
5980
- getProjectReplayCacheReleasers()?.releaseStrategyIndicatorsReplayCache
5981
- ),
5982
- releaseReplayCache: (keyPrefix) => releaseReplayCacheCopies(
5983
- keyPrefix,
5984
- import_strategies3.releaseStrategyReplayCache,
5985
- getProjectReplayCacheReleasers()?.releaseStrategyReplayCache
5986
- )
6122
+ enrichSignal: (signal) => (0, import_strategies4.enrichSignalWithBinanceMarketContext)({ signal, env: "PARITY" })
5987
6123
  }
5988
6124
  );
5989
6125
  memory?.("execution:complete", { signals: execution.signals.length });
@@ -6443,6 +6579,7 @@ var saveAndPrintReplayResultsByStrategy = async ({
6443
6579
 
6444
6580
  // src/lib/replay/runtimeComparison.ts
6445
6581
  var import_chalk12 = __toESM(require("chalk"));
6582
+ var import_data4 = require("@tradejs/core/data");
6446
6583
 
6447
6584
  // src/lib/paritySummary.ts
6448
6585
  var summarizeRuntimeTradesByStrategy = (trades) => {
@@ -6521,7 +6658,7 @@ var import_redis4 = require("@tradejs/infra/redis");
6521
6658
 
6522
6659
  // src/lib/runtimeSignalsStorage.ts
6523
6660
  var import_constants5 = require("@tradejs/core/constants");
6524
- var import_data2 = require("@tradejs/core/data");
6661
+ var import_data3 = require("@tradejs/core/data");
6525
6662
  var import_time4 = require("@tradejs/core/time");
6526
6663
  var isRuntimeSignalBucketRef = (value) => {
6527
6664
  if (!value || typeof value !== "object") {
@@ -8046,7 +8183,10 @@ var findExchangeRuntimeOutcome = ({
8046
8183
  return candidates.map((candidate) => ({
8047
8184
  ...candidate,
8048
8185
  timestampDiffMs: Math.abs(
8049
- candidate.timestamp + signalTimestampOffsetMs - exchangeEntry.entryTimestamp
8186
+ candidate.timestamp + resolveTradeParityTimestampOffset(
8187
+ strategyName,
8188
+ signalTimestampOffsetMs
8189
+ ) - exchangeEntry.entryTimestamp
8050
8190
  )
8051
8191
  })).filter((candidate) => candidate.timestampDiffMs <= toleranceMs).sort(
8052
8192
  (left, right) => left.timestampDiffMs - right.timestampDiffMs || Number(right.orderStatus === "failed") - Number(left.orderStatus === "failed")
@@ -8286,10 +8426,13 @@ var writeReplayComparisonReport = (lines) => {
8286
8426
  };
8287
8427
 
8288
8428
  // src/lib/replay/runtimeComparison.ts
8289
- var getReplayEntryTimestampCompareOffsetMs = () => {
8290
- const intervalMinutes = Number(replayInterval);
8291
- return Number.isFinite(intervalMinutes) && intervalMinutes > 0 ? intervalMinutes * 60 * 1e3 : 15 * 60 * 1e3;
8292
- };
8429
+ var replayStrategyIntervals = (summaries) => summaries.map(({ strategyName, strategyConfig }) => {
8430
+ const durationMs = (0, import_data4.intervalToMs)(strategyConfig.INTERVAL);
8431
+ if (!Number.isFinite(durationMs) || durationMs <= 0) {
8432
+ throw new Error(`Invalid replay interval for ${strategyName}`);
8433
+ }
8434
+ return { strategyName, interval: String(durationMs / 6e4) };
8435
+ });
8293
8436
  var buildReplaySignalEvaluations = (signals) => signals.map((signal) => ({
8294
8437
  evaluationId: `${signal.strategy}:${signal.symbol}:${signal.timestamp}`,
8295
8438
  userName: replayUserName,
@@ -8506,7 +8649,8 @@ var saveAndPrintReplayExchangeComparison = async ({
8506
8649
  runtimeSignals,
8507
8650
  runtimeSignalEvaluations,
8508
8651
  runtimeLineageScopes,
8509
- lineage
8652
+ lineage,
8653
+ timestampOffsetMs
8510
8654
  }) => {
8511
8655
  const { connector, connectorName, window } = getRuntimeCompareContext();
8512
8656
  const rawExchangeEntries = await loadExchangeEntriesForComparison({
@@ -8531,7 +8675,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8531
8675
  runtimeSignals,
8532
8676
  runtimeSignalEvaluations,
8533
8677
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
8534
- signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
8678
+ signalTimestampOffsetMs: timestampOffsetMs
8535
8679
  });
8536
8680
  });
8537
8681
  const comparisonLineage = {
@@ -8553,7 +8697,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8553
8697
  backtestEntries,
8554
8698
  strategyNameByOrderLinkKey,
8555
8699
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
8556
- backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs(),
8700
+ backtestTimestampOffsetMs: timestampOffsetMs,
8557
8701
  runtimeSignals,
8558
8702
  runtimeSignalEvaluations,
8559
8703
  replaySignals,
@@ -8592,7 +8736,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8592
8736
  exchangeEntries,
8593
8737
  backtestEntries,
8594
8738
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
8595
- backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
8739
+ backtestTimestampOffsetMs: timestampOffsetMs
8596
8740
  });
8597
8741
  const { completed: matched, orderFailed } = splitExchangeMatchesByRuntimeOrderStatus({
8598
8742
  matched: comparison.matched,
@@ -8600,7 +8744,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8600
8744
  runtimeSignals,
8601
8745
  runtimeSignalEvaluations,
8602
8746
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
8603
- signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
8747
+ signalTimestampOffsetMs: timestampOffsetMs
8604
8748
  });
8605
8749
  const details = buildReplayExchangeComparisonDetails({
8606
8750
  matched,
@@ -8611,7 +8755,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8611
8755
  backtestEntries,
8612
8756
  strategyNameByOrderLinkKey,
8613
8757
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
8614
- backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs(),
8758
+ backtestTimestampOffsetMs: timestampOffsetMs,
8615
8759
  runtimeSignals,
8616
8760
  runtimeSignalEvaluations,
8617
8761
  replaySignals,
@@ -8672,9 +8816,16 @@ var saveAndPrintReplayRuntimeComparison = async ({
8672
8816
  expectedUserName: replayUserName,
8673
8817
  expectedWindow: window
8674
8818
  }) : null;
8675
- const replayTimestampOffset = runtimeEvidence ? buildTradeParityTimestampOffset(
8676
- (0, runtimeEvidenceDeployment_exports.activeRuntimeEvidenceStrategies)(runtimeEvidence.deployment)
8677
- ) : getReplayEntryTimestampCompareOffsetMs();
8819
+ const declaredIntervals = runtimeEvidence ? [] : replayStrategyIntervals(liveStrategySummaries);
8820
+ const replayTimestampOffset = buildTradeParityTimestampOffset(
8821
+ runtimeEvidence ? (0, runtimeEvidenceDeployment_exports.activeRuntimeEvidenceStrategies)(runtimeEvidence.deployment) : declaredIntervals
8822
+ );
8823
+ const metadataStart = runtimeEvidence ? window.start : window.start - Math.max(
8824
+ 0,
8825
+ ...declaredIntervals.map(
8826
+ ({ interval }) => Number(interval) * 6e4 - (0, import_data4.intervalToMs)(replayInterval)
8827
+ )
8828
+ );
8678
8829
  const [
8679
8830
  rawRuntimeTrades,
8680
8831
  runtimeSignals,
@@ -8691,15 +8842,15 @@ var saveAndPrintReplayRuntimeComparison = async ({
8691
8842
  endTime: window.end
8692
8843
  }),
8693
8844
  loadRuntimeSignals(replayUserName, {
8694
- startTime: window.start,
8845
+ startTime: metadataStart,
8695
8846
  endTime: window.end
8696
8847
  }),
8697
8848
  loadRuntimeSignalEvaluations(replayUserName, {
8698
- startTime: window.start,
8849
+ startTime: metadataStart,
8699
8850
  endTime: window.end
8700
8851
  }),
8701
8852
  loadRuntimeLineageScopes(replayUserName, {
8702
- startTime: window.start,
8853
+ startTime: metadataStart,
8703
8854
  endTime: window.end
8704
8855
  })
8705
8856
  ]);
@@ -8767,6 +8918,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
8767
8918
  );
8768
8919
  return saveAndPrintReplayExchangeComparison({
8769
8920
  liveStrategySummaries,
8921
+ timestampOffsetMs: replayTimestampOffset,
8770
8922
  backtestEntries: comparableBacktestEntries,
8771
8923
  replaySignals,
8772
8924
  replaySignalEvaluations,
@@ -9916,16 +10068,10 @@ var replayBacktest = async () => {
9916
10068
  if (!replayStrategies.length) {
9917
10069
  throw new Error(`No enabled strategies in deployment ${deployment.id}`);
9918
10070
  }
9919
- const strategyIntervals = new Set(
9920
- replayStrategies.map(
9921
- ({ strategyConfig }) => String(strategyConfig.INTERVAL)
9922
- )
10071
+ resolveHistoricalReplayIntervals(
10072
+ replayInterval,
10073
+ replayStrategies.map(({ strategyConfig }) => strategyConfig.INTERVAL)
9923
10074
  );
9924
- if (strategyIntervals.size !== 1 || !strategyIntervals.has(replayInterval)) {
9925
- throw new Error(
9926
- `Replay interval does not match deployment ${deployment.id}: replay=${replayInterval}, strategies=${[...strategyIntervals].join(",")}`
9927
- );
9928
- }
9929
10075
  const strategyUniverses = new Set(
9930
10076
  replayStrategies.map(
9931
10077
  ({ strategyConfig }) => String(strategyConfig.UNIVERSE)
@@ -9971,13 +10117,13 @@ var replayBacktest = async () => {
9971
10117
  deploymentId: deployment.id,
9972
10118
  assetClasses: deployment.assetClasses,
9973
10119
  deployment,
9974
- closedIntervalMs: (0, import_data3.intervalToMs)(replayInterval)
10120
+ closedIntervalMs: (0, import_data5.intervalToMs)(replayInterval)
9975
10121
  });
9976
10122
  if (!preparedRun || isReplayUpdateOnlyRun) {
9977
10123
  return;
9978
10124
  }
9979
10125
  if (useAllData) {
9980
- const intervalMs = (0, import_data3.intervalToMs)(replayInterval);
10126
+ const intervalMs = (0, import_data5.intervalToMs)(replayInterval);
9981
10127
  const edges = await (0, import_candles.getDataEdgesForSymbols)(
9982
10128
  deployment.provider,
9983
10129
  preparedRun.tickers,