@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.
@@ -39,9 +39,482 @@ __export(replayRunner_exports, {
39
39
  resolveReplayStrategyNameFromExchangeEntry: () => resolveReplayStrategyNameFromExchangeEntry
40
40
  });
41
41
  module.exports = __toCommonJS(replayRunner_exports);
42
+
43
+ // src/lib/replay/historicalSignalsReplayPreparation.ts
44
+ var import_data = require("@tradejs/core/data");
45
+
46
+ // src/lib/replay/support.ts
47
+ var import_chalk = __toESM(require("chalk"));
48
+
49
+ // src/lib/runtimeModeConfig.ts
50
+ var buildRuntimeModeStrategyConfig = ({
51
+ strategyConfig,
52
+ env,
53
+ interval,
54
+ makeOrders,
55
+ recordRuntimeTrades,
56
+ aiReplayAnalyses
57
+ }) => ({
58
+ ...strategyConfig,
59
+ ENV: env,
60
+ MAKE_ORDERS: makeOrders,
61
+ INTERVAL: strategyConfig.INTERVAL ?? interval,
62
+ ...typeof recordRuntimeTrades === "boolean" ? { RECORD_RUNTIME_TRADES: recordRuntimeTrades } : {},
63
+ ...aiReplayAnalyses?.length ? { AI_REPLAY_ANALYSES: aiReplayAnalyses } : {}
64
+ });
65
+
66
+ // src/lib/replay/support.ts
67
+ var REPLAY_RESULTS_BY_STRATEGY_HEADERS = [
68
+ import_chalk.default.blue("STRATEGY"),
69
+ import_chalk.default.yellow("TICKERS"),
70
+ import_chalk.default.yellow("TRADE TICKERS"),
71
+ import_chalk.default.cyan("ORDERS"),
72
+ import_chalk.default.cyan("WIN/LOSS (%)"),
73
+ import_chalk.default.cyan("PROFIT"),
74
+ import_chalk.default.cyan("AVG TRADE")
75
+ ];
76
+ var REPLAY_RUNTIME_COMPARISON_HEADERS = [
77
+ import_chalk.default.blue("STRATEGY"),
78
+ import_chalk.default.cyan("BT ENTRIES"),
79
+ import_chalk.default.cyan("BT PNL"),
80
+ import_chalk.default.yellow("RT TRADES"),
81
+ import_chalk.default.yellow("RT PNL"),
82
+ import_chalk.default.green("MATCHED"),
83
+ import_chalk.default.red("ORDER FAILED"),
84
+ import_chalk.default.yellow("RT ONLY"),
85
+ import_chalk.default.magenta("BT ONLY"),
86
+ import_chalk.default.red("UNMATCHED")
87
+ ];
88
+ var REPLAY_RESULTS_CONFIG = "replay";
89
+ var REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS = 1;
90
+ var REPLAY_RUNTIME_COMPARE_TOLERANCE_MS = REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS * 15 * 60 * 1e3;
91
+ var formatReplayRuntimeCompareTolerance = () => `${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS} ${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS === 1 ? "bar" : "bars"}`;
92
+ var getReplayRuntimeUnmatchedCount = (row) => row.orderFailed + row.runtimeOnly + row.backtestOnly;
93
+ var buildReplayStrategyConfig = ({
94
+ strategyConfig,
95
+ interval
96
+ }) => ({
97
+ ...buildRuntimeModeStrategyConfig({
98
+ strategyConfig,
99
+ env: "PARITY",
100
+ interval,
101
+ makeOrders: false,
102
+ recordRuntimeTrades: false
103
+ }),
104
+ SIMULATE_ORDERS: true
105
+ });
106
+ var toRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
107
+ var compactReplayAdditionalIndicators = (additionalIndicators) => {
108
+ const additional = toRecord(additionalIndicators);
109
+ if (!additional) {
110
+ return void 0;
111
+ }
112
+ const compact = {};
113
+ if (additional.executionSlippage != null) {
114
+ compact.executionSlippage = additional.executionSlippage;
115
+ }
116
+ if (additional.marketContext != null) {
117
+ compact.marketContext = additional.marketContext;
118
+ }
119
+ const baseContext = toRecord(additional.baseContext);
120
+ const relative = toRecord(baseContext?.relative);
121
+ const execution = toRecord(relative?.execution);
122
+ if (execution) {
123
+ compact.baseContext = {
124
+ relative: {
125
+ execution
126
+ }
127
+ };
128
+ }
129
+ return Object.keys(compact).length ? compact : void 0;
130
+ };
131
+ var compactReplaySignal = (signal) => {
132
+ if (!signal) {
133
+ return void 0;
134
+ }
135
+ return {
136
+ signalId: signal.signalId,
137
+ strategy: signal.strategy,
138
+ symbol: signal.symbol,
139
+ interval: signal.interval,
140
+ direction: signal.direction,
141
+ timestamp: signal.timestamp,
142
+ prices: signal.prices,
143
+ indicators: {},
144
+ figures: {},
145
+ additionalIndicators: compactReplayAdditionalIndicators(
146
+ signal.additionalIndicators
147
+ ),
148
+ isConfigFromBacktest: signal.isConfigFromBacktest,
149
+ ...signal.orderStatus ? { orderStatus: signal.orderStatus } : {},
150
+ ...signal.orderSkipReason ? { orderSkipReason: signal.orderSkipReason } : {},
151
+ ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
152
+ ...signal.ml ? { ml: signal.ml } : {}
153
+ };
154
+ };
155
+
156
+ // src/lib/marketData/windows.ts
157
+ var import_indicators = require("@tradejs/core/indicators");
158
+ var splitCandlesForReplayWindow = (candles, start, preloadStart) => {
159
+ const prevData = [];
160
+ const replayData = [];
161
+ for (const candle of candles) {
162
+ if (candle.timestamp < preloadStart) {
163
+ continue;
164
+ }
165
+ if (candle.timestamp < start) {
166
+ prevData.push(candle);
167
+ continue;
168
+ }
169
+ replayData.push(candle);
170
+ }
171
+ return { prevData, replayData };
172
+ };
173
+ var alignSymbolWithBtcReference = (symbolCandles, btcCandles) => (0, import_indicators.alignSortedCandlesByTimestamp)(symbolCandles, btcCandles);
174
+
175
+ // src/lib/replay/historicalSignalsReplayPreparation.ts
176
+ var buildPreparedData = ({
177
+ data,
178
+ btcData,
179
+ ethData,
180
+ btcBinanceData,
181
+ btcCoinbaseData,
182
+ start,
183
+ preloadStart
184
+ }) => {
185
+ const { prevData: prevDataRaw, replayData: replayDataRaw } = splitCandlesForReplayWindow(data, start, preloadStart);
186
+ const { prevData: btcPrevDataRaw, replayData: btcReplayDataRaw } = splitCandlesForReplayWindow(btcData, start, preloadStart);
187
+ const { prevData: ethPrevDataRaw, replayData: ethReplayDataRaw } = splitCandlesForReplayWindow(ethData, start, preloadStart);
188
+ const { prevData: btcBinancePrevDataRaw } = splitCandlesForReplayWindow(
189
+ btcBinanceData,
190
+ start,
191
+ preloadStart
192
+ );
193
+ const { prevData: btcCoinbasePrevDataRaw } = splitCandlesForReplayWindow(
194
+ btcCoinbaseData,
195
+ start,
196
+ preloadStart
197
+ );
198
+ const { alignedCoinCandles: prevData, alignedBtcCandles: btcPrevData } = alignSymbolWithBtcReference(prevDataRaw, btcPrevDataRaw);
199
+ const { alignedCoinCandles: replayData, alignedBtcCandles: btcReplayData } = alignSymbolWithBtcReference(replayDataRaw, btcReplayDataRaw);
200
+ const { alignedBtcCandles: ethPrevData } = alignSymbolWithBtcReference(
201
+ prevData,
202
+ ethPrevDataRaw
203
+ );
204
+ const { alignedBtcCandles: ethReplayData } = alignSymbolWithBtcReference(
205
+ replayData,
206
+ ethReplayDataRaw
207
+ );
208
+ const { alignedBtcCandles: btcBinancePrevData } = alignSymbolWithBtcReference(
209
+ prevDataRaw,
210
+ btcBinancePrevDataRaw
211
+ );
212
+ const { alignedBtcCandles: btcCoinbasePrevData } = alignSymbolWithBtcReference(prevDataRaw, btcCoinbasePrevDataRaw);
213
+ return {
214
+ prevData,
215
+ btcPrevData,
216
+ ethPrevData,
217
+ replayData,
218
+ btcReplayData,
219
+ ethReplayData,
220
+ btcBinancePrevData,
221
+ btcCoinbasePrevData
222
+ };
223
+ };
224
+ var strategyIncludesSymbol = (strategy, symbol) => {
225
+ const tickers = strategy.selection?.tickers;
226
+ if (!tickers) return true;
227
+ const normalizedSymbol = symbol.trim().toUpperCase();
228
+ return tickers.some(
229
+ (ticker) => ticker.trim().toUpperCase() === normalizedSymbol
230
+ );
231
+ };
232
+ var buildSymbolSharedReplayKey = ({
233
+ userName,
234
+ connectorName,
235
+ interval,
236
+ symbol,
237
+ start,
238
+ end
239
+ }) => ["replay", userName, connectorName, symbol, interval, start, end].join(":");
240
+ var resolveHistoricalReplayIntervals = (baseInterval, strategyIntervals) => {
241
+ const intervals = [
242
+ ...new Set(
243
+ strategyIntervals.map((value) => {
244
+ if (!value || !Number.isFinite((0, import_data.intervalToMs)(value)) || (0, import_data.intervalToMs)(value) <= 0)
245
+ throw new Error(`Invalid replay strategy interval: ${value}`);
246
+ return value;
247
+ })
248
+ )
249
+ ].sort((a, b) => (0, import_data.intervalToMs)(a) - (0, import_data.intervalToMs)(b));
250
+ if (intervals.length > 1 && intervals.includes("M")) {
251
+ throw new Error("Mixed replay does not support calendar intervals");
252
+ }
253
+ const baseMs = (0, import_data.intervalToMs)(baseInterval);
254
+ if (intervals.some(
255
+ (value) => (0, import_data.intervalToMs)(value) < baseMs || (0, import_data.intervalToMs)(value) % baseMs !== 0
256
+ )) {
257
+ throw new Error(
258
+ "Replay strategy intervals must be multiples of the base timeframe"
259
+ );
260
+ }
261
+ if (!intervals.length || !intervals.includes(baseInterval)) {
262
+ throw new Error(
263
+ "Replay base timeframe must match an active strategy interval"
264
+ );
265
+ }
266
+ return intervals;
267
+ };
268
+ var prepareMultiIntervalHistoricalReplay = async (context, adapters, loadReferences) => {
269
+ const intervals = resolveHistoricalReplayIntervals(
270
+ context.interval,
271
+ context.strategies.map((s) => s.strategyConfig.INTERVAL)
272
+ );
273
+ const baseMs = (0, import_data.intervalToMs)(context.interval);
274
+ const plan = {
275
+ symbolRuntimes: [],
276
+ orderedTimestamps: [],
277
+ sharedReplayKeyPrefixes: [],
278
+ runtimeLineages: []
279
+ };
280
+ const timestamps = /* @__PURE__ */ new Set();
281
+ const baseRuntimes = /* @__PURE__ */ new Map();
282
+ const validatedSymbols = /* @__PURE__ */ new Set();
283
+ try {
284
+ for (const interval of intervals) {
285
+ const offset = (0, import_data.intervalToMs)(interval) - baseMs;
286
+ const nativeRun = {
287
+ ...context.preparedRun,
288
+ preloadStart: context.preparedRun.preloadStart - offset,
289
+ window: {
290
+ ...context.preparedRun.window,
291
+ start: context.preparedRun.window.start - offset,
292
+ end: context.preparedRun.window.end - offset
293
+ }
294
+ };
295
+ const part = await prepareHistoricalReplay(
296
+ {
297
+ ...context,
298
+ interval,
299
+ preparedRun: nativeRun,
300
+ strategies: context.strategies.filter(
301
+ (strategy) => strategy.strategyConfig.INTERVAL === interval
302
+ ),
303
+ references: interval === context.interval ? context.references : await loadReferences(interval, nativeRun)
304
+ },
305
+ adapters
306
+ );
307
+ plan.sharedReplayKeyPrefixes.push(...part.sharedReplayKeyPrefixes);
308
+ plan.runtimeLineages.push(...part.runtimeLineages);
309
+ for (const runtime of part.symbolRuntimes) {
310
+ if (interval === context.interval) {
311
+ baseRuntimes.set(runtime.symbol, runtime);
312
+ } else if (runtime.strategies.length && !validatedSymbols.has(runtime.symbol)) {
313
+ assertBaseReplayCoverage(
314
+ baseRuntimes.get(runtime.symbol),
315
+ runtime.symbol,
316
+ context.preparedRun.window,
317
+ baseMs
318
+ );
319
+ validatedSymbols.add(runtime.symbol);
320
+ }
321
+ runtime.timestampOffsetMs = offset;
322
+ runtime.advanceMarket = interval === context.interval;
323
+ plan.symbolRuntimes.push(runtime);
324
+ }
325
+ for (const timestamp of part.orderedTimestamps) {
326
+ const scheduled = timestamp + offset;
327
+ if (scheduled >= context.preparedRun.window.start && scheduled <= context.preparedRun.window.end)
328
+ timestamps.add(scheduled);
329
+ }
330
+ }
331
+ plan.orderedTimestamps = [...timestamps].sort((a, b) => a - b);
332
+ return plan;
333
+ } catch (error) {
334
+ releaseHistoricalReplayCaches(plan, adapters);
335
+ throw error;
336
+ }
337
+ };
338
+ var assertBaseReplayCoverage = (runtime, symbol, window, baseMs) => {
339
+ let index = 0;
340
+ for (let timestamp = Math.ceil(window.start / baseMs) * baseMs; timestamp <= window.end; timestamp += baseMs) {
341
+ if (runtime?.replayData[index]?.timestamp !== timestamp) {
342
+ throw new Error(`Missing base replay candle: ${symbol} at ${timestamp}`);
343
+ }
344
+ index += 1;
345
+ }
346
+ };
347
+ var prepareHistoricalReplay = async (context, adapters) => {
348
+ const {
349
+ userName,
350
+ projectRoot,
351
+ preparedRun,
352
+ interval,
353
+ connectorName,
354
+ replayConnector,
355
+ strategies,
356
+ references
357
+ } = context;
358
+ const symbolRuntimes = [];
359
+ const orderedTimestamps = /* @__PURE__ */ new Set();
360
+ const sharedReplayKeyPrefixes = [];
361
+ const runtimeLineages = [];
362
+ let skippedSymbols = 0;
363
+ try {
364
+ for (const symbol of preparedRun.tickers) {
365
+ const data = await preparedRun.marketConnector.kline({
366
+ symbol,
367
+ start: preparedRun.preloadStart,
368
+ end: preparedRun.window.end,
369
+ cacheOnly: true,
370
+ interval
371
+ });
372
+ if (symbolRuntimes.length % 50 === 0)
373
+ adapters.sampleMemory?.("history:loaded", {
374
+ symbols: symbolRuntimes.length + 1,
375
+ candles: data.length
376
+ });
377
+ const preparedData = buildPreparedData({
378
+ data,
379
+ btcData: references.btcMarketData,
380
+ ethData: references.ethMarketData,
381
+ btcBinanceData: references.btcBinanceData,
382
+ btcCoinbaseData: references.btcCoinbaseData,
383
+ start: preparedRun.window.start,
384
+ preloadStart: preparedRun.preloadStart
385
+ });
386
+ if (!preparedData.replayData.length || !preparedData.btcReplayData.length) {
387
+ skippedSymbols += 1;
388
+ adapters.progress.tick({
389
+ skipped: adapters.display.skipped(skippedSymbols),
390
+ symbol: adapters.display.symbol(symbol)
391
+ });
392
+ continue;
393
+ }
394
+ const sharedIndicatorsReplayKey = buildSymbolSharedReplayKey({
395
+ userName,
396
+ connectorName,
397
+ interval,
398
+ symbol,
399
+ start: preparedRun.window.start,
400
+ end: preparedRun.window.end
401
+ });
402
+ sharedReplayKeyPrefixes.push(sharedIndicatorsReplayKey);
403
+ const initializedStrategies = await Promise.allSettled(
404
+ strategies.filter((strategy) => strategyIncludesSymbol(strategy, symbol)).map(async (strategy) => {
405
+ const replayStrategyConfig = buildReplayStrategyConfig({
406
+ strategyConfig: strategy.strategyConfig,
407
+ interval
408
+ });
409
+ const runtimeLineage = await adapters.buildLineage({
410
+ projectRoot,
411
+ strategyName: strategy.strategyName,
412
+ strategyRevision: strategy.strategyRevision,
413
+ deploymentCompositionId: strategy.deploymentCompositionId,
414
+ strategyPackageVersion: strategy.strategyPackageVersion,
415
+ strategyDependencyVersions: strategy.strategyDependencyVersions,
416
+ runtimePackageVersion: strategy.runtimePackageVersion,
417
+ config: {
418
+ configId: strategy.strategyRevision,
419
+ strategyConfig: strategy.strategyConfig,
420
+ symbolResultConfig: strategy.strategyResults?.[symbol]?.config ?? null
421
+ },
422
+ runContext: {
423
+ connectorName: connectorName.toLowerCase(),
424
+ interval: String(interval),
425
+ universe: preparedRun.universe ?? null
426
+ }
427
+ });
428
+ runtimeLineages.push({
429
+ strategy: strategy.strategyName,
430
+ symbol,
431
+ ...preparedRun.deploymentId ? { deploymentId: preparedRun.deploymentId } : {},
432
+ ...preparedRun.accountId ? { accountId: preparedRun.accountId } : {},
433
+ lineage: runtimeLineage
434
+ });
435
+ return {
436
+ strategyName: strategy.strategyName,
437
+ strategyConfig: strategy.strategyConfig,
438
+ runtimeLineage,
439
+ run: await strategy.strategyCreator({
440
+ userName,
441
+ connectorName,
442
+ runtimeConfigId: strategy.strategyRevision,
443
+ strategyRevision: strategy.strategyRevision,
444
+ runtimeLineage,
445
+ universe: preparedRun.universe,
446
+ accountId: preparedRun.accountId,
447
+ deploymentId: preparedRun.deploymentId,
448
+ runtimeConfigSnapshot: {
449
+ userConfig: replayStrategyConfig
450
+ },
451
+ config: replayStrategyConfig,
452
+ symbol,
453
+ data: preparedData.prevData,
454
+ btcData: preparedData.btcPrevData,
455
+ ethData: preparedData.ethPrevData,
456
+ btcBinanceData: preparedData.btcBinancePrevData,
457
+ btcCoinbaseData: preparedData.btcCoinbasePrevData,
458
+ connector: replayConnector,
459
+ sharedIndicatorsReplayKey
460
+ })
461
+ };
462
+ })
463
+ );
464
+ const strategiesForSymbol = initializedStrategies.map((result) => {
465
+ if (result.status === "rejected") throw result.reason;
466
+ return result.value;
467
+ });
468
+ const symbolRuntime = {
469
+ symbol,
470
+ replayData: preparedData.replayData,
471
+ btcReplayData: preparedData.btcReplayData,
472
+ ethReplayData: preparedData.ethReplayData,
473
+ currentIndex: 0,
474
+ strategies: strategiesForSymbol
475
+ };
476
+ symbolRuntimes.push(symbolRuntime);
477
+ if (symbolRuntimes.length % 50 === 0 || symbol === preparedRun.tickers.at(-1))
478
+ adapters.sampleMemory?.("strategies:initializing", {
479
+ symbols: symbolRuntimes.length,
480
+ strategiesPerSymbol: strategiesForSymbol.length
481
+ });
482
+ adapters.progress.tick({
483
+ skipped: adapters.display.skipped(skippedSymbols),
484
+ symbol: adapters.display.symbol(symbol)
485
+ });
486
+ for (const candle of preparedData.replayData) {
487
+ orderedTimestamps.add(candle.timestamp);
488
+ }
489
+ }
490
+ return {
491
+ symbolRuntimes,
492
+ orderedTimestamps: [...orderedTimestamps].sort(
493
+ (left, right) => left - right
494
+ ),
495
+ sharedReplayKeyPrefixes,
496
+ runtimeLineages
497
+ };
498
+ } catch (error) {
499
+ releaseHistoricalReplayCaches({ sharedReplayKeyPrefixes }, adapters);
500
+ throw error;
501
+ }
502
+ };
503
+ var releaseHistoricalReplayPlan = (plan) => {
504
+ plan.symbolRuntimes.length = 0;
505
+ plan.sharedReplayKeyPrefixes.length = 0;
506
+ };
507
+ var releaseHistoricalReplayCaches = (plan, adapters) => {
508
+ for (const keyPrefix of plan.sharedReplayKeyPrefixes) {
509
+ adapters.releaseIndicatorsCache(keyPrefix);
510
+ adapters.releaseReplayCache(keyPrefix);
511
+ }
512
+ };
513
+
514
+ // src/scripts/replayRunner.ts
42
515
  var import_chalk13 = __toESM(require("chalk"));
43
516
  var import_constants7 = require("@tradejs/core/constants");
44
- var import_data3 = require("@tradejs/core/data");
517
+ var import_data5 = require("@tradejs/core/data");
45
518
  var import_time7 = require("@tradejs/core/time");
46
519
  var import_redis6 = require("@tradejs/infra/redis");
47
520
  var import_candles = require("@tradejs/infra/timescale/candles");
@@ -74,7 +547,7 @@ var timeOperation = async (label, operation, log = console.log) => {
74
547
  };
75
548
 
76
549
  // src/lib/runEnvironment.ts
77
- var import_chalk3 = __toESM(require("chalk"));
550
+ var import_chalk4 = __toESM(require("chalk"));
78
551
  var import_connectors3 = require("@tradejs/node/connectors");
79
552
  var import_cli3 = require("@tradejs/node/cli");
80
553
  var import_constants = require("@tradejs/core/constants");
@@ -82,7 +555,7 @@ var import_time2 = require("@tradejs/core/time");
82
555
  var import_runtimeStrategies = require("@tradejs/node/runtimeStrategies");
83
556
 
84
557
  // src/lib/runtimeStrategyBacktest.ts
85
- var import_chalk = __toESM(require("chalk"));
558
+ var import_chalk2 = __toESM(require("chalk"));
86
559
 
87
560
  // src/lib/runtimeRedis.ts
88
561
  var import_redis = require("@tradejs/infra/redis");
@@ -232,7 +705,7 @@ var resolveTimeWindow = ({
232
705
  };
233
706
 
234
707
  // src/lib/marketData/historyPrepare.ts
235
- var import_chalk2 = __toESM(require("chalk"));
708
+ var import_chalk3 = __toESM(require("chalk"));
236
709
  var import_connectors = require("@tradejs/connectors");
237
710
  var import_cli = require("@tradejs/node/cli");
238
711
  var import_connectors2 = require("@tradejs/node/connectors");
@@ -323,7 +796,7 @@ var updatePrimaryMarketHistory = async ({
323
796
  const timeOperation3 = (label, operation) => timeOperation(
324
797
  label,
325
798
  operation,
326
- log ?? ((message) => console.log(import_chalk2.default.gray(message)))
799
+ log ?? ((message) => console.log(import_chalk3.default.gray(message)))
327
800
  );
328
801
  const updateOptions = preloadStart != null || preloadEnd != null ? {
329
802
  connectorLabel: connectorName,
@@ -355,7 +828,7 @@ var updateBtcReferenceHistory = async ({
355
828
  const timeOperation3 = (label, operation) => timeOperation(
356
829
  label,
357
830
  operation,
358
- log ?? ((message) => console.log(import_chalk2.default.gray(message)))
831
+ log ?? ((message) => console.log(import_chalk3.default.gray(message)))
359
832
  );
360
833
  const updateReference = async ({
361
834
  connector,
@@ -477,7 +950,7 @@ var loadRunTickers = async ({
477
950
  var timeOperation2 = async (label, operation) => timeOperation(
478
951
  label,
479
952
  operation,
480
- (message) => console.log(import_chalk3.default.gray(message))
953
+ (message) => console.log(import_chalk4.default.gray(message))
481
954
  );
482
955
  var resolveBacktestExecutionPreloadInterval = (interval) => {
483
956
  const normalized = String(interval);
@@ -498,7 +971,7 @@ var resolveRunConnectorName = async ({
498
971
  return connectorName;
499
972
  }
500
973
  console.log(
501
- import_chalk3.default.yellow(
974
+ import_chalk4.default.yellow(
502
975
  `Unknown connector "${String(value || "").trim() || String(value)}". Fallback to ${import_connectors3.DEFAULT_CONNECTOR_NAME}.`
503
976
  )
504
977
  );
@@ -553,7 +1026,7 @@ var loadDeploymentReplayStrategies = async ({
553
1026
  ) : [];
554
1027
  if (!strategies.length) {
555
1028
  console.log(
556
- import_chalk3.default.yellow(
1029
+ import_chalk4.default.yellow(
557
1030
  `No enabled strategies found in runtime deployment ${deploymentId}`
558
1031
  )
559
1032
  );
@@ -619,7 +1092,7 @@ var prepareRunEnvironment = async ({
619
1092
  );
620
1093
  }
621
1094
  if (showTickersList) {
622
- console.log(import_chalk3.default.gray(JSON.stringify(loadedTickers.sort(), null, 2)));
1095
+ console.log(import_chalk4.default.gray(JSON.stringify(loadedTickers.sort(), null, 2)));
623
1096
  return null;
624
1097
  }
625
1098
  const instruments = !cacheOnly && typeof marketConnector.listInstruments === "function" ? await timeOperation2(
@@ -656,7 +1129,7 @@ var prepareRunEnvironment = async ({
656
1129
  projectRoot,
657
1130
  shouldUseDedicatedReferences: universe === "crypto",
658
1131
  requireDedicatedReferences: universe === "crypto",
659
- warn: (message) => console.log(import_chalk3.default.yellow(message))
1132
+ warn: (message) => console.log(import_chalk4.default.yellow(message))
660
1133
  });
661
1134
  await updateMarketHistoryWithBtcReferences({
662
1135
  marketConnector,
@@ -667,7 +1140,7 @@ var prepareRunEnvironment = async ({
667
1140
  preloadStart,
668
1141
  preloadEnd: window.end,
669
1142
  universe,
670
- log: (message) => console.log(import_chalk3.default.gray(message))
1143
+ log: (message) => console.log(import_chalk4.default.gray(message))
671
1144
  });
672
1145
  const backtestExecutionInterval = resolveBacktestExecutionPreloadInterval(interval);
673
1146
  if (import_constants.BACKTEST_LOWER_TIMEFRAME_EXECUTION_ENABLED && backtestExecutionInterval && String(backtestExecutionInterval) !== String(interval)) {
@@ -704,13 +1177,13 @@ var prepareRunEnvironment = async ({
704
1177
  };
705
1178
 
706
1179
  // src/lib/marketContextPrepare.ts
707
- var import_chalk7 = __toESM(require("chalk"));
1180
+ var import_chalk8 = __toESM(require("chalk"));
708
1181
  var import_marketContext3 = require("@tradejs/infra/timescale/marketContext");
709
1182
  var import_registry = require("@tradejs/node/registry");
710
1183
 
711
1184
  // src/lib/binanceMarketContextBackfill.ts
712
1185
  var import_progress = __toESM(require("progress"));
713
- var import_chalk4 = __toESM(require("chalk"));
1186
+ var import_chalk5 = __toESM(require("chalk"));
714
1187
  var import_connectors4 = require("@tradejs/connectors");
715
1188
  var import_connectors5 = require("@tradejs/node/connectors");
716
1189
  var import_strategies = require("@tradejs/node/strategies");
@@ -1264,7 +1737,7 @@ var backfillBinanceMarketContext = async (params, enabled) => {
1264
1737
  let tradeFlowRows = 0;
1265
1738
  let breadthRows = 0;
1266
1739
  console.log(
1267
- import_chalk4.default.cyan(
1740
+ import_chalk5.default.cyan(
1268
1741
  `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()}`
1269
1742
  )
1270
1743
  );
@@ -1400,7 +1873,7 @@ var backfillBinanceMarketContext = async (params, enabled) => {
1400
1873
  }) : [];
1401
1874
  if (!missingChunks2.length) {
1402
1875
  console.log(
1403
- import_chalk4.default.gray(`breadth cached: universe=${definition.universe}`)
1876
+ import_chalk5.default.gray(`breadth cached: universe=${definition.universe}`)
1404
1877
  );
1405
1878
  }
1406
1879
  return {
@@ -1488,7 +1961,7 @@ var backfillBinanceMarketContext = async (params, enabled) => {
1488
1961
  }
1489
1962
  }
1490
1963
  console.log(
1491
- import_chalk4.default.green(
1964
+ import_chalk5.default.green(
1492
1965
  `binance market context backfill done: tradeFlowRows=${tradeFlowRows}, depthRows=0, breadthRows=${breadthRows}, skippedSymbols=${skippedSymbols}`
1493
1966
  )
1494
1967
  );
@@ -1505,11 +1978,11 @@ var backfillBinanceMarketContextForSignals = (params) => backfillBinanceMarketCo
1505
1978
  var backfillBinanceMarketContextForReplay = (params) => backfillBinanceMarketContext(params, true);
1506
1979
 
1507
1980
  // src/lib/derivativesContextBackfill.ts
1508
- var import_chalk5 = __toESM(require("chalk"));
1981
+ var import_chalk6 = __toESM(require("chalk"));
1509
1982
  var import_progress2 = __toESM(require("progress"));
1510
1983
  var import_async = require("@tradejs/core/async");
1511
1984
  var import_constants2 = require("@tradejs/core/constants");
1512
- var import_indicators = require("@tradejs/core/indicators");
1985
+ var import_indicators2 = require("@tradejs/core/indicators");
1513
1986
  var import_derivatives = require("@tradejs/infra/timescale/derivatives");
1514
1987
  var import_client2 = require("@tradejs/infra/timescale/client");
1515
1988
  var import_userSettings = require("@tradejs/infra/userSettings");
@@ -1612,7 +2085,7 @@ var resolveDerivativesContextBackfillWindow = (params) => {
1612
2085
  };
1613
2086
  var resolveDerivativesContextIntervalBackfillWindow = (params) => {
1614
2087
  const intervalMs = derivativesIntervalMs(params.interval);
1615
- const alignedToMs = params.closedOnly ? (0, import_indicators.getLastClosedDerivativesBarStartMs)(params.toMs, params.interval) : Math.floor(params.toMs / intervalMs) * intervalMs;
2088
+ const alignedToMs = params.closedOnly ? (0, import_indicators2.getLastClosedDerivativesBarStartMs)(params.toMs, params.interval) : Math.floor(params.toMs / intervalMs) * intervalMs;
1616
2089
  return {
1617
2090
  fromMs: Math.floor(params.fromMs / intervalMs) * intervalMs,
1618
2091
  toMs: alignedToMs,
@@ -1649,7 +2122,7 @@ var resolveDerivativesContextRequiredFetchFromMs = (params) => {
1649
2122
  refreshClosedTail: params.mode === "signals"
1650
2123
  });
1651
2124
  const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
1652
- const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
2125
+ const confirmedLiquidationWindow = (0, import_indicators2.resolveCoinalyzeConfirmedIntradayCoverage)({
1653
2126
  interval: params.interval,
1654
2127
  fromMs: params.fromMs,
1655
2128
  toMs: params.toMs,
@@ -2040,7 +2513,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2040
2513
  const coverageNowMs = Date.now();
2041
2514
  await Promise.all(
2042
2515
  intervalWindows.map(async (window) => {
2043
- const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
2516
+ const confirmedLiquidationWindow = (0, import_indicators2.resolveCoinalyzeConfirmedIntradayCoverage)({
2044
2517
  interval: window.interval,
2045
2518
  fromMs: window.fromMs,
2046
2519
  toMs: window.toMs,
@@ -2136,7 +2609,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2136
2609
  toMs: backfillWindow.toMs
2137
2610
  })
2138
2611
  );
2139
- const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
2612
+ const confirmedLiquidationWindow = (0, import_indicators2.resolveCoinalyzeConfirmedIntradayCoverage)({
2140
2613
  interval: window.interval,
2141
2614
  fromMs: backfillWindow.fromMs,
2142
2615
  toMs: backfillWindow.toMs,
@@ -2154,7 +2627,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2154
2627
  });
2155
2628
  if (allBackfillWindowsCached) {
2156
2629
  console.log(
2157
- import_chalk5.default.gray(
2630
+ import_chalk6.default.gray(
2158
2631
  `derivatives context backfill: cached symbols=${symbols.length}, requestedSymbols=${requestedSymbols.length}, intervals=${intervals.join(",")}, window=${new Date(fromMs).toISOString()}..${new Date(safeEndMs).toISOString()}`
2159
2632
  )
2160
2633
  );
@@ -2219,7 +2692,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2219
2692
  let failedWindows = 0;
2220
2693
  let skippedWindows = 0;
2221
2694
  console.log(
2222
- import_chalk5.default.cyan(
2695
+ import_chalk6.default.cyan(
2223
2696
  `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()}`
2224
2697
  )
2225
2698
  );
@@ -2324,13 +2797,13 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2324
2797
  });
2325
2798
  rows = missingBatch.flatMap((item) => {
2326
2799
  const marketSymbol = item.marketSymbol.toUpperCase();
2327
- const points = (0, import_indicators.mergeCoinalyzeMetrics)({
2800
+ const points = (0, import_indicators2.mergeCoinalyzeMetrics)({
2328
2801
  symbol: item.symbol,
2329
2802
  oiRaw: oiMap.get(marketSymbol) ?? [],
2330
2803
  fundingRaw: fundingMap.get(marketSymbol) ?? [],
2331
2804
  liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
2332
2805
  });
2333
- return (0, import_indicators.coinalyzePointsToRows)(points, interval, "coinalyze");
2806
+ return (0, import_indicators2.coinalyzePointsToRows)(points, interval, "coinalyze");
2334
2807
  });
2335
2808
  missingClosedSymbols = mode === "signals" ? getMissingClosedDerivativesSymbols({
2336
2809
  symbols: missingBatch.map((item) => item.symbol),
@@ -2351,7 +2824,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2351
2824
  await (0, import_derivatives.upsertDerivatives)(rows);
2352
2825
  totalRows += rows.length;
2353
2826
  }
2354
- const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
2827
+ const confirmedLiquidationWindow = (0, import_indicators2.resolveCoinalyzeConfirmedIntradayCoverage)({
2355
2828
  interval,
2356
2829
  fromMs: group.fromMs,
2357
2830
  toMs,
@@ -2361,7 +2834,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2361
2834
  const metricCoverageRows = missingBatch.map((item) => {
2362
2835
  const marketSymbol = item.marketSymbol.toUpperCase();
2363
2836
  const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
2364
- const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
2837
+ const timestamp = (0, import_indicators2.toCoinalyzeTimestampMs)(
2365
2838
  point.t ?? point.ts ?? point.time ?? point.timestamp
2366
2839
  );
2367
2840
  return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
@@ -2433,13 +2906,13 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2433
2906
  } catch (error) {
2434
2907
  failedWindows += 1;
2435
2908
  console.error(
2436
- import_chalk5.default.red(
2909
+ import_chalk6.default.red(
2437
2910
  `derivatives context backfill window failed batch=${batchIdx + 1}/${symbolBatches.length} interval=${interval} ${new Date(cursor).toISOString()}..${new Date(toMs).toISOString()}: ${error}`
2438
2911
  )
2439
2912
  );
2440
2913
  } finally {
2441
2914
  bar.tick(1, {
2442
- batch: import_chalk5.default.gray(
2915
+ batch: import_chalk6.default.gray(
2443
2916
  `${batchIdx + 1}/${symbolBatches.length} ${interval}`
2444
2917
  ),
2445
2918
  rows: totalRows,
@@ -2453,7 +2926,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
2453
2926
  }
2454
2927
  console.log("");
2455
2928
  console.log(
2456
- import_chalk5.default.green(
2929
+ import_chalk6.default.green(
2457
2930
  `derivatives context backfill done: rows=${totalRows}, failed_windows=${failedWindows}, skipped_windows=${skippedWindows}`
2458
2931
  )
2459
2932
  );
@@ -2483,7 +2956,7 @@ var backfillDerivativesContextForSignals = async (params) => backfillDerivatives
2483
2956
  );
2484
2957
 
2485
2958
  // src/lib/coinMarketCapContextBackfill.ts
2486
- var import_chalk6 = __toESM(require("chalk"));
2959
+ var import_chalk7 = __toESM(require("chalk"));
2487
2960
  var import_async2 = require("@tradejs/core/async");
2488
2961
  var import_marketContext2 = require("@tradejs/infra/timescale/marketContext");
2489
2962
  var import_client3 = require("@tradejs/infra/timescale/client");
@@ -2745,7 +3218,7 @@ var formatRowTimeRange = (rows) => {
2745
3218
  };
2746
3219
  var logBackfillSourceDone = (params) => {
2747
3220
  console.log(
2748
- import_chalk6.default.gray(
3221
+ import_chalk7.default.gray(
2749
3222
  `coinmarketcap context source done: ${params.name}=${params.status} rows=${params.rows}${params.details ? ` ${params.details}` : ""}`
2750
3223
  )
2751
3224
  );
@@ -2779,7 +3252,7 @@ var coinMarketCapFetch = async (params) => {
2779
3252
  recordCoinMarketCapFetchStats(params.stats, params.path, creditCount);
2780
3253
  if (creditCount != null && isVerboseRequestLoggingEnabled()) {
2781
3254
  console.log(
2782
- import_chalk6.default.gray(`coinmarketcap ${params.path}: credits=${creditCount}`)
3255
+ import_chalk7.default.gray(`coinmarketcap ${params.path}: credits=${creditCount}`)
2783
3256
  );
2784
3257
  }
2785
3258
  return payload;
@@ -2824,7 +3297,7 @@ var fetchCoinMarketCapIndexRows = async (params) => {
2824
3297
  );
2825
3298
  if (chunk === 1 || chunk === totalChunks || chunk % 25 === 0) {
2826
3299
  console.log(
2827
- import_chalk6.default.gray(
3300
+ import_chalk7.default.gray(
2828
3301
  `coinmarketcap index backfill progress: ${params.index.slug} chunk=${chunk}/${totalChunks} rows=${rows.length} chunkRange=${new Date(cursorMs).toISOString()}..${new Date(chunkEndMs).toISOString()}`
2829
3302
  )
2830
3303
  );
@@ -3263,7 +3736,7 @@ var backfillCoinMarketCapContext = async (params) => {
3263
3736
  covered: indexesBackfillCovered
3264
3737
  });
3265
3738
  console.log(
3266
- import_chalk6.default.gray(
3739
+ import_chalk7.default.gray(
3267
3740
  [
3268
3741
  "coinmarketcap context status:",
3269
3742
  formatSourceStatus({
@@ -3298,7 +3771,7 @@ var backfillCoinMarketCapContext = async (params) => {
3298
3771
  );
3299
3772
  if (globalDailyReady && referencesDailyReady && exchangeLiquidityReady && fearGreedReady && indexesReady) {
3300
3773
  console.log(
3301
- import_chalk6.default.gray(
3774
+ import_chalk7.default.gray(
3302
3775
  `coinmarketcap context cached: ${new Date(fromMs).toISOString()}..${new Date(toMs).toISOString()}`
3303
3776
  )
3304
3777
  );
@@ -3325,7 +3798,7 @@ var backfillCoinMarketCapContext = async (params) => {
3325
3798
  !indexesReady ? "indexes" : null
3326
3799
  ].filter((source) => source != null);
3327
3800
  console.log(
3328
- import_chalk6.default.cyan(
3801
+ import_chalk7.default.cyan(
3329
3802
  `coinmarketcap context backfill: window=${new Date(fromMs).toISOString()}..${new Date(toMs).toISOString()} days=${Math.floor((toMs - fromMs) / DAY_MS3) + 1} fetch=${pendingSources.join(",")}`
3330
3803
  )
3331
3804
  );
@@ -3517,7 +3990,7 @@ var backfillCoinMarketCapContext = async (params) => {
3517
3990
  });
3518
3991
  }
3519
3992
  console.log(
3520
- import_chalk6.default.green(
3993
+ import_chalk7.default.green(
3521
3994
  [
3522
3995
  "coinmarketcap context backfill done:",
3523
3996
  `global=${globalStatus} rows=${globalRows}`,
@@ -4129,7 +4602,7 @@ var buildDerivativesBackfillParams = (params) => ({
4129
4602
  });
4130
4603
  var prepareMarketContextForRun = async (params) => {
4131
4604
  const log = params.log ?? ((message) => {
4132
- console.log(import_chalk7.default.gray(message));
4605
+ console.log(import_chalk8.default.gray(message));
4133
4606
  });
4134
4607
  const timeOperation3 = (label, operation) => timeOperation(label, operation, log);
4135
4608
  const requirements = await resolveMarketContextRunRequirements(params);
@@ -4580,174 +5053,64 @@ var buildRuntimeEvidenceLineageScopes = ({
4580
5053
  (left, right) => left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || left.firstTimestamp - right.firstTimestamp
4581
5054
  );
4582
5055
  };
4583
- var loadReplayRuntimeEvidenceSource = async ({
4584
- filePath,
4585
- projectRoot,
4586
- expectedUserName,
4587
- expectedWindow
4588
- }) => {
4589
- const { resolvedPath, runtime, userName, startTime, endTime, deployment } = await readRuntimeEvidenceArtifact({ filePath, projectRoot });
4590
- if (!userName || userName !== expectedUserName) {
4591
- throw new Error(
4592
- `Runtime evidence user mismatch: expected=${expectedUserName}, actual=${userName || "missing"}`
4593
- );
4594
- }
4595
- const endTimeMatches = endTime === expectedWindow.end || endTime === expectedWindow.end + 1;
4596
- if (startTime !== expectedWindow.start || !endTimeMatches) {
4597
- throw new Error(
4598
- `Runtime evidence window mismatch: expected=${expectedWindow.start}..${expectedWindow.end}, actual=${startTime ?? "missing"}..${endTime ?? "missing"}`
4599
- );
4600
- }
4601
- const trades = unwrapRows(runtime.trades, "trade");
4602
- const signals = unwrapRows(runtime.signals, "signal");
4603
- const evaluations = unwrapRows(
4604
- runtime.evaluations,
4605
- "evaluation"
4606
- );
4607
- const storedLineageScopes = unwrapRows(
4608
- runtime.lineageScopes,
4609
- "lineageScope"
4610
- );
4611
- assertRuntimeRowsMatchDeployment({
4612
- label: "trade",
4613
- rows: trades,
4614
- deployment
4615
- });
4616
- assertRuntimeRowsMatchDeployment({
4617
- label: "signal",
4618
- rows: signals,
4619
- deployment
4620
- });
4621
- assertRuntimeRowsMatchDeployment({
4622
- label: "evaluation",
4623
- rows: evaluations,
4624
- deployment
4625
- });
4626
- assertRuntimeRowsMatchDeployment({
4627
- label: "lineage scope",
4628
- rows: storedLineageScopes,
4629
- deployment
4630
- });
4631
- return {
4632
- path: resolvedPath,
4633
- userName,
4634
- startTime,
4635
- endTime,
4636
- trades,
4637
- signals,
4638
- evaluations,
4639
- lineageScopes: storedLineageScopes.length ? storedLineageScopes : buildRuntimeEvidenceLineageScopes({ signals, evaluations }),
4640
- deployment
4641
- };
4642
- };
4643
-
4644
- // src/lib/replay/support.ts
4645
- var import_chalk8 = __toESM(require("chalk"));
4646
-
4647
- // src/lib/runtimeModeConfig.ts
4648
- var buildRuntimeModeStrategyConfig = ({
4649
- strategyConfig,
4650
- env,
4651
- interval,
4652
- makeOrders,
4653
- recordRuntimeTrades,
4654
- aiReplayAnalyses
4655
- }) => ({
4656
- ...strategyConfig,
4657
- ENV: env,
4658
- MAKE_ORDERS: makeOrders,
4659
- INTERVAL: strategyConfig.INTERVAL ?? interval,
4660
- ...typeof recordRuntimeTrades === "boolean" ? { RECORD_RUNTIME_TRADES: recordRuntimeTrades } : {},
4661
- ...aiReplayAnalyses?.length ? { AI_REPLAY_ANALYSES: aiReplayAnalyses } : {}
4662
- });
4663
-
4664
- // src/lib/replay/support.ts
4665
- var REPLAY_RESULTS_BY_STRATEGY_HEADERS = [
4666
- import_chalk8.default.blue("STRATEGY"),
4667
- import_chalk8.default.yellow("TICKERS"),
4668
- import_chalk8.default.yellow("TRADE TICKERS"),
4669
- import_chalk8.default.cyan("ORDERS"),
4670
- import_chalk8.default.cyan("WIN/LOSS (%)"),
4671
- import_chalk8.default.cyan("PROFIT"),
4672
- import_chalk8.default.cyan("AVG TRADE")
4673
- ];
4674
- var REPLAY_RUNTIME_COMPARISON_HEADERS = [
4675
- import_chalk8.default.blue("STRATEGY"),
4676
- import_chalk8.default.cyan("BT ENTRIES"),
4677
- import_chalk8.default.cyan("BT PNL"),
4678
- import_chalk8.default.yellow("RT TRADES"),
4679
- import_chalk8.default.yellow("RT PNL"),
4680
- import_chalk8.default.green("MATCHED"),
4681
- import_chalk8.default.red("ORDER FAILED"),
4682
- import_chalk8.default.yellow("RT ONLY"),
4683
- import_chalk8.default.magenta("BT ONLY"),
4684
- import_chalk8.default.red("UNMATCHED")
4685
- ];
4686
- var REPLAY_RESULTS_CONFIG = "replay";
4687
- var REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS = 1;
4688
- var REPLAY_RUNTIME_COMPARE_TOLERANCE_MS = REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS * 15 * 60 * 1e3;
4689
- var formatReplayRuntimeCompareTolerance = () => `${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS} ${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS === 1 ? "bar" : "bars"}`;
4690
- var getReplayRuntimeUnmatchedCount = (row) => row.orderFailed + row.runtimeOnly + row.backtestOnly;
4691
- var buildReplayStrategyConfig = ({
4692
- strategyConfig,
4693
- interval
4694
- }) => ({
4695
- ...buildRuntimeModeStrategyConfig({
4696
- strategyConfig,
4697
- env: "PARITY",
4698
- interval,
4699
- makeOrders: false,
4700
- recordRuntimeTrades: false
4701
- }),
4702
- SIMULATE_ORDERS: true
4703
- });
4704
- var toRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4705
- var compactReplayAdditionalIndicators = (additionalIndicators) => {
4706
- const additional = toRecord(additionalIndicators);
4707
- if (!additional) {
4708
- return void 0;
4709
- }
4710
- const compact = {};
4711
- if (additional.executionSlippage != null) {
4712
- compact.executionSlippage = additional.executionSlippage;
4713
- }
4714
- if (additional.marketContext != null) {
4715
- compact.marketContext = additional.marketContext;
4716
- }
4717
- const baseContext = toRecord(additional.baseContext);
4718
- const relative = toRecord(baseContext?.relative);
4719
- const execution = toRecord(relative?.execution);
4720
- if (execution) {
4721
- compact.baseContext = {
4722
- relative: {
4723
- execution
4724
- }
4725
- };
4726
- }
4727
- return Object.keys(compact).length ? compact : void 0;
4728
- };
4729
- var compactReplaySignal = (signal) => {
4730
- if (!signal) {
4731
- return void 0;
5056
+ var loadReplayRuntimeEvidenceSource = async ({
5057
+ filePath,
5058
+ projectRoot,
5059
+ expectedUserName,
5060
+ expectedWindow
5061
+ }) => {
5062
+ const { resolvedPath, runtime, userName, startTime, endTime, deployment } = await readRuntimeEvidenceArtifact({ filePath, projectRoot });
5063
+ if (!userName || userName !== expectedUserName) {
5064
+ throw new Error(
5065
+ `Runtime evidence user mismatch: expected=${expectedUserName}, actual=${userName || "missing"}`
5066
+ );
5067
+ }
5068
+ const endTimeMatches = endTime === expectedWindow.end || endTime === expectedWindow.end + 1;
5069
+ if (startTime !== expectedWindow.start || !endTimeMatches) {
5070
+ throw new Error(
5071
+ `Runtime evidence window mismatch: expected=${expectedWindow.start}..${expectedWindow.end}, actual=${startTime ?? "missing"}..${endTime ?? "missing"}`
5072
+ );
4732
5073
  }
5074
+ const trades = unwrapRows(runtime.trades, "trade");
5075
+ const signals = unwrapRows(runtime.signals, "signal");
5076
+ const evaluations = unwrapRows(
5077
+ runtime.evaluations,
5078
+ "evaluation"
5079
+ );
5080
+ const storedLineageScopes = unwrapRows(
5081
+ runtime.lineageScopes,
5082
+ "lineageScope"
5083
+ );
5084
+ assertRuntimeRowsMatchDeployment({
5085
+ label: "trade",
5086
+ rows: trades,
5087
+ deployment
5088
+ });
5089
+ assertRuntimeRowsMatchDeployment({
5090
+ label: "signal",
5091
+ rows: signals,
5092
+ deployment
5093
+ });
5094
+ assertRuntimeRowsMatchDeployment({
5095
+ label: "evaluation",
5096
+ rows: evaluations,
5097
+ deployment
5098
+ });
5099
+ assertRuntimeRowsMatchDeployment({
5100
+ label: "lineage scope",
5101
+ rows: storedLineageScopes,
5102
+ deployment
5103
+ });
4733
5104
  return {
4734
- signalId: signal.signalId,
4735
- strategy: signal.strategy,
4736
- symbol: signal.symbol,
4737
- interval: signal.interval,
4738
- direction: signal.direction,
4739
- timestamp: signal.timestamp,
4740
- prices: signal.prices,
4741
- indicators: {},
4742
- figures: {},
4743
- additionalIndicators: compactReplayAdditionalIndicators(
4744
- signal.additionalIndicators
4745
- ),
4746
- isConfigFromBacktest: signal.isConfigFromBacktest,
4747
- ...signal.orderStatus ? { orderStatus: signal.orderStatus } : {},
4748
- ...signal.orderSkipReason ? { orderSkipReason: signal.orderSkipReason } : {},
4749
- ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
4750
- ...signal.ml ? { ml: signal.ml } : {}
5105
+ path: resolvedPath,
5106
+ userName,
5107
+ startTime,
5108
+ endTime,
5109
+ trades,
5110
+ signals,
5111
+ evaluations,
5112
+ lineageScopes: storedLineageScopes.length ? storedLineageScopes : buildRuntimeEvidenceLineageScopes({ signals, evaluations }),
5113
+ deployment
4751
5114
  };
4752
5115
  };
4753
5116
 
@@ -4816,6 +5179,7 @@ var getPositionPnlSnapshot = ({
4816
5179
  var createPortfolioReplayConnector = (connector) => {
4817
5180
  let state = {};
4818
5181
  let amount = import_constants3.INITIAL_BACKTEST_AMOUNT;
5182
+ let executionTimestamp = null;
4819
5183
  const positionsBySymbol = /* @__PURE__ */ new Map();
4820
5184
  const currentPriceBySymbol = /* @__PURE__ */ new Map();
4821
5185
  const orderLog = [];
@@ -5077,6 +5441,7 @@ var createPortfolioReplayConnector = (connector) => {
5077
5441
  });
5078
5442
  const nextPosition = {
5079
5443
  ...order,
5444
+ timestamp: executionTimestamp ?? order.timestamp,
5080
5445
  price: entryPrice,
5081
5446
  signal: compactReplaySignal(order.signal),
5082
5447
  amount,
@@ -5101,6 +5466,7 @@ var createPortfolioReplayConnector = (connector) => {
5101
5466
  positionState,
5102
5467
  data: {
5103
5468
  ...order,
5469
+ timestamp: executionTimestamp ?? order.timestamp,
5104
5470
  price: entryPrice,
5105
5471
  profit,
5106
5472
  fee,
@@ -5164,6 +5530,7 @@ var createPortfolioReplayConnector = (connector) => {
5164
5530
  positionState,
5165
5531
  data: {
5166
5532
  ...order,
5533
+ timestamp: executionTimestamp ?? order.timestamp,
5167
5534
  price: executionPrice,
5168
5535
  qty: position.qty,
5169
5536
  profit,
@@ -5172,11 +5539,12 @@ var createPortfolioReplayConnector = (connector) => {
5172
5539
  ...getExecutionSlippageLogData(slippageBreakdown, "exit")
5173
5540
  }
5174
5541
  });
5175
- clearPosition(order.symbol, order.timestamp);
5542
+ clearPosition(order.symbol, executionTimestamp ?? order.timestamp);
5176
5543
  return true;
5177
5544
  },
5178
5545
  getTickers: connector.getTickers,
5179
5546
  advanceMarket: async ({ symbol, candle }) => {
5547
+ executionTimestamp = candle.timestamp;
5180
5548
  currentPriceBySymbol.set(symbol, candle.close);
5181
5549
  await checkSl({ symbol, candle });
5182
5550
  await checkTp({ symbol, candle });
@@ -5217,244 +5585,8 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
5217
5585
  }
5218
5586
  };
5219
5587
 
5220
- // src/lib/marketData/windows.ts
5221
- var import_indicators2 = require("@tradejs/core/indicators");
5222
- var splitCandlesForReplayWindow = (candles, start, preloadStart) => {
5223
- const prevData = [];
5224
- const replayData = [];
5225
- for (const candle of candles) {
5226
- if (candle.timestamp < preloadStart) {
5227
- continue;
5228
- }
5229
- if (candle.timestamp < start) {
5230
- prevData.push(candle);
5231
- continue;
5232
- }
5233
- replayData.push(candle);
5234
- }
5235
- return { prevData, replayData };
5236
- };
5237
- var alignSymbolWithBtcReference = (symbolCandles, btcCandles) => (0, import_indicators2.alignSortedCandlesByTimestamp)(symbolCandles, btcCandles);
5238
-
5239
- // src/lib/replay/historicalSignalsReplayPreparation.ts
5240
- var buildPreparedData = ({
5241
- data,
5242
- btcData,
5243
- ethData,
5244
- btcBinanceData,
5245
- btcCoinbaseData,
5246
- start,
5247
- preloadStart
5248
- }) => {
5249
- const { prevData: prevDataRaw, replayData: replayDataRaw } = splitCandlesForReplayWindow(data, start, preloadStart);
5250
- const { prevData: btcPrevDataRaw, replayData: btcReplayDataRaw } = splitCandlesForReplayWindow(btcData, start, preloadStart);
5251
- const { prevData: ethPrevDataRaw, replayData: ethReplayDataRaw } = splitCandlesForReplayWindow(ethData, start, preloadStart);
5252
- const { prevData: btcBinancePrevDataRaw } = splitCandlesForReplayWindow(
5253
- btcBinanceData,
5254
- start,
5255
- preloadStart
5256
- );
5257
- const { prevData: btcCoinbasePrevDataRaw } = splitCandlesForReplayWindow(
5258
- btcCoinbaseData,
5259
- start,
5260
- preloadStart
5261
- );
5262
- const { alignedCoinCandles: prevData, alignedBtcCandles: btcPrevData } = alignSymbolWithBtcReference(prevDataRaw, btcPrevDataRaw);
5263
- const { alignedCoinCandles: replayData, alignedBtcCandles: btcReplayData } = alignSymbolWithBtcReference(replayDataRaw, btcReplayDataRaw);
5264
- const { alignedBtcCandles: ethPrevData } = alignSymbolWithBtcReference(
5265
- prevData,
5266
- ethPrevDataRaw
5267
- );
5268
- const { alignedBtcCandles: ethReplayData } = alignSymbolWithBtcReference(
5269
- replayData,
5270
- ethReplayDataRaw
5271
- );
5272
- const { alignedBtcCandles: btcBinancePrevData } = alignSymbolWithBtcReference(
5273
- prevDataRaw,
5274
- btcBinancePrevDataRaw
5275
- );
5276
- const { alignedBtcCandles: btcCoinbasePrevData } = alignSymbolWithBtcReference(prevDataRaw, btcCoinbasePrevDataRaw);
5277
- return {
5278
- prevData,
5279
- btcPrevData,
5280
- ethPrevData,
5281
- replayData,
5282
- btcReplayData,
5283
- ethReplayData,
5284
- btcBinancePrevData,
5285
- btcCoinbasePrevData
5286
- };
5287
- };
5288
- var strategyIncludesSymbol = (strategy, symbol) => {
5289
- const tickers = strategy.selection?.tickers;
5290
- if (!tickers) return true;
5291
- const normalizedSymbol = symbol.trim().toUpperCase();
5292
- return tickers.some(
5293
- (ticker) => ticker.trim().toUpperCase() === normalizedSymbol
5294
- );
5295
- };
5296
- var buildSymbolSharedReplayKey = ({
5297
- userName,
5298
- connectorName,
5299
- interval,
5300
- symbol,
5301
- start,
5302
- end
5303
- }) => ["replay", userName, connectorName, symbol, interval, start, end].join(":");
5304
- var prepareHistoricalReplay = async (context, adapters) => {
5305
- const {
5306
- userName,
5307
- projectRoot,
5308
- preparedRun,
5309
- interval,
5310
- connectorName,
5311
- replayConnector,
5312
- strategies,
5313
- references
5314
- } = context;
5315
- const symbolRuntimes = [];
5316
- const orderedTimestamps = /* @__PURE__ */ new Set();
5317
- const sharedReplayKeyPrefixes = [];
5318
- const runtimeLineages = [];
5319
- let skippedSymbols = 0;
5320
- for (const symbol of preparedRun.tickers) {
5321
- const data = await preparedRun.marketConnector.kline({
5322
- symbol,
5323
- start: preparedRun.preloadStart,
5324
- end: preparedRun.window.end,
5325
- cacheOnly: true,
5326
- interval
5327
- });
5328
- if (symbolRuntimes.length % 50 === 0)
5329
- adapters.sampleMemory?.("history:loaded", {
5330
- symbols: symbolRuntimes.length + 1,
5331
- candles: data.length
5332
- });
5333
- const preparedData = buildPreparedData({
5334
- data,
5335
- btcData: references.btcMarketData,
5336
- ethData: references.ethMarketData,
5337
- btcBinanceData: references.btcBinanceData,
5338
- btcCoinbaseData: references.btcCoinbaseData,
5339
- start: preparedRun.window.start,
5340
- preloadStart: preparedRun.preloadStart
5341
- });
5342
- if (!preparedData.replayData.length || !preparedData.btcReplayData.length) {
5343
- skippedSymbols += 1;
5344
- adapters.progress.tick({
5345
- skipped: adapters.display.skipped(skippedSymbols),
5346
- symbol: adapters.display.symbol(symbol)
5347
- });
5348
- continue;
5349
- }
5350
- const sharedIndicatorsReplayKey = buildSymbolSharedReplayKey({
5351
- userName,
5352
- connectorName,
5353
- interval,
5354
- symbol,
5355
- start: preparedRun.window.start,
5356
- end: preparedRun.window.end
5357
- });
5358
- sharedReplayKeyPrefixes.push(sharedIndicatorsReplayKey);
5359
- const strategiesForSymbol = await Promise.all(
5360
- strategies.filter((strategy) => strategyIncludesSymbol(strategy, symbol)).map(async (strategy) => {
5361
- const replayStrategyConfig = buildReplayStrategyConfig({
5362
- strategyConfig: strategy.strategyConfig,
5363
- interval
5364
- });
5365
- const runtimeLineage = await adapters.buildLineage({
5366
- projectRoot,
5367
- strategyName: strategy.strategyName,
5368
- strategyRevision: strategy.strategyRevision,
5369
- deploymentCompositionId: strategy.deploymentCompositionId,
5370
- strategyPackageVersion: strategy.strategyPackageVersion,
5371
- strategyDependencyVersions: strategy.strategyDependencyVersions,
5372
- runtimePackageVersion: strategy.runtimePackageVersion,
5373
- config: {
5374
- configId: strategy.strategyRevision,
5375
- strategyConfig: strategy.strategyConfig,
5376
- symbolResultConfig: strategy.strategyResults?.[symbol]?.config ?? null
5377
- },
5378
- runContext: {
5379
- connectorName: connectorName.toLowerCase(),
5380
- interval: String(interval),
5381
- universe: preparedRun.universe ?? null
5382
- }
5383
- });
5384
- runtimeLineages.push({
5385
- strategy: strategy.strategyName,
5386
- symbol,
5387
- ...preparedRun.deploymentId ? { deploymentId: preparedRun.deploymentId } : {},
5388
- ...preparedRun.accountId ? { accountId: preparedRun.accountId } : {},
5389
- lineage: runtimeLineage
5390
- });
5391
- return {
5392
- strategyName: strategy.strategyName,
5393
- strategyConfig: strategy.strategyConfig,
5394
- runtimeLineage,
5395
- run: await strategy.strategyCreator({
5396
- userName,
5397
- connectorName,
5398
- runtimeConfigId: strategy.strategyRevision,
5399
- strategyRevision: strategy.strategyRevision,
5400
- runtimeLineage,
5401
- universe: preparedRun.universe,
5402
- accountId: preparedRun.accountId,
5403
- deploymentId: preparedRun.deploymentId,
5404
- runtimeConfigSnapshot: {
5405
- userConfig: replayStrategyConfig
5406
- },
5407
- config: replayStrategyConfig,
5408
- symbol,
5409
- data: preparedData.prevData,
5410
- btcData: preparedData.btcPrevData,
5411
- ethData: preparedData.ethPrevData,
5412
- btcBinanceData: preparedData.btcBinancePrevData,
5413
- btcCoinbaseData: preparedData.btcCoinbasePrevData,
5414
- connector: replayConnector,
5415
- sharedIndicatorsReplayKey
5416
- })
5417
- };
5418
- })
5419
- );
5420
- const symbolRuntime = {
5421
- symbol,
5422
- replayData: preparedData.replayData,
5423
- btcReplayData: preparedData.btcReplayData,
5424
- ethReplayData: preparedData.ethReplayData,
5425
- currentIndex: 0,
5426
- strategies: strategiesForSymbol
5427
- };
5428
- symbolRuntimes.push(symbolRuntime);
5429
- if (symbolRuntimes.length % 50 === 0 || symbol === preparedRun.tickers.at(-1))
5430
- adapters.sampleMemory?.("strategies:initializing", {
5431
- symbols: symbolRuntimes.length,
5432
- strategiesPerSymbol: strategiesForSymbol.length
5433
- });
5434
- adapters.progress.tick({
5435
- skipped: adapters.display.skipped(skippedSymbols),
5436
- symbol: adapters.display.symbol(symbol)
5437
- });
5438
- for (const candle of preparedData.replayData) {
5439
- orderedTimestamps.add(candle.timestamp);
5440
- }
5441
- }
5442
- return {
5443
- symbolRuntimes,
5444
- orderedTimestamps: [...orderedTimestamps].sort(
5445
- (left, right) => left - right
5446
- ),
5447
- sharedReplayKeyPrefixes,
5448
- runtimeLineages
5449
- };
5450
- };
5451
- var releaseHistoricalReplayPlan = (plan) => {
5452
- plan.symbolRuntimes.length = 0;
5453
- plan.sharedReplayKeyPrefixes.length = 0;
5454
- };
5455
-
5456
5588
  // src/lib/replay/historicalSignalsReplaySkipEvidence.ts
5457
- var import_data = require("@tradejs/core/data");
5589
+ var import_data2 = require("@tradejs/core/data");
5458
5590
  var replayScopeKey = (strategy, symbol) => `${strategy}::${symbol}`;
5459
5591
  var appendReplaySignalEvaluationRun = ({
5460
5592
  runs = [],
@@ -5462,7 +5594,7 @@ var appendReplaySignalEvaluationRun = ({
5462
5594
  timestamp,
5463
5595
  interval
5464
5596
  }) => {
5465
- const stepMs = (0, import_data.intervalToMs)(interval);
5597
+ const stepMs = (0, import_data2.intervalToMs)(interval);
5466
5598
  const last = runs.at(-1);
5467
5599
  if (last?.reason === reason && last.stepMs === stepMs && timestamp === last.lastTimestamp + stepMs) {
5468
5600
  return [...runs.slice(0, -1), { ...last, lastTimestamp: timestamp }];
@@ -5534,11 +5666,12 @@ var createHistoricalReplaySkipEvidence = (runtimeLineages, enabled = true) => {
5534
5666
  // src/lib/replay/historicalSignalsReplayExecution.ts
5535
5667
  var advanceCycleMarket = async (plan, connector, timestamp) => {
5536
5668
  const cycleSymbols = plan.symbolRuntimes.filter(
5537
- (symbolRuntime) => symbolRuntime.replayData[symbolRuntime.currentIndex]?.timestamp === timestamp
5669
+ (symbolRuntime) => (symbolRuntime.replayData[symbolRuntime.currentIndex]?.timestamp ?? NaN) + (symbolRuntime.timestampOffsetMs ?? 0) === timestamp
5538
5670
  );
5539
5671
  for (const symbolRuntime of cycleSymbols) {
5540
5672
  const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
5541
- if (candle?.timestamp === timestamp) {
5673
+ if (candle && candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) === timestamp) {
5674
+ if (symbolRuntime.advanceMarket === false) continue;
5542
5675
  await connector.advanceMarket({
5543
5676
  symbol: symbolRuntime.symbol,
5544
5677
  candle
@@ -5550,7 +5683,7 @@ var advanceCycleMarket = async (plan, connector, timestamp) => {
5550
5683
  var advanceAbortedCycle = (cycleSymbols, timestamp) => {
5551
5684
  for (const symbolRuntime of cycleSymbols ?? []) {
5552
5685
  const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
5553
- if (candle?.timestamp === timestamp) {
5686
+ if (candle && candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) === timestamp) {
5554
5687
  symbolRuntime.currentIndex += 1;
5555
5688
  }
5556
5689
  }
@@ -5599,21 +5732,21 @@ var executeHistoricalReplay = async (context, adapters) => {
5599
5732
  const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
5600
5733
  const btcCandle = symbolRuntime.btcReplayData[symbolRuntime.currentIndex];
5601
5734
  const ethCandle = symbolRuntime.ethReplayData[symbolRuntime.currentIndex];
5602
- if (!candle || !btcCandle || candle.timestamp !== timestamp || btcCandle.timestamp !== timestamp) {
5735
+ if (!candle || !btcCandle || candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) !== timestamp || btcCandle.timestamp !== candle.timestamp) {
5603
5736
  continue;
5604
5737
  }
5605
5738
  for (const strategyRuntime of symbolRuntime.strategies) {
5606
5739
  const result = await strategyRuntime.run(
5607
5740
  candle,
5608
5741
  btcCandle,
5609
- ethCandle?.timestamp === timestamp ? ethCandle : void 0
5742
+ ethCandle?.timestamp === candle.timestamp ? ethCandle : void 0
5610
5743
  );
5611
5744
  skipEvidence.record({
5612
5745
  strategyName: strategyRuntime.strategyName,
5613
5746
  symbol: symbolRuntime.symbol,
5614
5747
  strategyConfig: strategyRuntime.strategyConfig,
5615
5748
  runtimeLineage: strategyRuntime.runtimeLineage,
5616
- timestamp,
5749
+ timestamp: candle.timestamp,
5617
5750
  result
5618
5751
  });
5619
5752
  if (result && typeof result !== "string") {
@@ -5634,10 +5767,7 @@ var executeHistoricalReplay = async (context, adapters) => {
5634
5767
  reportProgress(timestamp);
5635
5768
  }
5636
5769
  } finally {
5637
- for (const keyPrefix of context.plan.sharedReplayKeyPrefixes) {
5638
- adapters.releaseIndicatorsCache(keyPrefix);
5639
- adapters.releaseReplayCache(keyPrefix);
5640
- }
5770
+ releaseHistoricalReplayCaches(context.plan, adapters);
5641
5771
  }
5642
5772
  return {
5643
5773
  signals,
@@ -5911,7 +6041,19 @@ var runHistoricalSignalsReplay = async ({
5911
6041
  }
5912
6042
  );
5913
6043
  memory?.("references:loaded");
5914
- const plan = await prepareHistoricalReplay(
6044
+ const cacheAdapters = {
6045
+ releaseIndicatorsCache: (keyPrefix) => releaseReplayCacheCopies(
6046
+ keyPrefix,
6047
+ import_strategies3.releaseStrategyIndicatorsReplayCache,
6048
+ getProjectReplayCacheReleasers()?.releaseStrategyIndicatorsReplayCache
6049
+ ),
6050
+ releaseReplayCache: (keyPrefix) => releaseReplayCacheCopies(
6051
+ keyPrefix,
6052
+ import_strategies3.releaseStrategyReplayCache,
6053
+ getProjectReplayCacheReleasers()?.releaseStrategyReplayCache
6054
+ )
6055
+ };
6056
+ const plan = await prepareMultiIntervalHistoricalReplay(
5915
6057
  {
5916
6058
  userName: replayUserName,
5917
6059
  projectRoot: replayProjectRoot,
@@ -5928,6 +6070,7 @@ var runHistoricalSignalsReplay = async ({
5928
6070
  }
5929
6071
  },
5930
6072
  {
6073
+ ...cacheAdapters,
5931
6074
  progress: {
5932
6075
  tick: (tokens) => prepareBar.tick(1, tokens)
5933
6076
  },
@@ -5937,7 +6080,11 @@ var runHistoricalSignalsReplay = async ({
5937
6080
  },
5938
6081
  buildLineage: buildRuntimeLineage,
5939
6082
  sampleMemory: memory
5940
- }
6083
+ },
6084
+ (nativeInterval, nativeRun) => loadHistoricalReplayReferences({
6085
+ preparedRun: nativeRun,
6086
+ interval: nativeInterval
6087
+ })
5941
6088
  );
5942
6089
  memory?.("strategies:prepared", { symbols: plan.symbolRuntimes.length });
5943
6090
  const hookContext = buildAfterSignalsContext({
@@ -5964,6 +6111,7 @@ var runHistoricalSignalsReplay = async ({
5964
6111
  hookContext
5965
6112
  },
5966
6113
  {
6114
+ ...cacheAdapters,
5967
6115
  clock: { now: Date.now },
5968
6116
  progress: {
5969
6117
  enabled: showProgress,
@@ -5976,17 +6124,7 @@ var runHistoricalSignalsReplay = async ({
5976
6124
  },
5977
6125
  invokeBeforeSignals: invokeBeforeSignalsHooks,
5978
6126
  invokeAfterSignals: invokeAfterSignalsHooks,
5979
- enrichSignal: (signal) => (0, import_strategies4.enrichSignalWithBinanceMarketContext)({ signal, env: "PARITY" }),
5980
- releaseIndicatorsCache: (keyPrefix) => releaseReplayCacheCopies(
5981
- keyPrefix,
5982
- import_strategies3.releaseStrategyIndicatorsReplayCache,
5983
- getProjectReplayCacheReleasers()?.releaseStrategyIndicatorsReplayCache
5984
- ),
5985
- releaseReplayCache: (keyPrefix) => releaseReplayCacheCopies(
5986
- keyPrefix,
5987
- import_strategies3.releaseStrategyReplayCache,
5988
- getProjectReplayCacheReleasers()?.releaseStrategyReplayCache
5989
- )
6127
+ enrichSignal: (signal) => (0, import_strategies4.enrichSignalWithBinanceMarketContext)({ signal, env: "PARITY" })
5990
6128
  }
5991
6129
  );
5992
6130
  memory?.("execution:complete", { signals: execution.signals.length });
@@ -6446,6 +6584,7 @@ var saveAndPrintReplayResultsByStrategy = async ({
6446
6584
 
6447
6585
  // src/lib/replay/runtimeComparison.ts
6448
6586
  var import_chalk12 = __toESM(require("chalk"));
6587
+ var import_data4 = require("@tradejs/core/data");
6449
6588
 
6450
6589
  // src/lib/paritySummary.ts
6451
6590
  var summarizeRuntimeTradesByStrategy = (trades) => {
@@ -6524,7 +6663,7 @@ var import_redis4 = require("@tradejs/infra/redis");
6524
6663
 
6525
6664
  // src/lib/runtimeSignalsStorage.ts
6526
6665
  var import_constants5 = require("@tradejs/core/constants");
6527
- var import_data2 = require("@tradejs/core/data");
6666
+ var import_data3 = require("@tradejs/core/data");
6528
6667
  var import_time4 = require("@tradejs/core/time");
6529
6668
  var isRuntimeSignalBucketRef = (value) => {
6530
6669
  if (!value || typeof value !== "object") {
@@ -8049,7 +8188,10 @@ var findExchangeRuntimeOutcome = ({
8049
8188
  return candidates.map((candidate) => ({
8050
8189
  ...candidate,
8051
8190
  timestampDiffMs: Math.abs(
8052
- candidate.timestamp + signalTimestampOffsetMs - exchangeEntry.entryTimestamp
8191
+ candidate.timestamp + resolveTradeParityTimestampOffset(
8192
+ strategyName,
8193
+ signalTimestampOffsetMs
8194
+ ) - exchangeEntry.entryTimestamp
8053
8195
  )
8054
8196
  })).filter((candidate) => candidate.timestampDiffMs <= toleranceMs).sort(
8055
8197
  (left, right) => left.timestampDiffMs - right.timestampDiffMs || Number(right.orderStatus === "failed") - Number(left.orderStatus === "failed")
@@ -8289,10 +8431,13 @@ var writeReplayComparisonReport = (lines) => {
8289
8431
  };
8290
8432
 
8291
8433
  // src/lib/replay/runtimeComparison.ts
8292
- var getReplayEntryTimestampCompareOffsetMs = () => {
8293
- const intervalMinutes = Number(replayInterval);
8294
- return Number.isFinite(intervalMinutes) && intervalMinutes > 0 ? intervalMinutes * 60 * 1e3 : 15 * 60 * 1e3;
8295
- };
8434
+ var replayStrategyIntervals = (summaries) => summaries.map(({ strategyName, strategyConfig }) => {
8435
+ const durationMs = (0, import_data4.intervalToMs)(strategyConfig.INTERVAL);
8436
+ if (!Number.isFinite(durationMs) || durationMs <= 0) {
8437
+ throw new Error(`Invalid replay interval for ${strategyName}`);
8438
+ }
8439
+ return { strategyName, interval: String(durationMs / 6e4) };
8440
+ });
8296
8441
  var buildReplaySignalEvaluations = (signals) => signals.map((signal) => ({
8297
8442
  evaluationId: `${signal.strategy}:${signal.symbol}:${signal.timestamp}`,
8298
8443
  userName: replayUserName,
@@ -8509,7 +8654,8 @@ var saveAndPrintReplayExchangeComparison = async ({
8509
8654
  runtimeSignals,
8510
8655
  runtimeSignalEvaluations,
8511
8656
  runtimeLineageScopes,
8512
- lineage
8657
+ lineage,
8658
+ timestampOffsetMs
8513
8659
  }) => {
8514
8660
  const { connector, connectorName, window } = getRuntimeCompareContext();
8515
8661
  const rawExchangeEntries = await loadExchangeEntriesForComparison({
@@ -8534,7 +8680,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8534
8680
  runtimeSignals,
8535
8681
  runtimeSignalEvaluations,
8536
8682
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
8537
- signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
8683
+ signalTimestampOffsetMs: timestampOffsetMs
8538
8684
  });
8539
8685
  });
8540
8686
  const comparisonLineage = {
@@ -8556,7 +8702,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8556
8702
  backtestEntries,
8557
8703
  strategyNameByOrderLinkKey,
8558
8704
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
8559
- backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs(),
8705
+ backtestTimestampOffsetMs: timestampOffsetMs,
8560
8706
  runtimeSignals,
8561
8707
  runtimeSignalEvaluations,
8562
8708
  replaySignals,
@@ -8595,7 +8741,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8595
8741
  exchangeEntries,
8596
8742
  backtestEntries,
8597
8743
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
8598
- backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
8744
+ backtestTimestampOffsetMs: timestampOffsetMs
8599
8745
  });
8600
8746
  const { completed: matched, orderFailed } = splitExchangeMatchesByRuntimeOrderStatus({
8601
8747
  matched: comparison.matched,
@@ -8603,7 +8749,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8603
8749
  runtimeSignals,
8604
8750
  runtimeSignalEvaluations,
8605
8751
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
8606
- signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
8752
+ signalTimestampOffsetMs: timestampOffsetMs
8607
8753
  });
8608
8754
  const details = buildReplayExchangeComparisonDetails({
8609
8755
  matched,
@@ -8614,7 +8760,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8614
8760
  backtestEntries,
8615
8761
  strategyNameByOrderLinkKey,
8616
8762
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
8617
- backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs(),
8763
+ backtestTimestampOffsetMs: timestampOffsetMs,
8618
8764
  runtimeSignals,
8619
8765
  runtimeSignalEvaluations,
8620
8766
  replaySignals,
@@ -8675,9 +8821,16 @@ var saveAndPrintReplayRuntimeComparison = async ({
8675
8821
  expectedUserName: replayUserName,
8676
8822
  expectedWindow: window
8677
8823
  }) : null;
8678
- const replayTimestampOffset = runtimeEvidence ? buildTradeParityTimestampOffset(
8679
- (0, runtimeEvidenceDeployment_exports.activeRuntimeEvidenceStrategies)(runtimeEvidence.deployment)
8680
- ) : getReplayEntryTimestampCompareOffsetMs();
8824
+ const declaredIntervals = runtimeEvidence ? [] : replayStrategyIntervals(liveStrategySummaries);
8825
+ const replayTimestampOffset = buildTradeParityTimestampOffset(
8826
+ runtimeEvidence ? (0, runtimeEvidenceDeployment_exports.activeRuntimeEvidenceStrategies)(runtimeEvidence.deployment) : declaredIntervals
8827
+ );
8828
+ const metadataStart = runtimeEvidence ? window.start : window.start - Math.max(
8829
+ 0,
8830
+ ...declaredIntervals.map(
8831
+ ({ interval }) => Number(interval) * 6e4 - (0, import_data4.intervalToMs)(replayInterval)
8832
+ )
8833
+ );
8681
8834
  const [
8682
8835
  rawRuntimeTrades,
8683
8836
  runtimeSignals,
@@ -8694,15 +8847,15 @@ var saveAndPrintReplayRuntimeComparison = async ({
8694
8847
  endTime: window.end
8695
8848
  }),
8696
8849
  loadRuntimeSignals(replayUserName, {
8697
- startTime: window.start,
8850
+ startTime: metadataStart,
8698
8851
  endTime: window.end
8699
8852
  }),
8700
8853
  loadRuntimeSignalEvaluations(replayUserName, {
8701
- startTime: window.start,
8854
+ startTime: metadataStart,
8702
8855
  endTime: window.end
8703
8856
  }),
8704
8857
  loadRuntimeLineageScopes(replayUserName, {
8705
- startTime: window.start,
8858
+ startTime: metadataStart,
8706
8859
  endTime: window.end
8707
8860
  })
8708
8861
  ]);
@@ -8770,6 +8923,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
8770
8923
  );
8771
8924
  return saveAndPrintReplayExchangeComparison({
8772
8925
  liveStrategySummaries,
8926
+ timestampOffsetMs: replayTimestampOffset,
8773
8927
  backtestEntries: comparableBacktestEntries,
8774
8928
  replaySignals,
8775
8929
  replaySignalEvaluations,
@@ -9919,16 +10073,10 @@ var replayBacktest = async () => {
9919
10073
  if (!replayStrategies.length) {
9920
10074
  throw new Error(`No enabled strategies in deployment ${deployment.id}`);
9921
10075
  }
9922
- const strategyIntervals = new Set(
9923
- replayStrategies.map(
9924
- ({ strategyConfig }) => String(strategyConfig.INTERVAL)
9925
- )
10076
+ resolveHistoricalReplayIntervals(
10077
+ replayInterval,
10078
+ replayStrategies.map(({ strategyConfig }) => strategyConfig.INTERVAL)
9926
10079
  );
9927
- if (strategyIntervals.size !== 1 || !strategyIntervals.has(replayInterval)) {
9928
- throw new Error(
9929
- `Replay interval does not match deployment ${deployment.id}: replay=${replayInterval}, strategies=${[...strategyIntervals].join(",")}`
9930
- );
9931
- }
9932
10080
  const strategyUniverses = new Set(
9933
10081
  replayStrategies.map(
9934
10082
  ({ strategyConfig }) => String(strategyConfig.UNIVERSE)
@@ -9974,13 +10122,13 @@ var replayBacktest = async () => {
9974
10122
  deploymentId: deployment.id,
9975
10123
  assetClasses: deployment.assetClasses,
9976
10124
  deployment,
9977
- closedIntervalMs: (0, import_data3.intervalToMs)(replayInterval)
10125
+ closedIntervalMs: (0, import_data5.intervalToMs)(replayInterval)
9978
10126
  });
9979
10127
  if (!preparedRun || isReplayUpdateOnlyRun) {
9980
10128
  return;
9981
10129
  }
9982
10130
  if (useAllData) {
9983
- const intervalMs = (0, import_data3.intervalToMs)(replayInterval);
10131
+ const intervalMs = (0, import_data5.intervalToMs)(replayInterval);
9984
10132
  const edges = await (0, import_candles.getDataEdgesForSymbols)(
9985
10133
  deployment.provider,
9986
10134
  preparedRun.tickers,