backtest-kit 1.4.9 → 1.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.cjs CHANGED
@@ -3128,13 +3128,12 @@ class StrategyConnectionService {
3128
3128
  * @param strategyName - Name of strategy to stop
3129
3129
  * @returns Promise that resolves when stop flag is set
3130
3130
  */
3131
- this.stop = async (symbol, strategyName) => {
3131
+ this.stop = async (ctx) => {
3132
3132
  this.loggerService.log("strategyConnectionService stop", {
3133
- symbol,
3134
- strategyName,
3133
+ ctx
3135
3134
  });
3136
- const strategy = this.getStrategy(symbol, strategyName);
3137
- await strategy.stop(symbol, strategyName);
3135
+ const strategy = this.getStrategy(ctx.symbol, ctx.strategyName);
3136
+ await strategy.stop(ctx.symbol, ctx.strategyName);
3138
3137
  };
3139
3138
  /**
3140
3139
  * Clears the memoized ClientStrategy instance from cache.
@@ -4065,13 +4064,12 @@ class StrategyGlobalService {
4065
4064
  * @param strategyName - Name of strategy to stop
4066
4065
  * @returns Promise that resolves when stop flag is set
4067
4066
  */
4068
- this.stop = async (symbol, strategyName) => {
4067
+ this.stop = async (ctx) => {
4069
4068
  this.loggerService.log("strategyGlobalService stop", {
4070
- symbol,
4071
- strategyName,
4069
+ ctx,
4072
4070
  });
4073
- await this.validate(symbol, strategyName);
4074
- return await this.strategyConnectionService.stop(symbol, strategyName);
4071
+ await this.validate(ctx.symbol, ctx.strategyName);
4072
+ return await this.strategyConnectionService.stop(ctx);
4075
4073
  };
4076
4074
  /**
4077
4075
  * Clears the memoized ClientStrategy instance from cache.
@@ -5084,7 +5082,7 @@ class LiveLogicPrivateService {
5084
5082
  await functoolsKit.sleep(TICK_TTL);
5085
5083
  continue;
5086
5084
  }
5087
- // Yield opened, closed, cancelled results
5085
+ // Yield opened, closed results
5088
5086
  yield result;
5089
5087
  await functoolsKit.sleep(TICK_TTL);
5090
5088
  }
@@ -7264,6 +7262,118 @@ function formatMetric(value) {
7264
7262
  }
7265
7263
  return value.toFixed(2);
7266
7264
  }
7265
+ /**
7266
+ * Creates strategy comparison columns based on metric name.
7267
+ * Dynamically builds column configuration with metric-specific header.
7268
+ *
7269
+ * @param metric - Metric being optimized
7270
+ * @returns Array of column configurations for strategy comparison table
7271
+ */
7272
+ function createStrategyColumns(metric) {
7273
+ return [
7274
+ {
7275
+ key: "rank",
7276
+ label: "Rank",
7277
+ format: (data, index) => `${index + 1}`,
7278
+ },
7279
+ {
7280
+ key: "strategy",
7281
+ label: "Strategy",
7282
+ format: (data) => data.strategyName,
7283
+ },
7284
+ {
7285
+ key: "metric",
7286
+ label: metric,
7287
+ format: (data) => formatMetric(data.metricValue),
7288
+ },
7289
+ {
7290
+ key: "totalSignals",
7291
+ label: "Total Signals",
7292
+ format: (data) => `${data.stats.totalSignals}`,
7293
+ },
7294
+ {
7295
+ key: "winRate",
7296
+ label: "Win Rate",
7297
+ format: (data) => data.stats.winRate !== null
7298
+ ? `${data.stats.winRate.toFixed(2)}%`
7299
+ : "N/A",
7300
+ },
7301
+ {
7302
+ key: "avgPnl",
7303
+ label: "Avg PNL",
7304
+ format: (data) => data.stats.avgPnl !== null
7305
+ ? `${data.stats.avgPnl > 0 ? "+" : ""}${data.stats.avgPnl.toFixed(2)}%`
7306
+ : "N/A",
7307
+ },
7308
+ {
7309
+ key: "totalPnl",
7310
+ label: "Total PNL",
7311
+ format: (data) => data.stats.totalPnl !== null
7312
+ ? `${data.stats.totalPnl > 0 ? "+" : ""}${data.stats.totalPnl.toFixed(2)}%`
7313
+ : "N/A",
7314
+ },
7315
+ {
7316
+ key: "sharpeRatio",
7317
+ label: "Sharpe Ratio",
7318
+ format: (data) => data.stats.sharpeRatio !== null
7319
+ ? `${data.stats.sharpeRatio.toFixed(3)}`
7320
+ : "N/A",
7321
+ },
7322
+ {
7323
+ key: "stdDev",
7324
+ label: "Std Dev",
7325
+ format: (data) => data.stats.stdDev !== null
7326
+ ? `${data.stats.stdDev.toFixed(3)}%`
7327
+ : "N/A",
7328
+ },
7329
+ ];
7330
+ }
7331
+ /**
7332
+ * Column configuration for PNL table.
7333
+ * Defines all columns for displaying closed signals across strategies.
7334
+ */
7335
+ const pnlColumns = [
7336
+ {
7337
+ key: "strategy",
7338
+ label: "Strategy",
7339
+ format: (data) => data.strategyName,
7340
+ },
7341
+ {
7342
+ key: "signalId",
7343
+ label: "Signal ID",
7344
+ format: (data) => data.signalId,
7345
+ },
7346
+ {
7347
+ key: "symbol",
7348
+ label: "Symbol",
7349
+ format: (data) => data.symbol,
7350
+ },
7351
+ {
7352
+ key: "position",
7353
+ label: "Position",
7354
+ format: (data) => data.position.toUpperCase(),
7355
+ },
7356
+ {
7357
+ key: "pnl",
7358
+ label: "PNL (net)",
7359
+ format: (data) => `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`,
7360
+ },
7361
+ {
7362
+ key: "closeReason",
7363
+ label: "Close Reason",
7364
+ format: (data) => data.closeReason,
7365
+ },
7366
+ {
7367
+ key: "openTime",
7368
+ label: "Open Time",
7369
+ format: (data) => new Date(data.openTime).toISOString(),
7370
+ },
7371
+ {
7372
+ key: "closeTime",
7373
+ label: "Close Time",
7374
+ format: (data) => new Date(data.closeTime).toISOString(),
7375
+ },
7376
+ ];
7267
7377
  /**
7268
7378
  * Storage class for accumulating walker results.
7269
7379
  * Maintains a list of all strategy results and provides methods to generate reports.
@@ -7276,9 +7386,12 @@ let ReportStorage$1 = class ReportStorage {
7276
7386
  this._bestStats = null;
7277
7387
  this._bestMetric = null;
7278
7388
  this._bestStrategy = null;
7389
+ /** All strategy results for comparison table */
7390
+ this._strategyResults = [];
7279
7391
  }
7280
7392
  /**
7281
7393
  * Adds a strategy result to the storage.
7394
+ * Updates best strategy tracking and accumulates result for comparison table.
7282
7395
  *
7283
7396
  * @param data - Walker contract with strategy result
7284
7397
  */
@@ -7292,6 +7405,12 @@ let ReportStorage$1 = class ReportStorage {
7292
7405
  if (data.strategyName === data.bestStrategy) {
7293
7406
  this._bestStats = data.stats;
7294
7407
  }
7408
+ // Add strategy result to comparison list
7409
+ this._strategyResults.push({
7410
+ strategyName: data.strategyName,
7411
+ stats: data.stats,
7412
+ metricValue: data.metricValue,
7413
+ });
7295
7414
  }
7296
7415
  /**
7297
7416
  * Calculates walker results from strategy results.
@@ -7316,10 +7435,79 @@ let ReportStorage$1 = class ReportStorage {
7316
7435
  bestStrategy: this._bestStrategy,
7317
7436
  bestMetric: this._bestMetric,
7318
7437
  bestStats: this._bestStats,
7438
+ strategyResults: this._strategyResults,
7319
7439
  };
7320
7440
  }
7441
+ /**
7442
+ * Generates comparison table for top N strategies (View).
7443
+ * Sorts strategies by metric value and formats as markdown table.
7444
+ *
7445
+ * @param metric - Metric being optimized
7446
+ * @param topN - Number of top strategies to include (default: 10)
7447
+ * @returns Markdown formatted comparison table
7448
+ */
7449
+ getComparisonTable(metric, topN = 10) {
7450
+ if (this._strategyResults.length === 0) {
7451
+ return "No strategy results available.";
7452
+ }
7453
+ // Sort strategies by metric value (descending)
7454
+ const sortedResults = [...this._strategyResults].sort((a, b) => {
7455
+ const aValue = a.metricValue ?? -Infinity;
7456
+ const bValue = b.metricValue ?? -Infinity;
7457
+ return bValue - aValue;
7458
+ });
7459
+ // Take top N strategies
7460
+ const topStrategies = sortedResults.slice(0, topN);
7461
+ // Get columns configuration
7462
+ const columns = createStrategyColumns(metric);
7463
+ // Build table header
7464
+ const header = columns.map((col) => col.label);
7465
+ const separator = columns.map(() => "---");
7466
+ // Build table rows
7467
+ const rows = topStrategies.map((result, index) => columns.map((col) => col.format(result, index)));
7468
+ const tableData = [header, separator, ...rows];
7469
+ return functoolsKit.str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
7470
+ }
7471
+ /**
7472
+ * Generates PNL table showing all closed signals across all strategies (View).
7473
+ * Collects all signals from all strategies and formats as markdown table.
7474
+ *
7475
+ * @returns Markdown formatted PNL table
7476
+ */
7477
+ getPnlTable() {
7478
+ if (this._strategyResults.length === 0) {
7479
+ return "No strategy results available.";
7480
+ }
7481
+ // Collect all closed signals from all strategies
7482
+ const allSignals = [];
7483
+ for (const result of this._strategyResults) {
7484
+ for (const signal of result.stats.signalList) {
7485
+ allSignals.push({
7486
+ strategyName: result.strategyName,
7487
+ signalId: signal.signal.id,
7488
+ symbol: signal.signal.symbol,
7489
+ position: signal.signal.position,
7490
+ pnl: signal.pnl.pnlPercentage,
7491
+ closeReason: signal.closeReason,
7492
+ openTime: signal.signal.pendingAt,
7493
+ closeTime: signal.closeTimestamp,
7494
+ });
7495
+ }
7496
+ }
7497
+ if (allSignals.length === 0) {
7498
+ return "No closed signals available.";
7499
+ }
7500
+ // Build table header
7501
+ const header = pnlColumns.map((col) => col.label);
7502
+ const separator = pnlColumns.map(() => "---");
7503
+ // Build table rows
7504
+ const rows = allSignals.map((signal) => pnlColumns.map((col) => col.format(signal)));
7505
+ const tableData = [header, separator, ...rows];
7506
+ return functoolsKit.str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
7507
+ }
7321
7508
  /**
7322
7509
  * Generates markdown report with all strategy results (View).
7510
+ * Includes best strategy summary, comparison table, and PNL table.
7323
7511
  *
7324
7512
  * @param symbol - Trading symbol
7325
7513
  * @param metric - Metric being optimized
@@ -7328,7 +7516,9 @@ let ReportStorage$1 = class ReportStorage {
7328
7516
  */
7329
7517
  async getReport(symbol, metric, context) {
7330
7518
  const results = await this.getData(symbol, metric, context);
7331
- return functoolsKit.str.newline(`# Walker Comparison Report: ${results.walkerName}`, "", `**Symbol:** ${results.symbol}`, `**Exchange:** ${results.exchangeName}`, `**Frame:** ${results.frameName}`, `**Optimization Metric:** ${results.metric}`, `**Strategies Tested:** ${results.totalStrategies}`, "", `## Best Strategy: ${results.bestStrategy}`, "", `**Best ${results.metric}:** ${formatMetric(results.bestMetric)}`, "**Note:** Higher values are better for all metrics except Standard Deviation (lower is better).");
7519
+ // Get total signals for best strategy
7520
+ const bestStrategySignals = results.bestStats?.totalSignals ?? 0;
7521
+ return functoolsKit.str.newline(`# Walker Comparison Report: ${results.walkerName}`, "", `**Symbol:** ${results.symbol}`, `**Exchange:** ${results.exchangeName}`, `**Frame:** ${results.frameName}`, `**Optimization Metric:** ${results.metric}`, `**Strategies Tested:** ${results.totalStrategies}`, "", `## Best Strategy: ${results.bestStrategy}`, "", `**Best ${results.metric}:** ${formatMetric(results.bestMetric)}`, `**Total Signals:** ${bestStrategySignals}`, "", "## Top Strategies Comparison", "", this.getComparisonTable(metric, 10), "", "## All Signals (PNL Table)", "", this.getPnlTable(), "", "**Note:** Higher values are better for all metrics except Standard Deviation (lower is better).");
7332
7522
  }
7333
7523
  /**
7334
7524
  * Saves walker report to disk.
@@ -12606,7 +12796,7 @@ class BacktestUtils {
12606
12796
  };
12607
12797
  task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
12608
12798
  return () => {
12609
- backtest$1.strategyGlobalService.stop(symbol, context.strategyName);
12799
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
12610
12800
  backtest$1.strategyGlobalService
12611
12801
  .getPendingSignal(symbol, context.strategyName)
12612
12802
  .then(async (pendingSignal) => {
@@ -12819,7 +13009,7 @@ class LiveUtils {
12819
13009
  };
12820
13010
  task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
12821
13011
  return () => {
12822
- backtest$1.strategyGlobalService.stop(symbol, context.strategyName);
13012
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
12823
13013
  backtest$1.strategyGlobalService
12824
13014
  .getPendingSignal(symbol, context.strategyName)
12825
13015
  .then(async (pendingSignal) => {
@@ -13251,7 +13441,7 @@ class WalkerUtils {
13251
13441
  task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
13252
13442
  return () => {
13253
13443
  for (const strategyName of walkerSchema.strategies) {
13254
- backtest$1.strategyGlobalService.stop(symbol, strategyName);
13444
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName });
13255
13445
  walkerStopSubject.next({ symbol, strategyName });
13256
13446
  }
13257
13447
  if (!isDone) {
package/build/index.mjs CHANGED
@@ -3126,13 +3126,12 @@ class StrategyConnectionService {
3126
3126
  * @param strategyName - Name of strategy to stop
3127
3127
  * @returns Promise that resolves when stop flag is set
3128
3128
  */
3129
- this.stop = async (symbol, strategyName) => {
3129
+ this.stop = async (ctx) => {
3130
3130
  this.loggerService.log("strategyConnectionService stop", {
3131
- symbol,
3132
- strategyName,
3131
+ ctx
3133
3132
  });
3134
- const strategy = this.getStrategy(symbol, strategyName);
3135
- await strategy.stop(symbol, strategyName);
3133
+ const strategy = this.getStrategy(ctx.symbol, ctx.strategyName);
3134
+ await strategy.stop(ctx.symbol, ctx.strategyName);
3136
3135
  };
3137
3136
  /**
3138
3137
  * Clears the memoized ClientStrategy instance from cache.
@@ -4063,13 +4062,12 @@ class StrategyGlobalService {
4063
4062
  * @param strategyName - Name of strategy to stop
4064
4063
  * @returns Promise that resolves when stop flag is set
4065
4064
  */
4066
- this.stop = async (symbol, strategyName) => {
4065
+ this.stop = async (ctx) => {
4067
4066
  this.loggerService.log("strategyGlobalService stop", {
4068
- symbol,
4069
- strategyName,
4067
+ ctx,
4070
4068
  });
4071
- await this.validate(symbol, strategyName);
4072
- return await this.strategyConnectionService.stop(symbol, strategyName);
4069
+ await this.validate(ctx.symbol, ctx.strategyName);
4070
+ return await this.strategyConnectionService.stop(ctx);
4073
4071
  };
4074
4072
  /**
4075
4073
  * Clears the memoized ClientStrategy instance from cache.
@@ -5082,7 +5080,7 @@ class LiveLogicPrivateService {
5082
5080
  await sleep(TICK_TTL);
5083
5081
  continue;
5084
5082
  }
5085
- // Yield opened, closed, cancelled results
5083
+ // Yield opened, closed results
5086
5084
  yield result;
5087
5085
  await sleep(TICK_TTL);
5088
5086
  }
@@ -7262,6 +7260,118 @@ function formatMetric(value) {
7262
7260
  }
7263
7261
  return value.toFixed(2);
7264
7262
  }
7263
+ /**
7264
+ * Creates strategy comparison columns based on metric name.
7265
+ * Dynamically builds column configuration with metric-specific header.
7266
+ *
7267
+ * @param metric - Metric being optimized
7268
+ * @returns Array of column configurations for strategy comparison table
7269
+ */
7270
+ function createStrategyColumns(metric) {
7271
+ return [
7272
+ {
7273
+ key: "rank",
7274
+ label: "Rank",
7275
+ format: (data, index) => `${index + 1}`,
7276
+ },
7277
+ {
7278
+ key: "strategy",
7279
+ label: "Strategy",
7280
+ format: (data) => data.strategyName,
7281
+ },
7282
+ {
7283
+ key: "metric",
7284
+ label: metric,
7285
+ format: (data) => formatMetric(data.metricValue),
7286
+ },
7287
+ {
7288
+ key: "totalSignals",
7289
+ label: "Total Signals",
7290
+ format: (data) => `${data.stats.totalSignals}`,
7291
+ },
7292
+ {
7293
+ key: "winRate",
7294
+ label: "Win Rate",
7295
+ format: (data) => data.stats.winRate !== null
7296
+ ? `${data.stats.winRate.toFixed(2)}%`
7297
+ : "N/A",
7298
+ },
7299
+ {
7300
+ key: "avgPnl",
7301
+ label: "Avg PNL",
7302
+ format: (data) => data.stats.avgPnl !== null
7303
+ ? `${data.stats.avgPnl > 0 ? "+" : ""}${data.stats.avgPnl.toFixed(2)}%`
7304
+ : "N/A",
7305
+ },
7306
+ {
7307
+ key: "totalPnl",
7308
+ label: "Total PNL",
7309
+ format: (data) => data.stats.totalPnl !== null
7310
+ ? `${data.stats.totalPnl > 0 ? "+" : ""}${data.stats.totalPnl.toFixed(2)}%`
7311
+ : "N/A",
7312
+ },
7313
+ {
7314
+ key: "sharpeRatio",
7315
+ label: "Sharpe Ratio",
7316
+ format: (data) => data.stats.sharpeRatio !== null
7317
+ ? `${data.stats.sharpeRatio.toFixed(3)}`
7318
+ : "N/A",
7319
+ },
7320
+ {
7321
+ key: "stdDev",
7322
+ label: "Std Dev",
7323
+ format: (data) => data.stats.stdDev !== null
7324
+ ? `${data.stats.stdDev.toFixed(3)}%`
7325
+ : "N/A",
7326
+ },
7327
+ ];
7328
+ }
7329
+ /**
7330
+ * Column configuration for PNL table.
7331
+ * Defines all columns for displaying closed signals across strategies.
7332
+ */
7333
+ const pnlColumns = [
7334
+ {
7335
+ key: "strategy",
7336
+ label: "Strategy",
7337
+ format: (data) => data.strategyName,
7338
+ },
7339
+ {
7340
+ key: "signalId",
7341
+ label: "Signal ID",
7342
+ format: (data) => data.signalId,
7343
+ },
7344
+ {
7345
+ key: "symbol",
7346
+ label: "Symbol",
7347
+ format: (data) => data.symbol,
7348
+ },
7349
+ {
7350
+ key: "position",
7351
+ label: "Position",
7352
+ format: (data) => data.position.toUpperCase(),
7353
+ },
7354
+ {
7355
+ key: "pnl",
7356
+ label: "PNL (net)",
7357
+ format: (data) => `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`,
7358
+ },
7359
+ {
7360
+ key: "closeReason",
7361
+ label: "Close Reason",
7362
+ format: (data) => data.closeReason,
7363
+ },
7364
+ {
7365
+ key: "openTime",
7366
+ label: "Open Time",
7367
+ format: (data) => new Date(data.openTime).toISOString(),
7368
+ },
7369
+ {
7370
+ key: "closeTime",
7371
+ label: "Close Time",
7372
+ format: (data) => new Date(data.closeTime).toISOString(),
7373
+ },
7374
+ ];
7265
7375
  /**
7266
7376
  * Storage class for accumulating walker results.
7267
7377
  * Maintains a list of all strategy results and provides methods to generate reports.
@@ -7274,9 +7384,12 @@ let ReportStorage$1 = class ReportStorage {
7274
7384
  this._bestStats = null;
7275
7385
  this._bestMetric = null;
7276
7386
  this._bestStrategy = null;
7387
+ /** All strategy results for comparison table */
7388
+ this._strategyResults = [];
7277
7389
  }
7278
7390
  /**
7279
7391
  * Adds a strategy result to the storage.
7392
+ * Updates best strategy tracking and accumulates result for comparison table.
7280
7393
  *
7281
7394
  * @param data - Walker contract with strategy result
7282
7395
  */
@@ -7290,6 +7403,12 @@ let ReportStorage$1 = class ReportStorage {
7290
7403
  if (data.strategyName === data.bestStrategy) {
7291
7404
  this._bestStats = data.stats;
7292
7405
  }
7406
+ // Add strategy result to comparison list
7407
+ this._strategyResults.push({
7408
+ strategyName: data.strategyName,
7409
+ stats: data.stats,
7410
+ metricValue: data.metricValue,
7411
+ });
7293
7412
  }
7294
7413
  /**
7295
7414
  * Calculates walker results from strategy results.
@@ -7314,10 +7433,79 @@ let ReportStorage$1 = class ReportStorage {
7314
7433
  bestStrategy: this._bestStrategy,
7315
7434
  bestMetric: this._bestMetric,
7316
7435
  bestStats: this._bestStats,
7436
+ strategyResults: this._strategyResults,
7317
7437
  };
7318
7438
  }
7439
+ /**
7440
+ * Generates comparison table for top N strategies (View).
7441
+ * Sorts strategies by metric value and formats as markdown table.
7442
+ *
7443
+ * @param metric - Metric being optimized
7444
+ * @param topN - Number of top strategies to include (default: 10)
7445
+ * @returns Markdown formatted comparison table
7446
+ */
7447
+ getComparisonTable(metric, topN = 10) {
7448
+ if (this._strategyResults.length === 0) {
7449
+ return "No strategy results available.";
7450
+ }
7451
+ // Sort strategies by metric value (descending)
7452
+ const sortedResults = [...this._strategyResults].sort((a, b) => {
7453
+ const aValue = a.metricValue ?? -Infinity;
7454
+ const bValue = b.metricValue ?? -Infinity;
7455
+ return bValue - aValue;
7456
+ });
7457
+ // Take top N strategies
7458
+ const topStrategies = sortedResults.slice(0, topN);
7459
+ // Get columns configuration
7460
+ const columns = createStrategyColumns(metric);
7461
+ // Build table header
7462
+ const header = columns.map((col) => col.label);
7463
+ const separator = columns.map(() => "---");
7464
+ // Build table rows
7465
+ const rows = topStrategies.map((result, index) => columns.map((col) => col.format(result, index)));
7466
+ const tableData = [header, separator, ...rows];
7467
+ return str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
7468
+ }
7469
+ /**
7470
+ * Generates PNL table showing all closed signals across all strategies (View).
7471
+ * Collects all signals from all strategies and formats as markdown table.
7472
+ *
7473
+ * @returns Markdown formatted PNL table
7474
+ */
7475
+ getPnlTable() {
7476
+ if (this._strategyResults.length === 0) {
7477
+ return "No strategy results available.";
7478
+ }
7479
+ // Collect all closed signals from all strategies
7480
+ const allSignals = [];
7481
+ for (const result of this._strategyResults) {
7482
+ for (const signal of result.stats.signalList) {
7483
+ allSignals.push({
7484
+ strategyName: result.strategyName,
7485
+ signalId: signal.signal.id,
7486
+ symbol: signal.signal.symbol,
7487
+ position: signal.signal.position,
7488
+ pnl: signal.pnl.pnlPercentage,
7489
+ closeReason: signal.closeReason,
7490
+ openTime: signal.signal.pendingAt,
7491
+ closeTime: signal.closeTimestamp,
7492
+ });
7493
+ }
7494
+ }
7495
+ if (allSignals.length === 0) {
7496
+ return "No closed signals available.";
7497
+ }
7498
+ // Build table header
7499
+ const header = pnlColumns.map((col) => col.label);
7500
+ const separator = pnlColumns.map(() => "---");
7501
+ // Build table rows
7502
+ const rows = allSignals.map((signal) => pnlColumns.map((col) => col.format(signal)));
7503
+ const tableData = [header, separator, ...rows];
7504
+ return str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
7505
+ }
7319
7506
  /**
7320
7507
  * Generates markdown report with all strategy results (View).
7508
+ * Includes best strategy summary, comparison table, and PNL table.
7321
7509
  *
7322
7510
  * @param symbol - Trading symbol
7323
7511
  * @param metric - Metric being optimized
@@ -7326,7 +7514,9 @@ let ReportStorage$1 = class ReportStorage {
7326
7514
  */
7327
7515
  async getReport(symbol, metric, context) {
7328
7516
  const results = await this.getData(symbol, metric, context);
7329
- return str.newline(`# Walker Comparison Report: ${results.walkerName}`, "", `**Symbol:** ${results.symbol}`, `**Exchange:** ${results.exchangeName}`, `**Frame:** ${results.frameName}`, `**Optimization Metric:** ${results.metric}`, `**Strategies Tested:** ${results.totalStrategies}`, "", `## Best Strategy: ${results.bestStrategy}`, "", `**Best ${results.metric}:** ${formatMetric(results.bestMetric)}`, "**Note:** Higher values are better for all metrics except Standard Deviation (lower is better).");
7517
+ // Get total signals for best strategy
7518
+ const bestStrategySignals = results.bestStats?.totalSignals ?? 0;
7519
+ return str.newline(`# Walker Comparison Report: ${results.walkerName}`, "", `**Symbol:** ${results.symbol}`, `**Exchange:** ${results.exchangeName}`, `**Frame:** ${results.frameName}`, `**Optimization Metric:** ${results.metric}`, `**Strategies Tested:** ${results.totalStrategies}`, "", `## Best Strategy: ${results.bestStrategy}`, "", `**Best ${results.metric}:** ${formatMetric(results.bestMetric)}`, `**Total Signals:** ${bestStrategySignals}`, "", "## Top Strategies Comparison", "", this.getComparisonTable(metric, 10), "", "## All Signals (PNL Table)", "", this.getPnlTable(), "", "**Note:** Higher values are better for all metrics except Standard Deviation (lower is better).");
7330
7520
  }
7331
7521
  /**
7332
7522
  * Saves walker report to disk.
@@ -12604,7 +12794,7 @@ class BacktestUtils {
12604
12794
  };
12605
12795
  task().catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
12606
12796
  return () => {
12607
- backtest$1.strategyGlobalService.stop(symbol, context.strategyName);
12797
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
12608
12798
  backtest$1.strategyGlobalService
12609
12799
  .getPendingSignal(symbol, context.strategyName)
12610
12800
  .then(async (pendingSignal) => {
@@ -12817,7 +13007,7 @@ class LiveUtils {
12817
13007
  };
12818
13008
  task().catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
12819
13009
  return () => {
12820
- backtest$1.strategyGlobalService.stop(symbol, context.strategyName);
13010
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
12821
13011
  backtest$1.strategyGlobalService
12822
13012
  .getPendingSignal(symbol, context.strategyName)
12823
13013
  .then(async (pendingSignal) => {
@@ -13249,7 +13439,7 @@ class WalkerUtils {
13249
13439
  task().catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
13250
13440
  return () => {
13251
13441
  for (const strategyName of walkerSchema.strategies) {
13252
- backtest$1.strategyGlobalService.stop(symbol, strategyName);
13442
+ backtest$1.strategyGlobalService.stop({ symbol, strategyName });
13253
13443
  walkerStopSubject.next({ symbol, strategyName });
13254
13444
  }
13255
13445
  if (!isDone) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "1.4.9",
3
+ "version": "1.4.11",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
package/types.d.ts CHANGED
@@ -4258,8 +4258,24 @@ declare class PerformanceMarkdownService {
4258
4258
  * Alias for walker statistics result interface.
4259
4259
  * Used for clarity in markdown service context.
4260
4260
  *
4261
+ * Extends IWalkerResults with additional strategy comparison data.
4261
4262
  */
4262
- type WalkerStatistics = IWalkerResults;
4263
+ interface WalkerStatistics extends IWalkerResults {
4264
+ /** Array of all strategy results for comparison and analysis */
4265
+ strategyResults: IStrategyResult[];
4266
+ }
4267
+ /**
4268
+ * Strategy result entry for comparison table.
4269
+ * Contains strategy name, full statistics, and metric value for ranking.
4270
+ */
4271
+ interface IStrategyResult {
4272
+ /** Strategy name */
4273
+ strategyName: StrategyName;
4274
+ /** Complete backtest statistics for this strategy */
4275
+ stats: BacktestStatistics;
4276
+ /** Value of the optimization metric (null if invalid) */
4277
+ metricValue: number | null;
4278
+ }
4263
4279
  /**
4264
4280
  * Service for generating and saving walker markdown reports.
4265
4281
  *
@@ -5063,7 +5079,7 @@ declare class BacktestUtils {
5063
5079
  strategyName: string;
5064
5080
  exchangeName: string;
5065
5081
  frameName: string;
5066
- }) => AsyncGenerator<any, void, unknown>;
5082
+ }) => AsyncGenerator<IStrategyBacktestResult, void, unknown>;
5067
5083
  /**
5068
5084
  * Runs backtest in background without yielding results.
5069
5085
  *
@@ -5198,7 +5214,7 @@ declare class LiveUtils {
5198
5214
  run: (symbol: string, context: {
5199
5215
  strategyName: string;
5200
5216
  exchangeName: string;
5201
- }) => AsyncGenerator<any, void, unknown>;
5217
+ }) => AsyncGenerator<IStrategyTickResultOpened | IStrategyTickResultClosed, void, unknown>;
5202
5218
  /**
5203
5219
  * Runs live trading in background without yielding results.
5204
5220
  *
@@ -6520,7 +6536,10 @@ declare class StrategyConnectionService {
6520
6536
  * @param strategyName - Name of strategy to stop
6521
6537
  * @returns Promise that resolves when stop flag is set
6522
6538
  */
6523
- stop: (symbol: string, strategyName: StrategyName) => Promise<void>;
6539
+ stop: (ctx: {
6540
+ symbol: string;
6541
+ strategyName: StrategyName;
6542
+ }) => Promise<void>;
6524
6543
  /**
6525
6544
  * Clears the memoized ClientStrategy instance from cache.
6526
6545
  *
@@ -6988,7 +7007,10 @@ declare class StrategyGlobalService {
6988
7007
  * @param strategyName - Name of strategy to stop
6989
7008
  * @returns Promise that resolves when stop flag is set
6990
7009
  */
6991
- stop: (symbol: string, strategyName: StrategyName) => Promise<void>;
7010
+ stop: (ctx: {
7011
+ symbol: string;
7012
+ strategyName: StrategyName;
7013
+ }) => Promise<void>;
6992
7014
  /**
6993
7015
  * Clears the memoized ClientStrategy instance from cache.
6994
7016
  *
@@ -7451,7 +7473,7 @@ declare class BacktestLogicPrivateService {
7451
7473
  * }
7452
7474
  * ```
7453
7475
  */
7454
- run(symbol: string): AsyncGenerator<any, void, unknown>;
7476
+ run(symbol: string): AsyncGenerator<IStrategyBacktestResult, void, unknown>;
7455
7477
  }
7456
7478
 
7457
7479
  /**
@@ -7496,7 +7518,7 @@ declare class LiveLogicPrivateService {
7496
7518
  * }
7497
7519
  * ```
7498
7520
  */
7499
- run(symbol: string): AsyncGenerator<any, void, unknown>;
7521
+ run(symbol: string): AsyncGenerator<IStrategyTickResultOpened | IStrategyTickResultClosed, void, unknown>;
7500
7522
  }
7501
7523
 
7502
7524
  /**
@@ -7590,7 +7612,7 @@ declare class BacktestLogicPublicService {
7590
7612
  strategyName: string;
7591
7613
  exchangeName: string;
7592
7614
  frameName: string;
7593
- }) => AsyncGenerator<any, void, unknown>;
7615
+ }) => AsyncGenerator<IStrategyBacktestResult, void, unknown>;
7594
7616
  }
7595
7617
 
7596
7618
  /**
@@ -7641,7 +7663,7 @@ declare class LiveLogicPublicService {
7641
7663
  run: (symbol: string, context: {
7642
7664
  strategyName: string;
7643
7665
  exchangeName: string;
7644
- }) => AsyncGenerator<any, void, unknown>;
7666
+ }) => AsyncGenerator<IStrategyTickResultOpened | IStrategyTickResultClosed, void, unknown>;
7645
7667
  }
7646
7668
 
7647
7669
  /**
@@ -7709,7 +7731,7 @@ declare class LiveCommandService {
7709
7731
  run: (symbol: string, context: {
7710
7732
  strategyName: string;
7711
7733
  exchangeName: string;
7712
- }) => AsyncGenerator<any, void, unknown>;
7734
+ }) => AsyncGenerator<IStrategyTickResultOpened | IStrategyTickResultClosed, void, unknown>;
7713
7735
  }
7714
7736
 
7715
7737
  /**
@@ -7737,7 +7759,7 @@ declare class BacktestCommandService {
7737
7759
  strategyName: string;
7738
7760
  exchangeName: string;
7739
7761
  frameName: string;
7740
- }) => AsyncGenerator<any, void, unknown>;
7762
+ }) => AsyncGenerator<IStrategyBacktestResult, void, unknown>;
7741
7763
  }
7742
7764
 
7743
7765
  /**