backtest-kit 1.1.1 → 1.1.2

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
@@ -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
- // Generate and save report
170
- const markdown = await Backtest.getReport("my-strategy");
171
- await Backtest.dump("my-strategy"); // ./logs/backtest/my-strategy.md
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";
@@ -4131,6 +4203,12 @@ class BacktestUtils {
4131
4203
  break;
4132
4204
  }
4133
4205
  }
4206
+ await doneEmitter.next({
4207
+ exchangeName: context.exchangeName,
4208
+ strategyName: context.strategyName,
4209
+ backtest: true,
4210
+ symbol,
4211
+ });
4134
4212
  };
4135
4213
  task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
4136
4214
  return () => {
@@ -4293,6 +4371,12 @@ class LiveUtils {
4293
4371
  break;
4294
4372
  }
4295
4373
  }
4374
+ await doneEmitter.next({
4375
+ exchangeName: context.exchangeName,
4376
+ strategyName: context.strategyName,
4377
+ backtest: false,
4378
+ symbol,
4379
+ });
4296
4380
  };
4297
4381
  task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
4298
4382
  return () => {
@@ -4374,6 +4458,8 @@ exports.getCandles = getCandles;
4374
4458
  exports.getDate = getDate;
4375
4459
  exports.getMode = getMode;
4376
4460
  exports.lib = backtest;
4461
+ exports.listenDone = listenDone;
4462
+ exports.listenDoneOnce = listenDoneOnce;
4377
4463
  exports.listenError = listenError;
4378
4464
  exports.listenSignal = listenSignal;
4379
4465
  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";
@@ -4129,6 +4201,12 @@ class BacktestUtils {
4129
4201
  break;
4130
4202
  }
4131
4203
  }
4204
+ await doneEmitter.next({
4205
+ exchangeName: context.exchangeName,
4206
+ strategyName: context.strategyName,
4207
+ backtest: true,
4208
+ symbol,
4209
+ });
4132
4210
  };
4133
4211
  task().catch((error) => errorEmitter.next(new Error(getErrorMessage(error))));
4134
4212
  return () => {
@@ -4291,6 +4369,12 @@ class LiveUtils {
4291
4369
  break;
4292
4370
  }
4293
4371
  }
4372
+ await doneEmitter.next({
4373
+ exchangeName: context.exchangeName,
4374
+ strategyName: context.strategyName,
4375
+ backtest: false,
4376
+ symbol,
4377
+ });
4294
4378
  };
4295
4379
  task().catch((error) => errorEmitter.next(new Error(getErrorMessage(error))));
4296
4380
  return () => {
@@ -4356,4 +4440,4 @@ class LiveUtils {
4356
4440
  */
4357
4441
  const Live = new LiveUtils();
4358
4442
 
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 };
4443
+ 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.1",
3
+ "version": "1.1.2",
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 };