backtest-kit 1.1.7 → 1.1.8

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.mjs CHANGED
@@ -69,6 +69,7 @@ const logicPublicServices$1 = {
69
69
  const markdownServices$1 = {
70
70
  backtestMarkdownService: Symbol('backtestMarkdownService'),
71
71
  liveMarkdownService: Symbol('liveMarkdownService'),
72
+ performanceMarkdownService: Symbol('performanceMarkdownService'),
72
73
  };
73
74
  const validationServices$1 = {
74
75
  exchangeValidationService: Symbol('exchangeValidationService'),
@@ -1131,6 +1132,11 @@ const doneEmitter = new Subject();
1131
1132
  * Emits progress updates during backtest execution.
1132
1133
  */
1133
1134
  const progressEmitter = new Subject();
1135
+ /**
1136
+ * Performance emitter for execution metrics.
1137
+ * Emits performance metrics for profiling and bottleneck detection.
1138
+ */
1139
+ const performanceEmitter = new Subject();
1134
1140
 
1135
1141
  const INTERVAL_MINUTES$1 = {
1136
1142
  "1m": 1,
@@ -2425,10 +2431,12 @@ class BacktestLogicPrivateService {
2425
2431
  this.loggerService.log("backtestLogicPrivateService run", {
2426
2432
  symbol,
2427
2433
  });
2434
+ const backtestStartTime = performance.now();
2428
2435
  const timeframes = await this.frameGlobalService.getTimeframe(symbol);
2429
2436
  const totalFrames = timeframes.length;
2430
2437
  let i = 0;
2431
2438
  while (i < timeframes.length) {
2439
+ const timeframeStartTime = performance.now();
2432
2440
  const when = timeframes[i];
2433
2441
  // Emit progress event if context is available
2434
2442
  {
@@ -2444,6 +2452,7 @@ class BacktestLogicPrivateService {
2444
2452
  const result = await this.strategyGlobalService.tick(symbol, when, true);
2445
2453
  // Если сигнал открыт, вызываем backtest
2446
2454
  if (result.action === "opened") {
2455
+ const signalStartTime = performance.now();
2447
2456
  const signal = result.signal;
2448
2457
  this.loggerService.info("backtestLogicPrivateService signal opened", {
2449
2458
  symbol,
@@ -2468,6 +2477,17 @@ class BacktestLogicPrivateService {
2468
2477
  closeTimestamp: backtestResult.closeTimestamp,
2469
2478
  closeReason: backtestResult.closeReason,
2470
2479
  });
2480
+ // Track signal processing duration
2481
+ const signalEndTime = performance.now();
2482
+ await performanceEmitter.next({
2483
+ timestamp: Date.now(),
2484
+ metricType: "backtest_signal",
2485
+ duration: signalEndTime - signalStartTime,
2486
+ strategyName: this.methodContextService.context.strategyName,
2487
+ exchangeName: this.methodContextService.context.exchangeName,
2488
+ symbol,
2489
+ backtest: true,
2490
+ });
2471
2491
  // Пропускаем timeframes до closeTimestamp
2472
2492
  while (i < timeframes.length &&
2473
2493
  timeframes[i].getTime() < backtestResult.closeTimestamp) {
@@ -2475,6 +2495,17 @@ class BacktestLogicPrivateService {
2475
2495
  }
2476
2496
  yield backtestResult;
2477
2497
  }
2498
+ // Track timeframe processing duration
2499
+ const timeframeEndTime = performance.now();
2500
+ await performanceEmitter.next({
2501
+ timestamp: Date.now(),
2502
+ metricType: "backtest_timeframe",
2503
+ duration: timeframeEndTime - timeframeStartTime,
2504
+ strategyName: this.methodContextService.context.strategyName,
2505
+ exchangeName: this.methodContextService.context.exchangeName,
2506
+ symbol,
2507
+ backtest: true,
2508
+ });
2478
2509
  i++;
2479
2510
  }
2480
2511
  // Emit final progress event (100%)
@@ -2488,6 +2519,17 @@ class BacktestLogicPrivateService {
2488
2519
  progress: 1.0,
2489
2520
  });
2490
2521
  }
2522
+ // Track total backtest duration
2523
+ const backtestEndTime = performance.now();
2524
+ await performanceEmitter.next({
2525
+ timestamp: Date.now(),
2526
+ metricType: "backtest_total",
2527
+ duration: backtestEndTime - backtestStartTime,
2528
+ strategyName: this.methodContextService.context.strategyName,
2529
+ exchangeName: this.methodContextService.context.exchangeName,
2530
+ symbol,
2531
+ backtest: true,
2532
+ });
2491
2533
  }
2492
2534
  }
2493
2535
 
@@ -2512,6 +2554,7 @@ class LiveLogicPrivateService {
2512
2554
  constructor() {
2513
2555
  this.loggerService = inject(TYPES.loggerService);
2514
2556
  this.strategyGlobalService = inject(TYPES.strategyGlobalService);
2557
+ this.methodContextService = inject(TYPES.methodContextService);
2515
2558
  }
2516
2559
  /**
2517
2560
  * Runs live trading for a symbol, streaming results as async generator.
@@ -2540,12 +2583,24 @@ class LiveLogicPrivateService {
2540
2583
  symbol,
2541
2584
  });
2542
2585
  while (true) {
2586
+ const tickStartTime = performance.now();
2543
2587
  const when = new Date();
2544
2588
  const result = await this.strategyGlobalService.tick(symbol, when, false);
2545
2589
  this.loggerService.info("liveLogicPrivateService tick result", {
2546
2590
  symbol,
2547
2591
  action: result.action,
2548
2592
  });
2593
+ // Track tick duration
2594
+ const tickEndTime = performance.now();
2595
+ await performanceEmitter.next({
2596
+ timestamp: Date.now(),
2597
+ metricType: "live_tick",
2598
+ duration: tickEndTime - tickStartTime,
2599
+ strategyName: this.methodContextService.context.strategyName,
2600
+ exchangeName: this.methodContextService.context.exchangeName,
2601
+ symbol,
2602
+ backtest: false,
2603
+ });
2549
2604
  if (result.action === "active") {
2550
2605
  await sleep(TICK_TTL);
2551
2606
  continue;
@@ -3222,7 +3277,7 @@ const columns = [
3222
3277
  },
3223
3278
  ];
3224
3279
  /** Maximum number of events to store in live trading reports */
3225
- const MAX_EVENTS = 250;
3280
+ const MAX_EVENTS$1 = 250;
3226
3281
  /**
3227
3282
  * Storage class for accumulating all tick events per strategy.
3228
3283
  * Maintains a chronological list of all events (idle, opened, active, closed).
@@ -3255,7 +3310,7 @@ class ReportStorage {
3255
3310
  }
3256
3311
  {
3257
3312
  this._eventList.push(newEvent);
3258
- if (this._eventList.length > MAX_EVENTS) {
3313
+ if (this._eventList.length > MAX_EVENTS$1) {
3259
3314
  this._eventList.shift();
3260
3315
  }
3261
3316
  }
@@ -3279,7 +3334,7 @@ class ReportStorage {
3279
3334
  stopLoss: data.signal.priceStopLoss,
3280
3335
  });
3281
3336
  // Trim queue if exceeded MAX_EVENTS
3282
- if (this._eventList.length > MAX_EVENTS) {
3337
+ if (this._eventList.length > MAX_EVENTS$1) {
3283
3338
  this._eventList.shift();
3284
3339
  }
3285
3340
  }
@@ -3311,7 +3366,7 @@ class ReportStorage {
3311
3366
  else {
3312
3367
  this._eventList.push(newEvent);
3313
3368
  // Trim queue if exceeded MAX_EVENTS
3314
- if (this._eventList.length > MAX_EVENTS) {
3369
+ if (this._eventList.length > MAX_EVENTS$1) {
3315
3370
  this._eventList.shift();
3316
3371
  }
3317
3372
  }
@@ -3349,7 +3404,7 @@ class ReportStorage {
3349
3404
  else {
3350
3405
  this._eventList.push(newEvent);
3351
3406
  // Trim queue if exceeded MAX_EVENTS
3352
- if (this._eventList.length > MAX_EVENTS) {
3407
+ if (this._eventList.length > MAX_EVENTS$1) {
3353
3408
  this._eventList.shift();
3354
3409
  }
3355
3410
  }
@@ -3663,6 +3718,297 @@ class LiveMarkdownService {
3663
3718
  }
3664
3719
  }
3665
3720
 
3721
+ /**
3722
+ * Calculates percentile value from sorted array.
3723
+ */
3724
+ function percentile(sortedArray, p) {
3725
+ if (sortedArray.length === 0)
3726
+ return 0;
3727
+ const index = Math.ceil((sortedArray.length * p) / 100) - 1;
3728
+ return sortedArray[Math.max(0, index)];
3729
+ }
3730
+ /** Maximum number of performance events to store per strategy */
3731
+ const MAX_EVENTS = 10000;
3732
+ /**
3733
+ * Storage class for accumulating performance metrics per strategy.
3734
+ * Maintains a list of all performance events and provides aggregated statistics.
3735
+ */
3736
+ class PerformanceStorage {
3737
+ constructor() {
3738
+ /** Internal list of all performance events for this strategy */
3739
+ this._events = [];
3740
+ }
3741
+ /**
3742
+ * Adds a performance event to the storage.
3743
+ *
3744
+ * @param event - Performance event with timing data
3745
+ */
3746
+ addEvent(event) {
3747
+ this._events.push(event);
3748
+ // Trim queue if exceeded MAX_EVENTS (keep most recent)
3749
+ if (this._events.length > MAX_EVENTS) {
3750
+ this._events.shift();
3751
+ }
3752
+ }
3753
+ /**
3754
+ * Calculates aggregated statistics from all performance events.
3755
+ *
3756
+ * @returns Performance statistics with metrics grouped by type
3757
+ */
3758
+ async getData(strategyName) {
3759
+ if (this._events.length === 0) {
3760
+ return {
3761
+ strategyName,
3762
+ totalEvents: 0,
3763
+ totalDuration: 0,
3764
+ metricStats: {},
3765
+ events: [],
3766
+ };
3767
+ }
3768
+ // Group events by metric type
3769
+ const eventsByType = new Map();
3770
+ for (const event of this._events) {
3771
+ if (!eventsByType.has(event.metricType)) {
3772
+ eventsByType.set(event.metricType, []);
3773
+ }
3774
+ eventsByType.get(event.metricType).push(event);
3775
+ }
3776
+ // Calculate statistics for each metric type
3777
+ const metricStats = {};
3778
+ for (const [metricType, events] of eventsByType.entries()) {
3779
+ const durations = events.map((e) => e.duration).sort((a, b) => a - b);
3780
+ const totalDuration = durations.reduce((sum, d) => sum + d, 0);
3781
+ const avgDuration = totalDuration / durations.length;
3782
+ // Calculate standard deviation
3783
+ const variance = durations.reduce((sum, d) => sum + Math.pow(d - avgDuration, 2), 0) /
3784
+ durations.length;
3785
+ const stdDev = Math.sqrt(variance);
3786
+ metricStats[metricType] = {
3787
+ metricType,
3788
+ count: events.length,
3789
+ totalDuration,
3790
+ avgDuration,
3791
+ minDuration: durations[0],
3792
+ maxDuration: durations[durations.length - 1],
3793
+ stdDev,
3794
+ median: percentile(durations, 50),
3795
+ p95: percentile(durations, 95),
3796
+ p99: percentile(durations, 99),
3797
+ };
3798
+ }
3799
+ const totalDuration = this._events.reduce((sum, e) => sum + e.duration, 0);
3800
+ return {
3801
+ strategyName,
3802
+ totalEvents: this._events.length,
3803
+ totalDuration,
3804
+ metricStats,
3805
+ events: this._events,
3806
+ };
3807
+ }
3808
+ /**
3809
+ * Generates markdown report with performance statistics.
3810
+ *
3811
+ * @param strategyName - Strategy name
3812
+ * @returns Markdown formatted report
3813
+ */
3814
+ async getReport(strategyName) {
3815
+ const stats = await this.getData(strategyName);
3816
+ if (stats.totalEvents === 0) {
3817
+ return str.newline(`# Performance Report: ${strategyName}`, "", "No performance metrics recorded yet.");
3818
+ }
3819
+ // Sort metrics by total duration (descending) to show bottlenecks first
3820
+ const sortedMetrics = Object.values(stats.metricStats).sort((a, b) => b.totalDuration - a.totalDuration);
3821
+ // Generate summary table
3822
+ const summaryHeader = [
3823
+ "Metric Type",
3824
+ "Count",
3825
+ "Total (ms)",
3826
+ "Avg (ms)",
3827
+ "Min (ms)",
3828
+ "Max (ms)",
3829
+ "Std Dev (ms)",
3830
+ "Median (ms)",
3831
+ "P95 (ms)",
3832
+ "P99 (ms)",
3833
+ ];
3834
+ const summarySeparator = summaryHeader.map(() => "---");
3835
+ const summaryRows = sortedMetrics.map((metric) => [
3836
+ metric.metricType,
3837
+ metric.count.toString(),
3838
+ metric.totalDuration.toFixed(2),
3839
+ metric.avgDuration.toFixed(2),
3840
+ metric.minDuration.toFixed(2),
3841
+ metric.maxDuration.toFixed(2),
3842
+ metric.stdDev.toFixed(2),
3843
+ metric.median.toFixed(2),
3844
+ metric.p95.toFixed(2),
3845
+ metric.p99.toFixed(2),
3846
+ ]);
3847
+ const summaryTableData = [summaryHeader, summarySeparator, ...summaryRows];
3848
+ const summaryTable = str.newline(summaryTableData.map((row) => `| ${row.join(" | ")} |`));
3849
+ // Calculate percentage of total time for each metric
3850
+ const percentages = sortedMetrics.map((metric) => {
3851
+ const pct = (metric.totalDuration / stats.totalDuration) * 100;
3852
+ return `- **${metric.metricType}**: ${pct.toFixed(1)}% (${metric.totalDuration.toFixed(2)}ms total)`;
3853
+ });
3854
+ return str.newline(`# Performance Report: ${strategyName}`, "", `**Total events:** ${stats.totalEvents}`, `**Total execution time:** ${stats.totalDuration.toFixed(2)}ms`, `**Number of metric types:** ${Object.keys(stats.metricStats).length}`, "", "## Time Distribution", "", str.newline(percentages), "", "## Detailed Metrics", "", summaryTable, "", "**Note:** All durations are in milliseconds. P95/P99 represent 95th and 99th percentile response times.");
3855
+ }
3856
+ /**
3857
+ * Saves performance report to disk.
3858
+ *
3859
+ * @param strategyName - Strategy name
3860
+ * @param path - Directory path to save report
3861
+ */
3862
+ async dump(strategyName, path = "./logs/performance") {
3863
+ const markdown = await this.getReport(strategyName);
3864
+ try {
3865
+ const dir = join(process.cwd(), path);
3866
+ await mkdir(dir, { recursive: true });
3867
+ const filename = `${strategyName}.md`;
3868
+ const filepath = join(dir, filename);
3869
+ await writeFile(filepath, markdown, "utf-8");
3870
+ console.log(`Performance report saved: ${filepath}`);
3871
+ }
3872
+ catch (error) {
3873
+ console.error(`Failed to save performance report:`, error);
3874
+ }
3875
+ }
3876
+ }
3877
+ /**
3878
+ * Service for collecting and analyzing performance metrics.
3879
+ *
3880
+ * Features:
3881
+ * - Listens to performance events via performanceEmitter
3882
+ * - Accumulates metrics per strategy
3883
+ * - Calculates aggregated statistics (avg, min, max, percentiles)
3884
+ * - Generates markdown reports with bottleneck analysis
3885
+ * - Saves reports to disk in logs/performance/{strategyName}.md
3886
+ *
3887
+ * @example
3888
+ * ```typescript
3889
+ * import { listenPerformance } from "backtest-kit";
3890
+ *
3891
+ * // Subscribe to performance events
3892
+ * listenPerformance((event) => {
3893
+ * console.log(`${event.metricType}: ${event.duration.toFixed(2)}ms`);
3894
+ * });
3895
+ *
3896
+ * // After execution, generate report
3897
+ * const stats = await Performance.getData("my-strategy");
3898
+ * console.log("Bottlenecks:", stats.metricStats);
3899
+ *
3900
+ * // Save report to disk
3901
+ * await Performance.dump("my-strategy");
3902
+ * ```
3903
+ */
3904
+ class PerformanceMarkdownService {
3905
+ constructor() {
3906
+ /** Logger service for debug output */
3907
+ this.loggerService = inject(TYPES.loggerService);
3908
+ /**
3909
+ * Memoized function to get or create PerformanceStorage for a strategy.
3910
+ * Each strategy gets its own isolated storage instance.
3911
+ */
3912
+ this.getStorage = memoize(([strategyName]) => `${strategyName}`, () => new PerformanceStorage());
3913
+ /**
3914
+ * Processes performance events and accumulates metrics.
3915
+ * Should be called from performance tracking code.
3916
+ *
3917
+ * @param event - Performance event with timing data
3918
+ */
3919
+ this.track = async (event) => {
3920
+ this.loggerService.log("performanceMarkdownService track", {
3921
+ event,
3922
+ });
3923
+ const strategyName = event.strategyName || "global";
3924
+ const storage = this.getStorage(strategyName);
3925
+ storage.addEvent(event);
3926
+ };
3927
+ /**
3928
+ * Gets aggregated performance statistics for a strategy.
3929
+ *
3930
+ * @param strategyName - Strategy name to get data for
3931
+ * @returns Performance statistics with aggregated metrics
3932
+ *
3933
+ * @example
3934
+ * ```typescript
3935
+ * const stats = await performanceService.getData("my-strategy");
3936
+ * console.log("Total time:", stats.totalDuration);
3937
+ * console.log("Slowest operation:", Object.values(stats.metricStats)
3938
+ * .sort((a, b) => b.avgDuration - a.avgDuration)[0]);
3939
+ * ```
3940
+ */
3941
+ this.getData = async (strategyName) => {
3942
+ this.loggerService.log("performanceMarkdownService getData", {
3943
+ strategyName,
3944
+ });
3945
+ const storage = this.getStorage(strategyName);
3946
+ return storage.getData(strategyName);
3947
+ };
3948
+ /**
3949
+ * Generates markdown report with performance analysis.
3950
+ *
3951
+ * @param strategyName - Strategy name to generate report for
3952
+ * @returns Markdown formatted report string
3953
+ *
3954
+ * @example
3955
+ * ```typescript
3956
+ * const markdown = await performanceService.getReport("my-strategy");
3957
+ * console.log(markdown);
3958
+ * ```
3959
+ */
3960
+ this.getReport = async (strategyName) => {
3961
+ this.loggerService.log("performanceMarkdownService getReport", {
3962
+ strategyName,
3963
+ });
3964
+ const storage = this.getStorage(strategyName);
3965
+ return storage.getReport(strategyName);
3966
+ };
3967
+ /**
3968
+ * Saves performance report to disk.
3969
+ *
3970
+ * @param strategyName - Strategy name to save report for
3971
+ * @param path - Directory path to save report
3972
+ *
3973
+ * @example
3974
+ * ```typescript
3975
+ * // Save to default path: ./logs/performance/my-strategy.md
3976
+ * await performanceService.dump("my-strategy");
3977
+ *
3978
+ * // Save to custom path
3979
+ * await performanceService.dump("my-strategy", "./custom/path");
3980
+ * ```
3981
+ */
3982
+ this.dump = async (strategyName, path = "./logs/performance") => {
3983
+ this.loggerService.log("performanceMarkdownService dump", {
3984
+ strategyName,
3985
+ path,
3986
+ });
3987
+ const storage = this.getStorage(strategyName);
3988
+ await storage.dump(strategyName, path);
3989
+ };
3990
+ /**
3991
+ * Clears accumulated performance data from storage.
3992
+ *
3993
+ * @param strategyName - Optional strategy name to clear specific strategy data
3994
+ */
3995
+ this.clear = async (strategyName) => {
3996
+ this.loggerService.log("performanceMarkdownService clear", {
3997
+ strategyName,
3998
+ });
3999
+ this.getStorage.clear(strategyName);
4000
+ };
4001
+ /**
4002
+ * Initializes the service by subscribing to performance events.
4003
+ * Uses singleshot to ensure initialization happens only once.
4004
+ */
4005
+ this.init = singleshot(async () => {
4006
+ this.loggerService.log("performanceMarkdownService init");
4007
+ performanceEmitter.subscribe(this.track);
4008
+ });
4009
+ }
4010
+ }
4011
+
3666
4012
  /**
3667
4013
  * @class ExchangeValidationService
3668
4014
  * Service for managing and validating exchange configurations
@@ -3881,6 +4227,7 @@ class FrameValidationService {
3881
4227
  {
3882
4228
  provide(TYPES.backtestMarkdownService, () => new BacktestMarkdownService());
3883
4229
  provide(TYPES.liveMarkdownService, () => new LiveMarkdownService());
4230
+ provide(TYPES.performanceMarkdownService, () => new PerformanceMarkdownService());
3884
4231
  }
3885
4232
  {
3886
4233
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -3923,6 +4270,7 @@ const logicPublicServices = {
3923
4270
  const markdownServices = {
3924
4271
  backtestMarkdownService: inject(TYPES.backtestMarkdownService),
3925
4272
  liveMarkdownService: inject(TYPES.liveMarkdownService),
4273
+ performanceMarkdownService: inject(TYPES.performanceMarkdownService),
3926
4274
  };
3927
4275
  const validationServices = {
3928
4276
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -4194,6 +4542,7 @@ const LISTEN_ERROR_METHOD_NAME = "event.listenError";
4194
4542
  const LISTEN_DONE_METHOD_NAME = "event.listenDone";
4195
4543
  const LISTEN_DONE_ONCE_METHOD_NAME = "event.listenDoneOnce";
4196
4544
  const LISTEN_PROGRESS_METHOD_NAME = "event.listenProgress";
4545
+ const LISTEN_PERFORMANCE_METHOD_NAME = "event.listenPerformance";
4197
4546
  /**
4198
4547
  * Subscribes to all signal events with queued async processing.
4199
4548
  *
@@ -4483,6 +4832,42 @@ function listenProgress(fn) {
4483
4832
  backtest$1.loggerService.log(LISTEN_PROGRESS_METHOD_NAME);
4484
4833
  return progressEmitter.subscribe(queued(async (event) => fn(event)));
4485
4834
  }
4835
+ /**
4836
+ * Subscribes to performance metric events with queued async processing.
4837
+ *
4838
+ * Emits during strategy execution to track timing metrics for operations.
4839
+ * Useful for profiling and identifying performance bottlenecks.
4840
+ * Events are processed sequentially in order received, even if callback is async.
4841
+ * Uses queued wrapper to prevent concurrent execution of the callback.
4842
+ *
4843
+ * @param fn - Callback function to handle performance events
4844
+ * @returns Unsubscribe function to stop listening to events
4845
+ *
4846
+ * @example
4847
+ * ```typescript
4848
+ * import { listenPerformance, Backtest } from "backtest-kit";
4849
+ *
4850
+ * const unsubscribe = listenPerformance((event) => {
4851
+ * console.log(`${event.metricType}: ${event.duration.toFixed(2)}ms`);
4852
+ * if (event.duration > 100) {
4853
+ * console.warn("Slow operation detected:", event.metricType);
4854
+ * }
4855
+ * });
4856
+ *
4857
+ * Backtest.background("BTCUSDT", {
4858
+ * strategyName: "my-strategy",
4859
+ * exchangeName: "binance",
4860
+ * frameName: "1d-backtest"
4861
+ * });
4862
+ *
4863
+ * // Later: stop listening
4864
+ * unsubscribe();
4865
+ * ```
4866
+ */
4867
+ function listenPerformance(fn) {
4868
+ backtest$1.loggerService.log(LISTEN_PERFORMANCE_METHOD_NAME);
4869
+ return performanceEmitter.subscribe(queued(async (event) => fn(event)));
4870
+ }
4486
4871
 
4487
4872
  const GET_CANDLES_METHOD_NAME = "exchange.getCandles";
4488
4873
  const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
@@ -4967,4 +5352,131 @@ class LiveUtils {
4967
5352
  */
4968
5353
  const Live = new LiveUtils();
4969
5354
 
4970
- export { Backtest, ExecutionContextService, Live, MethodContextService, PersistBase, PersistSignalAdaper, addExchange, addFrame, addStrategy, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listExchanges, listFrames, listStrategies, listenDone, listenDoneOnce, listenError, listenProgress, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, setLogger };
5355
+ /**
5356
+ * Performance class provides static methods for performance metrics analysis.
5357
+ *
5358
+ * Features:
5359
+ * - Get aggregated performance statistics by strategy
5360
+ * - Generate markdown reports with bottleneck analysis
5361
+ * - Save reports to disk
5362
+ * - Clear accumulated metrics
5363
+ *
5364
+ * @example
5365
+ * ```typescript
5366
+ * import { Performance, listenPerformance } from "backtest-kit";
5367
+ *
5368
+ * // Subscribe to performance events
5369
+ * listenPerformance((event) => {
5370
+ * console.log(`${event.metricType}: ${event.duration.toFixed(2)}ms`);
5371
+ * });
5372
+ *
5373
+ * // Run backtest...
5374
+ *
5375
+ * // Get aggregated statistics
5376
+ * const stats = await Performance.getData("my-strategy");
5377
+ * console.log("Total time:", stats.totalDuration);
5378
+ * console.log("Slowest operations:", Object.values(stats.metricStats)
5379
+ * .sort((a, b) => b.avgDuration - a.avgDuration)
5380
+ * .slice(0, 5));
5381
+ *
5382
+ * // Generate and save report
5383
+ * await Performance.dump("my-strategy");
5384
+ * ```
5385
+ */
5386
+ class Performance {
5387
+ /**
5388
+ * Gets aggregated performance statistics for a strategy.
5389
+ *
5390
+ * Returns detailed metrics grouped by operation type:
5391
+ * - Count, total duration, average, min, max
5392
+ * - Standard deviation for volatility
5393
+ * - Percentiles (median, P95, P99) for outlier detection
5394
+ *
5395
+ * @param strategyName - Strategy name to analyze
5396
+ * @returns Performance statistics with aggregated metrics
5397
+ *
5398
+ * @example
5399
+ * ```typescript
5400
+ * const stats = await Performance.getData("my-strategy");
5401
+ *
5402
+ * // Find slowest operation type
5403
+ * const slowest = Object.values(stats.metricStats)
5404
+ * .sort((a, b) => b.avgDuration - a.avgDuration)[0];
5405
+ * console.log(`Slowest: ${slowest.metricType} (${slowest.avgDuration.toFixed(2)}ms avg)`);
5406
+ *
5407
+ * // Check for outliers
5408
+ * for (const metric of Object.values(stats.metricStats)) {
5409
+ * if (metric.p99 > metric.avgDuration * 5) {
5410
+ * console.warn(`High variance in ${metric.metricType}: P99=${metric.p99}ms, Avg=${metric.avgDuration}ms`);
5411
+ * }
5412
+ * }
5413
+ * ```
5414
+ */
5415
+ static async getData(strategyName) {
5416
+ return backtest$1.performanceMarkdownService.getData(strategyName);
5417
+ }
5418
+ /**
5419
+ * Generates markdown report with performance analysis.
5420
+ *
5421
+ * Report includes:
5422
+ * - Time distribution across operation types
5423
+ * - Detailed metrics table with statistics
5424
+ * - Percentile analysis for bottleneck detection
5425
+ *
5426
+ * @param strategyName - Strategy name to generate report for
5427
+ * @returns Markdown formatted report string
5428
+ *
5429
+ * @example
5430
+ * ```typescript
5431
+ * const markdown = await Performance.getReport("my-strategy");
5432
+ * console.log(markdown);
5433
+ *
5434
+ * // Or save to file
5435
+ * import fs from "fs/promises";
5436
+ * await fs.writeFile("performance-report.md", markdown);
5437
+ * ```
5438
+ */
5439
+ static async getReport(strategyName) {
5440
+ return backtest$1.performanceMarkdownService.getReport(strategyName);
5441
+ }
5442
+ /**
5443
+ * Saves performance report to disk.
5444
+ *
5445
+ * Creates directory if it doesn't exist.
5446
+ * Default path: ./logs/performance/{strategyName}.md
5447
+ *
5448
+ * @param strategyName - Strategy name to save report for
5449
+ * @param path - Optional custom directory path
5450
+ *
5451
+ * @example
5452
+ * ```typescript
5453
+ * // Save to default path: ./logs/performance/my-strategy.md
5454
+ * await Performance.dump("my-strategy");
5455
+ *
5456
+ * // Save to custom path: ./reports/perf/my-strategy.md
5457
+ * await Performance.dump("my-strategy", "./reports/perf");
5458
+ * ```
5459
+ */
5460
+ static async dump(strategyName, path = "./logs/performance") {
5461
+ return backtest$1.performanceMarkdownService.dump(strategyName, path);
5462
+ }
5463
+ /**
5464
+ * Clears accumulated performance metrics from memory.
5465
+ *
5466
+ * @param strategyName - Optional strategy name to clear specific strategy's metrics
5467
+ *
5468
+ * @example
5469
+ * ```typescript
5470
+ * // Clear specific strategy metrics
5471
+ * await Performance.clear("my-strategy");
5472
+ *
5473
+ * // Clear all metrics for all strategies
5474
+ * await Performance.clear();
5475
+ * ```
5476
+ */
5477
+ static async clear(strategyName) {
5478
+ return backtest$1.performanceMarkdownService.clear(strategyName);
5479
+ }
5480
+ }
5481
+
5482
+ export { Backtest, ExecutionContextService, Live, MethodContextService, Performance, PersistBase, PersistSignalAdaper, addExchange, addFrame, addStrategy, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listExchanges, listFrames, listStrategies, listenDone, listenDoneOnce, listenError, listenPerformance, listenProgress, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, setLogger };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
@@ -66,7 +66,7 @@
66
66
  "rollup-plugin-peer-deps-external": "2.2.4",
67
67
  "tslib": "2.7.0",
68
68
  "typedoc": "0.27.9",
69
- "worker-testbed": "1.0.10"
69
+ "worker-testbed": "1.0.11"
70
70
  },
71
71
  "peerDependencies": {
72
72
  "typescript": "^5.0.0"