backtest-kit 1.5.2 โ 1.5.4
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 +177 -7
- package/build/index.cjs +1155 -268
- package/build/index.mjs +1156 -269
- package/package.json +2 -2
- package/types.d.ts +186 -6
package/README.md
CHANGED
|
@@ -227,6 +227,7 @@ Backtest.background("BTCUSDT", {
|
|
|
227
227
|
|
|
228
228
|
- ๐ค **Mode Switching**: Seamlessly switch between backtest and live modes with identical strategy code. ๐
|
|
229
229
|
- ๐ **Crash Recovery**: Atomic persistence ensures state recovery after crashesโno duplicate signals. ๐๏ธ
|
|
230
|
+
- ๐ **Graceful Shutdown**: Stop backtests, live trading, and walkers programmatically with `Backtest.stop()`, `Live.stop()`, and `Walker.stop()`. Current signals complete normally, no forced closures. โน๏ธ
|
|
230
231
|
- ๐ ๏ธ **Custom Validators**: Define validation rules with strategy-level throttling and price logic checks. ๐ง
|
|
231
232
|
- ๐ก๏ธ **Signal Lifecycle**: Type-safe state machine prevents invalid state transitions. ๐
|
|
232
233
|
- ๐ฆ **Dependency Inversion**: Lazy-load components at runtime for modular, scalable designs. ๐งฉ
|
|
@@ -250,6 +251,7 @@ Backtest.background("BTCUSDT", {
|
|
|
250
251
|
- ๐ค **`addStrategy`**: Create trading strategies with custom signals and callbacks. ๐ก
|
|
251
252
|
- ๐ **`addFrame`**: Configure timeframes for backtesting. ๐
|
|
252
253
|
- ๐ **`Backtest` / `Live`**: Run strategies in backtest or live mode (generator or background). โก
|
|
254
|
+
- ๐ **`Backtest.stop()` / `Live.stop()` / `Walker.stop()`**: Gracefully stop running strategiesโcurrent signals complete, no forced exits. โน๏ธ
|
|
253
255
|
- ๐
**`Schedule`**: Track scheduled signals and cancellation rate for limit orders. ๐
|
|
254
256
|
- ๐ **`Partial`**: Access partial profit/loss statistics and reports for risk management. Track signals reaching milestone levels (10%, 20%, 30%, etc.). ๐น
|
|
255
257
|
- ๐ฏ **`Constant`**: Kelly Criterion-based constants for optimal take profit (TP_LEVEL1-3) and stop loss (SL_LEVEL1-2) levels. ๐
|
|
@@ -365,6 +367,12 @@ listenDoneBacktest((event) => {
|
|
|
365
367
|
Backtest.dump(event.strategyName); // ./logs/backtest/my-strategy.md
|
|
366
368
|
});
|
|
367
369
|
|
|
370
|
+
// Graceful shutdown - stop backtest programmatically
|
|
371
|
+
await Backtest.stop("BTCUSDT", "my-strategy");
|
|
372
|
+
// - Current signal completes execution (onClose callback fires)
|
|
373
|
+
// - No new signals are generated after stop
|
|
374
|
+
// - listenDoneBacktest event fires when complete
|
|
375
|
+
|
|
368
376
|
// Option 2: Manual iteration (for custom control)
|
|
369
377
|
for await (const result of Backtest.run("BTCUSDT", {
|
|
370
378
|
strategyName: "my-strategy",
|
|
@@ -384,7 +392,7 @@ Live mode automatically persists state to disk with atomic writes:
|
|
|
384
392
|
import { Live, listenSignalLive } from "backtest-kit";
|
|
385
393
|
|
|
386
394
|
// Run live trading in background (infinite loop, crash-safe)
|
|
387
|
-
const
|
|
395
|
+
const cancelFn = Live.background("BTCUSDT", {
|
|
388
396
|
strategyName: "my-strategy",
|
|
389
397
|
exchangeName: "binance"
|
|
390
398
|
});
|
|
@@ -403,7 +411,14 @@ listenSignalLive((event) => {
|
|
|
403
411
|
}
|
|
404
412
|
});
|
|
405
413
|
|
|
406
|
-
//
|
|
414
|
+
// Graceful shutdown - stop live trading programmatically
|
|
415
|
+
await Live.stop("BTCUSDT", "my-strategy");
|
|
416
|
+
// - Active signal completes normally (no forced close)
|
|
417
|
+
// - No new signals are generated after stop
|
|
418
|
+
// - State remains persisted for resume on restart
|
|
419
|
+
|
|
420
|
+
// Or use cancelFn() for immediate cancellation
|
|
421
|
+
cancelFn();
|
|
407
422
|
```
|
|
408
423
|
|
|
409
424
|
**Crash Recovery:** If process crashes, restart with same codeโstate automatically recovered from disk (no duplicate signals).
|
|
@@ -426,8 +441,11 @@ addWalker({
|
|
|
426
441
|
onStrategyStart: (strategyName, symbol) => {
|
|
427
442
|
console.log(`Starting strategy: ${strategyName}`);
|
|
428
443
|
},
|
|
429
|
-
onStrategyComplete: (strategyName, symbol, stats) => {
|
|
444
|
+
onStrategyComplete: async (strategyName, symbol, stats) => {
|
|
430
445
|
console.log(`${strategyName} completed:`, stats.sharpeRatio);
|
|
446
|
+
|
|
447
|
+
// Optional: Stop walker early after first strategy completes
|
|
448
|
+
// await Walker.stop("BTCUSDT", "btc-walker");
|
|
431
449
|
},
|
|
432
450
|
onComplete: (results) => {
|
|
433
451
|
console.log("Best strategy:", results.bestStrategy);
|
|
@@ -437,7 +455,7 @@ addWalker({
|
|
|
437
455
|
});
|
|
438
456
|
|
|
439
457
|
// Run walker in background
|
|
440
|
-
Walker.background("BTCUSDT", {
|
|
458
|
+
const cancelFn = Walker.background("BTCUSDT", {
|
|
441
459
|
walkerName: "btc-walker"
|
|
442
460
|
});
|
|
443
461
|
|
|
@@ -447,6 +465,16 @@ listenWalkerComplete((results) => {
|
|
|
447
465
|
Walker.dump("BTCUSDT", results.walkerName); // Save report
|
|
448
466
|
});
|
|
449
467
|
|
|
468
|
+
// Graceful shutdown - stop walker programmatically
|
|
469
|
+
await Walker.stop("BTCUSDT", "btc-walker");
|
|
470
|
+
// - Current strategy completes execution
|
|
471
|
+
// - Remaining strategies in queue won't run
|
|
472
|
+
// - listenWalkerComplete event fires with partial results
|
|
473
|
+
// - Use case: Early termination when first strategy is good enough
|
|
474
|
+
|
|
475
|
+
// Or use cancelFn() for immediate cancellation
|
|
476
|
+
cancelFn();
|
|
477
|
+
|
|
450
478
|
// Get raw comparison data
|
|
451
479
|
const results = await Walker.getData("BTCUSDT", "btc-walker");
|
|
452
480
|
console.log(results);
|
|
@@ -1328,7 +1356,148 @@ listenPartialProfit(({ symbol, signal, price, level, backtest }) => {
|
|
|
1328
1356
|
|
|
1329
1357
|
---
|
|
1330
1358
|
|
|
1331
|
-
### 11.
|
|
1359
|
+
### 11. Graceful Shutdown
|
|
1360
|
+
|
|
1361
|
+
The framework provides graceful shutdown mechanisms for all execution modes: backtests, live trading, and walkers. This ensures clean termination without forced signal closures.
|
|
1362
|
+
|
|
1363
|
+
#### How Graceful Shutdown Works
|
|
1364
|
+
|
|
1365
|
+
When you call `Backtest.stop()`, `Live.stop()`, or `Walker.stop()`:
|
|
1366
|
+
|
|
1367
|
+
1. **Current Signal Completes** - Active signals finish normally (reach TP/SL or expire)
|
|
1368
|
+
2. **No New Signals** - Strategy stops generating new signals after stop is called
|
|
1369
|
+
3. **Callbacks Fire** - All lifecycle callbacks (`onClose`, etc.) execute as expected
|
|
1370
|
+
4. **Events Emitted** - Completion events (`listenDoneBacktest`, `listenDoneLive`, `listenWalkerComplete`) fire
|
|
1371
|
+
5. **State Persisted** - In live mode, final state is saved to disk
|
|
1372
|
+
|
|
1373
|
+
#### Backtest Shutdown
|
|
1374
|
+
|
|
1375
|
+
```typescript
|
|
1376
|
+
import { Backtest, listenDoneBacktest } from "backtest-kit";
|
|
1377
|
+
|
|
1378
|
+
Backtest.background("BTCUSDT", {
|
|
1379
|
+
strategyName: "my-strategy",
|
|
1380
|
+
exchangeName: "binance",
|
|
1381
|
+
frameName: "1d-backtest"
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
// Stop backtest gracefully
|
|
1385
|
+
await Backtest.stop("BTCUSDT", "my-strategy");
|
|
1386
|
+
// - Current active signal completes (TP/SL reached)
|
|
1387
|
+
// - No new signals generated after stop
|
|
1388
|
+
// - listenDoneBacktest event fires
|
|
1389
|
+
|
|
1390
|
+
listenDoneBacktest((event) => {
|
|
1391
|
+
console.log("Backtest stopped:", event.strategyName);
|
|
1392
|
+
});
|
|
1393
|
+
```
|
|
1394
|
+
|
|
1395
|
+
#### Live Trading Shutdown
|
|
1396
|
+
|
|
1397
|
+
```typescript
|
|
1398
|
+
import { Live, listenDoneLive } from "backtest-kit";
|
|
1399
|
+
|
|
1400
|
+
Live.background("BTCUSDT", {
|
|
1401
|
+
strategyName: "my-strategy",
|
|
1402
|
+
exchangeName: "binance"
|
|
1403
|
+
});
|
|
1404
|
+
|
|
1405
|
+
// Stop live trading gracefully
|
|
1406
|
+
await Live.stop("BTCUSDT", "my-strategy");
|
|
1407
|
+
// - Active signal completes normally (no forced close)
|
|
1408
|
+
// - No new signals generated after stop
|
|
1409
|
+
// - State persisted to disk for resume on restart
|
|
1410
|
+
|
|
1411
|
+
listenDoneLive((event) => {
|
|
1412
|
+
console.log("Live trading stopped:", event.strategyName);
|
|
1413
|
+
});
|
|
1414
|
+
```
|
|
1415
|
+
|
|
1416
|
+
#### Walker Shutdown (Early Termination)
|
|
1417
|
+
|
|
1418
|
+
Walker shutdown is particularly useful for early termination when comparing strategies:
|
|
1419
|
+
|
|
1420
|
+
```typescript
|
|
1421
|
+
import { addWalker, Walker, listenWalkerComplete } from "backtest-kit";
|
|
1422
|
+
|
|
1423
|
+
addWalker({
|
|
1424
|
+
walkerName: "btc-walker",
|
|
1425
|
+
exchangeName: "binance",
|
|
1426
|
+
frameName: "1d-backtest",
|
|
1427
|
+
strategies: ["strategy-a", "strategy-b", "strategy-c"],
|
|
1428
|
+
callbacks: {
|
|
1429
|
+
onStrategyComplete: async (strategyName, symbol, stats) => {
|
|
1430
|
+
console.log(`${strategyName} completed with Sharpe: ${stats.sharpeRatio}`);
|
|
1431
|
+
|
|
1432
|
+
// Early termination: Stop walker if first strategy is good enough
|
|
1433
|
+
if (stats.sharpeRatio > 2.0) {
|
|
1434
|
+
console.log("Found excellent strategy, stopping walker early");
|
|
1435
|
+
await Walker.stop("BTCUSDT", "btc-walker");
|
|
1436
|
+
}
|
|
1437
|
+
},
|
|
1438
|
+
},
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
Walker.background("BTCUSDT", {
|
|
1442
|
+
walkerName: "btc-walker"
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
// Or stop walker manually after some condition
|
|
1446
|
+
await Walker.stop("BTCUSDT", "btc-walker");
|
|
1447
|
+
// - Current strategy completes execution
|
|
1448
|
+
// - Remaining strategies (not yet started) won't run
|
|
1449
|
+
// - listenWalkerComplete fires with partial results
|
|
1450
|
+
|
|
1451
|
+
listenWalkerComplete((results) => {
|
|
1452
|
+
console.log("Walker stopped early:", results.bestStrategy);
|
|
1453
|
+
console.log(`Tested ${results.strategies.length}/3 strategies`);
|
|
1454
|
+
});
|
|
1455
|
+
```
|
|
1456
|
+
|
|
1457
|
+
#### Multiple Walkers on Same Symbol
|
|
1458
|
+
|
|
1459
|
+
Walker shutdown only affects the specified walker, not others running on the same symbol:
|
|
1460
|
+
|
|
1461
|
+
```typescript
|
|
1462
|
+
// Start two independent walkers on same symbol
|
|
1463
|
+
Walker.background("BTCUSDT", { walkerName: "walker-A" });
|
|
1464
|
+
Walker.background("BTCUSDT", { walkerName: "walker-B" });
|
|
1465
|
+
|
|
1466
|
+
// Stop walker-A only
|
|
1467
|
+
await Walker.stop("BTCUSDT", "walker-A");
|
|
1468
|
+
// - walker-A stops gracefully
|
|
1469
|
+
// - walker-B continues unaffected
|
|
1470
|
+
```
|
|
1471
|
+
|
|
1472
|
+
#### Use Cases
|
|
1473
|
+
|
|
1474
|
+
1. **Backtest Early Exit** - Stop backtest when strategy performs poorly (e.g., drawdown > 10%)
|
|
1475
|
+
2. **Live Trading Maintenance** - Gracefully stop live trading for system updates
|
|
1476
|
+
3. **Walker Optimization** - Skip remaining strategies when first one is excellent
|
|
1477
|
+
4. **Resource Management** - Stop long-running backtests to free up resources
|
|
1478
|
+
5. **Conditional Termination** - Stop based on external events (API limits, market conditions)
|
|
1479
|
+
|
|
1480
|
+
#### Best Practices
|
|
1481
|
+
|
|
1482
|
+
1. **Always await stop()** - Ensure graceful shutdown completes before exiting process
|
|
1483
|
+
2. **Use listenDone events** - Track completion with `listenDoneBacktest`, `listenDoneLive`, `listenWalkerComplete`
|
|
1484
|
+
3. **Don't force-kill** - Let signals complete naturally instead of process.exit()
|
|
1485
|
+
4. **Save reports** - Call `dump()` methods before stopping to preserve results
|
|
1486
|
+
5. **Test shutdown paths** - Write tests that verify graceful shutdown behavior
|
|
1487
|
+
|
|
1488
|
+
```typescript
|
|
1489
|
+
// GOOD - Graceful shutdown with cleanup
|
|
1490
|
+
await Backtest.stop("BTCUSDT", "my-strategy");
|
|
1491
|
+
await Backtest.dump("my-strategy"); // Save report
|
|
1492
|
+
console.log("Shutdown complete");
|
|
1493
|
+
|
|
1494
|
+
// BAD - Forced exit without cleanup
|
|
1495
|
+
process.exit(0); // Signals may not complete, callbacks may not fire
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
---
|
|
1499
|
+
|
|
1500
|
+
### 12. Scheduled Signal Persistence
|
|
1332
1501
|
|
|
1333
1502
|
The framework includes a separate persistence system for scheduled signals (`PersistScheduleAdapter`) that works independently from pending/active signal persistence (`PersistSignalAdapter`). This separation ensures crash-safe recovery of both signal types.
|
|
1334
1503
|
|
|
@@ -2265,16 +2434,17 @@ await setConfig({
|
|
|
2265
2434
|
|
|
2266
2435
|
## โ
Tested & Reliable
|
|
2267
2436
|
|
|
2268
|
-
`backtest-kit` comes with **
|
|
2437
|
+
`backtest-kit` comes with **244 unit and integration tests** covering:
|
|
2269
2438
|
|
|
2270
2439
|
- Signal validation and throttling
|
|
2271
2440
|
- PNL calculation with fees and slippage
|
|
2272
2441
|
- Crash recovery and state persistence
|
|
2273
2442
|
- Dual-layer persistence (pending signals and scheduled signals)
|
|
2274
2443
|
- Crash recovery validation (exchange/strategy name mismatch protection)
|
|
2444
|
+
- Graceful shutdown (Backtest.stop, Live.stop, Walker.stop) with signal completion
|
|
2275
2445
|
- Callback execution order (onSchedule, onOpen, onActive, onClose, onCancel)
|
|
2276
2446
|
- Markdown report generation (backtest, live, scheduled signals)
|
|
2277
|
-
- Walker strategy comparison
|
|
2447
|
+
- Walker strategy comparison and early termination
|
|
2278
2448
|
- Heatmap portfolio analysis
|
|
2279
2449
|
- Position sizing calculations
|
|
2280
2450
|
- Risk management validation
|