@tradejs/cli 3.1.19-beta.234 → 3.1.20-beta.236

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.
@@ -38,7 +38,7 @@ __export(replayRunner_exports, {
38
38
  resolveReplayStrategyNameFromExchangeEntry: () => resolveReplayStrategyNameFromExchangeEntry
39
39
  });
40
40
  module.exports = __toCommonJS(replayRunner_exports);
41
- var import_chalk12 = __toESM(require("chalk"));
41
+ var import_chalk13 = __toESM(require("chalk"));
42
42
  var import_constants6 = require("@tradejs/core/constants");
43
43
  var import_data2 = require("@tradejs/core/data");
44
44
  var import_time7 = require("@tradejs/core/time");
@@ -4951,7 +4951,6 @@ var compactReplaySignal = (signal) => {
4951
4951
  // src/lib/replay/historicalSignalsReplay.ts
4952
4952
  var import_chalk9 = __toESM(require("chalk"));
4953
4953
  var import_progress4 = __toESM(require("progress"));
4954
- var import_backtest = require("@tradejs/core/backtest");
4955
4954
  var import_strategies3 = require("@tradejs/core/strategies");
4956
4955
  var import_time3 = require("@tradejs/core/time");
4957
4956
  var import_logger = require("@tradejs/infra/logger");
@@ -5364,25 +5363,6 @@ var createPortfolioReplayConnector = (connector) => {
5364
5363
  };
5365
5364
  };
5366
5365
 
5367
- // src/lib/marketData/windows.ts
5368
- var import_indicators2 = require("@tradejs/core/indicators");
5369
- var splitCandlesForReplayWindow = (candles, start, preloadStart) => {
5370
- const prevData = [];
5371
- const replayData = [];
5372
- for (const candle of candles) {
5373
- if (candle.timestamp < preloadStart) {
5374
- continue;
5375
- }
5376
- if (candle.timestamp < start) {
5377
- prevData.push(candle);
5378
- continue;
5379
- }
5380
- replayData.push(candle);
5381
- }
5382
- return { prevData, replayData };
5383
- };
5384
- var alignSymbolWithBtcReference = (symbolCandles, btcCandles) => (0, import_indicators2.alignSortedCandlesByTimestamp)(symbolCandles, btcCandles);
5385
-
5386
5366
  // src/lib/signals/hooks.ts
5387
5367
  var normalizeHookList = (value) => {
5388
5368
  if (Array.isArray(value)) {
@@ -5409,70 +5389,26 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
5409
5389
  }
5410
5390
  };
5411
5391
 
5412
- // src/lib/replay/historicalSignalsReplay.ts
5413
- var loadRuntimeStrategies = async (runtimeStrategies) => {
5414
- const strategies = await Promise.all(
5415
- runtimeStrategies.map(async (runtimeStrategy) => {
5416
- const {
5417
- strategyName,
5418
- strategyRevision,
5419
- deploymentCompositionId,
5420
- strategyPackage,
5421
- strategyPackageVersion,
5422
- strategyDependencyVersions,
5423
- runtimePackageVersion,
5424
- strategyConfig,
5425
- selection
5426
- } = runtimeStrategy;
5427
- const strategyCreator = await (0, import_strategies4.getStrategyCreator)(
5428
- strategyName,
5429
- replayProjectRoot
5430
- );
5431
- if (!strategyCreator) {
5432
- throw new Error(`Unknown strategy: ${strategyName}`);
5433
- }
5434
- const installed = await (0, import_runtimeStrategies3.getRuntimeStrategyPackageMetadata)({
5435
- strategyName,
5436
- projectRoot: replayProjectRoot
5437
- });
5438
- if (installed.strategyPackage !== strategyPackage || installed.strategyPackageVersion !== strategyPackageVersion || JSON.stringify(installed.strategyDependencyVersions) !== JSON.stringify(strategyDependencyVersions) || installed.runtimePackageVersion !== runtimePackageVersion) {
5439
- throw new Error(
5440
- `Runtime evidence package mismatch for ${strategyName}: expected=${strategyPackage}@${strategyPackageVersion}/@tradejs/node@${runtimePackageVersion}, installed=${installed.strategyPackage ?? "missing"}@${installed.strategyPackageVersion ?? "missing"}/@tradejs/node@${installed.runtimePackageVersion ?? "missing"}`
5441
- );
5442
- }
5443
- const strategyResults = await (0, import_redis3.getData)(
5444
- import_redis3.redisKeys.strategyResults(replayUserName, strategyName),
5445
- {}
5446
- );
5447
- return {
5448
- strategyName,
5449
- strategyRevision,
5450
- deploymentCompositionId,
5451
- strategyPackage,
5452
- strategyPackageVersion,
5453
- strategyDependencyVersions,
5454
- runtimePackageVersion,
5455
- strategyCreator,
5456
- strategyConfig,
5457
- strategyResults,
5458
- ...selection ? { selection } : {}
5459
- };
5460
- })
5461
- );
5462
- return strategies;
5463
- };
5464
- var loadReferenceConnector = async (connectorName) => {
5465
- const connectorFactory = await (0, import_connectors6.getConnectorCreatorByName)(
5466
- connectorName,
5467
- replayProjectRoot
5468
- );
5469
- if (!connectorFactory) {
5470
- throw new Error(`Connector "${connectorName}" is not registered`);
5392
+ // src/lib/marketData/windows.ts
5393
+ var import_indicators2 = require("@tradejs/core/indicators");
5394
+ var splitCandlesForReplayWindow = (candles, start, preloadStart) => {
5395
+ const prevData = [];
5396
+ const replayData = [];
5397
+ for (const candle of candles) {
5398
+ if (candle.timestamp < preloadStart) {
5399
+ continue;
5400
+ }
5401
+ if (candle.timestamp < start) {
5402
+ prevData.push(candle);
5403
+ continue;
5404
+ }
5405
+ replayData.push(candle);
5471
5406
  }
5472
- return await connectorFactory({
5473
- userName: replayUserName
5474
- });
5407
+ return { prevData, replayData };
5475
5408
  };
5409
+ var alignSymbolWithBtcReference = (symbolCandles, btcCandles) => (0, import_indicators2.alignSortedCandlesByTimestamp)(symbolCandles, btcCandles);
5410
+
5411
+ // src/lib/replay/historicalSignalsReplayPreparation.ts
5476
5412
  var buildPreparedData = ({
5477
5413
  data,
5478
5414
  btcData,
@@ -5521,25 +5457,6 @@ var buildPreparedData = ({
5521
5457
  btcCoinbasePrevData
5522
5458
  };
5523
5459
  };
5524
- var buildAfterSignalsContext = ({
5525
- connector,
5526
- connectorName,
5527
- tickers,
5528
- runtimeStrategies,
5529
- interval
5530
- }) => ({
5531
- connector,
5532
- connectorName,
5533
- userName: replayUserName,
5534
- interval,
5535
- tickers: [...tickers],
5536
- runtimeStrategies: runtimeStrategies.map(
5537
- ({ strategyName, strategyConfig }) => ({
5538
- strategyName,
5539
- strategyConfig
5540
- })
5541
- )
5542
- });
5543
5460
  var strategyIncludesSymbol = (strategy, symbol) => {
5544
5461
  const tickers = strategy.selection?.tickers;
5545
5462
  if (!tickers) return true;
@@ -5549,73 +5466,28 @@ var strategyIncludesSymbol = (strategy, symbol) => {
5549
5466
  );
5550
5467
  };
5551
5468
  var buildSymbolSharedReplayKey = ({
5469
+ userName,
5552
5470
  connectorName,
5553
5471
  interval,
5554
5472
  symbol,
5555
5473
  start,
5556
5474
  end
5557
- }) => ["replay", replayUserName, connectorName, symbol, interval, start, end].join(
5558
- ":"
5559
- );
5560
- var runHistoricalSignalsReplay = async ({
5561
- preparedRun,
5562
- interval,
5563
- runtimeStrategies
5564
- }) => {
5565
- const startedAt = Date.now();
5566
- const signals = [];
5567
- const runtimeLineages = [];
5568
- const projectConfig = await (0, import_cli4.loadTradejsConfig)(replayProjectRoot);
5569
- const projectHooks = projectConfig.hooks;
5570
- const loadedStrategies = await loadRuntimeStrategies(runtimeStrategies);
5571
- const replayConnector = createPortfolioReplayConnector(
5572
- preparedRun.marketConnector
5573
- );
5574
- const connectorName = String(
5575
- preparedRun.marketConnector?.name || import_connectors6.DEFAULT_CONNECTOR_NAME
5576
- ).trim() || import_connectors6.DEFAULT_CONNECTOR_NAME;
5577
- const binanceConnector = connectorName.toLowerCase() === import_connectors6.DEFAULT_CONNECTOR_NAME.toLowerCase() ? await loadReferenceConnector("Binance") : preparedRun.marketConnector;
5578
- const coinbaseConnector = connectorName.toLowerCase() === import_connectors6.DEFAULT_CONNECTOR_NAME.toLowerCase() ? await loadReferenceConnector("Coinbase") : preparedRun.marketConnector;
5579
- const [btcBinanceData, btcCoinbaseData] = await Promise.all([
5580
- binanceConnector.kline({
5581
- symbol: "BTCUSDT",
5582
- start: preparedRun.preloadStart,
5583
- end: preparedRun.window.end,
5584
- cacheOnly: true,
5585
- interval
5586
- }),
5587
- coinbaseConnector.kline({
5588
- symbol: "BTCUSDT",
5589
- start: preparedRun.preloadStart,
5590
- end: preparedRun.window.end,
5591
- cacheOnly: true,
5592
- interval
5593
- })
5594
- ]);
5595
- const btcMarketData = await preparedRun.marketConnector.kline({
5596
- symbol: "BTCUSDT",
5597
- start: preparedRun.preloadStart,
5598
- end: preparedRun.window.end,
5599
- cacheOnly: true,
5600
- interval
5601
- });
5602
- const ethMarketData = await preparedRun.marketConnector.kline({
5603
- symbol: "ETHUSDT",
5604
- start: preparedRun.preloadStart,
5605
- end: preparedRun.window.end,
5606
- cacheOnly: true,
5607
- interval
5608
- });
5475
+ }) => ["replay", userName, connectorName, symbol, interval, start, end].join(":");
5476
+ var prepareHistoricalReplay = async (context, adapters) => {
5477
+ const {
5478
+ userName,
5479
+ projectRoot,
5480
+ preparedRun,
5481
+ interval,
5482
+ connectorName,
5483
+ replayConnector,
5484
+ strategies,
5485
+ references
5486
+ } = context;
5609
5487
  const cycleSymbolsByTimestamp = /* @__PURE__ */ new Map();
5610
5488
  const sharedReplayKeyPrefixes = [];
5489
+ const runtimeLineages = [];
5611
5490
  let skippedSymbols = 0;
5612
- const prepareBar = new import_progress4.default(
5613
- "prepare :current/:total [:bar][:percent] skipped=:skipped :etas(s) :symbol",
5614
- {
5615
- total: preparedRun.tickers.length,
5616
- width: 30
5617
- }
5618
- );
5619
5491
  for (const symbol of preparedRun.tickers) {
5620
5492
  const data = await preparedRun.marketConnector.kline({
5621
5493
  symbol,
@@ -5626,22 +5498,23 @@ var runHistoricalSignalsReplay = async ({
5626
5498
  });
5627
5499
  const preparedData = buildPreparedData({
5628
5500
  data,
5629
- btcData: btcMarketData,
5630
- ethData: ethMarketData,
5631
- btcBinanceData,
5632
- btcCoinbaseData,
5501
+ btcData: references.btcMarketData,
5502
+ ethData: references.ethMarketData,
5503
+ btcBinanceData: references.btcBinanceData,
5504
+ btcCoinbaseData: references.btcCoinbaseData,
5633
5505
  start: preparedRun.window.start,
5634
5506
  preloadStart: preparedRun.preloadStart
5635
5507
  });
5636
5508
  if (!preparedData.replayData.length || !preparedData.btcReplayData.length) {
5637
5509
  skippedSymbols += 1;
5638
- prepareBar.tick(1, {
5639
- skipped: import_chalk9.default.yellow(skippedSymbols),
5640
- symbol: import_chalk9.default.gray(symbol)
5510
+ adapters.progress.tick({
5511
+ skipped: adapters.display.skipped(skippedSymbols),
5512
+ symbol: adapters.display.symbol(symbol)
5641
5513
  });
5642
5514
  continue;
5643
5515
  }
5644
5516
  const sharedIndicatorsReplayKey = buildSymbolSharedReplayKey({
5517
+ userName,
5645
5518
  connectorName,
5646
5519
  interval,
5647
5520
  symbol,
@@ -5650,66 +5523,61 @@ var runHistoricalSignalsReplay = async ({
5650
5523
  });
5651
5524
  sharedReplayKeyPrefixes.push(sharedIndicatorsReplayKey);
5652
5525
  const strategiesForSymbol = await Promise.all(
5653
- loadedStrategies.filter((strategy) => strategyIncludesSymbol(strategy, symbol)).map(
5654
- async ({
5655
- strategyName,
5656
- strategyRevision,
5657
- deploymentCompositionId,
5658
- strategyPackageVersion,
5659
- strategyDependencyVersions,
5660
- runtimePackageVersion,
5661
- strategyCreator,
5662
- strategyConfig,
5663
- strategyResults
5664
- }) => {
5665
- const runtimeLineage = await buildRuntimeLineage({
5666
- strategyRevision,
5667
- deploymentCompositionId,
5668
- strategyPackageVersion,
5669
- strategyDependencyVersions,
5670
- runtimePackageVersion,
5671
- config: {
5672
- configId: strategyRevision,
5673
- strategyConfig,
5674
- symbolResultConfig: strategyResults?.[symbol]?.config ?? null
5675
- }
5676
- });
5677
- runtimeLineages.push({
5678
- strategy: strategyName,
5679
- symbol,
5680
- ...preparedRun.deploymentId ? { deploymentId: preparedRun.deploymentId } : {},
5681
- ...preparedRun.accountId ? { accountId: preparedRun.accountId } : {},
5682
- lineage: runtimeLineage
5683
- });
5684
- return {
5685
- strategyName,
5686
- strategyConfig,
5526
+ strategies.filter((strategy) => strategyIncludesSymbol(strategy, symbol)).map(async (strategy) => {
5527
+ const runtimeLineage = await adapters.buildLineage({
5528
+ projectRoot,
5529
+ strategyName: strategy.strategyName,
5530
+ strategyRevision: strategy.strategyRevision,
5531
+ deploymentCompositionId: strategy.deploymentCompositionId,
5532
+ strategyPackageVersion: strategy.strategyPackageVersion,
5533
+ strategyDependencyVersions: strategy.strategyDependencyVersions,
5534
+ runtimePackageVersion: strategy.runtimePackageVersion,
5535
+ config: {
5536
+ configId: strategy.strategyRevision,
5537
+ strategyConfig: strategy.strategyConfig,
5538
+ symbolResultConfig: strategy.strategyResults?.[symbol]?.config ?? null
5539
+ },
5540
+ runContext: {
5541
+ connectorName: connectorName.toLowerCase(),
5542
+ interval: String(interval),
5543
+ universe: preparedRun.universe ?? null
5544
+ }
5545
+ });
5546
+ runtimeLineages.push({
5547
+ strategy: strategy.strategyName,
5548
+ symbol,
5549
+ ...preparedRun.deploymentId ? { deploymentId: preparedRun.deploymentId } : {},
5550
+ ...preparedRun.accountId ? { accountId: preparedRun.accountId } : {},
5551
+ lineage: runtimeLineage
5552
+ });
5553
+ return {
5554
+ strategyName: strategy.strategyName,
5555
+ strategyConfig: strategy.strategyConfig,
5556
+ runtimeLineage,
5557
+ run: await strategy.strategyCreator({
5558
+ userName,
5559
+ connectorName,
5560
+ runtimeConfigId: strategy.strategyRevision,
5561
+ strategyRevision: strategy.strategyRevision,
5687
5562
  runtimeLineage,
5688
- run: await strategyCreator({
5689
- userName: replayUserName,
5690
- connectorName,
5691
- runtimeConfigId: strategyRevision,
5692
- strategyRevision,
5693
- runtimeLineage,
5694
- universe: preparedRun.universe,
5695
- accountId: preparedRun.accountId,
5696
- deploymentId: preparedRun.deploymentId,
5697
- config: buildReplayStrategyConfig({
5698
- strategyConfig,
5699
- interval
5700
- }),
5701
- symbol,
5702
- data: preparedData.prevData,
5703
- btcData: preparedData.btcPrevData,
5704
- ethData: preparedData.ethPrevData,
5705
- btcBinanceData: preparedData.btcBinancePrevData,
5706
- btcCoinbaseData: preparedData.btcCoinbasePrevData,
5707
- connector: replayConnector,
5708
- sharedIndicatorsReplayKey
5709
- })
5710
- };
5711
- }
5712
- )
5563
+ universe: preparedRun.universe,
5564
+ accountId: preparedRun.accountId,
5565
+ deploymentId: preparedRun.deploymentId,
5566
+ config: buildReplayStrategyConfig({
5567
+ strategyConfig: strategy.strategyConfig,
5568
+ interval
5569
+ }),
5570
+ symbol,
5571
+ data: preparedData.prevData,
5572
+ btcData: preparedData.btcPrevData,
5573
+ ethData: preparedData.ethPrevData,
5574
+ btcBinanceData: preparedData.btcBinancePrevData,
5575
+ btcCoinbaseData: preparedData.btcCoinbasePrevData,
5576
+ connector: replayConnector,
5577
+ sharedIndicatorsReplayKey
5578
+ })
5579
+ };
5580
+ })
5713
5581
  );
5714
5582
  const symbolRuntime = {
5715
5583
  symbol,
@@ -5719,9 +5587,9 @@ var runHistoricalSignalsReplay = async ({
5719
5587
  currentIndex: 0,
5720
5588
  strategies: strategiesForSymbol
5721
5589
  };
5722
- prepareBar.tick(1, {
5723
- skipped: import_chalk9.default.yellow(skippedSymbols),
5724
- symbol: import_chalk9.default.gray(symbol)
5590
+ adapters.progress.tick({
5591
+ skipped: adapters.display.skipped(skippedSymbols),
5592
+ symbol: adapters.display.symbol(symbol)
5725
5593
  });
5726
5594
  for (const candle of preparedData.replayData) {
5727
5595
  const bucket = cycleSymbolsByTimestamp.get(candle.timestamp) ?? [];
@@ -5729,60 +5597,66 @@ var runHistoricalSignalsReplay = async ({
5729
5597
  cycleSymbolsByTimestamp.set(candle.timestamp, bucket);
5730
5598
  }
5731
5599
  }
5732
- const orderedTimestamps = [...cycleSymbolsByTimestamp.keys()].sort(
5733
- (left, right) => left - right
5734
- );
5735
- const afterSignalsContextBase = buildAfterSignalsContext({
5736
- connector: replayConnector,
5737
- connectorName: preparedRun.connectorName,
5738
- tickers: preparedRun.tickers,
5739
- runtimeStrategies: loadedStrategies,
5740
- interval
5741
- });
5742
- let abortedCycles = 0;
5743
- const cycleBar = new import_progress4.default(
5744
- "cycles :current/:total [:bar][:percent] sig=:signals abort=:aborted :etas(s) :ts",
5745
- {
5746
- total: orderedTimestamps.length,
5747
- width: 30
5600
+ return {
5601
+ cycleSymbolsByTimestamp,
5602
+ orderedTimestamps: [...cycleSymbolsByTimestamp.keys()].sort(
5603
+ (left, right) => left - right
5604
+ ),
5605
+ sharedReplayKeyPrefixes,
5606
+ runtimeLineages
5607
+ };
5608
+ };
5609
+
5610
+ // src/lib/replay/historicalSignalsReplayExecution.ts
5611
+ var advanceCycleMarket = async (plan, connector, timestamp) => {
5612
+ const cycleSymbols = plan.cycleSymbolsByTimestamp.get(timestamp) ?? [];
5613
+ for (const symbolRuntime of cycleSymbols) {
5614
+ const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
5615
+ if (candle?.timestamp === timestamp) {
5616
+ await connector.advanceMarket({
5617
+ symbol: symbolRuntime.symbol,
5618
+ candle
5619
+ });
5748
5620
  }
5749
- );
5621
+ }
5622
+ return cycleSymbols;
5623
+ };
5624
+ var advanceAbortedCycle = (cycleSymbols, timestamp) => {
5625
+ for (const symbolRuntime of cycleSymbols ?? []) {
5626
+ const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
5627
+ if (candle?.timestamp === timestamp) {
5628
+ symbolRuntime.currentIndex += 1;
5629
+ }
5630
+ }
5631
+ };
5632
+ var executeHistoricalReplay = async (context, adapters) => {
5633
+ const signals = [];
5634
+ let abortedCycles = 0;
5750
5635
  try {
5751
- for (const timestamp of orderedTimestamps) {
5752
- const cycleStartedAt = Date.now();
5753
- const cycleSymbols = cycleSymbolsByTimestamp.get(timestamp) ?? [];
5754
- for (const symbolRuntime of cycleSymbols) {
5755
- const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
5756
- if (!candle || candle.timestamp !== timestamp) {
5757
- continue;
5758
- }
5759
- await replayConnector.advanceMarket({
5760
- symbol: symbolRuntime.symbol,
5761
- candle
5762
- });
5763
- }
5764
- const beforeSignalsResult = await invokeBeforeSignalsHooks(
5765
- projectHooks,
5766
- afterSignalsContextBase
5636
+ for (const timestamp of context.plan.orderedTimestamps) {
5637
+ const cycleStartedAt = adapters.clock.now();
5638
+ const cycleSymbols = await advanceCycleMarket(
5639
+ context.plan,
5640
+ context.connector,
5641
+ timestamp
5642
+ );
5643
+ const beforeSignalsResult = await adapters.invokeBeforeSignals(
5644
+ context.hooks,
5645
+ context.hookContext
5767
5646
  );
5768
5647
  if (beforeSignalsResult?.abort === true) {
5769
5648
  abortedCycles += 1;
5770
- await invokeAfterSignalsHooks(projectHooks, {
5771
- ...afterSignalsContextBase,
5649
+ await adapters.invokeAfterSignals(context.hooks, {
5650
+ ...context.hookContext,
5772
5651
  signals: [],
5773
5652
  status: "completed",
5774
- durationMs: Date.now() - cycleStartedAt
5653
+ durationMs: adapters.clock.now() - cycleStartedAt
5775
5654
  });
5776
- for (const symbolRuntime of cycleSymbols) {
5777
- const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
5778
- if (candle?.timestamp === timestamp) {
5779
- symbolRuntime.currentIndex += 1;
5780
- }
5781
- }
5782
- cycleBar.tick(1, {
5783
- signals: import_chalk9.default.cyan(signals.length),
5784
- aborted: import_chalk9.default.yellow(abortedCycles),
5785
- ts: import_chalk9.default.gray((0, import_time3.formatUnix)(timestamp))
5655
+ advanceAbortedCycle(cycleSymbols, timestamp);
5656
+ adapters.progress.tick({
5657
+ signals: adapters.display.signals(signals.length),
5658
+ aborted: adapters.display.aborted(abortedCycles),
5659
+ ts: adapters.display.timestamp(timestamp)
5786
5660
  });
5787
5661
  continue;
5788
5662
  }
@@ -5802,73 +5676,286 @@ var runHistoricalSignalsReplay = async ({
5802
5676
  );
5803
5677
  if (result && typeof result !== "string") {
5804
5678
  result.runtimeLineage = strategyRuntime.runtimeLineage;
5805
- await (0, import_strategies4.enrichSignalWithBinanceMarketContext)({
5806
- signal: result,
5807
- env: "PARITY"
5808
- });
5679
+ await adapters.enrichSignal(result);
5809
5680
  cycleSignals.push(result);
5810
5681
  signals.push(result);
5811
5682
  }
5812
5683
  }
5813
5684
  symbolRuntime.currentIndex += 1;
5814
5685
  }
5815
- await invokeAfterSignalsHooks(projectHooks, {
5816
- ...afterSignalsContextBase,
5686
+ await adapters.invokeAfterSignals(context.hooks, {
5687
+ ...context.hookContext,
5817
5688
  signals: cycleSignals,
5818
5689
  status: "completed",
5819
- durationMs: Date.now() - cycleStartedAt
5690
+ durationMs: adapters.clock.now() - cycleStartedAt
5820
5691
  });
5821
- cycleBar.tick(1, {
5822
- signals: import_chalk9.default.cyan(signals.length),
5823
- aborted: import_chalk9.default.yellow(abortedCycles),
5824
- ts: import_chalk9.default.gray((0, import_time3.formatUnix)(timestamp))
5692
+ adapters.progress.tick({
5693
+ signals: adapters.display.signals(signals.length),
5694
+ aborted: adapters.display.aborted(abortedCycles),
5695
+ ts: adapters.display.timestamp(timestamp)
5825
5696
  });
5826
5697
  }
5827
5698
  } finally {
5828
- for (const keyPrefix of sharedReplayKeyPrefixes) {
5829
- (0, import_strategies3.releaseStrategyIndicatorsReplayCache)(keyPrefix);
5830
- (0, import_strategies3.releaseStrategyReplayCache)(keyPrefix);
5699
+ for (const keyPrefix of context.plan.sharedReplayKeyPrefixes) {
5700
+ adapters.releaseIndicatorsCache(keyPrefix);
5701
+ adapters.releaseReplayCache(keyPrefix);
5831
5702
  }
5832
5703
  }
5833
- const artifacts = replayConnector.getReplayArtifacts();
5834
- const strategies = loadedStrategies.map(
5835
- ({ strategyName, strategyConfig }) => {
5836
- const orderLog = artifacts.orderLogByStrategy.get(strategyName) ?? [];
5837
- const positionLog = artifacts.positionLogByStrategy.get(strategyName) ?? [];
5838
- return {
5839
- strategyName,
5840
- strategyConfig,
5841
- orderLog,
5842
- positionLog,
5843
- stat: positionLog.length ? (0, import_backtest.calculateStatsFull)(positionLog) : {
5844
- orders: 0,
5845
- wins: 0,
5846
- losses: 0,
5847
- netProfit: 0,
5848
- amount: 0
5849
- }
5850
- };
5851
- }
5852
- );
5853
- import_logger.logger.info(
5854
- import_chalk9.default.gray(
5855
- `signals replay historical cycles: ${orderedTimestamps.length} (aborted=${abortedCycles}) done in ${((Date.now() - startedAt) / 1e3).toFixed(1)}s`
5856
- )
5857
- );
5858
- return {
5859
- strategies,
5860
- signals,
5861
- orderLog: artifacts.orderLog,
5862
- positionLog: artifacts.positionLog,
5863
- cycleCount: orderedTimestamps.length,
5864
- abortedCycles,
5865
- runtimeLineages
5866
- };
5704
+ return { signals, abortedCycles };
5867
5705
  };
5868
5706
 
5869
- // src/lib/replay/chartSnapshot.ts
5870
- var formatPercent = (value) => typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(1)}%` : "n/a";
5871
- var formatSigned = (value) => typeof value === "number" && Number.isFinite(value) ? `${value >= 0 ? "+" : ""}${value.toFixed(2)}` : "n/a";
5707
+ // src/lib/replay/historicalSignalsReplayResults.ts
5708
+ var import_backtest = require("@tradejs/core/backtest");
5709
+ var emptyStat = () => ({
5710
+ orders: 0,
5711
+ wins: 0,
5712
+ losses: 0,
5713
+ netProfit: 0,
5714
+ amount: 0
5715
+ });
5716
+ var collectHistoricalReplayResult = (context) => ({
5717
+ strategies: context.strategies.map(({ strategyName, strategyConfig }) => {
5718
+ const orderLog = context.artifacts.orderLogByStrategy.get(strategyName) ?? [];
5719
+ const positionLog = context.artifacts.positionLogByStrategy.get(strategyName) ?? [];
5720
+ return {
5721
+ strategyName,
5722
+ strategyConfig,
5723
+ orderLog,
5724
+ positionLog,
5725
+ stat: positionLog.length ? (0, import_backtest.calculateStatsFull)(positionLog) : emptyStat()
5726
+ };
5727
+ }),
5728
+ signals: context.signals,
5729
+ orderLog: context.artifacts.orderLog,
5730
+ positionLog: context.artifacts.positionLog,
5731
+ cycleCount: context.cycleCount,
5732
+ abortedCycles: context.abortedCycles,
5733
+ runtimeLineages: context.runtimeLineages
5734
+ });
5735
+
5736
+ // src/lib/replay/historicalSignalsReplay.ts
5737
+ var loadRuntimeStrategies = async (runtimeStrategies) => {
5738
+ const strategies = await Promise.all(
5739
+ runtimeStrategies.map(async (runtimeStrategy) => {
5740
+ const {
5741
+ strategyName,
5742
+ strategyRevision,
5743
+ deploymentCompositionId,
5744
+ strategyPackage,
5745
+ strategyPackageVersion,
5746
+ strategyDependencyVersions,
5747
+ runtimePackageVersion,
5748
+ strategyConfig,
5749
+ selection
5750
+ } = runtimeStrategy;
5751
+ const strategyCreator = await (0, import_strategies4.getStrategyCreator)(
5752
+ strategyName,
5753
+ replayProjectRoot
5754
+ );
5755
+ if (!strategyCreator) {
5756
+ throw new Error(`Unknown strategy: ${strategyName}`);
5757
+ }
5758
+ const installed = await (0, import_runtimeStrategies3.getRuntimeStrategyPackageMetadata)({
5759
+ strategyName,
5760
+ projectRoot: replayProjectRoot
5761
+ });
5762
+ if (installed.strategyPackage !== strategyPackage || installed.strategyPackageVersion !== strategyPackageVersion || JSON.stringify(installed.strategyDependencyVersions) !== JSON.stringify(strategyDependencyVersions) || installed.runtimePackageVersion !== runtimePackageVersion) {
5763
+ throw new Error(
5764
+ `Runtime evidence package mismatch for ${strategyName}: expected=${strategyPackage}@${strategyPackageVersion}/@tradejs/node@${runtimePackageVersion}, installed=${installed.strategyPackage ?? "missing"}@${installed.strategyPackageVersion ?? "missing"}/@tradejs/node@${installed.runtimePackageVersion ?? "missing"}`
5765
+ );
5766
+ }
5767
+ const strategyResults = await (0, import_redis3.getData)(
5768
+ import_redis3.redisKeys.strategyResults(replayUserName, strategyName),
5769
+ {}
5770
+ );
5771
+ return {
5772
+ strategyName,
5773
+ strategyRevision,
5774
+ deploymentCompositionId,
5775
+ strategyPackage,
5776
+ strategyPackageVersion,
5777
+ strategyDependencyVersions,
5778
+ runtimePackageVersion,
5779
+ strategyCreator,
5780
+ strategyConfig,
5781
+ strategyResults,
5782
+ ...selection ? { selection } : {}
5783
+ };
5784
+ })
5785
+ );
5786
+ return strategies;
5787
+ };
5788
+ var loadReferenceConnector = async (connectorName) => {
5789
+ const connectorFactory = await (0, import_connectors6.getConnectorCreatorByName)(
5790
+ connectorName,
5791
+ replayProjectRoot
5792
+ );
5793
+ if (!connectorFactory) {
5794
+ throw new Error(`Connector "${connectorName}" is not registered`);
5795
+ }
5796
+ return await connectorFactory({
5797
+ userName: replayUserName
5798
+ });
5799
+ };
5800
+ var buildAfterSignalsContext = ({
5801
+ connector,
5802
+ connectorName,
5803
+ tickers,
5804
+ runtimeStrategies,
5805
+ interval
5806
+ }) => ({
5807
+ connector,
5808
+ connectorName,
5809
+ userName: replayUserName,
5810
+ interval,
5811
+ tickers: [...tickers],
5812
+ runtimeStrategies: runtimeStrategies.map(
5813
+ ({ strategyName, strategyConfig }) => ({
5814
+ strategyName,
5815
+ strategyConfig
5816
+ })
5817
+ )
5818
+ });
5819
+ var getConnectorName = (connector) => {
5820
+ const name = "name" in connector ? connector.name : void 0;
5821
+ return String(name || import_connectors6.DEFAULT_CONNECTOR_NAME).trim() || import_connectors6.DEFAULT_CONNECTOR_NAME;
5822
+ };
5823
+ var runHistoricalSignalsReplay = async ({
5824
+ preparedRun,
5825
+ interval,
5826
+ runtimeStrategies
5827
+ }) => {
5828
+ const startedAt = Date.now();
5829
+ const projectConfig = await (0, import_cli4.loadTradejsConfig)(replayProjectRoot);
5830
+ const projectHooks = projectConfig.hooks;
5831
+ const loadedStrategies = await loadRuntimeStrategies(runtimeStrategies);
5832
+ const replayConnector = createPortfolioReplayConnector(
5833
+ preparedRun.marketConnector
5834
+ );
5835
+ const connectorName = getConnectorName(preparedRun.marketConnector);
5836
+ const binanceConnector = connectorName.toLowerCase() === import_connectors6.DEFAULT_CONNECTOR_NAME.toLowerCase() ? await loadReferenceConnector("Binance") : preparedRun.marketConnector;
5837
+ const coinbaseConnector = connectorName.toLowerCase() === import_connectors6.DEFAULT_CONNECTOR_NAME.toLowerCase() ? await loadReferenceConnector("Coinbase") : preparedRun.marketConnector;
5838
+ const [btcBinanceData, btcCoinbaseData] = await Promise.all([
5839
+ binanceConnector.kline({
5840
+ symbol: "BTCUSDT",
5841
+ start: preparedRun.preloadStart,
5842
+ end: preparedRun.window.end,
5843
+ cacheOnly: true,
5844
+ interval
5845
+ }),
5846
+ coinbaseConnector.kline({
5847
+ symbol: "BTCUSDT",
5848
+ start: preparedRun.preloadStart,
5849
+ end: preparedRun.window.end,
5850
+ cacheOnly: true,
5851
+ interval
5852
+ })
5853
+ ]);
5854
+ const btcMarketData = await preparedRun.marketConnector.kline({
5855
+ symbol: "BTCUSDT",
5856
+ start: preparedRun.preloadStart,
5857
+ end: preparedRun.window.end,
5858
+ cacheOnly: true,
5859
+ interval
5860
+ });
5861
+ const ethMarketData = await preparedRun.marketConnector.kline({
5862
+ symbol: "ETHUSDT",
5863
+ start: preparedRun.preloadStart,
5864
+ end: preparedRun.window.end,
5865
+ cacheOnly: true,
5866
+ interval
5867
+ });
5868
+ const prepareBar = new import_progress4.default(
5869
+ "prepare :current/:total [:bar][:percent] skipped=:skipped :etas(s) :symbol",
5870
+ {
5871
+ total: preparedRun.tickers.length,
5872
+ width: 30
5873
+ }
5874
+ );
5875
+ const plan = await prepareHistoricalReplay(
5876
+ {
5877
+ userName: replayUserName,
5878
+ projectRoot: replayProjectRoot,
5879
+ preparedRun,
5880
+ interval,
5881
+ connectorName,
5882
+ replayConnector,
5883
+ strategies: loadedStrategies,
5884
+ references: {
5885
+ btcMarketData,
5886
+ ethMarketData,
5887
+ btcBinanceData,
5888
+ btcCoinbaseData
5889
+ }
5890
+ },
5891
+ {
5892
+ progress: {
5893
+ tick: (tokens) => prepareBar.tick(1, tokens)
5894
+ },
5895
+ display: {
5896
+ skipped: (value) => import_chalk9.default.yellow(value),
5897
+ symbol: (value) => import_chalk9.default.gray(value)
5898
+ },
5899
+ buildLineage: buildRuntimeLineage
5900
+ }
5901
+ );
5902
+ const hookContext = buildAfterSignalsContext({
5903
+ connector: replayConnector,
5904
+ connectorName: preparedRun.connectorName,
5905
+ tickers: preparedRun.tickers,
5906
+ runtimeStrategies: loadedStrategies,
5907
+ interval
5908
+ });
5909
+ const cycleBar = new import_progress4.default(
5910
+ "cycles :current/:total [:bar][:percent] sig=:signals abort=:aborted :etas(s) :ts",
5911
+ {
5912
+ total: plan.orderedTimestamps.length,
5913
+ width: 30
5914
+ }
5915
+ );
5916
+ const execution = await executeHistoricalReplay(
5917
+ {
5918
+ plan,
5919
+ connector: replayConnector,
5920
+ hooks: projectHooks,
5921
+ hookContext
5922
+ },
5923
+ {
5924
+ clock: { now: Date.now },
5925
+ progress: {
5926
+ tick: (tokens) => cycleBar.tick(1, tokens)
5927
+ },
5928
+ display: {
5929
+ signals: (value) => import_chalk9.default.cyan(value),
5930
+ aborted: (value) => import_chalk9.default.yellow(value),
5931
+ timestamp: (value) => import_chalk9.default.gray((0, import_time3.formatUnix)(value))
5932
+ },
5933
+ invokeBeforeSignals: invokeBeforeSignalsHooks,
5934
+ invokeAfterSignals: invokeAfterSignalsHooks,
5935
+ enrichSignal: (signal) => (0, import_strategies4.enrichSignalWithBinanceMarketContext)({ signal, env: "PARITY" }),
5936
+ releaseIndicatorsCache: import_strategies3.releaseStrategyIndicatorsReplayCache,
5937
+ releaseReplayCache: import_strategies3.releaseStrategyReplayCache
5938
+ }
5939
+ );
5940
+ const artifacts = replayConnector.getReplayArtifacts();
5941
+ import_logger.logger.info(
5942
+ import_chalk9.default.gray(
5943
+ `signals replay historical cycles: ${plan.orderedTimestamps.length} (aborted=${execution.abortedCycles}) done in ${((Date.now() - startedAt) / 1e3).toFixed(1)}s`
5944
+ )
5945
+ );
5946
+ return collectHistoricalReplayResult({
5947
+ strategies: loadedStrategies,
5948
+ artifacts,
5949
+ signals: execution.signals,
5950
+ cycleCount: plan.orderedTimestamps.length,
5951
+ abortedCycles: execution.abortedCycles,
5952
+ runtimeLineages: plan.runtimeLineages
5953
+ });
5954
+ };
5955
+
5956
+ // src/lib/replay/chartSnapshot.ts
5957
+ var formatPercent = (value) => typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(1)}%` : "n/a";
5958
+ var formatSigned = (value) => typeof value === "number" && Number.isFinite(value) ? `${value >= 0 ? "+" : ""}${value.toFixed(2)}` : "n/a";
5872
5959
  var toSimpleOrderLog = (orderLog) => orderLog.map((entry) => [entry.timestamp, entry.amount]);
5873
5960
  var toReplayChartOrders = (orderLog) => orderLog.map((entry, index) => {
5874
5961
  const isOpenOrder = entry.type.startsWith("OPEN");
@@ -6270,8 +6357,7 @@ var saveAndPrintReplayResultsByStrategy = async ({
6270
6357
  };
6271
6358
 
6272
6359
  // src/lib/replay/runtimeComparison.ts
6273
- var import_chalk11 = __toESM(require("chalk"));
6274
- var import_time6 = require("@tradejs/core/time");
6360
+ var import_chalk12 = __toESM(require("chalk"));
6275
6361
 
6276
6362
  // src/lib/paritySummary.ts
6277
6363
  var summarizeRuntimeTradesByStrategy = (trades) => {
@@ -7455,31 +7541,7 @@ var buildReplayExchangeComparisonDetails = ({
7455
7541
  };
7456
7542
  };
7457
7543
 
7458
- // src/lib/replay/runtimeComparison.ts
7459
- var getReplayEntryTimestampCompareOffsetMs = () => {
7460
- const intervalMinutes = Number(replayInterval);
7461
- return Number.isFinite(intervalMinutes) && intervalMinutes > 0 ? intervalMinutes * 60 * 1e3 : 15 * 60 * 1e3;
7462
- };
7463
- var buildReplaySignalEvaluations = (signals) => signals.map((signal) => ({
7464
- evaluationId: `${signal.strategy}:${signal.symbol}:${signal.timestamp}`,
7465
- userName: replayUserName,
7466
- strategy: signal.strategy,
7467
- symbol: signal.symbol,
7468
- interval: signal.interval,
7469
- timestamp: signal.timestamp,
7470
- evaluatedAt: signal.timestamp,
7471
- status: "signal",
7472
- reason: signal.orderSkipReason || signal.orderStatus || "SIGNAL",
7473
- signalId: signal.signalId,
7474
- direction: signal.direction,
7475
- accountId: signal.accountId,
7476
- deploymentId: signal.deploymentId,
7477
- runtimeLineage: signal.runtimeLineage,
7478
- orderStatus: signal.orderStatus,
7479
- orderSkipReason: signal.orderSkipReason,
7480
- ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
7481
- ...signal.ml ? { ml: signal.ml } : {}
7482
- }));
7544
+ // src/lib/replay/runtimeComparisonCalculations.ts
7483
7545
  var lineageScopeKey = ({
7484
7546
  strategy,
7485
7547
  symbol,
@@ -7499,12 +7561,7 @@ var hasExpectedLineage = ({
7499
7561
  }) => runtimeLineagesComparable(
7500
7562
  lineage,
7501
7563
  expectedByScope.get(
7502
- lineageScopeKey({
7503
- strategy,
7504
- symbol,
7505
- deploymentId,
7506
- accountId
7507
- })
7564
+ lineageScopeKey({ strategy, symbol, deploymentId, accountId })
7508
7565
  )
7509
7566
  );
7510
7567
  var filterReplayComparisonByLineage = ({
@@ -7626,13 +7683,7 @@ var filterReplayComparisonByLineage = ({
7626
7683
  reason: comparableScopeKeys.size > 0 ? null : expectedByScope.size === 0 ? "replay_produced_no_lineage_scopes" : "no_runtime_artifacts_with_matching_lineage",
7627
7684
  replay: [...expectedByScope.entries()].map(([scope, lineage]) => {
7628
7685
  const [deploymentId, accountId, strategy, symbol] = scope.split("::");
7629
- return {
7630
- deploymentId,
7631
- accountId,
7632
- strategy,
7633
- symbol,
7634
- lineage
7635
- };
7686
+ return { deploymentId, accountId, strategy, symbol, lineage };
7636
7687
  }).sort(
7637
7688
  (left, right) => left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || runtimeLineageKey(left.lineage).localeCompare(
7638
7689
  runtimeLineageKey(right.lineage)
@@ -7641,29 +7692,174 @@ var filterReplayComparisonByLineage = ({
7641
7692
  }
7642
7693
  };
7643
7694
  };
7644
- var formatDrilldownSummary = (summary) => Object.entries(summary).filter(([, count]) => count > 0).map(([classification, count]) => `${classification}=${count}`).join(", ");
7645
- var toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
7646
- var sumOptionalNumbers = (values) => {
7647
- let sum = 0;
7648
- let hasValue = false;
7649
- for (const value of values) {
7650
- if (typeof value !== "number" || !Number.isFinite(value)) {
7651
- continue;
7652
- }
7653
- sum += value;
7654
- hasValue = true;
7695
+ var buildPriceDeltaPct3 = (leftPrice, rightPrice) => {
7696
+ if (leftPrice == null || rightPrice == null || !Number.isFinite(leftPrice) || !Number.isFinite(rightPrice) || leftPrice === 0) {
7697
+ return null;
7655
7698
  }
7656
- return hasValue ? Number(sum.toFixed(12)) : null;
7699
+ return Math.abs((rightPrice - leftPrice) / leftPrice * 100);
7657
7700
  };
7658
- var firstFiniteNumber = (values) => {
7659
- for (const value of values) {
7660
- if (typeof value === "number" && Number.isFinite(value)) {
7661
- return value;
7662
- }
7701
+ var compareExchangeEntriesToBacktest = ({
7702
+ exchangeEntries,
7703
+ backtestEntries,
7704
+ toleranceMs,
7705
+ backtestTimestampOffsetMs = 0
7706
+ }) => {
7707
+ const groupedExchange = /* @__PURE__ */ new Map();
7708
+ const groupedBacktest = /* @__PURE__ */ new Map();
7709
+ for (const entry of exchangeEntries) {
7710
+ const key = `${entry.symbol}::${entry.direction}`;
7711
+ const bucket = groupedExchange.get(key) ?? [];
7712
+ bucket.push(entry);
7713
+ groupedExchange.set(key, bucket);
7663
7714
  }
7664
- return null;
7715
+ for (const entry of backtestEntries) {
7716
+ const key = `${entry.symbol}::${entry.direction}`;
7717
+ const bucket = groupedBacktest.get(key) ?? [];
7718
+ bucket.push(entry);
7719
+ groupedBacktest.set(key, bucket);
7720
+ }
7721
+ const matched = [];
7722
+ const exchangeOnly = [];
7723
+ const backtestOnly = [];
7724
+ const matchedBacktestEntries = /* @__PURE__ */ new Set();
7725
+ const groupKeys = /* @__PURE__ */ new Set([
7726
+ ...groupedExchange.keys(),
7727
+ ...groupedBacktest.keys()
7728
+ ]);
7729
+ for (const key of groupKeys) {
7730
+ const exchangeGroup = [...groupedExchange.get(key) ?? []].sort(
7731
+ (left, right) => left.entryTimestamp - right.entryTimestamp
7732
+ );
7733
+ const unmatchedBacktest = [...groupedBacktest.get(key) ?? []].sort((left, right) => left.timestamp - right.timestamp).map((entry) => ({ entry, used: false }));
7734
+ for (const exchangeEntry of exchangeGroup) {
7735
+ let bestIndex = -1;
7736
+ let bestDiff = Number.POSITIVE_INFINITY;
7737
+ for (let index = 0; index < unmatchedBacktest.length; index += 1) {
7738
+ const candidate = unmatchedBacktest[index];
7739
+ if (candidate.used) continue;
7740
+ const diff = Math.abs(
7741
+ getBacktestParityComparisonTimestamp(
7742
+ candidate.entry,
7743
+ backtestTimestampOffsetMs
7744
+ ) - exchangeEntry.entryTimestamp
7745
+ );
7746
+ if (diff > toleranceMs || diff >= bestDiff) continue;
7747
+ bestIndex = index;
7748
+ bestDiff = diff;
7749
+ }
7750
+ if (bestIndex < 0) {
7751
+ exchangeOnly.push(exchangeEntry);
7752
+ continue;
7753
+ }
7754
+ unmatchedBacktest[bestIndex].used = true;
7755
+ const backtestEntry = unmatchedBacktest[bestIndex].entry;
7756
+ matchedBacktestEntries.add(backtestEntry);
7757
+ matched.push({
7758
+ exchange: exchangeEntry,
7759
+ backtest: backtestEntry,
7760
+ timestampDiffMs: bestDiff,
7761
+ priceDeltaPct: buildPriceDeltaPct3(
7762
+ exchangeEntry.entryPrice,
7763
+ backtestEntry.price
7764
+ )
7765
+ });
7766
+ }
7767
+ }
7768
+ for (const entry of backtestEntries) {
7769
+ if (!matchedBacktestEntries.has(entry)) backtestOnly.push(entry);
7770
+ }
7771
+ matched.sort(
7772
+ (left, right) => left.exchange.entryTimestamp - right.exchange.entryTimestamp || left.backtest.strategy.localeCompare(right.backtest.strategy)
7773
+ );
7774
+ exchangeOnly.sort(
7775
+ (left, right) => left.entryTimestamp - right.entryTimestamp
7776
+ );
7777
+ backtestOnly.sort((left, right) => left.timestamp - right.timestamp);
7778
+ return { matched, exchangeOnly, backtestOnly };
7779
+ };
7780
+ var findExchangeRuntimeOutcome = ({
7781
+ exchangeEntry,
7782
+ strategyName,
7783
+ runtimeSignals,
7784
+ runtimeSignalEvaluations,
7785
+ toleranceMs,
7786
+ signalTimestampOffsetMs
7787
+ }) => {
7788
+ const candidates = [];
7789
+ for (const signal of runtimeSignals) {
7790
+ if (signal.strategy === strategyName && signal.symbol === exchangeEntry.symbol && signal.direction === exchangeEntry.direction) {
7791
+ candidates.push({
7792
+ timestamp: signal.timestamp,
7793
+ orderStatus: signal.orderStatus,
7794
+ reason: signal.orderFailureReason || signal.orderSkipReason || signal.orderStatus
7795
+ });
7796
+ }
7797
+ }
7798
+ for (const evaluation of runtimeSignalEvaluations) {
7799
+ if (evaluation.strategy === strategyName && evaluation.symbol === exchangeEntry.symbol && (evaluation.direction == null || evaluation.direction === exchangeEntry.direction)) {
7800
+ candidates.push({
7801
+ timestamp: evaluation.timestamp,
7802
+ orderStatus: evaluation.orderStatus,
7803
+ reason: evaluation.orderSkipReason || evaluation.reason || evaluation.orderStatus
7804
+ });
7805
+ }
7806
+ }
7807
+ return candidates.map((candidate) => ({
7808
+ ...candidate,
7809
+ timestampDiffMs: Math.abs(
7810
+ candidate.timestamp + signalTimestampOffsetMs - exchangeEntry.entryTimestamp
7811
+ )
7812
+ })).filter((candidate) => candidate.timestampDiffMs <= toleranceMs).sort(
7813
+ (left, right) => left.timestampDiffMs - right.timestampDiffMs || Number(right.orderStatus === "failed") - Number(left.orderStatus === "failed")
7814
+ )[0] ?? null;
7815
+ };
7816
+ var hasLineageLinkedRuntimeOutcome = ({
7817
+ exchangeEntry,
7818
+ strategyName,
7819
+ runtimeSignals,
7820
+ runtimeSignalEvaluations,
7821
+ toleranceMs,
7822
+ signalTimestampOffsetMs
7823
+ }) => findExchangeRuntimeOutcome({
7824
+ exchangeEntry,
7825
+ strategyName,
7826
+ runtimeSignals,
7827
+ runtimeSignalEvaluations,
7828
+ toleranceMs,
7829
+ signalTimestampOffsetMs
7830
+ }) != null;
7831
+ var splitExchangeMatchesByRuntimeOrderStatus = ({
7832
+ matched,
7833
+ strategyNameByOrderLinkKey,
7834
+ runtimeSignals,
7835
+ runtimeSignalEvaluations,
7836
+ toleranceMs,
7837
+ signalTimestampOffsetMs
7838
+ }) => {
7839
+ const orderFailed = [];
7840
+ const completed = matched.filter((item) => {
7841
+ const strategyName = resolveReplayStrategyNameFromExchangeEntry({
7842
+ exchangeEntry: item.exchange,
7843
+ strategyNameByOrderLinkKey
7844
+ });
7845
+ if (!strategyName) return true;
7846
+ const outcome = findExchangeRuntimeOutcome({
7847
+ exchangeEntry: item.exchange,
7848
+ strategyName,
7849
+ runtimeSignals,
7850
+ runtimeSignalEvaluations,
7851
+ toleranceMs,
7852
+ signalTimestampOffsetMs
7853
+ });
7854
+ if (outcome?.orderStatus !== "failed") return true;
7855
+ orderFailed.push({
7856
+ ...item,
7857
+ reason: outcome.reason || "orderStatus=failed"
7858
+ });
7859
+ return false;
7860
+ });
7861
+ return { completed, orderFailed };
7665
7862
  };
7666
- var firstString = (values) => values.find((value) => typeof value === "string" && value.trim());
7667
7863
  var summarizeBacktestPnlByStrategy = (entries) => {
7668
7864
  const totals = /* @__PURE__ */ new Map();
7669
7865
  for (const entry of entries) {
@@ -7672,6 +7868,231 @@ var summarizeBacktestPnlByStrategy = (entries) => {
7672
7868
  }
7673
7869
  return totals;
7674
7870
  };
7871
+ var buildExchangeComparisonRows = ({
7872
+ liveStrategySummaries,
7873
+ backtestEntries,
7874
+ matched,
7875
+ orderFailed,
7876
+ exchangeOnly,
7877
+ strategyNameByOrderLinkKey
7878
+ }) => {
7879
+ const backtestPnlByStrategy = summarizeBacktestPnlByStrategy(backtestEntries);
7880
+ const rowByStrategy = /* @__PURE__ */ new Map();
7881
+ const ensureRow = (strategyName) => {
7882
+ const existing = rowByStrategy.get(strategyName);
7883
+ if (existing) return existing;
7884
+ const next = {
7885
+ strategyName,
7886
+ backtestEntries: 0,
7887
+ backtestNetProfit: backtestPnlByStrategy.get(strategyName) ?? 0,
7888
+ runtimeTrades: 0,
7889
+ runtimePnl: 0,
7890
+ matched: 0,
7891
+ orderFailed: 0,
7892
+ runtimeOnly: 0,
7893
+ backtestOnly: 0
7894
+ };
7895
+ rowByStrategy.set(strategyName, next);
7896
+ return next;
7897
+ };
7898
+ for (const summary of liveStrategySummaries) ensureRow(summary.strategyName);
7899
+ for (const entry of backtestEntries) {
7900
+ ensureRow(entry.strategy).backtestEntries += 1;
7901
+ }
7902
+ for (const item of matched) {
7903
+ const row = ensureRow(item.backtest.strategy);
7904
+ row.runtimeTrades += 1;
7905
+ row.matched += 1;
7906
+ if (typeof item.exchange.closedPnl === "number" && Number.isFinite(item.exchange.closedPnl)) {
7907
+ row.runtimePnl += item.exchange.closedPnl;
7908
+ }
7909
+ }
7910
+ for (const item of orderFailed) {
7911
+ const row = ensureRow(item.backtest.strategy);
7912
+ row.runtimeTrades += 1;
7913
+ row.orderFailed += 1;
7914
+ }
7915
+ const matchedBacktestEntries = /* @__PURE__ */ new Set([
7916
+ ...matched.map(({ backtest }) => backtest),
7917
+ ...orderFailed.map(({ backtest }) => backtest)
7918
+ ]);
7919
+ for (const entry of backtestEntries) {
7920
+ if (!matchedBacktestEntries.has(entry)) {
7921
+ ensureRow(entry.strategy).backtestOnly += 1;
7922
+ }
7923
+ }
7924
+ for (const entry of exchangeOnly) {
7925
+ const strategyName = resolveReplayStrategyNameFromExchangeEntry({
7926
+ exchangeEntry: entry,
7927
+ strategyNameByOrderLinkKey
7928
+ }) ?? "[exchange-unmatched]";
7929
+ const row = ensureRow(strategyName);
7930
+ row.runtimeTrades += 1;
7931
+ row.runtimeOnly += 1;
7932
+ if (typeof entry.closedPnl === "number" && Number.isFinite(entry.closedPnl)) {
7933
+ row.runtimePnl += entry.closedPnl;
7934
+ }
7935
+ }
7936
+ return [...rowByStrategy.values()].map((row) => ({
7937
+ ...row,
7938
+ runtimePnl: Number(row.runtimePnl.toFixed(2))
7939
+ })).sort((left, right) => left.strategyName.localeCompare(right.strategyName));
7940
+ };
7941
+ var buildRuntimeComparisonRows = ({
7942
+ liveStrategySummaries,
7943
+ runtimeSummaries,
7944
+ parityRows,
7945
+ backtestEntries
7946
+ }) => {
7947
+ const runtimeSummaryByStrategy = new Map(
7948
+ runtimeSummaries.map((summary) => [summary.strategyName, summary])
7949
+ );
7950
+ const parityByStrategy = new Map(parityRows);
7951
+ const backtestPnlByStrategy = summarizeBacktestPnlByStrategy(backtestEntries);
7952
+ const strategyNames = /* @__PURE__ */ new Set([
7953
+ ...liveStrategySummaries.map(({ strategyName }) => strategyName),
7954
+ ...runtimeSummaryByStrategy.keys(),
7955
+ ...parityByStrategy.keys()
7956
+ ]);
7957
+ return [...strategyNames].sort((left, right) => left.localeCompare(right)).map((strategyName) => {
7958
+ const runtimeSummary = runtimeSummaryByStrategy.get(strategyName);
7959
+ const parity = parityByStrategy.get(strategyName);
7960
+ return {
7961
+ strategyName,
7962
+ backtestEntries: parity?.backtest ?? 0,
7963
+ backtestNetProfit: backtestPnlByStrategy.get(strategyName) ?? 0,
7964
+ runtimeTrades: runtimeSummary?.trades ?? 0,
7965
+ runtimePnl: runtimeSummary?.totalPnl ?? 0,
7966
+ matched: parity?.matched ?? 0,
7967
+ orderFailed: 0,
7968
+ runtimeOnly: parity?.runtimeOnly ?? 0,
7969
+ backtestOnly: parity?.backtestOnly ?? 0
7970
+ };
7971
+ });
7972
+ };
7973
+
7974
+ // src/lib/replay/runtimeComparisonReporting.ts
7975
+ var import_chalk11 = __toESM(require("chalk"));
7976
+ var import_time6 = require("@tradejs/core/time");
7977
+ var formatDrilldownSummary = (summary) => Object.entries(summary).filter(([, count]) => count > 0).map(([classification, count]) => `${classification}=${count}`).join(", ");
7978
+ var colorizeRows = (rows) => rows.map((row) => {
7979
+ const btPnlColor = row.backtestNetProfit > 0 ? import_chalk11.default.green : row.backtestNetProfit < 0 ? import_chalk11.default.red : import_chalk11.default.gray;
7980
+ const rtPnlColor = row.runtimePnl > 0 ? import_chalk11.default.green : row.runtimePnl < 0 ? import_chalk11.default.red : import_chalk11.default.gray;
7981
+ return [
7982
+ import_chalk11.default.blue(row.strategyName),
7983
+ import_chalk11.default.cyan(String(row.backtestEntries)),
7984
+ btPnlColor(`${row.backtestNetProfit.toFixed(2)}$`),
7985
+ import_chalk11.default.yellow(String(row.runtimeTrades)),
7986
+ rtPnlColor(`${row.runtimePnl.toFixed(2)}$`),
7987
+ import_chalk11.default.green(String(row.matched)),
7988
+ import_chalk11.default.red(String(row.orderFailed)),
7989
+ import_chalk11.default.yellow(String(row.runtimeOnly)),
7990
+ import_chalk11.default.magenta(String(row.backtestOnly)),
7991
+ import_chalk11.default.red(String(getReplayRuntimeUnmatchedCount(row)))
7992
+ ];
7993
+ });
7994
+ var buildReplayComparisonReport = (context) => {
7995
+ const source = context.mode === "exchange" ? "EXCHANGE" : "RUNTIME";
7996
+ const inference = context.mode === "exchange" ? ", inferredStrategy=orderLinkId | nearest backtest entry" : "";
7997
+ const runtimeOnlyLabel = context.mode === "exchange" ? "exchangeOnly" : "runtimeOnly";
7998
+ const runtimeOnlyDrilldownSummary = formatDrilldownSummary(
7999
+ context.details?.mismatchDrilldown?.summary.runtimeOnly ?? {}
8000
+ );
8001
+ const backtestOnlyDrilldownSummary = formatDrilldownSummary(
8002
+ context.details?.mismatchDrilldown?.summary.backtestOnly ?? {}
8003
+ );
8004
+ const lines = [
8005
+ "",
8006
+ `SIGNALS REPLAY VS ${source} BY STRATEGY (connector=${context.connectorName}${inference}, tolerance=${formatReplayRuntimeCompareTolerance()})`,
8007
+ createTable(REPLAY_RUNTIME_COMPARISON_HEADERS, colorizeRows(context.rows))
8008
+ ];
8009
+ if (runtimeOnlyDrilldownSummary || backtestOnlyDrilldownSummary) {
8010
+ lines.push(
8011
+ import_chalk11.default.gray(
8012
+ `Mismatch drilldown: ${runtimeOnlyLabel}=[${runtimeOnlyDrilldownSummary || "none"}], backtestOnly=[${backtestOnlyDrilldownSummary || "none"}]`
8013
+ )
8014
+ );
8015
+ }
8016
+ lines.push("");
8017
+ return lines;
8018
+ };
8019
+ var buildNoExchangeEntriesReport = ({
8020
+ connectorName,
8021
+ window
8022
+ }) => [
8023
+ "",
8024
+ import_chalk11.default.yellow(
8025
+ `SIGNALS REPLAY VS EXCHANGE: no lineage-linked exchange entry executions found for ${connectorName} in ${(0, import_time6.formatUnix)(
8026
+ window.start
8027
+ )} -> ${(0, import_time6.formatUnix)(window.end)}`
8028
+ ),
8029
+ ""
8030
+ ];
8031
+ var buildNoRuntimeTradesReport = ({
8032
+ connectorName,
8033
+ window
8034
+ }) => [
8035
+ "",
8036
+ import_chalk11.default.yellow(
8037
+ `SIGNALS REPLAY VS RUNTIME: no local runtime trades found for ${connectorName} in ${(0, import_time6.formatUnix)(
8038
+ window.start
8039
+ )} -> ${(0, import_time6.formatUnix)(window.end)} with matching lineage; checking lineage-linked exchange executions`
8040
+ ),
8041
+ ""
8042
+ ];
8043
+ var writeReplayComparisonReport = (lines) => {
8044
+ for (const line of lines) {
8045
+ console.log(line);
8046
+ }
8047
+ };
8048
+
8049
+ // src/lib/replay/runtimeComparison.ts
8050
+ var getReplayEntryTimestampCompareOffsetMs = () => {
8051
+ const intervalMinutes = Number(replayInterval);
8052
+ return Number.isFinite(intervalMinutes) && intervalMinutes > 0 ? intervalMinutes * 60 * 1e3 : 15 * 60 * 1e3;
8053
+ };
8054
+ var buildReplaySignalEvaluations = (signals) => signals.map((signal) => ({
8055
+ evaluationId: `${signal.strategy}:${signal.symbol}:${signal.timestamp}`,
8056
+ userName: replayUserName,
8057
+ strategy: signal.strategy,
8058
+ symbol: signal.symbol,
8059
+ interval: signal.interval,
8060
+ timestamp: signal.timestamp,
8061
+ evaluatedAt: signal.timestamp,
8062
+ status: "signal",
8063
+ reason: signal.orderSkipReason || signal.orderStatus || "SIGNAL",
8064
+ signalId: signal.signalId,
8065
+ direction: signal.direction,
8066
+ accountId: signal.accountId,
8067
+ deploymentId: signal.deploymentId,
8068
+ runtimeLineage: signal.runtimeLineage,
8069
+ orderStatus: signal.orderStatus,
8070
+ orderSkipReason: signal.orderSkipReason,
8071
+ ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
8072
+ ...signal.ml ? { ml: signal.ml } : {}
8073
+ }));
8074
+ var toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
8075
+ var sumOptionalNumbers = (values) => {
8076
+ let sum = 0;
8077
+ let hasValue = false;
8078
+ for (const value of values) {
8079
+ if (typeof value !== "number" || !Number.isFinite(value)) {
8080
+ continue;
8081
+ }
8082
+ sum += value;
8083
+ hasValue = true;
8084
+ }
8085
+ return hasValue ? Number(sum.toFixed(12)) : null;
8086
+ };
8087
+ var firstFiniteNumber = (values) => {
8088
+ for (const value of values) {
8089
+ if (typeof value === "number" && Number.isFinite(value)) {
8090
+ return value;
8091
+ }
8092
+ }
8093
+ return null;
8094
+ };
8095
+ var firstString = (values) => values.find((value) => typeof value === "string" && value.trim());
7675
8096
  var getExchangeExecutionMergeKey = (entry) => {
7676
8097
  const orderId = firstString([entry.orderId, entry.orderLinkId]);
7677
8098
  return orderId ? `${entry.symbol}::${entry.direction}::${orderId}` : null;
@@ -7739,276 +8160,103 @@ var mergeExchangeEntryExecutions = (rows) => {
7739
8160
  );
7740
8161
  }
7741
8162
  return mergedRows.sort(
7742
- (left, right) => left.entryTimestamp - right.entryTimestamp
7743
- );
7744
- };
7745
- var loadExchangeEntryRows = async ({
7746
- connector,
7747
- startTime,
7748
- endTime
7749
- }) => {
7750
- if (typeof connector.getEntryExecutions !== "function") {
7751
- console.log(
7752
- import_chalk11.default.yellow(
7753
- "runtime compare: connector does not support entry execution history"
7754
- )
7755
- );
7756
- return [];
7757
- }
7758
- const rows = [];
7759
- const chunks = splitExchangeHistoryTimeRange({ startTime, endTime });
7760
- for (const chunk of chunks) {
7761
- try {
7762
- const chunkRows = await connector.getEntryExecutions({
7763
- startTime: chunk.startTime,
7764
- endTime: chunk.endTime,
7765
- limit: 100
7766
- });
7767
- if (chunkRows.length >= 100) {
7768
- console.log(
7769
- import_chalk11.default.yellow(
7770
- "runtime compare: exchange entry executions returned 100 rows (connector cap); older entry trades in this chunk may be truncated"
7771
- )
7772
- );
7773
- }
7774
- rows.push(...chunkRows);
7775
- } catch (error) {
7776
- console.log(
7777
- import_chalk11.default.yellow(
7778
- `runtime compare: getEntryExecutions failed: ${formatRuntimeTradeSyncError(error)}`
7779
- )
7780
- );
7781
- }
7782
- }
7783
- return mergeExchangeEntryExecutions(rows);
7784
- };
7785
- var buildPriceDeltaPct3 = (leftPrice, rightPrice) => {
7786
- if (leftPrice == null || rightPrice == null || !Number.isFinite(leftPrice) || !Number.isFinite(rightPrice) || leftPrice === 0) {
7787
- return null;
7788
- }
7789
- return Math.abs((rightPrice - leftPrice) / leftPrice * 100);
7790
- };
7791
- var loadExchangeEntriesForComparison = async ({
7792
- connector,
7793
- startTime,
7794
- endTime
7795
- }) => {
7796
- const entryRows = await loadExchangeEntryRows({
7797
- connector,
7798
- startTime,
7799
- endTime
7800
- });
7801
- const closedPnlRows = await loadClosedPnlRows({
7802
- connector,
7803
- startTime,
7804
- endTime,
7805
- callbacks: {
7806
- onUnsupported: () => {
7807
- console.log(
7808
- import_chalk11.default.yellow(
7809
- "runtime compare: connector does not support getClosedPnl, using runtime trade records as-is"
7810
- )
7811
- );
7812
- },
7813
- onCapped: () => {
7814
- console.log(
7815
- import_chalk11.default.yellow(
7816
- "runtime compare: exchange closed pnl returned 100 rows (connector cap); older closed trades in the window may be truncated"
7817
- )
7818
- );
7819
- },
7820
- onError: (error) => {
7821
- console.log(
7822
- import_chalk11.default.yellow(
7823
- `runtime compare: getClosedPnl failed: ${formatRuntimeTradeSyncError(error)}`
7824
- )
7825
- );
7826
- }
7827
- }
7828
- });
7829
- const closedPnlByOrderId = new Map(
7830
- closedPnlRows.filter((row) => typeof row.orderId === "string" && row.orderId.trim()).map((row) => [row.orderId, row])
7831
- );
7832
- return entryRows.map((entry) => {
7833
- const closedPnl = typeof entry.orderId === "string" ? closedPnlByOrderId.get(entry.orderId) : null;
7834
- return {
7835
- ...entry,
7836
- exitPrice: closedPnl?.exitPrice ?? entry.exitPrice ?? null,
7837
- exitTimestamp: closedPnl?.closedAt ?? entry.exitTimestamp ?? null,
7838
- closedPnl: closedPnl?.closedPnl ?? entry.closedPnl ?? null,
7839
- openFee: closedPnl?.openFee ?? entry.openFee ?? null,
7840
- closeFee: closedPnl?.closeFee ?? entry.closeFee ?? null,
7841
- fundingFee: closedPnl?.fundingFee ?? entry.fundingFee ?? null,
7842
- totalFee: closedPnl?.totalFee ?? entry.totalFee ?? null
7843
- };
7844
- });
7845
- };
7846
- var compareExchangeEntriesToBacktest = ({
7847
- exchangeEntries,
7848
- backtestEntries,
7849
- toleranceMs,
7850
- backtestTimestampOffsetMs = 0
7851
- }) => {
7852
- const groupedExchange = /* @__PURE__ */ new Map();
7853
- const groupedBacktest = /* @__PURE__ */ new Map();
7854
- for (const entry of exchangeEntries) {
7855
- const key = `${entry.symbol}::${entry.direction}`;
7856
- const bucket = groupedExchange.get(key) ?? [];
7857
- bucket.push(entry);
7858
- groupedExchange.set(key, bucket);
7859
- }
7860
- for (const entry of backtestEntries) {
7861
- const key = `${entry.symbol}::${entry.direction}`;
7862
- const bucket = groupedBacktest.get(key) ?? [];
7863
- bucket.push(entry);
7864
- groupedBacktest.set(key, bucket);
7865
- }
7866
- const matched = [];
7867
- const exchangeOnly = [];
7868
- const backtestOnly = [];
7869
- const matchedBacktestEntries = /* @__PURE__ */ new Set();
7870
- const groupKeys = /* @__PURE__ */ new Set([
7871
- ...groupedExchange.keys(),
7872
- ...groupedBacktest.keys()
7873
- ]);
7874
- for (const key of groupKeys) {
7875
- const exchangeGroup = [...groupedExchange.get(key) ?? []].sort(
7876
- (left, right) => left.entryTimestamp - right.entryTimestamp
7877
- );
7878
- const availableBacktest = [...groupedBacktest.get(key) ?? []].sort(
7879
- (left, right) => left.timestamp - right.timestamp
7880
- );
7881
- const unmatchedBacktest = availableBacktest.map((entry) => ({
7882
- entry,
7883
- used: false
7884
- }));
7885
- for (const exchangeEntry of exchangeGroup) {
7886
- let bestIndex = -1;
7887
- let bestDiff = Number.POSITIVE_INFINITY;
7888
- for (let index = 0; index < unmatchedBacktest.length; index += 1) {
7889
- const candidate = unmatchedBacktest[index];
7890
- if (candidate.used) {
7891
- continue;
7892
- }
7893
- const diff = Math.abs(
7894
- getBacktestParityComparisonTimestamp(
7895
- candidate.entry,
7896
- backtestTimestampOffsetMs
7897
- ) - exchangeEntry.entryTimestamp
7898
- );
7899
- if (diff > toleranceMs || diff >= bestDiff) {
7900
- continue;
7901
- }
7902
- bestIndex = index;
7903
- bestDiff = diff;
7904
- }
7905
- if (bestIndex < 0) {
7906
- exchangeOnly.push(exchangeEntry);
7907
- continue;
7908
- }
7909
- unmatchedBacktest[bestIndex].used = true;
7910
- const backtestEntry = unmatchedBacktest[bestIndex].entry;
7911
- matchedBacktestEntries.add(backtestEntry);
7912
- matched.push({
7913
- exchange: exchangeEntry,
7914
- backtest: backtestEntry,
7915
- timestampDiffMs: bestDiff,
7916
- priceDeltaPct: buildPriceDeltaPct3(
7917
- exchangeEntry.entryPrice,
7918
- backtestEntry.price
7919
- )
7920
- });
7921
- }
7922
- }
7923
- for (const entry of backtestEntries) {
7924
- if (!matchedBacktestEntries.has(entry)) {
7925
- backtestOnly.push(entry);
7926
- }
7927
- }
7928
- matched.sort(
7929
- (left, right) => left.exchange.entryTimestamp - right.exchange.entryTimestamp || left.backtest.strategy.localeCompare(right.backtest.strategy)
7930
- );
7931
- exchangeOnly.sort(
7932
- (left, right) => left.entryTimestamp - right.entryTimestamp
7933
- );
7934
- backtestOnly.sort((left, right) => left.timestamp - right.timestamp);
7935
- return {
7936
- matched,
7937
- exchangeOnly,
7938
- backtestOnly
7939
- };
7940
- };
7941
- var findExchangeRuntimeOutcome = ({
7942
- exchangeEntry,
7943
- strategyName,
7944
- runtimeSignals,
7945
- runtimeSignalEvaluations,
7946
- toleranceMs,
7947
- signalTimestampOffsetMs
8163
+ (left, right) => left.entryTimestamp - right.entryTimestamp
8164
+ );
8165
+ };
8166
+ var loadExchangeEntryRows = async ({
8167
+ connector,
8168
+ startTime,
8169
+ endTime
7948
8170
  }) => {
7949
- const candidates = [];
7950
- for (const signal of runtimeSignals) {
7951
- if (signal.strategy === strategyName && signal.symbol === exchangeEntry.symbol && signal.direction === exchangeEntry.direction) {
7952
- candidates.push({
7953
- timestamp: signal.timestamp,
7954
- orderStatus: signal.orderStatus,
7955
- reason: signal.orderFailureReason || signal.orderSkipReason || signal.orderStatus
7956
- });
7957
- }
8171
+ if (typeof connector.getEntryExecutions !== "function") {
8172
+ console.log(
8173
+ import_chalk12.default.yellow(
8174
+ "runtime compare: connector does not support entry execution history"
8175
+ )
8176
+ );
8177
+ return [];
7958
8178
  }
7959
- for (const evaluation of runtimeSignalEvaluations) {
7960
- if (evaluation.strategy === strategyName && evaluation.symbol === exchangeEntry.symbol && (evaluation.direction == null || evaluation.direction === exchangeEntry.direction)) {
7961
- candidates.push({
7962
- timestamp: evaluation.timestamp,
7963
- orderStatus: evaluation.orderStatus,
7964
- reason: evaluation.orderSkipReason || evaluation.reason || evaluation.orderStatus
8179
+ const rows = [];
8180
+ const chunks = splitExchangeHistoryTimeRange({ startTime, endTime });
8181
+ for (const chunk of chunks) {
8182
+ try {
8183
+ const chunkRows = await connector.getEntryExecutions({
8184
+ startTime: chunk.startTime,
8185
+ endTime: chunk.endTime,
8186
+ limit: 100
7965
8187
  });
8188
+ if (chunkRows.length >= 100) {
8189
+ console.log(
8190
+ import_chalk12.default.yellow(
8191
+ "runtime compare: exchange entry executions returned 100 rows (connector cap); older entry trades in this chunk may be truncated"
8192
+ )
8193
+ );
8194
+ }
8195
+ rows.push(...chunkRows);
8196
+ } catch (error) {
8197
+ console.log(
8198
+ import_chalk12.default.yellow(
8199
+ `runtime compare: getEntryExecutions failed: ${formatRuntimeTradeSyncError(error)}`
8200
+ )
8201
+ );
7966
8202
  }
7967
8203
  }
7968
- return candidates.map((candidate) => ({
7969
- ...candidate,
7970
- timestampDiffMs: Math.abs(
7971
- candidate.timestamp + signalTimestampOffsetMs - exchangeEntry.entryTimestamp
7972
- )
7973
- })).filter((candidate) => candidate.timestampDiffMs <= toleranceMs).sort(
7974
- (left, right) => left.timestampDiffMs - right.timestampDiffMs || Number(right.orderStatus === "failed") - Number(left.orderStatus === "failed")
7975
- )[0] ?? null;
8204
+ return mergeExchangeEntryExecutions(rows);
7976
8205
  };
7977
- var splitExchangeMatchesByRuntimeOrderStatus = ({
7978
- matched,
7979
- strategyNameByOrderLinkKey,
7980
- runtimeSignals,
7981
- runtimeSignalEvaluations,
7982
- toleranceMs,
7983
- signalTimestampOffsetMs
8206
+ var loadExchangeEntriesForComparison = async ({
8207
+ connector,
8208
+ startTime,
8209
+ endTime
7984
8210
  }) => {
7985
- const orderFailed = [];
7986
- const completed = matched.filter((item) => {
7987
- const strategyName = resolveReplayStrategyNameFromExchangeEntry({
7988
- exchangeEntry: item.exchange,
7989
- strategyNameByOrderLinkKey
7990
- });
7991
- if (!strategyName) {
7992
- return true;
7993
- }
7994
- const outcome = findExchangeRuntimeOutcome({
7995
- exchangeEntry: item.exchange,
7996
- strategyName,
7997
- runtimeSignals,
7998
- runtimeSignalEvaluations,
7999
- toleranceMs,
8000
- signalTimestampOffsetMs
8001
- });
8002
- if (outcome?.orderStatus !== "failed") {
8003
- return true;
8211
+ const entryRows = await loadExchangeEntryRows({
8212
+ connector,
8213
+ startTime,
8214
+ endTime
8215
+ });
8216
+ const closedPnlRows = await loadClosedPnlRows({
8217
+ connector,
8218
+ startTime,
8219
+ endTime,
8220
+ callbacks: {
8221
+ onUnsupported: () => {
8222
+ console.log(
8223
+ import_chalk12.default.yellow(
8224
+ "runtime compare: connector does not support getClosedPnl, using runtime trade records as-is"
8225
+ )
8226
+ );
8227
+ },
8228
+ onCapped: () => {
8229
+ console.log(
8230
+ import_chalk12.default.yellow(
8231
+ "runtime compare: exchange closed pnl returned 100 rows (connector cap); older closed trades in the window may be truncated"
8232
+ )
8233
+ );
8234
+ },
8235
+ onError: (error) => {
8236
+ console.log(
8237
+ import_chalk12.default.yellow(
8238
+ `runtime compare: getClosedPnl failed: ${formatRuntimeTradeSyncError(error)}`
8239
+ )
8240
+ );
8241
+ }
8004
8242
  }
8005
- orderFailed.push({
8006
- ...item,
8007
- reason: outcome.reason || "orderStatus=failed"
8008
- });
8009
- return false;
8010
8243
  });
8011
- return { completed, orderFailed };
8244
+ const closedPnlByOrderId = new Map(
8245
+ closedPnlRows.filter((row) => typeof row.orderId === "string" && row.orderId.trim()).map((row) => [row.orderId, row])
8246
+ );
8247
+ return entryRows.map((entry) => {
8248
+ const closedPnl = typeof entry.orderId === "string" ? closedPnlByOrderId.get(entry.orderId) : null;
8249
+ return {
8250
+ ...entry,
8251
+ exitPrice: closedPnl?.exitPrice ?? entry.exitPrice ?? null,
8252
+ exitTimestamp: closedPnl?.closedAt ?? entry.exitTimestamp ?? null,
8253
+ closedPnl: closedPnl?.closedPnl ?? entry.closedPnl ?? null,
8254
+ openFee: closedPnl?.openFee ?? entry.openFee ?? null,
8255
+ closeFee: closedPnl?.closeFee ?? entry.closeFee ?? null,
8256
+ fundingFee: closedPnl?.fundingFee ?? entry.fundingFee ?? null,
8257
+ totalFee: closedPnl?.totalFee ?? entry.totalFee ?? null
8258
+ };
8259
+ });
8012
8260
  };
8013
8261
  var saveAndPrintReplayExchangeComparison = async ({
8014
8262
  liveStrategySummaries,
@@ -8028,7 +8276,6 @@ var saveAndPrintReplayExchangeComparison = async ({
8028
8276
  const strategyNameByOrderLinkKey = buildStrategyNameByOrderLinkKey(
8029
8277
  liveStrategySummaries.map((summary) => summary.strategyName)
8030
8278
  );
8031
- const exchangeOutcomeByEntry = /* @__PURE__ */ new Map();
8032
8279
  const exchangeEntries = rawExchangeEntries.filter((entry) => {
8033
8280
  const strategyName = resolveReplayStrategyNameFromExchangeEntry({
8034
8281
  exchangeEntry: entry,
@@ -8037,7 +8284,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8037
8284
  if (!strategyName) {
8038
8285
  return false;
8039
8286
  }
8040
- const outcome = findExchangeRuntimeOutcome({
8287
+ return hasLineageLinkedRuntimeOutcome({
8041
8288
  exchangeEntry: entry,
8042
8289
  strategyName,
8043
8290
  runtimeSignals,
@@ -8045,24 +8292,18 @@ var saveAndPrintReplayExchangeComparison = async ({
8045
8292
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
8046
8293
  signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
8047
8294
  });
8048
- exchangeOutcomeByEntry.set(entry, outcome);
8049
- return outcome != null;
8050
8295
  });
8051
8296
  const comparisonLineage = {
8052
8297
  ...lineage,
8053
8298
  excludedExchangeEntries: lineage.excludedExchangeEntries + rawExchangeEntries.length - exchangeEntries.length
8054
8299
  };
8055
- const backtestPnlByStrategy = summarizeBacktestPnlByStrategy(backtestEntries);
8056
8300
  if (!exchangeEntries.length) {
8057
- console.log("");
8058
- console.log(
8059
- import_chalk11.default.yellow(
8060
- `SIGNALS REPLAY VS EXCHANGE: no lineage-linked exchange entry executions found for ${connectorName} in ${(0, import_time6.formatUnix)(
8061
- window.start
8062
- )} -> ${(0, import_time6.formatUnix)(window.end)}`
8063
- )
8301
+ writeReplayComparisonReport(
8302
+ buildNoExchangeEntriesReport({
8303
+ connectorName,
8304
+ window
8305
+ })
8064
8306
  );
8065
- console.log("");
8066
8307
  const details2 = buildReplayExchangeComparisonDetails({
8067
8308
  matched: [],
8068
8309
  exchangeOnly: [],
@@ -8077,6 +8318,16 @@ var saveAndPrintReplayExchangeComparison = async ({
8077
8318
  replaySignals,
8078
8319
  replaySignalEvaluations
8079
8320
  });
8321
+ const emptyRowsByStrategy = new Map(
8322
+ buildExchangeComparisonRows({
8323
+ liveStrategySummaries,
8324
+ backtestEntries,
8325
+ matched: [],
8326
+ orderFailed: [],
8327
+ exchangeOnly: [],
8328
+ strategyNameByOrderLinkKey
8329
+ }).map((row) => [row.strategyName, row])
8330
+ );
8080
8331
  return {
8081
8332
  mode: "exchange",
8082
8333
  syncedTradesCount: 0,
@@ -8089,21 +8340,9 @@ var saveAndPrintReplayExchangeComparison = async ({
8089
8340
  backtestOnlyCount: backtestEntries.length,
8090
8341
  details: details2,
8091
8342
  lineage: comparisonLineage,
8092
- rows: liveStrategySummaries.map((summary) => ({
8093
- strategyName: summary.strategyName,
8094
- backtestEntries: backtestEntries.filter(
8095
- (entry) => entry.strategy === summary.strategyName
8096
- ).length,
8097
- backtestNetProfit: backtestPnlByStrategy.get(summary.strategyName) ?? 0,
8098
- runtimeTrades: 0,
8099
- runtimePnl: 0,
8100
- matched: 0,
8101
- orderFailed: 0,
8102
- runtimeOnly: 0,
8103
- backtestOnly: backtestEntries.filter(
8104
- (entry) => entry.strategy === summary.strategyName
8105
- ).length
8106
- }))
8343
+ rows: liveStrategySummaries.map(
8344
+ ({ strategyName }) => emptyRowsByStrategy.get(strategyName)
8345
+ )
8107
8346
  };
8108
8347
  }
8109
8348
  const comparison = compareExchangeEntriesToBacktest({
@@ -8120,62 +8359,6 @@ var saveAndPrintReplayExchangeComparison = async ({
8120
8359
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
8121
8360
  signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
8122
8361
  });
8123
- const rowByStrategy = /* @__PURE__ */ new Map();
8124
- const ensureRow = (strategyName) => {
8125
- const existing = rowByStrategy.get(strategyName);
8126
- if (existing) {
8127
- return existing;
8128
- }
8129
- const next = {
8130
- strategyName,
8131
- backtestEntries: 0,
8132
- backtestNetProfit: backtestPnlByStrategy.get(strategyName) ?? 0,
8133
- runtimeTrades: 0,
8134
- runtimePnl: 0,
8135
- matched: 0,
8136
- orderFailed: 0,
8137
- runtimeOnly: 0,
8138
- backtestOnly: 0
8139
- };
8140
- rowByStrategy.set(strategyName, next);
8141
- return next;
8142
- };
8143
- for (const summary of liveStrategySummaries) {
8144
- ensureRow(summary.strategyName);
8145
- }
8146
- for (const entry of backtestEntries) {
8147
- ensureRow(entry.strategy).backtestEntries += 1;
8148
- }
8149
- for (const item of matched) {
8150
- const row = ensureRow(item.backtest.strategy);
8151
- row.runtimeTrades += 1;
8152
- row.matched += 1;
8153
- if (typeof item.exchange.closedPnl === "number" && Number.isFinite(item.exchange.closedPnl)) {
8154
- row.runtimePnl += item.exchange.closedPnl;
8155
- }
8156
- }
8157
- for (const item of orderFailed) {
8158
- const row = ensureRow(item.backtest.strategy);
8159
- row.runtimeTrades += 1;
8160
- row.orderFailed += 1;
8161
- }
8162
- for (const entry of comparison.backtestOnly) {
8163
- ensureRow(entry.strategy).backtestOnly += 1;
8164
- }
8165
- if (comparison.exchangeOnly.length) {
8166
- for (const entry of comparison.exchangeOnly) {
8167
- const strategyName = resolveReplayStrategyNameFromExchangeEntry({
8168
- exchangeEntry: entry,
8169
- strategyNameByOrderLinkKey
8170
- }) ?? "[exchange-unmatched]";
8171
- const row = ensureRow(strategyName);
8172
- row.runtimeTrades += 1;
8173
- row.runtimeOnly += 1;
8174
- if (typeof entry.closedPnl === "number" && Number.isFinite(entry.closedPnl)) {
8175
- row.runtimePnl += entry.closedPnl;
8176
- }
8177
- }
8178
- }
8179
8362
  const details = buildReplayExchangeComparisonDetails({
8180
8363
  matched,
8181
8364
  orderFailed,
@@ -8191,45 +8374,22 @@ var saveAndPrintReplayExchangeComparison = async ({
8191
8374
  replaySignals,
8192
8375
  replaySignalEvaluations
8193
8376
  });
8194
- const rows = [...rowByStrategy.values()].map((row) => ({
8195
- ...row,
8196
- runtimePnl: Number(row.runtimePnl.toFixed(2))
8197
- })).sort((left, right) => left.strategyName.localeCompare(right.strategyName));
8198
- const colorizedRows = rows.map((row) => {
8199
- const btPnlColor = row.backtestNetProfit > 0 ? import_chalk11.default.green : row.backtestNetProfit < 0 ? import_chalk11.default.red : import_chalk11.default.gray;
8200
- const rtPnlColor = row.runtimePnl > 0 ? import_chalk11.default.green : row.runtimePnl < 0 ? import_chalk11.default.red : import_chalk11.default.gray;
8201
- return [
8202
- import_chalk11.default.blue(row.strategyName),
8203
- import_chalk11.default.cyan(String(row.backtestEntries)),
8204
- btPnlColor(`${row.backtestNetProfit.toFixed(2)}$`),
8205
- import_chalk11.default.yellow(String(row.runtimeTrades)),
8206
- rtPnlColor(`${row.runtimePnl.toFixed(2)}$`),
8207
- import_chalk11.default.green(String(row.matched)),
8208
- import_chalk11.default.red(String(row.orderFailed)),
8209
- import_chalk11.default.yellow(String(row.runtimeOnly)),
8210
- import_chalk11.default.magenta(String(row.backtestOnly)),
8211
- import_chalk11.default.red(String(getReplayRuntimeUnmatchedCount(row)))
8212
- ];
8377
+ const rows = buildExchangeComparisonRows({
8378
+ liveStrategySummaries,
8379
+ backtestEntries,
8380
+ matched,
8381
+ orderFailed,
8382
+ exchangeOnly: comparison.exchangeOnly,
8383
+ strategyNameByOrderLinkKey
8213
8384
  });
8214
- console.log("");
8215
- console.log(
8216
- `SIGNALS REPLAY VS EXCHANGE BY STRATEGY (connector=${connectorName}, inferredStrategy=orderLinkId | nearest backtest entry, tolerance=${formatReplayRuntimeCompareTolerance()})`
8217
- );
8218
- console.log(createTable(REPLAY_RUNTIME_COMPARISON_HEADERS, colorizedRows));
8219
- const runtimeOnlyDrilldownSummary = formatDrilldownSummary(
8220
- details.mismatchDrilldown?.summary.runtimeOnly ?? {}
8221
- );
8222
- const backtestOnlyDrilldownSummary = formatDrilldownSummary(
8223
- details.mismatchDrilldown?.summary.backtestOnly ?? {}
8385
+ writeReplayComparisonReport(
8386
+ buildReplayComparisonReport({
8387
+ mode: "exchange",
8388
+ connectorName,
8389
+ rows,
8390
+ details
8391
+ })
8224
8392
  );
8225
- if (runtimeOnlyDrilldownSummary || backtestOnlyDrilldownSummary) {
8226
- console.log(
8227
- import_chalk11.default.gray(
8228
- `Mismatch drilldown: exchangeOnly=[${runtimeOnlyDrilldownSummary || "none"}], backtestOnly=[${backtestOnlyDrilldownSummary || "none"}]`
8229
- )
8230
- );
8231
- }
8232
- console.log("");
8233
8393
  return {
8234
8394
  mode: "exchange",
8235
8395
  syncedTradesCount: exchangeEntries.length,
@@ -8296,7 +8456,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
8296
8456
  ]);
8297
8457
  if (runtimeEvidence) {
8298
8458
  console.log(
8299
- import_chalk11.default.gray(
8459
+ import_chalk12.default.gray(
8300
8460
  `runtime compare: using immutable evidence ${runtimeEvidence.path}`
8301
8461
  )
8302
8462
  );
@@ -8310,7 +8470,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
8310
8470
  openPositionCallbacks: {
8311
8471
  onError: (error) => {
8312
8472
  console.log(
8313
- import_chalk11.default.yellow(
8473
+ import_chalk12.default.yellow(
8314
8474
  `runtime compare: getOpenPositionPnl failed: ${formatRuntimeTradeSyncError(error)}; continuing without open-position mark prices`
8315
8475
  )
8316
8476
  );
@@ -8319,21 +8479,21 @@ var saveAndPrintReplayRuntimeComparison = async ({
8319
8479
  closedPnlCallbacks: {
8320
8480
  onUnsupported: () => {
8321
8481
  console.log(
8322
- import_chalk11.default.yellow(
8482
+ import_chalk12.default.yellow(
8323
8483
  "runtime compare: connector does not support getClosedPnl, using runtime trade records as-is"
8324
8484
  )
8325
8485
  );
8326
8486
  },
8327
8487
  onCapped: () => {
8328
8488
  console.log(
8329
- import_chalk11.default.yellow(
8489
+ import_chalk12.default.yellow(
8330
8490
  "runtime compare: exchange closed pnl returned 100 rows (connector cap); older closed trades in the window may be truncated"
8331
8491
  )
8332
8492
  );
8333
8493
  },
8334
8494
  onError: (error) => {
8335
8495
  console.log(
8336
- import_chalk11.default.yellow(
8496
+ import_chalk12.default.yellow(
8337
8497
  `runtime compare: getClosedPnl failed: ${formatRuntimeTradeSyncError(error)}`
8338
8498
  )
8339
8499
  );
@@ -8353,15 +8513,9 @@ var saveAndPrintReplayRuntimeComparison = async ({
8353
8513
  (trade) => trade.entryTimestamp >= window.start && trade.entryTimestamp < window.end && relevantStrategies.has(trade.strategy)
8354
8514
  );
8355
8515
  if (!windowRuntimeTrades.length && !runtimeEvidence) {
8356
- console.log("");
8357
- console.log(
8358
- import_chalk11.default.yellow(
8359
- `SIGNALS REPLAY VS RUNTIME: no local runtime trades found for ${connectorName} in ${(0, import_time6.formatUnix)(
8360
- window.start
8361
- )} -> ${(0, import_time6.formatUnix)(window.end)} with matching lineage; checking lineage-linked exchange executions`
8362
- )
8516
+ writeReplayComparisonReport(
8517
+ buildNoRuntimeTradesReport({ connectorName, window })
8363
8518
  );
8364
- console.log("");
8365
8519
  return saveAndPrintReplayExchangeComparison({
8366
8520
  liveStrategySummaries,
8367
8521
  backtestEntries: comparableBacktestEntries,
@@ -8373,9 +8527,6 @@ var saveAndPrintReplayRuntimeComparison = async ({
8373
8527
  });
8374
8528
  }
8375
8529
  const runtimeSummaries = summarizeRuntimeTradesByStrategy(windowRuntimeTrades);
8376
- const runtimeSummaryByStrategy = new Map(
8377
- runtimeSummaries.map((summary) => [summary.strategyName, summary])
8378
- );
8379
8530
  const rawRuntimeEntries = extractRuntimeParityEntries(windowRuntimeTrades);
8380
8531
  const runtimeDedupe = dedupeRuntimeParityEntries(rawRuntimeEntries);
8381
8532
  const comparison = compareTradeParityEntries({
@@ -8405,68 +8556,20 @@ var saveAndPrintReplayRuntimeComparison = async ({
8405
8556
  runtimeOnlyEntries: comparison.runtimeOnly,
8406
8557
  backtestOnlyEntries: comparison.backtestOnly
8407
8558
  });
8408
- const parityByStrategy = new Map(parityRows);
8409
- const liveSummaryByStrategy = new Map(
8410
- liveStrategySummaries.map((summary) => [summary.strategyName, summary])
8411
- );
8412
- const backtestPnlByStrategy = summarizeBacktestPnlByStrategy(
8413
- comparableBacktestEntries
8414
- );
8415
- const strategyNames = /* @__PURE__ */ new Set([
8416
- ...liveSummaryByStrategy.keys(),
8417
- ...runtimeSummaryByStrategy.keys(),
8418
- ...parityByStrategy.keys()
8419
- ]);
8420
- const rows = [...strategyNames].sort((left, right) => left.localeCompare(right)).map((strategyName) => {
8421
- const runtimeSummary = runtimeSummaryByStrategy.get(strategyName);
8422
- const parity = parityByStrategy.get(strategyName);
8423
- return {
8424
- strategyName,
8425
- backtestEntries: parity?.backtest ?? 0,
8426
- backtestNetProfit: backtestPnlByStrategy.get(strategyName) ?? 0,
8427
- runtimeTrades: runtimeSummary?.trades ?? 0,
8428
- runtimePnl: runtimeSummary?.totalPnl ?? 0,
8429
- matched: parity?.matched ?? 0,
8430
- orderFailed: 0,
8431
- runtimeOnly: parity?.runtimeOnly ?? 0,
8432
- backtestOnly: parity?.backtestOnly ?? 0
8433
- };
8434
- });
8435
- const colorizedRows = rows.map((row) => {
8436
- const btPnlColor = row.backtestNetProfit > 0 ? import_chalk11.default.green : row.backtestNetProfit < 0 ? import_chalk11.default.red : import_chalk11.default.gray;
8437
- const rtPnlColor = row.runtimePnl > 0 ? import_chalk11.default.green : row.runtimePnl < 0 ? import_chalk11.default.red : import_chalk11.default.gray;
8438
- return [
8439
- import_chalk11.default.blue(row.strategyName),
8440
- import_chalk11.default.cyan(String(row.backtestEntries)),
8441
- btPnlColor(`${row.backtestNetProfit.toFixed(2)}$`),
8442
- import_chalk11.default.yellow(String(row.runtimeTrades)),
8443
- rtPnlColor(`${row.runtimePnl.toFixed(2)}$`),
8444
- import_chalk11.default.green(String(row.matched)),
8445
- import_chalk11.default.red(String(row.orderFailed)),
8446
- import_chalk11.default.yellow(String(row.runtimeOnly)),
8447
- import_chalk11.default.magenta(String(row.backtestOnly)),
8448
- import_chalk11.default.red(String(getReplayRuntimeUnmatchedCount(row)))
8449
- ];
8559
+ const rows = buildRuntimeComparisonRows({
8560
+ liveStrategySummaries,
8561
+ runtimeSummaries,
8562
+ parityRows,
8563
+ backtestEntries: comparableBacktestEntries
8450
8564
  });
8451
- console.log("");
8452
- console.log(
8453
- `SIGNALS REPLAY VS RUNTIME BY STRATEGY (connector=${connectorName}, tolerance=${formatReplayRuntimeCompareTolerance()})`
8454
- );
8455
- console.log(createTable(REPLAY_RUNTIME_COMPARISON_HEADERS, colorizedRows));
8456
- const runtimeOnlyDrilldownSummary = formatDrilldownSummary(
8457
- details.mismatchDrilldown?.summary.runtimeOnly ?? {}
8458
- );
8459
- const backtestOnlyDrilldownSummary = formatDrilldownSummary(
8460
- details.mismatchDrilldown?.summary.backtestOnly ?? {}
8565
+ writeReplayComparisonReport(
8566
+ buildReplayComparisonReport({
8567
+ mode: "runtime",
8568
+ connectorName,
8569
+ rows,
8570
+ details
8571
+ })
8461
8572
  );
8462
- if (runtimeOnlyDrilldownSummary || backtestOnlyDrilldownSummary) {
8463
- console.log(
8464
- import_chalk11.default.gray(
8465
- `Mismatch drilldown: runtimeOnly=[${runtimeOnlyDrilldownSummary || "none"}], backtestOnly=[${backtestOnlyDrilldownSummary || "none"}]`
8466
- )
8467
- );
8468
- }
8469
- console.log("");
8470
8573
  return {
8471
8574
  mode: "runtime",
8472
8575
  syncedTradesCount: syncedRuntimeTrades.length,
@@ -8917,7 +9020,7 @@ var prepareReplayBinanceMarketContext = async (preparedRun) => {
8917
9020
  aiEnabled: preparedRun.aiEnabled,
8918
9021
  mlEnabled: preparedRun.mlEnabled,
8919
9022
  strategyNames: preparedRun.strategyNames,
8920
- log: (message) => console.log(import_chalk12.default.gray(message))
9023
+ log: (message) => console.log(import_chalk13.default.gray(message))
8921
9024
  });
8922
9025
  };
8923
9026
  var finishReplay = async ({
@@ -8970,8 +9073,8 @@ var finishReplay = async ({
8970
9073
  strategySnapshot: replayStrategySnapshot,
8971
9074
  runtimeComparison: replayRuntimeComparison
8972
9075
  });
8973
- console.log(import_chalk12.default.green(`Replay report: ${outputReport.markdownPath}`));
8974
- console.log(import_chalk12.default.green(`Replay report JSON: ${outputReport.jsonPath}`));
9076
+ console.log(import_chalk13.default.green(`Replay report: ${outputReport.markdownPath}`));
9077
+ console.log(import_chalk13.default.green(`Replay report JSON: ${outputReport.jsonPath}`));
8975
9078
  await (0, import_redis6.setData)(
8976
9079
  replayKey,
8977
9080
  {
@@ -9124,9 +9227,9 @@ var replayBacktest = async () => {
9124
9227
  ),
9125
9228
  strategyNames: replayStrategies.map(({ strategyName }) => strategyName)
9126
9229
  });
9127
- console.log(import_chalk12.default.yellow(`tickers: ${preparedRun.tickers.length}`));
9230
+ console.log(import_chalk13.default.yellow(`tickers: ${preparedRun.tickers.length}`));
9128
9231
  console.log(
9129
- import_chalk12.default.gray(
9232
+ import_chalk13.default.gray(
9130
9233
  `mode: replay (${replayStrategies.map(({ strategyName }) => strategyName).join(", ")})`
9131
9234
  )
9132
9235
  );