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 +169 -32
- package/build/index.cjs +278 -36
- package/build/index.mjs +278 -36
- package/package.json +1 -1
- package/types.d.ts +448 -264
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
|
-
-
|
|
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** -
|
|
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
|
|
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: (
|
|
122
|
+
onOpen: (symbol, signal, currentPrice, backtest) => {
|
|
122
123
|
console.log(`[${backtest ? "BT" : "LIVE"}] Signal opened:`, signal.id);
|
|
123
124
|
},
|
|
124
|
-
onClose: (
|
|
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
|
-
//
|
|
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
|
-
**
|
|
480
|
-
-
|
|
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
|
-
//
|
|
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
|
-
**
|
|
497
|
-
-
|
|
498
|
-
-
|
|
499
|
-
-
|
|
500
|
-
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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;
|
|
919
|
+
id: string; // UUID v4 auto-generated
|
|
806
920
|
position: "long" | "short";
|
|
807
|
-
note
|
|
921
|
+
note?: string;
|
|
808
922
|
priceOpen: number;
|
|
809
923
|
priceTakeProfit: number;
|
|
810
924
|
priceStopLoss: number;
|
|
811
925
|
minuteEstimatedTime: number;
|
|
812
|
-
|
|
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
|
-
| {
|
|
821
|
-
|
|
822
|
-
|
|
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
|
|