backtest-kit 1.1.6 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "1.1.6",
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"
package/types.d.ts CHANGED
@@ -370,7 +370,7 @@ interface ISignalDto {
370
370
  /** Human-readable description of signal reason */
371
371
  note?: string;
372
372
  /** Entry price for the position */
373
- priceOpen: number;
373
+ priceOpen?: number;
374
374
  /** Take profit target price (must be > priceOpen for long, < priceOpen for short) */
375
375
  priceTakeProfit: number;
376
376
  /** Stop loss exit price (must be < priceOpen for long, > priceOpen for short) */
@@ -385,6 +385,8 @@ interface ISignalDto {
385
385
  interface ISignalRow extends ISignalDto {
386
386
  /** Unique signal identifier (UUID v4 auto-generated) */
387
387
  id: string;
388
+ /** Entry price for the position */
389
+ priceOpen: number;
388
390
  /** Unique exchange identifier for execution */
389
391
  exchangeName: ExchangeName;
390
392
  /** Unique strategy identifier for execution */
@@ -597,8 +599,8 @@ type StrategyName = string;
597
599
  * timestamp: Date.now(),
598
600
  * }),
599
601
  * callbacks: {
600
- * onOpen: (backtest, symbol, signal) => console.log("Signal opened"),
601
- * onClose: (backtest, symbol, priceClose, signal) => console.log("Signal closed"),
602
+ * onOpen: (symbol, signal, currentPrice, backtest) => console.log("Signal opened"),
603
+ * onClose: (symbol, signal, priceClose, backtest) => console.log("Signal closed"),
602
604
  * },
603
605
  * });
604
606
  * ```
@@ -817,6 +819,51 @@ interface ProgressContract {
817
819
  progress: number;
818
820
  }
819
821
 
822
+ /**
823
+ * Performance metric types tracked by the system.
824
+ *
825
+ * Backtest metrics:
826
+ * - backtest_total: Total backtest duration from start to finish
827
+ * - backtest_timeframe: Duration to process a single timeframe iteration
828
+ * - backtest_signal: Duration to process a signal (tick + getNextCandles + backtest)
829
+ *
830
+ * Live metrics:
831
+ * - live_tick: Duration of a single live tick iteration
832
+ */
833
+ type PerformanceMetricType = "backtest_total" | "backtest_timeframe" | "backtest_signal" | "live_tick";
834
+ /**
835
+ * Contract for performance tracking events.
836
+ *
837
+ * Emitted during execution to track performance metrics for various operations.
838
+ * Useful for profiling and identifying bottlenecks.
839
+ *
840
+ * @example
841
+ * ```typescript
842
+ * import { listenPerformance } from "backtest-kit";
843
+ *
844
+ * listenPerformance((event) => {
845
+ * console.log(`${event.metricType}: ${event.duration.toFixed(2)}ms`);
846
+ * console.log(`${event.strategyName} @ ${event.exchangeName}`);
847
+ * });
848
+ * ```
849
+ */
850
+ interface PerformanceContract {
851
+ /** Timestamp when the metric was recorded (milliseconds since epoch) */
852
+ timestamp: number;
853
+ /** Type of operation being measured */
854
+ metricType: PerformanceMetricType;
855
+ /** Duration of the operation in milliseconds */
856
+ duration: number;
857
+ /** Strategy name associated with this metric */
858
+ strategyName: string;
859
+ /** Exchange name associated with this metric */
860
+ exchangeName: string;
861
+ /** Trading symbol associated with this metric */
862
+ symbol: string;
863
+ /** Whether this metric is from backtest mode (true) or live mode (false) */
864
+ backtest: boolean;
865
+ }
866
+
820
867
  /**
821
868
  * Subscribes to all signal events with queued async processing.
822
869
  *
@@ -1076,6 +1123,39 @@ declare function listenDoneOnce(filterFn: (event: DoneContract) => boolean, fn:
1076
1123
  * ```
1077
1124
  */
1078
1125
  declare function listenProgress(fn: (event: ProgressContract) => void): () => void;
1126
+ /**
1127
+ * Subscribes to performance metric events with queued async processing.
1128
+ *
1129
+ * Emits during strategy execution to track timing metrics for operations.
1130
+ * Useful for profiling and identifying performance bottlenecks.
1131
+ * Events are processed sequentially in order received, even if callback is async.
1132
+ * Uses queued wrapper to prevent concurrent execution of the callback.
1133
+ *
1134
+ * @param fn - Callback function to handle performance events
1135
+ * @returns Unsubscribe function to stop listening to events
1136
+ *
1137
+ * @example
1138
+ * ```typescript
1139
+ * import { listenPerformance, Backtest } from "backtest-kit";
1140
+ *
1141
+ * const unsubscribe = listenPerformance((event) => {
1142
+ * console.log(`${event.metricType}: ${event.duration.toFixed(2)}ms`);
1143
+ * if (event.duration > 100) {
1144
+ * console.warn("Slow operation detected:", event.metricType);
1145
+ * }
1146
+ * });
1147
+ *
1148
+ * Backtest.background("BTCUSDT", {
1149
+ * strategyName: "my-strategy",
1150
+ * exchangeName: "binance",
1151
+ * frameName: "1d-backtest"
1152
+ * });
1153
+ *
1154
+ * // Later: stop listening
1155
+ * unsubscribe();
1156
+ * ```
1157
+ */
1158
+ declare function listenPerformance(fn: (event: PerformanceContract) => void): () => void;
1079
1159
 
1080
1160
  /**
1081
1161
  * Fetches historical candle data from the registered exchange.
@@ -1588,15 +1668,151 @@ declare class LiveMarkdownService {
1588
1668
  protected init: (() => Promise<void>) & functools_kit.ISingleshotClearable;
1589
1669
  }
1590
1670
 
1671
+ /**
1672
+ * Aggregated statistics for a specific metric type.
1673
+ */
1674
+ interface MetricStats {
1675
+ /** Type of metric */
1676
+ metricType: PerformanceMetricType;
1677
+ /** Number of recorded samples */
1678
+ count: number;
1679
+ /** Total duration across all samples (ms) */
1680
+ totalDuration: number;
1681
+ /** Average duration (ms) */
1682
+ avgDuration: number;
1683
+ /** Minimum duration (ms) */
1684
+ minDuration: number;
1685
+ /** Maximum duration (ms) */
1686
+ maxDuration: number;
1687
+ /** Standard deviation of duration (ms) */
1688
+ stdDev: number;
1689
+ /** Median duration (ms) */
1690
+ median: number;
1691
+ /** 95th percentile duration (ms) */
1692
+ p95: number;
1693
+ /** 99th percentile duration (ms) */
1694
+ p99: number;
1695
+ }
1696
+ /**
1697
+ * Performance statistics aggregated by strategy.
1698
+ */
1699
+ interface PerformanceStatistics {
1700
+ /** Strategy name */
1701
+ strategyName: string;
1702
+ /** Total number of performance events recorded */
1703
+ totalEvents: number;
1704
+ /** Total execution time across all metrics (ms) */
1705
+ totalDuration: number;
1706
+ /** Statistics grouped by metric type */
1707
+ metricStats: Record<string, MetricStats>;
1708
+ /** All raw performance events */
1709
+ events: PerformanceContract[];
1710
+ }
1711
+ /**
1712
+ * Service for collecting and analyzing performance metrics.
1713
+ *
1714
+ * Features:
1715
+ * - Listens to performance events via performanceEmitter
1716
+ * - Accumulates metrics per strategy
1717
+ * - Calculates aggregated statistics (avg, min, max, percentiles)
1718
+ * - Generates markdown reports with bottleneck analysis
1719
+ * - Saves reports to disk in logs/performance/{strategyName}.md
1720
+ *
1721
+ * @example
1722
+ * ```typescript
1723
+ * import { listenPerformance } from "backtest-kit";
1724
+ *
1725
+ * // Subscribe to performance events
1726
+ * listenPerformance((event) => {
1727
+ * console.log(`${event.metricType}: ${event.duration.toFixed(2)}ms`);
1728
+ * });
1729
+ *
1730
+ * // After execution, generate report
1731
+ * const stats = await Performance.getData("my-strategy");
1732
+ * console.log("Bottlenecks:", stats.metricStats);
1733
+ *
1734
+ * // Save report to disk
1735
+ * await Performance.dump("my-strategy");
1736
+ * ```
1737
+ */
1738
+ declare class PerformanceMarkdownService {
1739
+ /** Logger service for debug output */
1740
+ private readonly loggerService;
1741
+ /**
1742
+ * Memoized function to get or create PerformanceStorage for a strategy.
1743
+ * Each strategy gets its own isolated storage instance.
1744
+ */
1745
+ private getStorage;
1746
+ /**
1747
+ * Processes performance events and accumulates metrics.
1748
+ * Should be called from performance tracking code.
1749
+ *
1750
+ * @param event - Performance event with timing data
1751
+ */
1752
+ private track;
1753
+ /**
1754
+ * Gets aggregated performance statistics for a strategy.
1755
+ *
1756
+ * @param strategyName - Strategy name to get data for
1757
+ * @returns Performance statistics with aggregated metrics
1758
+ *
1759
+ * @example
1760
+ * ```typescript
1761
+ * const stats = await performanceService.getData("my-strategy");
1762
+ * console.log("Total time:", stats.totalDuration);
1763
+ * console.log("Slowest operation:", Object.values(stats.metricStats)
1764
+ * .sort((a, b) => b.avgDuration - a.avgDuration)[0]);
1765
+ * ```
1766
+ */
1767
+ getData: (strategyName: string) => Promise<PerformanceStatistics>;
1768
+ /**
1769
+ * Generates markdown report with performance analysis.
1770
+ *
1771
+ * @param strategyName - Strategy name to generate report for
1772
+ * @returns Markdown formatted report string
1773
+ *
1774
+ * @example
1775
+ * ```typescript
1776
+ * const markdown = await performanceService.getReport("my-strategy");
1777
+ * console.log(markdown);
1778
+ * ```
1779
+ */
1780
+ getReport: (strategyName: string) => Promise<string>;
1781
+ /**
1782
+ * Saves performance report to disk.
1783
+ *
1784
+ * @param strategyName - Strategy name to save report for
1785
+ * @param path - Directory path to save report
1786
+ *
1787
+ * @example
1788
+ * ```typescript
1789
+ * // Save to default path: ./logs/performance/my-strategy.md
1790
+ * await performanceService.dump("my-strategy");
1791
+ *
1792
+ * // Save to custom path
1793
+ * await performanceService.dump("my-strategy", "./custom/path");
1794
+ * ```
1795
+ */
1796
+ dump: (strategyName: string, path?: string) => Promise<void>;
1797
+ /**
1798
+ * Clears accumulated performance data from storage.
1799
+ *
1800
+ * @param strategyName - Optional strategy name to clear specific strategy data
1801
+ */
1802
+ clear: (strategyName?: string) => Promise<void>;
1803
+ /**
1804
+ * Initializes the service by subscribing to performance events.
1805
+ * Uses singleshot to ensure initialization happens only once.
1806
+ */
1807
+ protected init: (() => Promise<void>) & functools_kit.ISingleshotClearable;
1808
+ }
1809
+
1591
1810
  declare const BASE_WAIT_FOR_INIT_SYMBOL: unique symbol;
1592
1811
  /**
1593
1812
  * Signal data stored in persistence layer.
1594
1813
  * Contains nullable signal for atomic updates.
1595
1814
  */
1596
- interface ISignalData {
1597
- /** Current signal state (null when no active signal) */
1598
- signalRow: ISignalRow | null;
1599
- }
1815
+ type SignalData = ISignalRow | null;
1600
1816
  /**
1601
1817
  * Type helper for PersistBase instance.
1602
1818
  */
@@ -1777,7 +1993,7 @@ declare class PersistSignalUtils {
1777
1993
  * PersistSignalAdaper.usePersistSignalAdapter(RedisPersist);
1778
1994
  * ```
1779
1995
  */
1780
- usePersistSignalAdapter(Ctor: TPersistBaseCtor<StrategyName, ISignalData>): void;
1996
+ usePersistSignalAdapter(Ctor: TPersistBaseCtor<StrategyName, SignalData>): void;
1781
1997
  /**
1782
1998
  * Reads persisted signal data for a strategy and symbol.
1783
1999
  *
@@ -2070,6 +2286,125 @@ declare class LiveUtils {
2070
2286
  */
2071
2287
  declare const Live: LiveUtils;
2072
2288
 
2289
+ /**
2290
+ * Performance class provides static methods for performance metrics analysis.
2291
+ *
2292
+ * Features:
2293
+ * - Get aggregated performance statistics by strategy
2294
+ * - Generate markdown reports with bottleneck analysis
2295
+ * - Save reports to disk
2296
+ * - Clear accumulated metrics
2297
+ *
2298
+ * @example
2299
+ * ```typescript
2300
+ * import { Performance, listenPerformance } from "backtest-kit";
2301
+ *
2302
+ * // Subscribe to performance events
2303
+ * listenPerformance((event) => {
2304
+ * console.log(`${event.metricType}: ${event.duration.toFixed(2)}ms`);
2305
+ * });
2306
+ *
2307
+ * // Run backtest...
2308
+ *
2309
+ * // Get aggregated statistics
2310
+ * const stats = await Performance.getData("my-strategy");
2311
+ * console.log("Total time:", stats.totalDuration);
2312
+ * console.log("Slowest operations:", Object.values(stats.metricStats)
2313
+ * .sort((a, b) => b.avgDuration - a.avgDuration)
2314
+ * .slice(0, 5));
2315
+ *
2316
+ * // Generate and save report
2317
+ * await Performance.dump("my-strategy");
2318
+ * ```
2319
+ */
2320
+ declare class Performance {
2321
+ /**
2322
+ * Gets aggregated performance statistics for a strategy.
2323
+ *
2324
+ * Returns detailed metrics grouped by operation type:
2325
+ * - Count, total duration, average, min, max
2326
+ * - Standard deviation for volatility
2327
+ * - Percentiles (median, P95, P99) for outlier detection
2328
+ *
2329
+ * @param strategyName - Strategy name to analyze
2330
+ * @returns Performance statistics with aggregated metrics
2331
+ *
2332
+ * @example
2333
+ * ```typescript
2334
+ * const stats = await Performance.getData("my-strategy");
2335
+ *
2336
+ * // Find slowest operation type
2337
+ * const slowest = Object.values(stats.metricStats)
2338
+ * .sort((a, b) => b.avgDuration - a.avgDuration)[0];
2339
+ * console.log(`Slowest: ${slowest.metricType} (${slowest.avgDuration.toFixed(2)}ms avg)`);
2340
+ *
2341
+ * // Check for outliers
2342
+ * for (const metric of Object.values(stats.metricStats)) {
2343
+ * if (metric.p99 > metric.avgDuration * 5) {
2344
+ * console.warn(`High variance in ${metric.metricType}: P99=${metric.p99}ms, Avg=${metric.avgDuration}ms`);
2345
+ * }
2346
+ * }
2347
+ * ```
2348
+ */
2349
+ static getData(strategyName: string): Promise<PerformanceStatistics>;
2350
+ /**
2351
+ * Generates markdown report with performance analysis.
2352
+ *
2353
+ * Report includes:
2354
+ * - Time distribution across operation types
2355
+ * - Detailed metrics table with statistics
2356
+ * - Percentile analysis for bottleneck detection
2357
+ *
2358
+ * @param strategyName - Strategy name to generate report for
2359
+ * @returns Markdown formatted report string
2360
+ *
2361
+ * @example
2362
+ * ```typescript
2363
+ * const markdown = await Performance.getReport("my-strategy");
2364
+ * console.log(markdown);
2365
+ *
2366
+ * // Or save to file
2367
+ * import fs from "fs/promises";
2368
+ * await fs.writeFile("performance-report.md", markdown);
2369
+ * ```
2370
+ */
2371
+ static getReport(strategyName: string): Promise<string>;
2372
+ /**
2373
+ * Saves performance report to disk.
2374
+ *
2375
+ * Creates directory if it doesn't exist.
2376
+ * Default path: ./logs/performance/{strategyName}.md
2377
+ *
2378
+ * @param strategyName - Strategy name to save report for
2379
+ * @param path - Optional custom directory path
2380
+ *
2381
+ * @example
2382
+ * ```typescript
2383
+ * // Save to default path: ./logs/performance/my-strategy.md
2384
+ * await Performance.dump("my-strategy");
2385
+ *
2386
+ * // Save to custom path: ./reports/perf/my-strategy.md
2387
+ * await Performance.dump("my-strategy", "./reports/perf");
2388
+ * ```
2389
+ */
2390
+ static dump(strategyName: string, path?: string): Promise<void>;
2391
+ /**
2392
+ * Clears accumulated performance metrics from memory.
2393
+ *
2394
+ * @param strategyName - Optional strategy name to clear specific strategy's metrics
2395
+ *
2396
+ * @example
2397
+ * ```typescript
2398
+ * // Clear specific strategy metrics
2399
+ * await Performance.clear("my-strategy");
2400
+ *
2401
+ * // Clear all metrics for all strategies
2402
+ * await Performance.clear();
2403
+ * ```
2404
+ */
2405
+ static clear(strategyName?: string): Promise<void>;
2406
+ }
2407
+
2073
2408
  /**
2074
2409
  * Logger service with automatic context injection.
2075
2410
  *
@@ -2779,6 +3114,7 @@ declare class BacktestLogicPrivateService {
2779
3114
  declare class LiveLogicPrivateService {
2780
3115
  private readonly loggerService;
2781
3116
  private readonly strategyGlobalService;
3117
+ private readonly methodContextService;
2782
3118
  /**
2783
3119
  * Runs live trading for a symbol, streaming results as async generator.
2784
3120
  *
@@ -3068,6 +3404,7 @@ declare const backtest: {
3068
3404
  frameValidationService: FrameValidationService;
3069
3405
  backtestMarkdownService: BacktestMarkdownService;
3070
3406
  liveMarkdownService: LiveMarkdownService;
3407
+ performanceMarkdownService: PerformanceMarkdownService;
3071
3408
  backtestLogicPublicService: BacktestLogicPublicService;
3072
3409
  liveLogicPublicService: LiveLogicPublicService;
3073
3410
  backtestLogicPrivateService: BacktestLogicPrivateService;
@@ -3092,4 +3429,4 @@ declare const backtest: {
3092
3429
  loggerService: LoggerService;
3093
3430
  };
3094
3431
 
3095
- export { Backtest, type BacktestStatistics, type CandleInterval, type DoneContract, type EntityId, ExecutionContextService, type FrameInterval, type ICandleData, type IExchangeSchema, type IFrameSchema, type IPersistBase, type ISignalData, type ISignalDto, type ISignalRow, type IStrategyPnL, type IStrategySchema, type IStrategyTickResult, type IStrategyTickResultActive, type IStrategyTickResultClosed, type IStrategyTickResultIdle, type IStrategyTickResultOpened, Live, type LiveStatistics, MethodContextService, PersistBase, PersistSignalAdaper, type ProgressContract, type SignalInterval, type TPersistBase, type TPersistBaseCtor, 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 };
3432
+ export { Backtest, type BacktestStatistics, type CandleInterval, type DoneContract, type EntityId, ExecutionContextService, type FrameInterval, type ICandleData, type IExchangeSchema, type IFrameSchema, type IPersistBase, type ISignalDto, type ISignalRow, type IStrategyPnL, type IStrategySchema, type IStrategyTickResult, type IStrategyTickResultActive, type IStrategyTickResultClosed, type IStrategyTickResultIdle, type IStrategyTickResultOpened, Live, type LiveStatistics, MethodContextService, Performance, type PerformanceContract, type PerformanceMetricType, type PerformanceStatistics, PersistBase, PersistSignalAdaper, type ProgressContract, type SignalData, type SignalInterval, type TPersistBase, type TPersistBaseCtor, 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 };