backtest-kit 1.1.5 โ†’ 1.1.6

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/README.md CHANGED
@@ -14,15 +14,15 @@
14
14
  - ๐Ÿ”„ **Async Generators** - Memory-efficient streaming for backtest and live execution
15
15
  - ๐Ÿ“Š **VWAP Pricing** - Volume-weighted average price from last 5 1m candles
16
16
  - ๐ŸŽฏ **Signal Lifecycle** - Type-safe state machine (idle โ†’ opened โ†’ active โ†’ closed)
17
- - ๐Ÿ“‰ **Accurate PNL** - Calculation with fees (0.1%) and slippage (0.1%)
17
+ - ๐Ÿ“ˆ **Accurate PNL** - Calculation with fees (0.1%) and slippage (0.1%)
18
18
  - ๐Ÿง  **Interval Throttling** - Prevents signal spam at strategy level
19
19
  - โšก **Memory Optimized** - Prototype methods + memoization + streaming
20
20
  - ๐Ÿ”Œ **Flexible Architecture** - Plug your own exchanges and strategies
21
- - ๐Ÿ“ **Markdown Reports** - Auto-generated trading reports with statistics (win rate, avg PNL)
21
+ - ๐Ÿ“ **Markdown Reports** - Auto-generated trading reports with statistics (win rate, avg PNL, Sharpe Ratio, Standard Deviation, Certainty Ratio, Expected Yearly Returns, Risk-Adjusted Returns)
22
22
  - ๐Ÿ›‘ **Graceful Shutdown** - Live.background() waits for open positions to close before stopping
23
23
  - ๐Ÿ’‰ **Strategy Dependency Injection** - addStrategy() enables DI pattern for trading strategies
24
24
  - ๐Ÿ” **Schema Reflection API** - listExchanges(), listStrategies(), listFrames() for runtime introspection
25
- - ๐Ÿงช **Comprehensive Test Coverage** - 30+ unit tests covering validation, PNL, callbacks, reports, and event system
25
+ - ๐Ÿงช **Comprehensive Test Coverage** - 45+ unit tests covering validation, PNL, callbacks, reports, and event system
26
26
 
27
27
  ## Installation
28
28
 
package/build/index.cjs CHANGED
@@ -2742,6 +2742,24 @@ class BacktestGlobalService {
2742
2742
  }
2743
2743
  }
2744
2744
 
2745
+ /**
2746
+ * Checks if a value is unsafe for display (not a number, NaN, or Infinity).
2747
+ *
2748
+ * @param value - Value to check
2749
+ * @returns true if value is unsafe, false otherwise
2750
+ */
2751
+ function isUnsafe$1(value) {
2752
+ if (typeof value !== "number") {
2753
+ return true;
2754
+ }
2755
+ if (isNaN(value)) {
2756
+ return true;
2757
+ }
2758
+ if (!isFinite(value)) {
2759
+ return true;
2760
+ }
2761
+ return false;
2762
+ }
2745
2763
  const columns$1 = [
2746
2764
  {
2747
2765
  key: "signalId",
@@ -2834,13 +2852,80 @@ let ReportStorage$1 = class ReportStorage {
2834
2852
  this._signalList.push(data);
2835
2853
  }
2836
2854
  /**
2837
- * Generates markdown report with all closed signals for a strategy.
2855
+ * Calculates statistical data from closed signals (Controller).
2856
+ * Returns null for any unsafe numeric values (NaN, Infinity, etc).
2857
+ *
2858
+ * @returns Statistical data (empty object if no signals)
2859
+ */
2860
+ async getData() {
2861
+ if (this._signalList.length === 0) {
2862
+ return {
2863
+ signalList: [],
2864
+ totalSignals: 0,
2865
+ winCount: 0,
2866
+ lossCount: 0,
2867
+ winRate: null,
2868
+ avgPnl: null,
2869
+ totalPnl: null,
2870
+ stdDev: null,
2871
+ sharpeRatio: null,
2872
+ annualizedSharpeRatio: null,
2873
+ certaintyRatio: null,
2874
+ expectedYearlyReturns: null,
2875
+ };
2876
+ }
2877
+ const totalSignals = this._signalList.length;
2878
+ const winCount = this._signalList.filter((s) => s.pnl.pnlPercentage > 0).length;
2879
+ const lossCount = this._signalList.filter((s) => s.pnl.pnlPercentage < 0).length;
2880
+ // Calculate basic statistics
2881
+ const avgPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0) / totalSignals;
2882
+ const totalPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0);
2883
+ const winRate = (winCount / totalSignals) * 100;
2884
+ // Calculate Sharpe Ratio (risk-free rate = 0)
2885
+ const returns = this._signalList.map((s) => s.pnl.pnlPercentage);
2886
+ const variance = returns.reduce((sum, r) => sum + Math.pow(r - avgPnl, 2), 0) / totalSignals;
2887
+ const stdDev = Math.sqrt(variance);
2888
+ const sharpeRatio = stdDev > 0 ? avgPnl / stdDev : 0;
2889
+ const annualizedSharpeRatio = sharpeRatio * Math.sqrt(365);
2890
+ // Calculate Certainty Ratio
2891
+ const wins = this._signalList.filter((s) => s.pnl.pnlPercentage > 0);
2892
+ const losses = this._signalList.filter((s) => s.pnl.pnlPercentage < 0);
2893
+ const avgWin = wins.length > 0
2894
+ ? wins.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0) / wins.length
2895
+ : 0;
2896
+ const avgLoss = losses.length > 0
2897
+ ? losses.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0) / losses.length
2898
+ : 0;
2899
+ const certaintyRatio = avgLoss < 0 ? avgWin / Math.abs(avgLoss) : 0;
2900
+ // Calculate Expected Yearly Returns
2901
+ const avgDurationMs = this._signalList.reduce((sum, s) => sum + (s.closeTimestamp - s.signal.timestamp), 0) / totalSignals;
2902
+ const avgDurationDays = avgDurationMs / (1000 * 60 * 60 * 24);
2903
+ const tradesPerYear = avgDurationDays > 0 ? 365 / avgDurationDays : 0;
2904
+ const expectedYearlyReturns = avgPnl * tradesPerYear;
2905
+ return {
2906
+ signalList: this._signalList,
2907
+ totalSignals,
2908
+ winCount,
2909
+ lossCount,
2910
+ winRate: isUnsafe$1(winRate) ? null : winRate,
2911
+ avgPnl: isUnsafe$1(avgPnl) ? null : avgPnl,
2912
+ totalPnl: isUnsafe$1(totalPnl) ? null : totalPnl,
2913
+ stdDev: isUnsafe$1(stdDev) ? null : stdDev,
2914
+ sharpeRatio: isUnsafe$1(sharpeRatio) ? null : sharpeRatio,
2915
+ annualizedSharpeRatio: isUnsafe$1(annualizedSharpeRatio) ? null : annualizedSharpeRatio,
2916
+ certaintyRatio: isUnsafe$1(certaintyRatio) ? null : certaintyRatio,
2917
+ expectedYearlyReturns: isUnsafe$1(expectedYearlyReturns) ? null : expectedYearlyReturns,
2918
+ };
2919
+ }
2920
+ /**
2921
+ * Generates markdown report with all closed signals for a strategy (View).
2838
2922
  *
2839
2923
  * @param strategyName - Strategy name
2840
2924
  * @returns Markdown formatted report with all signals
2841
2925
  */
2842
- getReport(strategyName) {
2843
- if (this._signalList.length === 0) {
2926
+ async getReport(strategyName) {
2927
+ const stats = await this.getData();
2928
+ if (stats.totalSignals === 0) {
2844
2929
  return functoolsKit.str.newline(`# Backtest Report: ${strategyName}`, "", "No signals closed yet.");
2845
2930
  }
2846
2931
  const header = columns$1.map((col) => col.label);
@@ -2848,13 +2933,7 @@ let ReportStorage$1 = class ReportStorage {
2848
2933
  const rows = this._signalList.map((closedSignal) => columns$1.map((col) => col.format(closedSignal)));
2849
2934
  const tableData = [header, separator, ...rows];
2850
2935
  const table = functoolsKit.str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
2851
- // Calculate statistics
2852
- const totalSignals = this._signalList.length;
2853
- const winCount = this._signalList.filter((s) => s.pnl.pnlPercentage > 0).length;
2854
- const lossCount = this._signalList.filter((s) => s.pnl.pnlPercentage < 0).length;
2855
- const avgPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0) / totalSignals;
2856
- const totalPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0);
2857
- return functoolsKit.str.newline(`# Backtest Report: ${strategyName}`, "", table, "", `**Total signals:** ${totalSignals}`, `**Closed signals:** ${totalSignals}`, `**Win rate:** ${((winCount / totalSignals) * 100).toFixed(2)}% (${winCount}W / ${lossCount}L)`, `**Average PNL:** ${avgPnl > 0 ? "+" : ""}${avgPnl.toFixed(2)}%`, `**Total PNL:** ${totalPnl > 0 ? "+" : ""}${totalPnl.toFixed(2)}%`);
2936
+ return functoolsKit.str.newline(`# Backtest Report: ${strategyName}`, "", table, "", `**Total signals:** ${stats.totalSignals}`, `**Closed signals:** ${stats.totalSignals}`, `**Win rate:** ${stats.winRate === null ? "N/A" : `${stats.winRate.toFixed(2)}% (${stats.winCount}W / ${stats.lossCount}L) (higher is better)`}`, `**Average PNL:** ${stats.avgPnl === null ? "N/A" : `${stats.avgPnl > 0 ? "+" : ""}${stats.avgPnl.toFixed(2)}% (higher is better)`}`, `**Total PNL:** ${stats.totalPnl === null ? "N/A" : `${stats.totalPnl > 0 ? "+" : ""}${stats.totalPnl.toFixed(2)}% (higher is better)`}`, `**Standard Deviation:** ${stats.stdDev === null ? "N/A" : `${stats.stdDev.toFixed(3)}% (lower is better)`}`, `**Sharpe Ratio:** ${stats.sharpeRatio === null ? "N/A" : `${stats.sharpeRatio.toFixed(3)} (higher is better)`}`, `**Annualized Sharpe Ratio:** ${stats.annualizedSharpeRatio === null ? "N/A" : `${stats.annualizedSharpeRatio.toFixed(3)} (higher is better)`}`, `**Certainty Ratio:** ${stats.certaintyRatio === null ? "N/A" : `${stats.certaintyRatio.toFixed(3)} (higher is better)`}`, `**Expected Yearly Returns:** ${stats.expectedYearlyReturns === null ? "N/A" : `${stats.expectedYearlyReturns > 0 ? "+" : ""}${stats.expectedYearlyReturns.toFixed(2)}% (higher is better)`}`);
2858
2937
  }
2859
2938
  /**
2860
2939
  * Saves strategy report to disk.
@@ -2863,7 +2942,7 @@ let ReportStorage$1 = class ReportStorage {
2863
2942
  * @param path - Directory path to save report (default: "./logs/backtest")
2864
2943
  */
2865
2944
  async dump(strategyName, path$1 = "./logs/backtest") {
2866
- const markdown = this.getReport(strategyName);
2945
+ const markdown = await this.getReport(strategyName);
2867
2946
  try {
2868
2947
  const dir = path.join(process.cwd(), path$1);
2869
2948
  await fs.mkdir(dir, { recursive: true });
@@ -2942,6 +3021,27 @@ class BacktestMarkdownService {
2942
3021
  const storage = this.getStorage(data.strategyName);
2943
3022
  storage.addSignal(data);
2944
3023
  };
3024
+ /**
3025
+ * Gets statistical data from all closed signals for a strategy.
3026
+ * Delegates to ReportStorage.getData().
3027
+ *
3028
+ * @param strategyName - Strategy name to get data for
3029
+ * @returns Statistical data object with all metrics
3030
+ *
3031
+ * @example
3032
+ * ```typescript
3033
+ * const service = new BacktestMarkdownService();
3034
+ * const stats = await service.getData("my-strategy");
3035
+ * console.log(stats.sharpeRatio, stats.winRate);
3036
+ * ```
3037
+ */
3038
+ this.getData = async (strategyName) => {
3039
+ this.loggerService.log("backtestMarkdownService getData", {
3040
+ strategyName,
3041
+ });
3042
+ const storage = this.getStorage(strategyName);
3043
+ return storage.getData();
3044
+ };
2945
3045
  /**
2946
3046
  * Generates markdown report with all closed signals for a strategy.
2947
3047
  * Delegates to ReportStorage.generateReport().
@@ -2952,7 +3052,7 @@ class BacktestMarkdownService {
2952
3052
  * @example
2953
3053
  * ```typescript
2954
3054
  * const service = new BacktestMarkdownService();
2955
- * const markdown = service.generateReport("my-strategy");
3055
+ * const markdown = await service.getReport("my-strategy");
2956
3056
  * console.log(markdown);
2957
3057
  * ```
2958
3058
  */
@@ -3032,6 +3132,24 @@ class BacktestMarkdownService {
3032
3132
  }
3033
3133
  }
3034
3134
 
3135
+ /**
3136
+ * Checks if a value is unsafe for display (not a number, NaN, or Infinity).
3137
+ *
3138
+ * @param value - Value to check
3139
+ * @returns true if value is unsafe, false otherwise
3140
+ */
3141
+ function isUnsafe(value) {
3142
+ if (typeof value !== "number") {
3143
+ return true;
3144
+ }
3145
+ if (isNaN(value)) {
3146
+ return true;
3147
+ }
3148
+ if (!isFinite(value)) {
3149
+ return true;
3150
+ }
3151
+ return false;
3152
+ }
3035
3153
  const columns = [
3036
3154
  {
3037
3155
  key: "timestamp",
@@ -3239,36 +3357,103 @@ class ReportStorage {
3239
3357
  }
3240
3358
  }
3241
3359
  /**
3242
- * Generates markdown report with all tick events for a strategy.
3360
+ * Calculates statistical data from live trading events (Controller).
3361
+ * Returns null for any unsafe numeric values (NaN, Infinity, etc).
3243
3362
  *
3244
- * @param strategyName - Strategy name
3245
- * @returns Markdown formatted report with all events
3363
+ * @returns Statistical data (empty object if no events)
3246
3364
  */
3247
- getReport(strategyName) {
3365
+ async getData() {
3248
3366
  if (this._eventList.length === 0) {
3249
- return functoolsKit.str.newline(`# Live Trading Report: ${strategyName}`, "", "No events recorded yet.");
3367
+ return {
3368
+ eventList: [],
3369
+ totalEvents: 0,
3370
+ totalClosed: 0,
3371
+ winCount: 0,
3372
+ lossCount: 0,
3373
+ winRate: null,
3374
+ avgPnl: null,
3375
+ totalPnl: null,
3376
+ stdDev: null,
3377
+ sharpeRatio: null,
3378
+ annualizedSharpeRatio: null,
3379
+ certaintyRatio: null,
3380
+ expectedYearlyReturns: null,
3381
+ };
3250
3382
  }
3251
- const header = columns.map((col) => col.label);
3252
- const separator = columns.map(() => "---");
3253
- const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
3254
- const tableData = [header, separator, ...rows];
3255
- const table = functoolsKit.str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
3256
- // Calculate statistics
3257
3383
  const closedEvents = this._eventList.filter((e) => e.action === "closed");
3258
3384
  const totalClosed = closedEvents.length;
3259
3385
  const winCount = closedEvents.filter((e) => e.pnl && e.pnl > 0).length;
3260
3386
  const lossCount = closedEvents.filter((e) => e.pnl && e.pnl < 0).length;
3387
+ // Calculate basic statistics
3261
3388
  const avgPnl = totalClosed > 0
3262
3389
  ? closedEvents.reduce((sum, e) => sum + (e.pnl || 0), 0) / totalClosed
3263
3390
  : 0;
3264
3391
  const totalPnl = closedEvents.reduce((sum, e) => sum + (e.pnl || 0), 0);
3265
- return functoolsKit.str.newline(`# Live Trading Report: ${strategyName}`, "", table, "", `**Total events:** ${this._eventList.length}`, `**Closed signals:** ${totalClosed}`, totalClosed > 0
3266
- ? `**Win rate:** ${((winCount / totalClosed) * 100).toFixed(2)}% (${winCount}W / ${lossCount}L)`
3267
- : "", totalClosed > 0
3268
- ? `**Average PNL:** ${avgPnl > 0 ? "+" : ""}${avgPnl.toFixed(2)}%`
3269
- : "", totalClosed > 0
3270
- ? `**Total PNL:** ${totalPnl > 0 ? "+" : ""}${totalPnl.toFixed(2)}%`
3271
- : "");
3392
+ const winRate = (winCount / totalClosed) * 100;
3393
+ // Calculate Sharpe Ratio (risk-free rate = 0)
3394
+ let sharpeRatio = 0;
3395
+ let stdDev = 0;
3396
+ if (totalClosed > 0) {
3397
+ const returns = closedEvents.map((e) => e.pnl || 0);
3398
+ const variance = returns.reduce((sum, r) => sum + Math.pow(r - avgPnl, 2), 0) / totalClosed;
3399
+ stdDev = Math.sqrt(variance);
3400
+ sharpeRatio = stdDev > 0 ? avgPnl / stdDev : 0;
3401
+ }
3402
+ const annualizedSharpeRatio = sharpeRatio * Math.sqrt(365);
3403
+ // Calculate Certainty Ratio
3404
+ let certaintyRatio = 0;
3405
+ if (totalClosed > 0) {
3406
+ const wins = closedEvents.filter((e) => e.pnl && e.pnl > 0);
3407
+ const losses = closedEvents.filter((e) => e.pnl && e.pnl < 0);
3408
+ const avgWin = wins.length > 0
3409
+ ? wins.reduce((sum, e) => sum + (e.pnl || 0), 0) / wins.length
3410
+ : 0;
3411
+ const avgLoss = losses.length > 0
3412
+ ? losses.reduce((sum, e) => sum + (e.pnl || 0), 0) / losses.length
3413
+ : 0;
3414
+ certaintyRatio = avgLoss < 0 ? avgWin / Math.abs(avgLoss) : 0;
3415
+ }
3416
+ // Calculate Expected Yearly Returns
3417
+ let expectedYearlyReturns = 0;
3418
+ if (totalClosed > 0) {
3419
+ const avgDurationMin = closedEvents.reduce((sum, e) => sum + (e.duration || 0), 0) / totalClosed;
3420
+ const avgDurationDays = avgDurationMin / (60 * 24);
3421
+ const tradesPerYear = avgDurationDays > 0 ? 365 / avgDurationDays : 0;
3422
+ expectedYearlyReturns = avgPnl * tradesPerYear;
3423
+ }
3424
+ return {
3425
+ eventList: this._eventList,
3426
+ totalEvents: this._eventList.length,
3427
+ totalClosed,
3428
+ winCount,
3429
+ lossCount,
3430
+ winRate: isUnsafe(winRate) ? null : winRate,
3431
+ avgPnl: isUnsafe(avgPnl) ? null : avgPnl,
3432
+ totalPnl: isUnsafe(totalPnl) ? null : totalPnl,
3433
+ stdDev: isUnsafe(stdDev) ? null : stdDev,
3434
+ sharpeRatio: isUnsafe(sharpeRatio) ? null : sharpeRatio,
3435
+ annualizedSharpeRatio: isUnsafe(annualizedSharpeRatio) ? null : annualizedSharpeRatio,
3436
+ certaintyRatio: isUnsafe(certaintyRatio) ? null : certaintyRatio,
3437
+ expectedYearlyReturns: isUnsafe(expectedYearlyReturns) ? null : expectedYearlyReturns,
3438
+ };
3439
+ }
3440
+ /**
3441
+ * Generates markdown report with all tick events for a strategy (View).
3442
+ *
3443
+ * @param strategyName - Strategy name
3444
+ * @returns Markdown formatted report with all events
3445
+ */
3446
+ async getReport(strategyName) {
3447
+ const stats = await this.getData();
3448
+ if (stats.totalEvents === 0) {
3449
+ return functoolsKit.str.newline(`# Live Trading Report: ${strategyName}`, "", "No events recorded yet.");
3450
+ }
3451
+ const header = columns.map((col) => col.label);
3452
+ const separator = columns.map(() => "---");
3453
+ const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
3454
+ const tableData = [header, separator, ...rows];
3455
+ const table = functoolsKit.str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
3456
+ return functoolsKit.str.newline(`# Live Trading Report: ${strategyName}`, "", table, "", `**Total events:** ${stats.totalEvents}`, `**Closed signals:** ${stats.totalClosed}`, `**Win rate:** ${stats.winRate === null ? "N/A" : `${stats.winRate.toFixed(2)}% (${stats.winCount}W / ${stats.lossCount}L) (higher is better)`}`, `**Average PNL:** ${stats.avgPnl === null ? "N/A" : `${stats.avgPnl > 0 ? "+" : ""}${stats.avgPnl.toFixed(2)}% (higher is better)`}`, `**Total PNL:** ${stats.totalPnl === null ? "N/A" : `${stats.totalPnl > 0 ? "+" : ""}${stats.totalPnl.toFixed(2)}% (higher is better)`}`, `**Standard Deviation:** ${stats.stdDev === null ? "N/A" : `${stats.stdDev.toFixed(3)}% (lower is better)`}`, `**Sharpe Ratio:** ${stats.sharpeRatio === null ? "N/A" : `${stats.sharpeRatio.toFixed(3)} (higher is better)`}`, `**Annualized Sharpe Ratio:** ${stats.annualizedSharpeRatio === null ? "N/A" : `${stats.annualizedSharpeRatio.toFixed(3)} (higher is better)`}`, `**Certainty Ratio:** ${stats.certaintyRatio === null ? "N/A" : `${stats.certaintyRatio.toFixed(3)} (higher is better)`}`, `**Expected Yearly Returns:** ${stats.expectedYearlyReturns === null ? "N/A" : `${stats.expectedYearlyReturns > 0 ? "+" : ""}${stats.expectedYearlyReturns.toFixed(2)}% (higher is better)`}`);
3272
3457
  }
3273
3458
  /**
3274
3459
  * Saves strategy report to disk.
@@ -3277,7 +3462,7 @@ class ReportStorage {
3277
3462
  * @param path - Directory path to save report (default: "./logs/live")
3278
3463
  */
3279
3464
  async dump(strategyName, path$1 = "./logs/live") {
3280
- const markdown = this.getReport(strategyName);
3465
+ const markdown = await this.getReport(strategyName);
3281
3466
  try {
3282
3467
  const dir = path.join(process.cwd(), path$1);
3283
3468
  await fs.mkdir(dir, { recursive: true });
@@ -3369,6 +3554,27 @@ class LiveMarkdownService {
3369
3554
  storage.addClosedEvent(data);
3370
3555
  }
3371
3556
  };
3557
+ /**
3558
+ * Gets statistical data from all live trading events for a strategy.
3559
+ * Delegates to ReportStorage.getData().
3560
+ *
3561
+ * @param strategyName - Strategy name to get data for
3562
+ * @returns Statistical data object with all metrics
3563
+ *
3564
+ * @example
3565
+ * ```typescript
3566
+ * const service = new LiveMarkdownService();
3567
+ * const stats = await service.getData("my-strategy");
3568
+ * console.log(stats.sharpeRatio, stats.winRate);
3569
+ * ```
3570
+ */
3571
+ this.getData = async (strategyName) => {
3572
+ this.loggerService.log("liveMarkdownService getData", {
3573
+ strategyName,
3574
+ });
3575
+ const storage = this.getStorage(strategyName);
3576
+ return storage.getData();
3577
+ };
3372
3578
  /**
3373
3579
  * Generates markdown report with all events for a strategy.
3374
3580
  * Delegates to ReportStorage.getReport().
@@ -4506,6 +4712,24 @@ class BacktestUtils {
4506
4712
  isStopped = true;
4507
4713
  };
4508
4714
  };
4715
+ /**
4716
+ * Gets statistical data from all closed signals for a strategy.
4717
+ *
4718
+ * @param strategyName - Strategy name to get data for
4719
+ * @returns Promise resolving to statistical data object
4720
+ *
4721
+ * @example
4722
+ * ```typescript
4723
+ * const stats = await Backtest.getData("my-strategy");
4724
+ * console.log(stats.sharpeRatio, stats.winRate);
4725
+ * ```
4726
+ */
4727
+ this.getData = async (strategyName) => {
4728
+ backtest$1.loggerService.info("BacktestUtils.getData", {
4729
+ strategyName,
4730
+ });
4731
+ return await backtest$1.backtestMarkdownService.getData(strategyName);
4732
+ };
4509
4733
  /**
4510
4734
  * Generates markdown report with all closed signals for a strategy.
4511
4735
  *
@@ -4668,6 +4892,24 @@ class LiveUtils {
4668
4892
  isStopped = true;
4669
4893
  };
4670
4894
  };
4895
+ /**
4896
+ * Gets statistical data from all live trading events for a strategy.
4897
+ *
4898
+ * @param strategyName - Strategy name to get data for
4899
+ * @returns Promise resolving to statistical data object
4900
+ *
4901
+ * @example
4902
+ * ```typescript
4903
+ * const stats = await Live.getData("my-strategy");
4904
+ * console.log(stats.sharpeRatio, stats.winRate);
4905
+ * ```
4906
+ */
4907
+ this.getData = async (strategyName) => {
4908
+ backtest$1.loggerService.info("LiveUtils.getData", {
4909
+ strategyName,
4910
+ });
4911
+ return await backtest$1.liveMarkdownService.getData(strategyName);
4912
+ };
4671
4913
  /**
4672
4914
  * Generates markdown report with all events for a strategy.
4673
4915
  *