backtest-kit 1.1.5 โ†’ 1.1.7

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
@@ -1,4 +1,4 @@
1
- # Backtest Kit
1
+ # ๐Ÿงฟ Backtest Kit
2
2
 
3
3
  > A production-ready TypeScript framework for backtesting and live trading strategies with crash-safe state persistence, signal validation, and memory-optimized architecture.
4
4
 
@@ -14,15 +14,17 @@
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** - 50+ unit tests covering validation, PNL, callbacks, reports, and event system
26
+ - ๐Ÿ’พ **Zero Data Download** - Unlike Freqtrade, no need to download gigabytes of historical data - plug any data source (CCXT, database, API)
27
+ - ๐Ÿ”’ **Safe Math & Robustness** - All metrics protected against NaN/Infinity with unsafe numeric checks, returns N/A for invalid calculations
26
28
 
27
29
  ## Installation
28
30
 
@@ -106,7 +108,7 @@ addStrategy({
106
108
  interval: "5m", // Throttling: signals generated max once per 5 minutes
107
109
  getSignal: async (symbol) => {
108
110
  // Your signal generation logic
109
- // Validation happens automatically (prices, TP/SL logic, timestamps)
111
+ // Validation happens automatically (prices, TP/SL logic)
110
112
  return {
111
113
  position: "long",
112
114
  note: "BTC breakout",
@@ -114,14 +116,13 @@ addStrategy({
114
116
  priceTakeProfit: 51000, // Must be > priceOpen for long
115
117
  priceStopLoss: 49000, // Must be < priceOpen for long
116
118
  minuteEstimatedTime: 60, // Signal duration in minutes
117
- timestamp: Date.now(),
118
119
  };
119
120
  },
120
121
  callbacks: {
121
- onOpen: (backtest, symbol, signal) => {
122
+ onOpen: (symbol, signal, currentPrice, backtest) => {
122
123
  console.log(`[${backtest ? "BT" : "LIVE"}] Signal opened:`, signal.id);
123
124
  },
124
- onClose: (backtest, symbol, priceClose, signal) => {
125
+ onClose: (symbol, signal, priceClose, backtest) => {
125
126
  console.log(`[${backtest ? "BT" : "LIVE"}] Signal closed:`, priceClose);
126
127
  },
127
128
  },
@@ -348,7 +349,6 @@ All signals are validated automatically before execution:
348
349
  priceTakeProfit: 51000, // โœ… 51000 > 50000
349
350
  priceStopLoss: 49000, // โœ… 49000 < 50000
350
351
  minuteEstimatedTime: 60, // โœ… positive
351
- timestamp: Date.now(), // โœ… positive
352
352
  }
353
353
 
354
354
  // โŒ Invalid long signal - throws error
@@ -465,7 +465,26 @@ const stopBacktest = Backtest.background("BTCUSDT", {
465
465
  frameName: "1d-backtest"
466
466
  });
467
467
 
468
- // Generate markdown report
468
+ // Get raw statistical data (Controller)
469
+ const stats = await Backtest.getData("my-strategy");
470
+ console.log(stats);
471
+ // Returns:
472
+ // {
473
+ // signalList: [...], // All closed signals
474
+ // totalSignals: 10,
475
+ // winCount: 7,
476
+ // lossCount: 3,
477
+ // winRate: 70.0, // Percentage (higher is better)
478
+ // avgPnl: 1.23, // Average PNL % (higher is better)
479
+ // totalPnl: 12.30, // Total PNL % (higher is better)
480
+ // stdDev: 2.45, // Standard deviation (lower is better)
481
+ // sharpeRatio: 0.50, // Risk-adjusted return (higher is better)
482
+ // annualizedSharpeRatio: 9.55, // Sharpe ร— โˆš365 (higher is better)
483
+ // certaintyRatio: 1.75, // avgWin / |avgLoss| (higher is better)
484
+ // expectedYearlyReturns: 156 // Estimated yearly trades (higher is better)
485
+ // }
486
+
487
+ // Generate markdown report (View)
469
488
  const markdown = await Backtest.getReport("my-strategy");
470
489
  console.log(markdown);
471
490
 
@@ -476,29 +495,75 @@ await Backtest.dump("my-strategy");
476
495
  await Backtest.dump("my-strategy", "./custom/path");
477
496
  ```
478
497
 
479
- **Report includes:**
480
- - Total closed signals
498
+ **getData() returns BacktestStatistics:**
499
+ - `signalList` - Array of all closed signals
500
+ - `totalSignals` - Total number of closed signals
501
+ - `winCount` / `lossCount` - Number of winning/losing trades
502
+ - `winRate` - Win percentage (higher is better)
503
+ - `avgPnl` - Average PNL percentage (higher is better)
504
+ - `totalPnl` - Total PNL percentage (higher is better)
505
+ - `stdDev` - Standard deviation / volatility (lower is better)
506
+ - `sharpeRatio` - Risk-adjusted return (higher is better)
507
+ - `annualizedSharpeRatio` - Sharpe Ratio ร— โˆš365 (higher is better)
508
+ - `certaintyRatio` - avgWin / |avgLoss| (higher is better)
509
+ - `expectedYearlyReturns` - Estimated number of trades per year (higher is better)
510
+
511
+ **getReport() includes:**
512
+ - All metrics from getData() formatted as markdown
481
513
  - All signal details (prices, TP/SL, PNL, duration, close reason)
482
514
  - Timestamps for each signal
515
+ - "Higher is better" / "Lower is better" annotations
483
516
 
484
517
  ### Live Trading Reports
485
518
 
486
519
  ```typescript
487
520
  import { Live } from "backtest-kit";
488
521
 
489
- // Generate live trading report
522
+ // Get raw statistical data (Controller)
523
+ const stats = await Live.getData("my-strategy");
524
+ console.log(stats);
525
+ // Returns:
526
+ // {
527
+ // eventList: [...], // All events (idle, opened, active, closed)
528
+ // totalEvents: 15,
529
+ // totalClosed: 5,
530
+ // winCount: 3,
531
+ // lossCount: 2,
532
+ // winRate: 60.0, // Percentage (higher is better)
533
+ // avgPnl: 1.23, // Average PNL % (higher is better)
534
+ // totalPnl: 6.15, // Total PNL % (higher is better)
535
+ // stdDev: 1.85, // Standard deviation (lower is better)
536
+ // sharpeRatio: 0.66, // Risk-adjusted return (higher is better)
537
+ // annualizedSharpeRatio: 12.61,// Sharpe ร— โˆš365 (higher is better)
538
+ // certaintyRatio: 2.10, // avgWin / |avgLoss| (higher is better)
539
+ // expectedYearlyReturns: 365 // Estimated yearly trades (higher is better)
540
+ // }
541
+
542
+ // Generate markdown report (View)
490
543
  const markdown = await Live.getReport("my-strategy");
491
544
 
492
545
  // Save to disk (default: ./logs/live/my-strategy.md)
493
546
  await Live.dump("my-strategy");
494
547
  ```
495
548
 
496
- **Report includes:**
497
- - Total events (idle, opened, active, closed)
498
- - Closed signals count
499
- - Win rate (% wins, wins/losses)
500
- - Average PNL percentage
549
+ **getData() returns LiveStatistics:**
550
+ - `eventList` - Array of all events (idle, opened, active, closed)
551
+ - `totalEvents` - Total number of events
552
+ - `totalClosed` - Total number of closed signals
553
+ - `winCount` / `lossCount` - Number of winning/losing trades
554
+ - `winRate` - Win percentage (higher is better)
555
+ - `avgPnl` - Average PNL percentage (higher is better)
556
+ - `totalPnl` - Total PNL percentage (higher is better)
557
+ - `stdDev` - Standard deviation / volatility (lower is better)
558
+ - `sharpeRatio` - Risk-adjusted return (higher is better)
559
+ - `annualizedSharpeRatio` - Sharpe Ratio ร— โˆš365 (higher is better)
560
+ - `certaintyRatio` - avgWin / |avgLoss| (higher is better)
561
+ - `expectedYearlyReturns` - Estimated number of trades per year (higher is better)
562
+
563
+ **getReport() includes:**
564
+ - All metrics from getData() formatted as markdown
501
565
  - Signal-by-signal details with current state
566
+ - "Higher is better" / "Lower is better" annotations
502
567
 
503
568
  **Report example:**
504
569
  ```markdown
@@ -506,8 +571,14 @@ await Live.dump("my-strategy");
506
571
 
507
572
  Total events: 15
508
573
  Closed signals: 5
509
- Win rate: 60.00% (3W / 2L)
510
- Average PNL: +1.23%
574
+ Win rate: 60.00% (3W / 2L) (higher is better)
575
+ Average PNL: +1.23% (higher is better)
576
+ Total PNL: +6.15% (higher is better)
577
+ Standard Deviation: 1.85% (lower is better)
578
+ Sharpe Ratio: 0.66 (higher is better)
579
+ Annualized Sharpe Ratio: 12.61 (higher is better)
580
+ Certainty Ratio: 2.10 (higher is better)
581
+ Expected Yearly Returns: 365 trades (higher is better)
511
582
 
512
583
  | Timestamp | Action | Symbol | Signal ID | Position | ... | PNL (net) | Close Reason |
513
584
  |-----------|--------|--------|-----------|----------|-----|-----------|--------------|
@@ -744,7 +815,7 @@ const quantity = await formatQuantity("BTCUSDT", 0.123456789);
744
815
  #### Backtest API
745
816
 
746
817
  ```typescript
747
- import { Backtest } from "backtest-kit";
818
+ import { Backtest, BacktestStatistics } from "backtest-kit";
748
819
 
749
820
  // Stream backtest results
750
821
  Backtest.run(
@@ -762,7 +833,10 @@ Backtest.background(
762
833
  context: { strategyName, exchangeName, frameName }
763
834
  ): Promise<() => void> // Returns cancellation function
764
835
 
765
- // Generate markdown report
836
+ // Get raw statistical data (Controller)
837
+ Backtest.getData(strategyName: string): Promise<BacktestStatistics>
838
+
839
+ // Generate markdown report (View)
766
840
  Backtest.getReport(strategyName: string): Promise<string>
767
841
 
768
842
  // Save report to disk
@@ -772,7 +846,7 @@ Backtest.dump(strategyName: string, path?: string): Promise<void>
772
846
  #### Live Trading API
773
847
 
774
848
  ```typescript
775
- import { Live } from "backtest-kit";
849
+ import { Live, LiveStatistics } from "backtest-kit";
776
850
 
777
851
  // Stream live results (infinite)
778
852
  Live.run(
@@ -789,7 +863,10 @@ Live.background(
789
863
  context: { strategyName, exchangeName }
790
864
  ): Promise<() => void> // Returns cancellation function
791
865
 
792
- // Generate markdown report
866
+ // Get raw statistical data (Controller)
867
+ Live.getData(strategyName: string): Promise<LiveStatistics>
868
+
869
+ // Generate markdown report (View)
793
870
  Live.getReport(strategyName: string): Promise<string>
794
871
 
795
872
  // Save report to disk
@@ -798,18 +875,58 @@ Live.dump(strategyName: string, path?: string): Promise<void>
798
875
 
799
876
  ## Type Definitions
800
877
 
878
+ ### Statistics Types
879
+
880
+ ```typescript
881
+ // Backtest statistics (exported from "backtest-kit")
882
+ interface BacktestStatistics {
883
+ signalList: IStrategyTickResultClosed[]; // All closed signals
884
+ totalSignals: number;
885
+ winCount: number;
886
+ lossCount: number;
887
+ winRate: number | null; // Win percentage (higher is better)
888
+ avgPnl: number | null; // Average PNL % (higher is better)
889
+ totalPnl: number | null; // Total PNL % (higher is better)
890
+ stdDev: number | null; // Standard deviation (lower is better)
891
+ sharpeRatio: number | null; // Risk-adjusted return (higher is better)
892
+ annualizedSharpeRatio: number | null; // Sharpe ร— โˆš365 (higher is better)
893
+ certaintyRatio: number | null; // avgWin / |avgLoss| (higher is better)
894
+ expectedYearlyReturns: number | null; // Estimated yearly trades (higher is better)
895
+ }
896
+
897
+ // Live statistics (exported from "backtest-kit")
898
+ interface LiveStatistics {
899
+ eventList: TickEvent[]; // All events (idle, opened, active, closed)
900
+ totalEvents: number;
901
+ totalClosed: number;
902
+ winCount: number;
903
+ lossCount: number;
904
+ winRate: number | null; // Win percentage (higher is better)
905
+ avgPnl: number | null; // Average PNL % (higher is better)
906
+ totalPnl: number | null; // Total PNL % (higher is better)
907
+ stdDev: number | null; // Standard deviation (lower is better)
908
+ sharpeRatio: number | null; // Risk-adjusted return (higher is better)
909
+ annualizedSharpeRatio: number | null; // Sharpe ร— โˆš365 (higher is better)
910
+ certaintyRatio: number | null; // avgWin / |avgLoss| (higher is better)
911
+ expectedYearlyReturns: number | null; // Estimated yearly trades (higher is better)
912
+ }
913
+ ```
914
+
801
915
  ### Signal Data
802
916
 
803
917
  ```typescript
804
918
  interface ISignalRow {
805
- id: string; // Auto-generated
919
+ id: string; // UUID v4 auto-generated
806
920
  position: "long" | "short";
807
- note: string;
921
+ note?: string;
808
922
  priceOpen: number;
809
923
  priceTakeProfit: number;
810
924
  priceStopLoss: number;
811
925
  minuteEstimatedTime: number;
812
- timestamp: number;
926
+ exchangeName: string;
927
+ strategyName: string;
928
+ timestamp: number; // Signal creation timestamp
929
+ symbol: string; // Trading pair (e.g., "BTCUSDT")
813
930
  }
814
931
  ```
815
932
 
@@ -817,9 +934,27 @@ interface ISignalRow {
817
934
 
818
935
  ```typescript
819
936
  type IStrategyTickResult =
820
- | { action: "idle"; signal: null }
821
- | { action: "opened"; signal: ISignalRow }
822
- | { action: "active"; signal: ISignalRow; currentPrice: number }
937
+ | {
938
+ action: "idle";
939
+ signal: null;
940
+ strategyName: string;
941
+ exchangeName: string;
942
+ currentPrice: number;
943
+ }
944
+ | {
945
+ action: "opened";
946
+ signal: ISignalRow;
947
+ strategyName: string;
948
+ exchangeName: string;
949
+ currentPrice: number;
950
+ }
951
+ | {
952
+ action: "active";
953
+ signal: ISignalRow;
954
+ currentPrice: number;
955
+ strategyName: string;
956
+ exchangeName: string;
957
+ }
823
958
  | {
824
959
  action: "closed";
825
960
  signal: ISignalRow;
@@ -827,10 +962,12 @@ type IStrategyTickResult =
827
962
  closeReason: "take_profit" | "stop_loss" | "time_expired";
828
963
  closeTimestamp: number;
829
964
  pnl: {
830
- priceOpenWithCosts: number;
831
- priceCloseWithCosts: number;
832
965
  pnlPercentage: number;
966
+ priceOpen: number; // Entry price adjusted with slippage and fees
967
+ priceClose: number; // Exit price adjusted with slippage and fees
833
968
  };
969
+ strategyName: string;
970
+ exchangeName: string;
834
971
  };
835
972
  ```
836
973