backtest-kit 1.1.9 โ†’ 1.2.0

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
@@ -10,10 +10,6 @@ Build sophisticated trading systems with confidence. Backtest Kit empowers you t
10
10
 
11
11
  ๐Ÿ“š **[API Reference](https://github.com/tripolskypetr/backtest-kit)** | ๐ŸŒŸ **[Quick Start](#quick-start)**
12
12
 
13
- ## ๐ŸŽฏ Supported Order Types
14
-
15
- Backtest Kit supports multiple execution styles to match real trading behavior:
16
-
17
13
  ## โœจ Why Choose Backtest Kit?
18
14
 
19
15
  - ๐Ÿš€ **Production-Ready Architecture**: Seamlessly switch between backtest and live modes with robust error recovery and graceful shutdown mechanisms. Your strategy code remains identical across environments. โœ…
@@ -26,7 +22,7 @@ Backtest Kit supports multiple execution styles to match real trading behavior:
26
22
 
27
23
  - ๐Ÿ“Š **VWAP Pricing**: Volume-weighted average price from last 5 1-minute candles ensures realistic backtest results that match live execution. ๐Ÿ“ˆ
28
24
 
29
- - ๐ŸŽฏ **Type-Safe Signal Lifecycle**: State machine with compile-time guarantees (idle โ†’ opened โ†’ active โ†’ closed). No runtime state confusion. ๐Ÿ”’
25
+ - ๐ŸŽฏ **Type-Safe Signal Lifecycle**: State machine with compile-time guarantees (idle โ†’ scheduled โ†’ opened โ†’ active โ†’ closed/cancelled). No runtime state confusion. ๐Ÿ”’
30
26
 
31
27
  - ๐Ÿ“ˆ **Accurate PNL Calculation**: Realistic profit/loss with configurable fees (0.1%) and slippage (0.1%). Track gross and net returns separately. ๐Ÿ’ฐ
32
28
 
@@ -50,26 +46,28 @@ Backtest Kit supports multiple execution styles to match real trading behavior:
50
46
 
51
47
  - ๐Ÿ”’ **Safe Math & Robustness**: All metrics protected against NaN/Infinity with unsafe numeric checks. Returns N/A for invalid calculations. โœจ
52
48
 
53
- - ๐Ÿงช **Comprehensive Test Coverage**: 109 unit and integration tests covering validation, PNL, callbacks, reports, performance tracking, walker, heatmap, position sizing, risk management, and event system. โœ…
49
+ - ๐Ÿงช **Comprehensive Test Coverage**: 123 unit and integration tests covering validation, PNL, callbacks, reports, performance tracking, walker, heatmap, position sizing, risk management, scheduled signals, and event system. โœ…
54
50
 
55
51
  ---
56
52
 
57
- ### โœ… Built-in Order Types
53
+ ### ๐ŸŽณ Supported Order Types
54
+
55
+ Backtest Kit supports multiple execution styles to match real trading behavior:
58
56
 
59
57
  - **Market** โ€” instant execution using current VWAP
60
-
58
+
61
59
  - **Limit** โ€” entry at a specified `priceOpen`
62
-
60
+
63
61
  - **Take Profit (TP)** โ€” automatic exit at the target price
64
-
62
+
65
63
  - **Stop Loss (SL)** โ€” protective exit at the stop level
66
-
64
+
67
65
  - **OCO (TP + SL)** โ€” linked exits; one cancels the other
68
-
69
- - **Time-Expired** โ€” automatic closure after `minuteEstimatedTime` โฑ๏ธ
66
+
67
+ - **Grid** โ€” auto-cancel if price never reaches entry point or hits SL before activation
70
68
 
71
69
 
72
- ### โž• Extendable Order Types
70
+ ### ๐Ÿ†• Extendable Order Types
73
71
 
74
72
  Easy to add without modifying the core:
75
73
 
@@ -128,23 +126,32 @@ addStrategy({
128
126
  strategyName: "sma-crossover",
129
127
  interval: "5m", // Throttling: signals generated max once per 5 minutes
130
128
  getSignal: async (symbol) => {
131
- // Your signal generation logic
129
+ const price = await getAveragePrice(symbol);
132
130
  return {
133
131
  position: "long",
134
132
  note: "BTC breakout",
135
- priceOpen: 50000,
136
- priceTakeProfit: 51000, // Must be > priceOpen for long
137
- priceStopLoss: 49000, // Must be < priceOpen for long
138
- minuteEstimatedTime: 60, // Signal duration in minutes
133
+ priceOpen: price,
134
+ priceTakeProfit: price + 1_000, // Must be > priceOpen for long
135
+ priceStopLoss: price - 1_000, // Must be < priceOpen for long
136
+ minuteEstimatedTime: 60,
139
137
  };
140
138
  },
141
139
  callbacks: {
140
+ onSchedule: (symbol, signal, currentPrice, backtest) => {
141
+ console.log(`[${backtest ? "BT" : "LIVE"}] Scheduled signal created:`, signal.id);
142
+ },
142
143
  onOpen: (symbol, signal, currentPrice, backtest) => {
143
144
  console.log(`[${backtest ? "BT" : "LIVE"}] Signal opened:`, signal.id);
144
145
  },
146
+ onActive: (symbol, signal, currentPrice, backtest) => {
147
+ console.log(`[${backtest ? "BT" : "LIVE"}] Signal active:`, signal.id);
148
+ },
145
149
  onClose: (symbol, signal, priceClose, backtest) => {
146
150
  console.log(`[${backtest ? "BT" : "LIVE"}] Signal closed:`, priceClose);
147
151
  },
152
+ onCancel: (symbol, signal, currentPrice, backtest) => {
153
+ console.log(`[${backtest ? "BT" : "LIVE"}] Scheduled signal cancelled:`, signal.id);
154
+ },
148
155
  },
149
156
  });
150
157
 
@@ -238,6 +245,7 @@ Backtest.background("BTCUSDT", {
238
245
  - ๐Ÿค– **`addStrategy`**: Create trading strategies with custom signals and callbacks. ๐Ÿ’ก
239
246
  - ๐ŸŒ **`addFrame`**: Configure timeframes for backtesting. ๐Ÿ“…
240
247
  - ๐Ÿ”„ **`Backtest` / `Live`**: Run strategies in backtest or live mode (generator or background). โšก
248
+ - ๐Ÿ“… **`Schedule`**: Track scheduled signals and cancellation rate for limit orders. ๐Ÿ“Š
241
249
  - ๐Ÿƒ **`Walker`**: Compare multiple strategies in parallel with ranking. ๐Ÿ†
242
250
  - ๐Ÿ”ฅ **`Heat`**: Portfolio-wide performance analysis across multiple symbols. ๐Ÿ“Š
243
251
  - ๐Ÿ’ฐ **`PositionSize`**: Calculate position sizes with Fixed %, Kelly Criterion, or ATR-based methods. ๐Ÿ’ต
@@ -303,14 +311,14 @@ addStrategy({
303
311
  strategyName: "my-strategy",
304
312
  interval: "5m", // Throttling: signals generated max once per 5 minutes
305
313
  getSignal: async (symbol) => {
306
- // Your signal generation logic
314
+ const price = await getAveragePrice(symbol);
307
315
  return {
308
316
  position: "long",
309
317
  note: "BTC breakout",
310
- priceOpen: 50000,
311
- priceTakeProfit: 51000, // Must be > priceOpen for long
312
- priceStopLoss: 49000, // Must be < priceOpen for long
313
- minuteEstimatedTime: 60, // Signal duration in minutes
318
+ priceOpen: price,
319
+ priceTakeProfit: price + 1_000, // Must be > priceOpen for long
320
+ priceStopLoss: price - 1_000, // Must be < priceOpen for long
321
+ minuteEstimatedTime: 60,
314
322
  };
315
323
  },
316
324
  callbacks: {
@@ -1062,24 +1070,377 @@ test("Custom Redis adapter works correctly", async ({ pass, fail }) => {
1062
1070
 
1063
1071
  ---
1064
1072
 
1073
+ ## ๐Ÿ“ Architecture Overview
1074
+
1075
+ The framework follows **clean architecture** with:
1076
+
1077
+ - **Client Layer** - Pure business logic without DI (ClientStrategy, ClientExchange, ClientFrame)
1078
+ - **Service Layer** - DI-based services organized by responsibility
1079
+ - **Schema Services** - Registry pattern for configuration
1080
+ - **Connection Services** - Memoized client instance creators
1081
+ - **Global Services** - Context wrappers for public API
1082
+ - **Logic Services** - Async generator orchestration (backtest/live)
1083
+ - **Persistence Layer** - Crash-safe atomic file writes with `PersistSignalAdapter`
1084
+
1085
+ ---
1086
+
1087
+ ## โœ… Signal Validation
1088
+
1089
+ All signals are validated automatically before execution:
1090
+
1091
+ ```typescript
1092
+ // โœ… Valid long signal
1093
+ {
1094
+ position: "long",
1095
+ priceOpen: 50000,
1096
+ priceTakeProfit: 51000, // โœ… 51000 > 50000
1097
+ priceStopLoss: 49000, // โœ… 49000 < 50000
1098
+ minuteEstimatedTime: 60, // โœ… positive
1099
+ }
1100
+
1101
+ // โŒ Invalid long signal - throws error
1102
+ {
1103
+ position: "long",
1104
+ priceOpen: 50000,
1105
+ priceTakeProfit: 49000, // โŒ 49000 < 50000 (must be higher for long)
1106
+ priceStopLoss: 51000, // โŒ 51000 > 50000 (must be lower for long)
1107
+ }
1108
+
1109
+ // โœ… Valid short signal
1110
+ {
1111
+ position: "short",
1112
+ priceOpen: 50000,
1113
+ priceTakeProfit: 49000, // โœ… 49000 < 50000 (profit goes down for short)
1114
+ priceStopLoss: 51000, // โœ… 51000 > 50000 (stop loss goes up for short)
1115
+ }
1116
+ ```
1117
+
1118
+ Validation errors include detailed messages for debugging.
1119
+
1120
+ ---
1121
+
1122
+ ## ๐Ÿง  Interval Throttling
1123
+
1124
+ Prevent signal spam with automatic throttling:
1125
+
1126
+ ```typescript
1127
+ addStrategy({
1128
+ strategyName: "my-strategy",
1129
+ interval: "5m", // Signals generated max once per 5 minutes
1130
+ getSignal: async (symbol) => {
1131
+ // This function will be called max once per 5 minutes
1132
+ // Even if tick() is called every second
1133
+ return signal;
1134
+ },
1135
+ });
1136
+ ```
1137
+
1138
+ Supported intervals: `"1m"`, `"3m"`, `"5m"`, `"15m"`, `"30m"`, `"1h"`
1139
+
1140
+ ---
1141
+
1142
+ ## ๐Ÿ“ Markdown Reports
1143
+
1144
+ Generate detailed trading reports with statistics:
1145
+
1146
+ ### Backtest Reports
1147
+
1148
+ ```typescript
1149
+ import { Backtest } from "backtest-kit";
1150
+
1151
+ // Get raw statistical data (Controller)
1152
+ const stats = await Backtest.getData("my-strategy");
1153
+ console.log(stats);
1154
+ // Returns:
1155
+ // {
1156
+ // signalList: [...], // All closed signals
1157
+ // totalSignals: 10,
1158
+ // winCount: 7,
1159
+ // lossCount: 3,
1160
+ // winRate: 70.0, // Percentage (higher is better)
1161
+ // avgPnl: 1.23, // Average PNL % (higher is better)
1162
+ // totalPnl: 12.30, // Total PNL % (higher is better)
1163
+ // stdDev: 2.45, // Standard deviation (lower is better)
1164
+ // sharpeRatio: 0.50, // Risk-adjusted return (higher is better)
1165
+ // annualizedSharpeRatio: 9.55, // Sharpe ร— โˆš365 (higher is better)
1166
+ // certaintyRatio: 1.75, // avgWin / |avgLoss| (higher is better)
1167
+ // expectedYearlyReturns: 156 // Estimated yearly trades (higher is better)
1168
+ // }
1169
+
1170
+ // Generate markdown report (View)
1171
+ const markdown = await Backtest.getReport("my-strategy");
1172
+
1173
+ // Save to disk (default: ./logs/backtest/my-strategy.md)
1174
+ await Backtest.dump("my-strategy");
1175
+ ```
1176
+
1177
+ ### Live Trading Reports
1178
+
1179
+ ```typescript
1180
+ import { Live } from "backtest-kit";
1181
+
1182
+ // Get raw statistical data (Controller)
1183
+ const stats = await Live.getData("my-strategy");
1184
+ console.log(stats);
1185
+ // Returns:
1186
+ // {
1187
+ // eventList: [...], // All events (idle, scheduled, opened, active, closed, cancelled)
1188
+ // totalEvents: 15,
1189
+ // totalClosed: 5,
1190
+ // winCount: 3,
1191
+ // lossCount: 2,
1192
+ // winRate: 60.0, // Percentage (higher is better)
1193
+ // avgPnl: 1.23, // Average PNL % (higher is better)
1194
+ // totalPnl: 6.15, // Total PNL % (higher is better)
1195
+ // stdDev: 1.85, // Standard deviation (lower is better)
1196
+ // sharpeRatio: 0.66, // Risk-adjusted return (higher is better)
1197
+ // annualizedSharpeRatio: 12.61,// Sharpe ร— โˆš365 (higher is better)
1198
+ // certaintyRatio: 2.10, // avgWin / |avgLoss| (higher is better)
1199
+ // expectedYearlyReturns: 365 // Estimated yearly trades (higher is better)
1200
+ // }
1201
+
1202
+ // Generate markdown report (View)
1203
+ const markdown = await Live.getReport("my-strategy");
1204
+
1205
+ // Save to disk (default: ./logs/live/my-strategy.md)
1206
+ await Live.dump("my-strategy");
1207
+ ```
1208
+
1209
+ ### Scheduled Signals Reports
1210
+
1211
+ ```typescript
1212
+ import { Schedule } from "backtest-kit";
1213
+
1214
+ // Get raw scheduled signals data (Controller)
1215
+ const stats = await Schedule.getData("my-strategy");
1216
+ console.log(stats);
1217
+ // Returns:
1218
+ // {
1219
+ // eventList: [...], // All scheduled/cancelled events
1220
+ // totalEvents: 8,
1221
+ // totalScheduled: 6, // Number of scheduled signals
1222
+ // totalCancelled: 2, // Number of cancelled signals
1223
+ // cancellationRate: 33.33, // Percentage (lower is better)
1224
+ // avgWaitTime: 45.5, // Average wait time for cancelled signals in minutes
1225
+ // }
1226
+
1227
+ // Generate markdown report (View)
1228
+ const markdown = await Schedule.getReport("my-strategy");
1229
+
1230
+ // Save to disk (default: ./logs/schedule/my-strategy.md)
1231
+ await Schedule.dump("my-strategy");
1232
+
1233
+ // Clear accumulated data
1234
+ await Schedule.clear("my-strategy");
1235
+ ```
1236
+
1237
+ **Scheduled Signals Report Example:**
1238
+ ```markdown
1239
+ # Scheduled Signals Report: my-strategy
1240
+
1241
+ | Timestamp | Action | Symbol | Signal ID | Position | Note | Current Price | Entry Price | Take Profit | Stop Loss | Wait Time (min) |
1242
+ |-----------|--------|--------|-----------|----------|------|---------------|-------------|-------------|-----------|-----------------|
1243
+ | 2024-01-15T10:30:00Z | SCHEDULED | BTCUSDT | sig-001 | LONG | BTC breakout | 42150.50 USD | 42000.00 USD | 43000.00 USD | 41000.00 USD | N/A |
1244
+ | 2024-01-15T10:35:00Z | CANCELLED | BTCUSDT | sig-002 | LONG | BTC breakout | 42350.80 USD | 10000.00 USD | 11000.00 USD | 9000.00 USD | 60 |
1245
+
1246
+ **Total events:** 8
1247
+ **Scheduled signals:** 6
1248
+ **Cancelled signals:** 2
1249
+ **Cancellation rate:** 33.33% (lower is better)
1250
+ **Average wait time (cancelled):** 45.50 minutes
1251
+ ```
1252
+
1253
+ ---
1254
+
1255
+ ## ๐ŸŽง Event Listeners
1256
+
1257
+ ### Listen to All Signals (Backtest + Live)
1258
+
1259
+ ```typescript
1260
+ import { listenSignal } from "backtest-kit";
1261
+
1262
+ // Listen to both backtest and live signals
1263
+ listenSignal((event) => {
1264
+ console.log(`[${event.backtest ? "BT" : "LIVE"}] ${event.action}:`, event.signal.id);
1265
+
1266
+ if (event.action === "closed") {
1267
+ console.log("PNL:", event.pnl.pnlPercentage);
1268
+ console.log("Close reason:", event.closeReason);
1269
+ }
1270
+ });
1271
+ ```
1272
+
1273
+ ### Listen Once with Filter
1274
+
1275
+ ```typescript
1276
+ import { listenSignalOnce, listenSignalLiveOnce } from "backtest-kit";
1277
+
1278
+ // Listen once with filter
1279
+ listenSignalOnce(
1280
+ (event) => event.action === "closed" && event.pnl.pnlPercentage > 5,
1281
+ (event) => {
1282
+ console.log("Big win detected:", event.pnl.pnlPercentage);
1283
+ }
1284
+ );
1285
+
1286
+ // Listen once for specific symbol in live mode
1287
+ listenSignalLiveOnce(
1288
+ (event) => event.signal.symbol === "BTCUSDT" && event.action === "opened",
1289
+ (event) => {
1290
+ console.log("BTC signal opened:", event.signal.id);
1291
+ }
1292
+ );
1293
+ ```
1294
+
1295
+ ### Listen to Background Completion
1296
+
1297
+ ```typescript
1298
+ import { listenDoneBacktest, listenDoneLive, listenDoneWalker } from "backtest-kit";
1299
+
1300
+ // Backtest completion
1301
+ listenDoneBacktest((event) => {
1302
+ console.log("Backtest completed:", event.strategyName);
1303
+ console.log("Symbol:", event.symbol);
1304
+ console.log("Exchange:", event.exchangeName);
1305
+ });
1306
+
1307
+ // Live trading completion
1308
+ listenDoneLive((event) => {
1309
+ console.log("Live trading stopped:", event.strategyName);
1310
+ });
1311
+
1312
+ // Walker completion
1313
+ listenDoneWalker((event) => {
1314
+ console.log("Walker completed:", event.strategyName);
1315
+ console.log("Best strategy:", event.bestStrategy);
1316
+ });
1317
+ ```
1318
+
1319
+ ---
1320
+
1321
+ ## โš™๏ธ Global Configuration
1322
+
1323
+ You can customize framework behavior using the `setConfig()` function. This allows you to adjust global parameters without modifying the source code.
1324
+
1325
+ ### Available Configuration Options
1326
+
1327
+ ```typescript
1328
+ import { setConfig } from "backtest-kit";
1329
+
1330
+ // Configure global parameters
1331
+ await setConfig({
1332
+ // Time to wait for scheduled signal activation (in minutes)
1333
+ // If a scheduled signal doesn't activate within this time, it will be cancelled
1334
+ // Default: 120 minutes
1335
+ CC_SCHEDULE_AWAIT_MINUTES: 90,
1336
+
1337
+ // Number of candles to use for average price calculation (VWAP)
1338
+ // Used in both backtest and live modes for price calculations
1339
+ // Default: 5 candles (last 5 minutes when using 1m interval)
1340
+ CC_AVG_PRICE_CANDLES_COUNT: 10,
1341
+ });
1342
+ ```
1343
+
1344
+ ### Configuration Parameters
1345
+
1346
+ #### `CC_SCHEDULE_AWAIT_MINUTES`
1347
+
1348
+ Controls how long scheduled signals wait for activation before being cancelled.
1349
+
1350
+ - **Default:** `120` minutes (2 hours)
1351
+ - **Use case:** Adjust based on market volatility and strategy timeframe
1352
+ - **Example:** Lower for scalping strategies (30-60 min), higher for swing trading (180-360 min)
1353
+
1354
+ ```typescript
1355
+ // For scalping strategies with tight entry windows
1356
+ await setConfig({
1357
+ CC_SCHEDULE_AWAIT_MINUTES: 30,
1358
+ });
1359
+
1360
+ // For swing trading with wider entry windows
1361
+ await setConfig({
1362
+ CC_SCHEDULE_AWAIT_MINUTES: 240,
1363
+ });
1364
+ ```
1365
+
1366
+ #### `CC_AVG_PRICE_CANDLES_COUNT`
1367
+
1368
+ Controls the number of 1-minute candles used for VWAP (Volume Weighted Average Price) calculations.
1369
+
1370
+ - **Default:** `5` candles (5 minutes of data)
1371
+ - **Use case:** Adjust for more stable (higher) or responsive (lower) price calculations
1372
+ - **Impact:** Affects entry/exit prices in both backtest and live modes
1373
+
1374
+ ```typescript
1375
+ // More responsive to recent price changes (3 minutes)
1376
+ await setConfig({
1377
+ CC_AVG_PRICE_CANDLES_COUNT: 3,
1378
+ });
1379
+
1380
+ // More stable, less sensitive to spikes (10 minutes)
1381
+ await setConfig({
1382
+ CC_AVG_PRICE_CANDLES_COUNT: 10,
1383
+ });
1384
+ ```
1385
+
1386
+ ### When to Call `setConfig()`
1387
+
1388
+ Always call `setConfig()` **before** running any strategies to ensure configuration is applied:
1389
+
1390
+ ```typescript
1391
+ import { setConfig, Backtest, Live } from "backtest-kit";
1392
+
1393
+ // 1. Configure framework first
1394
+ await setConfig({
1395
+ CC_SCHEDULE_AWAIT_MINUTES: 90,
1396
+ CC_AVG_PRICE_CANDLES_COUNT: 7,
1397
+ });
1398
+
1399
+ // 2. Then run strategies
1400
+ Backtest.background("BTCUSDT", {
1401
+ strategyName: "my-strategy",
1402
+ exchangeName: "binance",
1403
+ frameName: "1d-backtest"
1404
+ });
1405
+
1406
+ Live.background("ETHUSDT", {
1407
+ strategyName: "my-strategy",
1408
+ exchangeName: "binance"
1409
+ });
1410
+ ```
1411
+
1412
+ ### Partial Configuration
1413
+
1414
+ You can update individual parameters without specifying all of them:
1415
+
1416
+ ```typescript
1417
+ // Only change candle count, keep other defaults
1418
+ await setConfig({
1419
+ CC_AVG_PRICE_CANDLES_COUNT: 8,
1420
+ });
1421
+
1422
+ // Later, only change timeout
1423
+ await setConfig({
1424
+ CC_SCHEDULE_AWAIT_MINUTES: 60,
1425
+ });
1426
+ ```
1427
+
1428
+ ---
1429
+
1065
1430
  ## โœ… Tested & Reliable
1066
1431
 
1067
- `backtest-kit` comes with a robust test suite covering:
1068
- - ๐Ÿ›ก๏ธ **Validation**: Ensures all components (exchanges, strategies, frames, risk profiles) are properly configured. โœ…
1069
- - ๐Ÿš‘ **Recovery**: Handles edge cases like invalid signals or empty outputs. ๐Ÿ› ๏ธ
1070
- - ๐Ÿ”„ **Navigation**: Smoothly switches between backtest and live modes without errors. ๐ŸŒ
1071
- - โšก **Performance**: Efficient memory usage and history management. ๐Ÿ“ˆ
1432
+ `backtest-kit` comes with **123 unit and integration tests** covering:
1072
1433
 
1073
- **109 unit and integration tests** covering:
1074
1434
  - Signal validation and throttling
1075
1435
  - PNL calculation with fees and slippage
1076
1436
  - Crash recovery and state persistence
1077
- - Callback execution order
1078
- - Markdown report generation
1437
+ - Callback execution order (onSchedule, onOpen, onActive, onClose, onCancel)
1438
+ - Markdown report generation (backtest, live, scheduled signals)
1079
1439
  - Walker strategy comparison
1080
1440
  - Heatmap portfolio analysis
1081
1441
  - Position sizing calculations
1082
1442
  - Risk management validation
1443
+ - Scheduled signals lifecycle and cancellation tracking
1083
1444
  - Event system
1084
1445
 
1085
1446
  ---