backtest-kit 1.1.1 → 1.1.3
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 +125 -5
- package/build/index.cjs +93 -5
- package/build/index.mjs +92 -6
- package/package.json +2 -2
- package/types.d.ts +90 -1
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
- ⚡ **Memory Optimized** - Prototype methods + memoization + streaming
|
|
20
20
|
- 🔌 **Flexible Architecture** - Plug your own exchanges and strategies
|
|
21
21
|
- 📝 **Markdown Reports** - Auto-generated trading reports with statistics (win rate, avg PNL)
|
|
22
|
+
- 🛑 **Graceful Shutdown** - Live.background() waits for open positions to close before stopping
|
|
22
23
|
|
|
23
24
|
## Installation
|
|
24
25
|
|
|
@@ -145,7 +146,7 @@ addFrame({
|
|
|
145
146
|
### 4. Run Backtest
|
|
146
147
|
|
|
147
148
|
```typescript
|
|
148
|
-
import { Backtest, listenSignalBacktest, listenError } from "backtest-kit";
|
|
149
|
+
import { Backtest, listenSignalBacktest, listenError, listenDone } from "backtest-kit";
|
|
149
150
|
|
|
150
151
|
// Run backtest in background
|
|
151
152
|
const stopBacktest = Backtest.background("BTCUSDT", {
|
|
@@ -166,15 +167,20 @@ listenError((error) => {
|
|
|
166
167
|
console.error("Error:", error.message);
|
|
167
168
|
});
|
|
168
169
|
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
// Listen to completion
|
|
171
|
+
listenDone((event) => {
|
|
172
|
+
if (event.backtest) {
|
|
173
|
+
console.log("Backtest completed:", event.symbol);
|
|
174
|
+
// Generate and save report
|
|
175
|
+
Backtest.dump(event.strategyName); // ./logs/backtest/my-strategy.md
|
|
176
|
+
}
|
|
177
|
+
});
|
|
172
178
|
```
|
|
173
179
|
|
|
174
180
|
### 5. Run Live Trading (Crash-Safe)
|
|
175
181
|
|
|
176
182
|
```typescript
|
|
177
|
-
import { Live, listenSignalLive, listenError } from "backtest-kit";
|
|
183
|
+
import { Live, listenSignalLive, listenError, listenDone } from "backtest-kit";
|
|
178
184
|
|
|
179
185
|
// Run live trading in background (infinite loop, crash-safe)
|
|
180
186
|
const stop = Live.background("BTCUSDT", {
|
|
@@ -204,6 +210,13 @@ listenError((error) => {
|
|
|
204
210
|
console.error("Error:", error.message);
|
|
205
211
|
});
|
|
206
212
|
|
|
213
|
+
// Listen to completion
|
|
214
|
+
listenDone((event) => {
|
|
215
|
+
if (!event.backtest) {
|
|
216
|
+
console.log("Live trading stopped:", event.symbol);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
207
220
|
// Stop when needed: stop();
|
|
208
221
|
```
|
|
209
222
|
|
|
@@ -285,6 +298,67 @@ All signals are validated automatically before execution:
|
|
|
285
298
|
|
|
286
299
|
Validation errors include detailed messages for debugging.
|
|
287
300
|
|
|
301
|
+
## Custom Persistence Adapter
|
|
302
|
+
|
|
303
|
+
By default, signals are persisted to disk using atomic file writes (`./logs/data/signal/`). You can override the persistence layer with a custom adapter (e.g., Redis, MongoDB):
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { PersistBase, PersistSignalAdaper, ISignalData, EntityId } from "backtest-kit";
|
|
307
|
+
import Redis from "ioredis";
|
|
308
|
+
|
|
309
|
+
// Create custom Redis adapter
|
|
310
|
+
class RedisPersist extends PersistBase {
|
|
311
|
+
private redis = new Redis({
|
|
312
|
+
host: "localhost",
|
|
313
|
+
port: 6379,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
async waitForInit(initial: boolean): Promise<void> {
|
|
317
|
+
// Initialize Redis connection if needed
|
|
318
|
+
await this.redis.ping();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async readValue(entityId: EntityId): Promise<ISignalData> {
|
|
322
|
+
const key = `${this.entityName}:${entityId}`;
|
|
323
|
+
const data = await this.redis.get(key);
|
|
324
|
+
|
|
325
|
+
if (!data) {
|
|
326
|
+
throw new Error(`Entity ${this.entityName}:${entityId} not found`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return JSON.parse(data);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async hasValue(entityId: EntityId): Promise<boolean> {
|
|
333
|
+
const key = `${this.entityName}:${entityId}`;
|
|
334
|
+
const exists = await this.redis.exists(key);
|
|
335
|
+
return exists === 1;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async writeValue(entityId: EntityId, entity: ISignalData): Promise<void> {
|
|
339
|
+
const key = `${this.entityName}:${entityId}`;
|
|
340
|
+
await this.redis.set(key, JSON.stringify(entity));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Register custom adapter
|
|
345
|
+
PersistSignalAdaper.usePersistSignalAdapter(RedisPersist);
|
|
346
|
+
|
|
347
|
+
// Now all signal persistence uses Redis
|
|
348
|
+
Live.background("BTCUSDT", {
|
|
349
|
+
strategyName: "my-strategy",
|
|
350
|
+
exchangeName: "binance"
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**Key methods to implement:**
|
|
355
|
+
- `waitForInit(initial)` - Initialize storage connection
|
|
356
|
+
- `readValue(entityId)` - Read entity from storage
|
|
357
|
+
- `hasValue(entityId)` - Check if entity exists
|
|
358
|
+
- `writeValue(entityId, entity)` - Write entity to storage
|
|
359
|
+
|
|
360
|
+
The adapter is registered globally and applies to all strategies.
|
|
361
|
+
|
|
288
362
|
## Interval Throttling
|
|
289
363
|
|
|
290
364
|
Prevent signal spam with automatic throttling:
|
|
@@ -493,9 +567,55 @@ Live.background("BTCUSDT", {
|
|
|
493
567
|
- `listenSignalBacktestOnce(filter, callback)` - Subscribe to backtest signals once
|
|
494
568
|
- `listenSignalLive(callback)` - Subscribe to live signals only
|
|
495
569
|
- `listenSignalLiveOnce(filter, callback)` - Subscribe to live signals once
|
|
570
|
+
- `listenError(callback)` - Subscribe to background execution errors
|
|
571
|
+
- `listenDone(callback)` - Subscribe to background completion events
|
|
572
|
+
- `listenDoneOnce(filter, callback)` - Subscribe to background completion once
|
|
496
573
|
|
|
497
574
|
All listeners return an `unsubscribe` function. All callbacks are processed sequentially using queued async execution.
|
|
498
575
|
|
|
576
|
+
### Listen to Background Completion
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
import { listenDone, listenDoneOnce, Backtest, Live } from "backtest-kit";
|
|
580
|
+
|
|
581
|
+
// Listen to all completion events
|
|
582
|
+
listenDone((event) => {
|
|
583
|
+
console.log("Execution completed:", {
|
|
584
|
+
mode: event.backtest ? "backtest" : "live",
|
|
585
|
+
symbol: event.symbol,
|
|
586
|
+
strategy: event.strategyName,
|
|
587
|
+
exchange: event.exchangeName,
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// Auto-generate report on completion
|
|
591
|
+
if (event.backtest) {
|
|
592
|
+
Backtest.dump(event.strategyName);
|
|
593
|
+
} else {
|
|
594
|
+
Live.dump(event.strategyName);
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// Wait for specific backtest to complete
|
|
599
|
+
listenDoneOnce(
|
|
600
|
+
(event) => event.backtest && event.symbol === "BTCUSDT",
|
|
601
|
+
(event) => {
|
|
602
|
+
console.log("BTCUSDT backtest finished");
|
|
603
|
+
// Start next backtest or live trading
|
|
604
|
+
Live.background(event.symbol, {
|
|
605
|
+
strategyName: event.strategyName,
|
|
606
|
+
exchangeName: event.exchangeName,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
// Run backtests
|
|
612
|
+
Backtest.background("BTCUSDT", {
|
|
613
|
+
strategyName: "my-strategy",
|
|
614
|
+
exchangeName: "binance",
|
|
615
|
+
frameName: "1d-backtest"
|
|
616
|
+
});
|
|
617
|
+
```
|
|
618
|
+
|
|
499
619
|
## API Reference
|
|
500
620
|
|
|
501
621
|
### High-Level Functions
|
package/build/index.cjs
CHANGED
|
@@ -1585,6 +1585,11 @@ const signalBacktestEmitter = new functoolsKit.Subject();
|
|
|
1585
1585
|
* Emits errors caught in background tasks (Live.background, Backtest.background).
|
|
1586
1586
|
*/
|
|
1587
1587
|
const errorEmitter = new functoolsKit.Subject();
|
|
1588
|
+
/**
|
|
1589
|
+
* Done emitter for background execution completion.
|
|
1590
|
+
* Emits when background tasks complete (Live.background, Backtest.background).
|
|
1591
|
+
*/
|
|
1592
|
+
const doneEmitter = new functoolsKit.Subject();
|
|
1588
1593
|
|
|
1589
1594
|
/**
|
|
1590
1595
|
* Connection service routing strategy operations to correct ClientStrategy instance.
|
|
@@ -1651,12 +1656,12 @@ class StrategyConnectionService {
|
|
|
1651
1656
|
const tick = await strategy.tick();
|
|
1652
1657
|
{
|
|
1653
1658
|
if (this.executionContextService.context.backtest) {
|
|
1654
|
-
signalBacktestEmitter.next(tick);
|
|
1659
|
+
await signalBacktestEmitter.next(tick);
|
|
1655
1660
|
}
|
|
1656
1661
|
if (!this.executionContextService.context.backtest) {
|
|
1657
|
-
signalLiveEmitter.next(tick);
|
|
1662
|
+
await signalLiveEmitter.next(tick);
|
|
1658
1663
|
}
|
|
1659
|
-
signalEmitter.next(tick);
|
|
1664
|
+
await signalEmitter.next(tick);
|
|
1660
1665
|
}
|
|
1661
1666
|
return tick;
|
|
1662
1667
|
};
|
|
@@ -1678,9 +1683,9 @@ class StrategyConnectionService {
|
|
|
1678
1683
|
const tick = await strategy.backtest(candles);
|
|
1679
1684
|
{
|
|
1680
1685
|
if (this.executionContextService.context.backtest) {
|
|
1681
|
-
signalBacktestEmitter.next(tick);
|
|
1686
|
+
await signalBacktestEmitter.next(tick);
|
|
1682
1687
|
}
|
|
1683
|
-
signalEmitter.next(tick);
|
|
1688
|
+
await signalEmitter.next(tick);
|
|
1684
1689
|
}
|
|
1685
1690
|
return tick;
|
|
1686
1691
|
};
|
|
@@ -3724,6 +3729,8 @@ const LISTEN_SIGNAL_LIVE_ONCE_METHOD_NAME = "event.listenSignalLiveOnce";
|
|
|
3724
3729
|
const LISTEN_SIGNAL_BACKTEST_METHOD_NAME = "event.listenSignalBacktest";
|
|
3725
3730
|
const LISTEN_SIGNAL_BACKTEST_ONCE_METHOD_NAME = "event.listenSignalBacktestOnce";
|
|
3726
3731
|
const LISTEN_ERROR_METHOD_NAME = "event.listenError";
|
|
3732
|
+
const LISTEN_DONE_METHOD_NAME = "event.listenDone";
|
|
3733
|
+
const LISTEN_DONE_ONCE_METHOD_NAME = "event.listenDoneOnce";
|
|
3727
3734
|
/**
|
|
3728
3735
|
* Subscribes to all signal events with queued async processing.
|
|
3729
3736
|
*
|
|
@@ -3914,6 +3921,71 @@ function listenError(fn) {
|
|
|
3914
3921
|
backtest$1.loggerService.log(LISTEN_ERROR_METHOD_NAME);
|
|
3915
3922
|
return errorEmitter.subscribe(functoolsKit.queued(async (error) => fn(error)));
|
|
3916
3923
|
}
|
|
3924
|
+
/**
|
|
3925
|
+
* Subscribes to background execution completion events with queued async processing.
|
|
3926
|
+
*
|
|
3927
|
+
* Emits when Live.background() or Backtest.background() completes execution.
|
|
3928
|
+
* Events are processed sequentially in order received, even if callback is async.
|
|
3929
|
+
* Uses queued wrapper to prevent concurrent execution of the callback.
|
|
3930
|
+
*
|
|
3931
|
+
* @param fn - Callback function to handle completion events
|
|
3932
|
+
* @returns Unsubscribe function to stop listening to events
|
|
3933
|
+
*
|
|
3934
|
+
* @example
|
|
3935
|
+
* ```typescript
|
|
3936
|
+
* import { listenDone, Live } from "backtest-kit";
|
|
3937
|
+
*
|
|
3938
|
+
* const unsubscribe = listenDone((event) => {
|
|
3939
|
+
* console.log("Completed:", event.strategyName, event.exchangeName, event.symbol);
|
|
3940
|
+
* if (event.backtest) {
|
|
3941
|
+
* console.log("Backtest mode completed");
|
|
3942
|
+
* }
|
|
3943
|
+
* });
|
|
3944
|
+
*
|
|
3945
|
+
* Live.background("BTCUSDT", {
|
|
3946
|
+
* strategyName: "my-strategy",
|
|
3947
|
+
* exchangeName: "binance"
|
|
3948
|
+
* });
|
|
3949
|
+
*
|
|
3950
|
+
* // Later: stop listening
|
|
3951
|
+
* unsubscribe();
|
|
3952
|
+
* ```
|
|
3953
|
+
*/
|
|
3954
|
+
function listenDone(fn) {
|
|
3955
|
+
backtest$1.loggerService.log(LISTEN_DONE_METHOD_NAME);
|
|
3956
|
+
return doneEmitter.subscribe(functoolsKit.queued(async (event) => fn(event)));
|
|
3957
|
+
}
|
|
3958
|
+
/**
|
|
3959
|
+
* Subscribes to filtered background execution completion events with one-time execution.
|
|
3960
|
+
*
|
|
3961
|
+
* Emits when Live.background() or Backtest.background() completes execution.
|
|
3962
|
+
* Executes callback once and automatically unsubscribes.
|
|
3963
|
+
*
|
|
3964
|
+
* @param filterFn - Predicate to filter which events trigger the callback
|
|
3965
|
+
* @param fn - Callback function to handle the filtered event (called only once)
|
|
3966
|
+
* @returns Unsubscribe function to cancel the listener before it fires
|
|
3967
|
+
*
|
|
3968
|
+
* @example
|
|
3969
|
+
* ```typescript
|
|
3970
|
+
* import { listenDoneOnce, Backtest } from "backtest-kit";
|
|
3971
|
+
*
|
|
3972
|
+
* // Wait for first backtest completion
|
|
3973
|
+
* listenDoneOnce(
|
|
3974
|
+
* (event) => event.backtest && event.symbol === "BTCUSDT",
|
|
3975
|
+
* (event) => console.log("BTCUSDT backtest completed:", event.strategyName)
|
|
3976
|
+
* );
|
|
3977
|
+
*
|
|
3978
|
+
* Backtest.background("BTCUSDT", {
|
|
3979
|
+
* strategyName: "my-strategy",
|
|
3980
|
+
* exchangeName: "binance",
|
|
3981
|
+
* frameName: "1d-backtest"
|
|
3982
|
+
* });
|
|
3983
|
+
* ```
|
|
3984
|
+
*/
|
|
3985
|
+
function listenDoneOnce(filterFn, fn) {
|
|
3986
|
+
backtest$1.loggerService.log(LISTEN_DONE_ONCE_METHOD_NAME);
|
|
3987
|
+
return doneEmitter.filter(filterFn).once(fn);
|
|
3988
|
+
}
|
|
3917
3989
|
|
|
3918
3990
|
const GET_CANDLES_METHOD_NAME = "exchange.getCandles";
|
|
3919
3991
|
const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
|
|
@@ -4091,6 +4163,7 @@ class BacktestUtils {
|
|
|
4091
4163
|
symbol,
|
|
4092
4164
|
context,
|
|
4093
4165
|
});
|
|
4166
|
+
backtest$1.backtestMarkdownService.clear(context.strategyName);
|
|
4094
4167
|
return backtest$1.backtestGlobalService.run(symbol, context);
|
|
4095
4168
|
};
|
|
4096
4169
|
/**
|
|
@@ -4131,6 +4204,12 @@ class BacktestUtils {
|
|
|
4131
4204
|
break;
|
|
4132
4205
|
}
|
|
4133
4206
|
}
|
|
4207
|
+
await doneEmitter.next({
|
|
4208
|
+
exchangeName: context.exchangeName,
|
|
4209
|
+
strategyName: context.strategyName,
|
|
4210
|
+
backtest: true,
|
|
4211
|
+
symbol,
|
|
4212
|
+
});
|
|
4134
4213
|
};
|
|
4135
4214
|
task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
4136
4215
|
return () => {
|
|
@@ -4249,6 +4328,7 @@ class LiveUtils {
|
|
|
4249
4328
|
symbol,
|
|
4250
4329
|
context,
|
|
4251
4330
|
});
|
|
4331
|
+
backtest$1.liveMarkdownService.clear(context.strategyName);
|
|
4252
4332
|
return backtest$1.liveGlobalService.run(symbol, context);
|
|
4253
4333
|
};
|
|
4254
4334
|
/**
|
|
@@ -4293,6 +4373,12 @@ class LiveUtils {
|
|
|
4293
4373
|
break;
|
|
4294
4374
|
}
|
|
4295
4375
|
}
|
|
4376
|
+
await doneEmitter.next({
|
|
4377
|
+
exchangeName: context.exchangeName,
|
|
4378
|
+
strategyName: context.strategyName,
|
|
4379
|
+
backtest: false,
|
|
4380
|
+
symbol,
|
|
4381
|
+
});
|
|
4296
4382
|
};
|
|
4297
4383
|
task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
4298
4384
|
return () => {
|
|
@@ -4374,6 +4460,8 @@ exports.getCandles = getCandles;
|
|
|
4374
4460
|
exports.getDate = getDate;
|
|
4375
4461
|
exports.getMode = getMode;
|
|
4376
4462
|
exports.lib = backtest;
|
|
4463
|
+
exports.listenDone = listenDone;
|
|
4464
|
+
exports.listenDoneOnce = listenDoneOnce;
|
|
4377
4465
|
exports.listenError = listenError;
|
|
4378
4466
|
exports.listenSignal = listenSignal;
|
|
4379
4467
|
exports.listenSignalBacktest = listenSignalBacktest;
|
package/build/index.mjs
CHANGED
|
@@ -1583,6 +1583,11 @@ const signalBacktestEmitter = new Subject();
|
|
|
1583
1583
|
* Emits errors caught in background tasks (Live.background, Backtest.background).
|
|
1584
1584
|
*/
|
|
1585
1585
|
const errorEmitter = new Subject();
|
|
1586
|
+
/**
|
|
1587
|
+
* Done emitter for background execution completion.
|
|
1588
|
+
* Emits when background tasks complete (Live.background, Backtest.background).
|
|
1589
|
+
*/
|
|
1590
|
+
const doneEmitter = new Subject();
|
|
1586
1591
|
|
|
1587
1592
|
/**
|
|
1588
1593
|
* Connection service routing strategy operations to correct ClientStrategy instance.
|
|
@@ -1649,12 +1654,12 @@ class StrategyConnectionService {
|
|
|
1649
1654
|
const tick = await strategy.tick();
|
|
1650
1655
|
{
|
|
1651
1656
|
if (this.executionContextService.context.backtest) {
|
|
1652
|
-
signalBacktestEmitter.next(tick);
|
|
1657
|
+
await signalBacktestEmitter.next(tick);
|
|
1653
1658
|
}
|
|
1654
1659
|
if (!this.executionContextService.context.backtest) {
|
|
1655
|
-
signalLiveEmitter.next(tick);
|
|
1660
|
+
await signalLiveEmitter.next(tick);
|
|
1656
1661
|
}
|
|
1657
|
-
signalEmitter.next(tick);
|
|
1662
|
+
await signalEmitter.next(tick);
|
|
1658
1663
|
}
|
|
1659
1664
|
return tick;
|
|
1660
1665
|
};
|
|
@@ -1676,9 +1681,9 @@ class StrategyConnectionService {
|
|
|
1676
1681
|
const tick = await strategy.backtest(candles);
|
|
1677
1682
|
{
|
|
1678
1683
|
if (this.executionContextService.context.backtest) {
|
|
1679
|
-
signalBacktestEmitter.next(tick);
|
|
1684
|
+
await signalBacktestEmitter.next(tick);
|
|
1680
1685
|
}
|
|
1681
|
-
signalEmitter.next(tick);
|
|
1686
|
+
await signalEmitter.next(tick);
|
|
1682
1687
|
}
|
|
1683
1688
|
return tick;
|
|
1684
1689
|
};
|
|
@@ -3722,6 +3727,8 @@ const LISTEN_SIGNAL_LIVE_ONCE_METHOD_NAME = "event.listenSignalLiveOnce";
|
|
|
3722
3727
|
const LISTEN_SIGNAL_BACKTEST_METHOD_NAME = "event.listenSignalBacktest";
|
|
3723
3728
|
const LISTEN_SIGNAL_BACKTEST_ONCE_METHOD_NAME = "event.listenSignalBacktestOnce";
|
|
3724
3729
|
const LISTEN_ERROR_METHOD_NAME = "event.listenError";
|
|
3730
|
+
const LISTEN_DONE_METHOD_NAME = "event.listenDone";
|
|
3731
|
+
const LISTEN_DONE_ONCE_METHOD_NAME = "event.listenDoneOnce";
|
|
3725
3732
|
/**
|
|
3726
3733
|
* Subscribes to all signal events with queued async processing.
|
|
3727
3734
|
*
|
|
@@ -3912,6 +3919,71 @@ function listenError(fn) {
|
|
|
3912
3919
|
backtest$1.loggerService.log(LISTEN_ERROR_METHOD_NAME);
|
|
3913
3920
|
return errorEmitter.subscribe(queued(async (error) => fn(error)));
|
|
3914
3921
|
}
|
|
3922
|
+
/**
|
|
3923
|
+
* Subscribes to background execution completion events with queued async processing.
|
|
3924
|
+
*
|
|
3925
|
+
* Emits when Live.background() or Backtest.background() completes execution.
|
|
3926
|
+
* Events are processed sequentially in order received, even if callback is async.
|
|
3927
|
+
* Uses queued wrapper to prevent concurrent execution of the callback.
|
|
3928
|
+
*
|
|
3929
|
+
* @param fn - Callback function to handle completion events
|
|
3930
|
+
* @returns Unsubscribe function to stop listening to events
|
|
3931
|
+
*
|
|
3932
|
+
* @example
|
|
3933
|
+
* ```typescript
|
|
3934
|
+
* import { listenDone, Live } from "backtest-kit";
|
|
3935
|
+
*
|
|
3936
|
+
* const unsubscribe = listenDone((event) => {
|
|
3937
|
+
* console.log("Completed:", event.strategyName, event.exchangeName, event.symbol);
|
|
3938
|
+
* if (event.backtest) {
|
|
3939
|
+
* console.log("Backtest mode completed");
|
|
3940
|
+
* }
|
|
3941
|
+
* });
|
|
3942
|
+
*
|
|
3943
|
+
* Live.background("BTCUSDT", {
|
|
3944
|
+
* strategyName: "my-strategy",
|
|
3945
|
+
* exchangeName: "binance"
|
|
3946
|
+
* });
|
|
3947
|
+
*
|
|
3948
|
+
* // Later: stop listening
|
|
3949
|
+
* unsubscribe();
|
|
3950
|
+
* ```
|
|
3951
|
+
*/
|
|
3952
|
+
function listenDone(fn) {
|
|
3953
|
+
backtest$1.loggerService.log(LISTEN_DONE_METHOD_NAME);
|
|
3954
|
+
return doneEmitter.subscribe(queued(async (event) => fn(event)));
|
|
3955
|
+
}
|
|
3956
|
+
/**
|
|
3957
|
+
* Subscribes to filtered background execution completion events with one-time execution.
|
|
3958
|
+
*
|
|
3959
|
+
* Emits when Live.background() or Backtest.background() completes execution.
|
|
3960
|
+
* Executes callback once and automatically unsubscribes.
|
|
3961
|
+
*
|
|
3962
|
+
* @param filterFn - Predicate to filter which events trigger the callback
|
|
3963
|
+
* @param fn - Callback function to handle the filtered event (called only once)
|
|
3964
|
+
* @returns Unsubscribe function to cancel the listener before it fires
|
|
3965
|
+
*
|
|
3966
|
+
* @example
|
|
3967
|
+
* ```typescript
|
|
3968
|
+
* import { listenDoneOnce, Backtest } from "backtest-kit";
|
|
3969
|
+
*
|
|
3970
|
+
* // Wait for first backtest completion
|
|
3971
|
+
* listenDoneOnce(
|
|
3972
|
+
* (event) => event.backtest && event.symbol === "BTCUSDT",
|
|
3973
|
+
* (event) => console.log("BTCUSDT backtest completed:", event.strategyName)
|
|
3974
|
+
* );
|
|
3975
|
+
*
|
|
3976
|
+
* Backtest.background("BTCUSDT", {
|
|
3977
|
+
* strategyName: "my-strategy",
|
|
3978
|
+
* exchangeName: "binance",
|
|
3979
|
+
* frameName: "1d-backtest"
|
|
3980
|
+
* });
|
|
3981
|
+
* ```
|
|
3982
|
+
*/
|
|
3983
|
+
function listenDoneOnce(filterFn, fn) {
|
|
3984
|
+
backtest$1.loggerService.log(LISTEN_DONE_ONCE_METHOD_NAME);
|
|
3985
|
+
return doneEmitter.filter(filterFn).once(fn);
|
|
3986
|
+
}
|
|
3915
3987
|
|
|
3916
3988
|
const GET_CANDLES_METHOD_NAME = "exchange.getCandles";
|
|
3917
3989
|
const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
|
|
@@ -4089,6 +4161,7 @@ class BacktestUtils {
|
|
|
4089
4161
|
symbol,
|
|
4090
4162
|
context,
|
|
4091
4163
|
});
|
|
4164
|
+
backtest$1.backtestMarkdownService.clear(context.strategyName);
|
|
4092
4165
|
return backtest$1.backtestGlobalService.run(symbol, context);
|
|
4093
4166
|
};
|
|
4094
4167
|
/**
|
|
@@ -4129,6 +4202,12 @@ class BacktestUtils {
|
|
|
4129
4202
|
break;
|
|
4130
4203
|
}
|
|
4131
4204
|
}
|
|
4205
|
+
await doneEmitter.next({
|
|
4206
|
+
exchangeName: context.exchangeName,
|
|
4207
|
+
strategyName: context.strategyName,
|
|
4208
|
+
backtest: true,
|
|
4209
|
+
symbol,
|
|
4210
|
+
});
|
|
4132
4211
|
};
|
|
4133
4212
|
task().catch((error) => errorEmitter.next(new Error(getErrorMessage(error))));
|
|
4134
4213
|
return () => {
|
|
@@ -4247,6 +4326,7 @@ class LiveUtils {
|
|
|
4247
4326
|
symbol,
|
|
4248
4327
|
context,
|
|
4249
4328
|
});
|
|
4329
|
+
backtest$1.liveMarkdownService.clear(context.strategyName);
|
|
4250
4330
|
return backtest$1.liveGlobalService.run(symbol, context);
|
|
4251
4331
|
};
|
|
4252
4332
|
/**
|
|
@@ -4291,6 +4371,12 @@ class LiveUtils {
|
|
|
4291
4371
|
break;
|
|
4292
4372
|
}
|
|
4293
4373
|
}
|
|
4374
|
+
await doneEmitter.next({
|
|
4375
|
+
exchangeName: context.exchangeName,
|
|
4376
|
+
strategyName: context.strategyName,
|
|
4377
|
+
backtest: false,
|
|
4378
|
+
symbol,
|
|
4379
|
+
});
|
|
4294
4380
|
};
|
|
4295
4381
|
task().catch((error) => errorEmitter.next(new Error(getErrorMessage(error))));
|
|
4296
4382
|
return () => {
|
|
@@ -4356,4 +4442,4 @@ class LiveUtils {
|
|
|
4356
4442
|
*/
|
|
4357
4443
|
const Live = new LiveUtils();
|
|
4358
4444
|
|
|
4359
|
-
export { Backtest, ExecutionContextService, Live, MethodContextService, PersistBase, PersistSignalAdaper, addExchange, addFrame, addStrategy, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listenError, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, setLogger };
|
|
4445
|
+
export { Backtest, ExecutionContextService, Live, MethodContextService, PersistBase, PersistSignalAdaper, addExchange, addFrame, addStrategy, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listenDone, listenDoneOnce, listenError, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, setLogger };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backtest-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "A TypeScript library for trading system backtest",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Petr Tripolsky",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"build:docs": "rimraf docs && mkdir docs && node ./scripts/dts-docs.cjs ./types.d.ts ./docs",
|
|
43
43
|
"docs:gpt": "npm run build && node ./scripts/gpt-docs.mjs",
|
|
44
44
|
"docs:uml": "npm run build && node ./scripts/uml.mjs",
|
|
45
|
-
"docs:www": "rimraf docs/wwwroot && typedoc",
|
|
45
|
+
"docs:www": "rimraf docs/wwwroot && typedoc && node ./packages/typedoc-yandex-metrica/index.mjs",
|
|
46
46
|
"repl": "dotenv -e .env -- npm run build && node -e \"import('./scripts/repl.mjs')\" --interactive"
|
|
47
47
|
},
|
|
48
48
|
"main": "build/index.cjs",
|
package/types.d.ts
CHANGED
|
@@ -645,6 +645,36 @@ declare function addExchange(exchangeSchema: IExchangeSchema): void;
|
|
|
645
645
|
*/
|
|
646
646
|
declare function addFrame(frameSchema: IFrameSchema): void;
|
|
647
647
|
|
|
648
|
+
/**
|
|
649
|
+
* Contract for background execution completion events.
|
|
650
|
+
*
|
|
651
|
+
* Emitted when Live.background() or Backtest.background() completes execution.
|
|
652
|
+
* Contains metadata about the completed execution context.
|
|
653
|
+
*
|
|
654
|
+
* @example
|
|
655
|
+
* ```typescript
|
|
656
|
+
* import { listenDone } from "backtest-kit";
|
|
657
|
+
*
|
|
658
|
+
* listenDone((event) => {
|
|
659
|
+
* if (event.backtest) {
|
|
660
|
+
* console.log("Backtest completed:", event.symbol);
|
|
661
|
+
* } else {
|
|
662
|
+
* console.log("Live trading completed:", event.symbol);
|
|
663
|
+
* }
|
|
664
|
+
* });
|
|
665
|
+
* ```
|
|
666
|
+
*/
|
|
667
|
+
interface DoneContract {
|
|
668
|
+
/** exchangeName - Name of the exchange used in execution */
|
|
669
|
+
exchangeName: string;
|
|
670
|
+
/** strategyName - Name of the strategy that completed */
|
|
671
|
+
strategyName: string;
|
|
672
|
+
/** backtest - True if backtest mode, false if live mode */
|
|
673
|
+
backtest: boolean;
|
|
674
|
+
/** symbol - Trading symbol (e.g., "BTCUSDT") */
|
|
675
|
+
symbol: string;
|
|
676
|
+
}
|
|
677
|
+
|
|
648
678
|
/**
|
|
649
679
|
* Subscribes to all signal events with queued async processing.
|
|
650
680
|
*
|
|
@@ -814,6 +844,65 @@ declare function listenSignalBacktestOnce(filterFn: (event: IStrategyTickResult)
|
|
|
814
844
|
* ```
|
|
815
845
|
*/
|
|
816
846
|
declare function listenError(fn: (error: Error) => void): () => void;
|
|
847
|
+
/**
|
|
848
|
+
* Subscribes to background execution completion events with queued async processing.
|
|
849
|
+
*
|
|
850
|
+
* Emits when Live.background() or Backtest.background() completes execution.
|
|
851
|
+
* Events are processed sequentially in order received, even if callback is async.
|
|
852
|
+
* Uses queued wrapper to prevent concurrent execution of the callback.
|
|
853
|
+
*
|
|
854
|
+
* @param fn - Callback function to handle completion events
|
|
855
|
+
* @returns Unsubscribe function to stop listening to events
|
|
856
|
+
*
|
|
857
|
+
* @example
|
|
858
|
+
* ```typescript
|
|
859
|
+
* import { listenDone, Live } from "backtest-kit";
|
|
860
|
+
*
|
|
861
|
+
* const unsubscribe = listenDone((event) => {
|
|
862
|
+
* console.log("Completed:", event.strategyName, event.exchangeName, event.symbol);
|
|
863
|
+
* if (event.backtest) {
|
|
864
|
+
* console.log("Backtest mode completed");
|
|
865
|
+
* }
|
|
866
|
+
* });
|
|
867
|
+
*
|
|
868
|
+
* Live.background("BTCUSDT", {
|
|
869
|
+
* strategyName: "my-strategy",
|
|
870
|
+
* exchangeName: "binance"
|
|
871
|
+
* });
|
|
872
|
+
*
|
|
873
|
+
* // Later: stop listening
|
|
874
|
+
* unsubscribe();
|
|
875
|
+
* ```
|
|
876
|
+
*/
|
|
877
|
+
declare function listenDone(fn: (event: DoneContract) => void): () => void;
|
|
878
|
+
/**
|
|
879
|
+
* Subscribes to filtered background execution completion events with one-time execution.
|
|
880
|
+
*
|
|
881
|
+
* Emits when Live.background() or Backtest.background() completes execution.
|
|
882
|
+
* Executes callback once and automatically unsubscribes.
|
|
883
|
+
*
|
|
884
|
+
* @param filterFn - Predicate to filter which events trigger the callback
|
|
885
|
+
* @param fn - Callback function to handle the filtered event (called only once)
|
|
886
|
+
* @returns Unsubscribe function to cancel the listener before it fires
|
|
887
|
+
*
|
|
888
|
+
* @example
|
|
889
|
+
* ```typescript
|
|
890
|
+
* import { listenDoneOnce, Backtest } from "backtest-kit";
|
|
891
|
+
*
|
|
892
|
+
* // Wait for first backtest completion
|
|
893
|
+
* listenDoneOnce(
|
|
894
|
+
* (event) => event.backtest && event.symbol === "BTCUSDT",
|
|
895
|
+
* (event) => console.log("BTCUSDT backtest completed:", event.strategyName)
|
|
896
|
+
* );
|
|
897
|
+
*
|
|
898
|
+
* Backtest.background("BTCUSDT", {
|
|
899
|
+
* strategyName: "my-strategy",
|
|
900
|
+
* exchangeName: "binance",
|
|
901
|
+
* frameName: "1d-backtest"
|
|
902
|
+
* });
|
|
903
|
+
* ```
|
|
904
|
+
*/
|
|
905
|
+
declare function listenDoneOnce(filterFn: (event: DoneContract) => boolean, fn: (event: DoneContract) => void): () => void;
|
|
817
906
|
|
|
818
907
|
/**
|
|
819
908
|
* Fetches historical candle data from the registered exchange.
|
|
@@ -2588,4 +2677,4 @@ declare const backtest: {
|
|
|
2588
2677
|
loggerService: LoggerService;
|
|
2589
2678
|
};
|
|
2590
2679
|
|
|
2591
|
-
export { Backtest, type CandleInterval, 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, MethodContextService, PersistBase, PersistSignalAdaper, type SignalInterval, type TPersistBase, type TPersistBaseCtor, addExchange, addFrame, addStrategy, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listenError, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, setLogger };
|
|
2680
|
+
export { Backtest, type CandleInterval, 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, MethodContextService, PersistBase, PersistSignalAdaper, type SignalInterval, type TPersistBase, type TPersistBaseCtor, addExchange, addFrame, addStrategy, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listenDone, listenDoneOnce, listenError, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, setLogger };
|