@tradejs/cli 3.1.19 → 3.1.20

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