backtest-kit 1.1.4 โ†’ 1.1.5

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
@@ -21,6 +21,7 @@
21
21
  - ๐Ÿ“ **Markdown Reports** - Auto-generated trading reports with statistics (win rate, avg PNL)
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
+ - ๐Ÿ” **Schema Reflection API** - listExchanges(), listStrategies(), listFrames() for runtime introspection
24
25
  - ๐Ÿงช **Comprehensive Test Coverage** - 30+ unit tests covering validation, PNL, callbacks, reports, and event system
25
26
 
26
27
  ## Installation
@@ -252,6 +253,75 @@ for await (const result of Live.run("BTCUSDT", {
252
253
  }
253
254
  ```
254
255
 
256
+ ### 7. Schema Reflection API (Optional)
257
+
258
+ Retrieve registered schemas at runtime for debugging, documentation, or building dynamic UIs:
259
+
260
+ ```typescript
261
+ import {
262
+ addExchange,
263
+ addStrategy,
264
+ addFrame,
265
+ listExchanges,
266
+ listStrategies,
267
+ listFrames
268
+ } from "backtest-kit";
269
+
270
+ // Register schemas with notes
271
+ addExchange({
272
+ exchangeName: "binance",
273
+ note: "Binance cryptocurrency exchange with database backend",
274
+ getCandles: async (symbol, interval, since, limit) => [...],
275
+ formatPrice: async (symbol, price) => price.toFixed(2),
276
+ formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
277
+ });
278
+
279
+ addStrategy({
280
+ strategyName: "sma-crossover",
281
+ note: "Simple moving average crossover strategy (50/200)",
282
+ interval: "5m",
283
+ getSignal: async (symbol) => ({...}),
284
+ });
285
+
286
+ addFrame({
287
+ frameName: "january-2024",
288
+ note: "Full month backtest for January 2024",
289
+ interval: "1m",
290
+ startDate: new Date("2024-01-01"),
291
+ endDate: new Date("2024-02-01"),
292
+ });
293
+
294
+ // List all registered schemas
295
+ const exchanges = await listExchanges();
296
+ console.log("Available exchanges:", exchanges.map(e => ({
297
+ name: e.exchangeName,
298
+ note: e.note
299
+ })));
300
+ // Output: [{ name: "binance", note: "Binance cryptocurrency exchange..." }]
301
+
302
+ const strategies = await listStrategies();
303
+ console.log("Available strategies:", strategies.map(s => ({
304
+ name: s.strategyName,
305
+ note: s.note,
306
+ interval: s.interval
307
+ })));
308
+ // Output: [{ name: "sma-crossover", note: "Simple moving average...", interval: "5m" }]
309
+
310
+ const frames = await listFrames();
311
+ console.log("Available frames:", frames.map(f => ({
312
+ name: f.frameName,
313
+ note: f.note,
314
+ period: `${f.startDate.toISOString()} - ${f.endDate.toISOString()}`
315
+ })));
316
+ // Output: [{ name: "january-2024", note: "Full month backtest...", period: "2024-01-01..." }]
317
+ ```
318
+
319
+ **Use cases:**
320
+ - Generate documentation automatically from registered schemas
321
+ - Build admin dashboards showing available strategies and exchanges
322
+ - Create CLI tools with auto-completion based on registered schemas
323
+ - Validate configuration files against registered schemas
324
+
255
325
  ## Architecture Overview
256
326
 
257
327
  The framework follows **clean architecture** with:
@@ -794,41 +864,6 @@ pnl% = (priceOpenWithCosts - priceCloseWithCosts) / priceOpenWithCosts * 100
794
864
  6. **Live Trading Ready** - Full implementation with real-time progression
795
865
  7. **Error Recovery** - Stateless process with disk-based state
796
866
 
797
- ## File Structure
798
-
799
- ```
800
- src/
801
- โ”œโ”€โ”€ client/ # Pure business logic (no DI)
802
- โ”‚ โ”œโ”€โ”€ ClientStrategy.ts # Signal lifecycle + validation + persistence
803
- โ”‚ โ”œโ”€โ”€ ClientExchange.ts # VWAP calculation
804
- โ”‚ โ””โ”€โ”€ ClientFrame.ts # Timeframe generation
805
- โ”œโ”€โ”€ classes/
806
- โ”‚ โ””โ”€โ”€ Persist.ts # Atomic file persistence
807
- โ”œโ”€โ”€ function/ # High-level API
808
- โ”‚ โ”œโ”€โ”€ add.ts # addStrategy, addExchange, addFrame
809
- โ”‚ โ”œโ”€โ”€ exchange.ts # getCandles, getAveragePrice, getDate, getMode
810
- โ”‚ โ””โ”€โ”€ run.ts # DEPRECATED - use logic services instead
811
- โ”œโ”€โ”€ interfaces/ # TypeScript interfaces
812
- โ”‚ โ”œโ”€โ”€ Strategy.interface.ts
813
- โ”‚ โ”œโ”€โ”€ Exchange.interface.ts
814
- โ”‚ โ””โ”€โ”€ Frame.interface.ts
815
- โ”œโ”€โ”€ lib/
816
- โ”‚ โ”œโ”€โ”€ core/ # DI container
817
- โ”‚ โ”œโ”€โ”€ services/
818
- โ”‚ โ”‚ โ”œโ”€โ”€ base/ # LoggerService
819
- โ”‚ โ”‚ โ”œโ”€โ”€ context/ # ExecutionContext, MethodContext
820
- โ”‚ โ”‚ โ”œโ”€โ”€ connection/ # Client instance creators
821
- โ”‚ โ”‚ โ”œโ”€โ”€ global/ # Context wrappers
822
- โ”‚ โ”‚ โ”œโ”€โ”€ schema/ # Registry services
823
- โ”‚ โ”‚ โ””โ”€โ”€ logic/
824
- โ”‚ โ”‚ โ””โ”€โ”€ private/ # Async generator orchestration
825
- โ”‚ โ”‚ โ”œโ”€โ”€ BacktestLogicPrivateService.ts
826
- โ”‚ โ”‚ โ””โ”€โ”€ LiveLogicPrivateService.ts
827
- โ”‚ โ””โ”€โ”€ index.ts # Public API
828
- โ””โ”€โ”€ helpers/
829
- โ””โ”€โ”€ toProfitLossDto.ts # PNL calculation
830
- ```
831
-
832
867
  ## Advanced Examples
833
868
 
834
869
  ### Multi-Symbol Live Trading
@@ -856,6 +891,24 @@ await Promise.all(
856
891
  );
857
892
  ```
858
893
 
894
+ ### Backtest Progress Listener
895
+
896
+ ```typescript
897
+ import { listenProgress, Backtest } from "backtest-kit";
898
+
899
+ listenProgress((event) => {
900
+ console.log(`Progress: ${(event.progress * 100).toFixed(2)}%`);
901
+ console.log(`${event.processedFrames} / ${event.totalFrames} frames`);
902
+ console.log(`Strategy: ${event.strategyName}, Symbol: ${event.symbol}`);
903
+ });
904
+
905
+ Backtest.background("BTCUSDT", {
906
+ strategyName: "my-strategy",
907
+ exchangeName: "binance",
908
+ frameName: "1d-backtest"
909
+ });
910
+ ```
911
+
859
912
  ### Early Termination
860
913
 
861
914
  **Using async generator with break:**
package/build/index.cjs CHANGED
@@ -1129,6 +1129,11 @@ const errorEmitter = new functoolsKit.Subject();
1129
1129
  * Emits when background tasks complete (Live.background, Backtest.background).
1130
1130
  */
1131
1131
  const doneEmitter = new functoolsKit.Subject();
1132
+ /**
1133
+ * Progress emitter for backtest execution progress.
1134
+ * Emits progress updates during backtest execution.
1135
+ */
1136
+ const progressEmitter = new functoolsKit.Subject();
1132
1137
 
1133
1138
  const INTERVAL_MINUTES$1 = {
1134
1139
  "1m": 1,
@@ -2402,6 +2407,7 @@ class BacktestLogicPrivateService {
2402
2407
  this.strategyGlobalService = inject(TYPES.strategyGlobalService);
2403
2408
  this.exchangeGlobalService = inject(TYPES.exchangeGlobalService);
2404
2409
  this.frameGlobalService = inject(TYPES.frameGlobalService);
2410
+ this.methodContextService = inject(TYPES.methodContextService);
2405
2411
  }
2406
2412
  /**
2407
2413
  * Runs backtest for a symbol, streaming closed signals as async generator.
@@ -2422,9 +2428,21 @@ class BacktestLogicPrivateService {
2422
2428
  symbol,
2423
2429
  });
2424
2430
  const timeframes = await this.frameGlobalService.getTimeframe(symbol);
2431
+ const totalFrames = timeframes.length;
2425
2432
  let i = 0;
2426
2433
  while (i < timeframes.length) {
2427
2434
  const when = timeframes[i];
2435
+ // Emit progress event if context is available
2436
+ {
2437
+ await progressEmitter.next({
2438
+ exchangeName: this.methodContextService.context.exchangeName,
2439
+ strategyName: this.methodContextService.context.strategyName,
2440
+ symbol,
2441
+ totalFrames,
2442
+ processedFrames: i,
2443
+ progress: totalFrames > 0 ? i / totalFrames : 0,
2444
+ });
2445
+ }
2428
2446
  const result = await this.strategyGlobalService.tick(symbol, when, true);
2429
2447
  // ะ•ัะปะธ ัะธะณะฝะฐะป ะพั‚ะบั€ั‹ั‚, ะฒั‹ะทั‹ะฒะฐะตะผ backtest
2430
2448
  if (result.action === "opened") {
@@ -2461,6 +2479,17 @@ class BacktestLogicPrivateService {
2461
2479
  }
2462
2480
  i++;
2463
2481
  }
2482
+ // Emit final progress event (100%)
2483
+ {
2484
+ await progressEmitter.next({
2485
+ exchangeName: this.methodContextService.context.exchangeName,
2486
+ strategyName: this.methodContextService.context.strategyName,
2487
+ symbol,
2488
+ totalFrames,
2489
+ processedFrames: totalFrames,
2490
+ progress: 1.0,
2491
+ });
2492
+ }
2464
2493
  }
2465
2494
  }
2466
2495
 
@@ -3479,6 +3508,15 @@ class ExchangeValidationService {
3479
3508
  }
3480
3509
  return true;
3481
3510
  });
3511
+ /**
3512
+ * Returns a list of all registered exchange schemas
3513
+ * @public
3514
+ * @returns Array of exchange schemas with their configurations
3515
+ */
3516
+ this.list = async () => {
3517
+ this.loggerService.log("exchangeValidationService list");
3518
+ return Array.from(this._exchangeMap.values());
3519
+ };
3482
3520
  }
3483
3521
  }
3484
3522
 
@@ -3531,6 +3569,15 @@ class StrategyValidationService {
3531
3569
  }
3532
3570
  return true;
3533
3571
  });
3572
+ /**
3573
+ * Returns a list of all registered strategy schemas
3574
+ * @public
3575
+ * @returns Array of strategy schemas with their configurations
3576
+ */
3577
+ this.list = async () => {
3578
+ this.loggerService.log("strategyValidationService list");
3579
+ return Array.from(this._strategyMap.values());
3580
+ };
3534
3581
  }
3535
3582
  }
3536
3583
 
@@ -3583,6 +3630,15 @@ class FrameValidationService {
3583
3630
  }
3584
3631
  return true;
3585
3632
  });
3633
+ /**
3634
+ * Returns a list of all registered frame schemas
3635
+ * @public
3636
+ * @returns Array of frame schemas with their configurations
3637
+ */
3638
+ this.list = async () => {
3639
+ this.loggerService.log("frameValidationService list");
3640
+ return Array.from(this._frameMap.values());
3641
+ };
3586
3642
  }
3587
3643
  }
3588
3644
 
@@ -3828,6 +3884,102 @@ function addFrame(frameSchema) {
3828
3884
  backtest$1.frameSchemaService.register(frameSchema.frameName, frameSchema);
3829
3885
  }
3830
3886
 
3887
+ const LIST_EXCHANGES_METHOD_NAME = "list.listExchanges";
3888
+ const LIST_STRATEGIES_METHOD_NAME = "list.listStrategies";
3889
+ const LIST_FRAMES_METHOD_NAME = "list.listFrames";
3890
+ /**
3891
+ * Returns a list of all registered exchange schemas.
3892
+ *
3893
+ * Retrieves all exchanges that have been registered via addExchange().
3894
+ * Useful for debugging, documentation, or building dynamic UIs.
3895
+ *
3896
+ * @returns Array of exchange schemas with their configurations
3897
+ *
3898
+ * @example
3899
+ * ```typescript
3900
+ * import { listExchanges, addExchange } from "backtest-kit";
3901
+ *
3902
+ * addExchange({
3903
+ * exchangeName: "binance",
3904
+ * note: "Binance cryptocurrency exchange",
3905
+ * getCandles: async (symbol, interval, since, limit) => [...],
3906
+ * formatPrice: async (symbol, price) => price.toFixed(2),
3907
+ * formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
3908
+ * });
3909
+ *
3910
+ * const exchanges = listExchanges();
3911
+ * console.log(exchanges);
3912
+ * // [{ exchangeName: "binance", note: "Binance cryptocurrency exchange", ... }]
3913
+ * ```
3914
+ */
3915
+ async function listExchanges() {
3916
+ backtest$1.loggerService.log(LIST_EXCHANGES_METHOD_NAME);
3917
+ return await backtest$1.exchangeValidationService.list();
3918
+ }
3919
+ /**
3920
+ * Returns a list of all registered strategy schemas.
3921
+ *
3922
+ * Retrieves all strategies that have been registered via addStrategy().
3923
+ * Useful for debugging, documentation, or building dynamic UIs.
3924
+ *
3925
+ * @returns Array of strategy schemas with their configurations
3926
+ *
3927
+ * @example
3928
+ * ```typescript
3929
+ * import { listStrategies, addStrategy } from "backtest-kit";
3930
+ *
3931
+ * addStrategy({
3932
+ * strategyName: "my-strategy",
3933
+ * note: "Simple moving average crossover strategy",
3934
+ * interval: "5m",
3935
+ * getSignal: async (symbol) => ({
3936
+ * position: "long",
3937
+ * priceOpen: 50000,
3938
+ * priceTakeProfit: 51000,
3939
+ * priceStopLoss: 49000,
3940
+ * minuteEstimatedTime: 60,
3941
+ * }),
3942
+ * });
3943
+ *
3944
+ * const strategies = listStrategies();
3945
+ * console.log(strategies);
3946
+ * // [{ strategyName: "my-strategy", note: "Simple moving average...", ... }]
3947
+ * ```
3948
+ */
3949
+ async function listStrategies() {
3950
+ backtest$1.loggerService.log(LIST_STRATEGIES_METHOD_NAME);
3951
+ return await backtest$1.strategyValidationService.list();
3952
+ }
3953
+ /**
3954
+ * Returns a list of all registered frame schemas.
3955
+ *
3956
+ * Retrieves all frames that have been registered via addFrame().
3957
+ * Useful for debugging, documentation, or building dynamic UIs.
3958
+ *
3959
+ * @returns Array of frame schemas with their configurations
3960
+ *
3961
+ * @example
3962
+ * ```typescript
3963
+ * import { listFrames, addFrame } from "backtest-kit";
3964
+ *
3965
+ * addFrame({
3966
+ * frameName: "1d-backtest",
3967
+ * note: "One day backtest period for testing",
3968
+ * interval: "1m",
3969
+ * startDate: new Date("2024-01-01T00:00:00Z"),
3970
+ * endDate: new Date("2024-01-02T00:00:00Z"),
3971
+ * });
3972
+ *
3973
+ * const frames = listFrames();
3974
+ * console.log(frames);
3975
+ * // [{ frameName: "1d-backtest", note: "One day backtest...", ... }]
3976
+ * ```
3977
+ */
3978
+ async function listFrames() {
3979
+ backtest$1.loggerService.log(LIST_FRAMES_METHOD_NAME);
3980
+ return await backtest$1.frameValidationService.list();
3981
+ }
3982
+
3831
3983
  const LISTEN_SIGNAL_METHOD_NAME = "event.listenSignal";
3832
3984
  const LISTEN_SIGNAL_ONCE_METHOD_NAME = "event.listenSignalOnce";
3833
3985
  const LISTEN_SIGNAL_LIVE_METHOD_NAME = "event.listenSignalLive";
@@ -3837,6 +3989,7 @@ const LISTEN_SIGNAL_BACKTEST_ONCE_METHOD_NAME = "event.listenSignalBacktestOnce"
3837
3989
  const LISTEN_ERROR_METHOD_NAME = "event.listenError";
3838
3990
  const LISTEN_DONE_METHOD_NAME = "event.listenDone";
3839
3991
  const LISTEN_DONE_ONCE_METHOD_NAME = "event.listenDoneOnce";
3992
+ const LISTEN_PROGRESS_METHOD_NAME = "event.listenProgress";
3840
3993
  /**
3841
3994
  * Subscribes to all signal events with queued async processing.
3842
3995
  *
@@ -4092,6 +4245,40 @@ function listenDoneOnce(filterFn, fn) {
4092
4245
  backtest$1.loggerService.log(LISTEN_DONE_ONCE_METHOD_NAME);
4093
4246
  return doneEmitter.filter(filterFn).once(fn);
4094
4247
  }
4248
+ /**
4249
+ * Subscribes to backtest progress events with queued async processing.
4250
+ *
4251
+ * Emits during Backtest.background() execution to track progress.
4252
+ * Events are processed sequentially in order received, even if callback is async.
4253
+ * Uses queued wrapper to prevent concurrent execution of the callback.
4254
+ *
4255
+ * @param fn - Callback function to handle progress events
4256
+ * @returns Unsubscribe function to stop listening to events
4257
+ *
4258
+ * @example
4259
+ * ```typescript
4260
+ * import { listenProgress, Backtest } from "backtest-kit";
4261
+ *
4262
+ * const unsubscribe = listenProgress((event) => {
4263
+ * console.log(`Progress: ${(event.progress * 100).toFixed(2)}%`);
4264
+ * console.log(`${event.processedFrames} / ${event.totalFrames} frames`);
4265
+ * console.log(`Strategy: ${event.strategyName}, Symbol: ${event.symbol}`);
4266
+ * });
4267
+ *
4268
+ * Backtest.background("BTCUSDT", {
4269
+ * strategyName: "my-strategy",
4270
+ * exchangeName: "binance",
4271
+ * frameName: "1d-backtest"
4272
+ * });
4273
+ *
4274
+ * // Later: stop listening
4275
+ * unsubscribe();
4276
+ * ```
4277
+ */
4278
+ function listenProgress(fn) {
4279
+ backtest$1.loggerService.log(LISTEN_PROGRESS_METHOD_NAME);
4280
+ return progressEmitter.subscribe(functoolsKit.queued(async (event) => fn(event)));
4281
+ }
4095
4282
 
4096
4283
  const GET_CANDLES_METHOD_NAME = "exchange.getCandles";
4097
4284
  const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
@@ -4556,9 +4743,13 @@ exports.getCandles = getCandles;
4556
4743
  exports.getDate = getDate;
4557
4744
  exports.getMode = getMode;
4558
4745
  exports.lib = backtest;
4746
+ exports.listExchanges = listExchanges;
4747
+ exports.listFrames = listFrames;
4748
+ exports.listStrategies = listStrategies;
4559
4749
  exports.listenDone = listenDone;
4560
4750
  exports.listenDoneOnce = listenDoneOnce;
4561
4751
  exports.listenError = listenError;
4752
+ exports.listenProgress = listenProgress;
4562
4753
  exports.listenSignal = listenSignal;
4563
4754
  exports.listenSignalBacktest = listenSignalBacktest;
4564
4755
  exports.listenSignalBacktestOnce = listenSignalBacktestOnce;
package/build/index.mjs CHANGED
@@ -1127,6 +1127,11 @@ const errorEmitter = new Subject();
1127
1127
  * Emits when background tasks complete (Live.background, Backtest.background).
1128
1128
  */
1129
1129
  const doneEmitter = new Subject();
1130
+ /**
1131
+ * Progress emitter for backtest execution progress.
1132
+ * Emits progress updates during backtest execution.
1133
+ */
1134
+ const progressEmitter = new Subject();
1130
1135
 
1131
1136
  const INTERVAL_MINUTES$1 = {
1132
1137
  "1m": 1,
@@ -2400,6 +2405,7 @@ class BacktestLogicPrivateService {
2400
2405
  this.strategyGlobalService = inject(TYPES.strategyGlobalService);
2401
2406
  this.exchangeGlobalService = inject(TYPES.exchangeGlobalService);
2402
2407
  this.frameGlobalService = inject(TYPES.frameGlobalService);
2408
+ this.methodContextService = inject(TYPES.methodContextService);
2403
2409
  }
2404
2410
  /**
2405
2411
  * Runs backtest for a symbol, streaming closed signals as async generator.
@@ -2420,9 +2426,21 @@ class BacktestLogicPrivateService {
2420
2426
  symbol,
2421
2427
  });
2422
2428
  const timeframes = await this.frameGlobalService.getTimeframe(symbol);
2429
+ const totalFrames = timeframes.length;
2423
2430
  let i = 0;
2424
2431
  while (i < timeframes.length) {
2425
2432
  const when = timeframes[i];
2433
+ // Emit progress event if context is available
2434
+ {
2435
+ await progressEmitter.next({
2436
+ exchangeName: this.methodContextService.context.exchangeName,
2437
+ strategyName: this.methodContextService.context.strategyName,
2438
+ symbol,
2439
+ totalFrames,
2440
+ processedFrames: i,
2441
+ progress: totalFrames > 0 ? i / totalFrames : 0,
2442
+ });
2443
+ }
2426
2444
  const result = await this.strategyGlobalService.tick(symbol, when, true);
2427
2445
  // ะ•ัะปะธ ัะธะณะฝะฐะป ะพั‚ะบั€ั‹ั‚, ะฒั‹ะทั‹ะฒะฐะตะผ backtest
2428
2446
  if (result.action === "opened") {
@@ -2459,6 +2477,17 @@ class BacktestLogicPrivateService {
2459
2477
  }
2460
2478
  i++;
2461
2479
  }
2480
+ // Emit final progress event (100%)
2481
+ {
2482
+ await progressEmitter.next({
2483
+ exchangeName: this.methodContextService.context.exchangeName,
2484
+ strategyName: this.methodContextService.context.strategyName,
2485
+ symbol,
2486
+ totalFrames,
2487
+ processedFrames: totalFrames,
2488
+ progress: 1.0,
2489
+ });
2490
+ }
2462
2491
  }
2463
2492
  }
2464
2493
 
@@ -3477,6 +3506,15 @@ class ExchangeValidationService {
3477
3506
  }
3478
3507
  return true;
3479
3508
  });
3509
+ /**
3510
+ * Returns a list of all registered exchange schemas
3511
+ * @public
3512
+ * @returns Array of exchange schemas with their configurations
3513
+ */
3514
+ this.list = async () => {
3515
+ this.loggerService.log("exchangeValidationService list");
3516
+ return Array.from(this._exchangeMap.values());
3517
+ };
3480
3518
  }
3481
3519
  }
3482
3520
 
@@ -3529,6 +3567,15 @@ class StrategyValidationService {
3529
3567
  }
3530
3568
  return true;
3531
3569
  });
3570
+ /**
3571
+ * Returns a list of all registered strategy schemas
3572
+ * @public
3573
+ * @returns Array of strategy schemas with their configurations
3574
+ */
3575
+ this.list = async () => {
3576
+ this.loggerService.log("strategyValidationService list");
3577
+ return Array.from(this._strategyMap.values());
3578
+ };
3532
3579
  }
3533
3580
  }
3534
3581
 
@@ -3581,6 +3628,15 @@ class FrameValidationService {
3581
3628
  }
3582
3629
  return true;
3583
3630
  });
3631
+ /**
3632
+ * Returns a list of all registered frame schemas
3633
+ * @public
3634
+ * @returns Array of frame schemas with their configurations
3635
+ */
3636
+ this.list = async () => {
3637
+ this.loggerService.log("frameValidationService list");
3638
+ return Array.from(this._frameMap.values());
3639
+ };
3584
3640
  }
3585
3641
  }
3586
3642
 
@@ -3826,6 +3882,102 @@ function addFrame(frameSchema) {
3826
3882
  backtest$1.frameSchemaService.register(frameSchema.frameName, frameSchema);
3827
3883
  }
3828
3884
 
3885
+ const LIST_EXCHANGES_METHOD_NAME = "list.listExchanges";
3886
+ const LIST_STRATEGIES_METHOD_NAME = "list.listStrategies";
3887
+ const LIST_FRAMES_METHOD_NAME = "list.listFrames";
3888
+ /**
3889
+ * Returns a list of all registered exchange schemas.
3890
+ *
3891
+ * Retrieves all exchanges that have been registered via addExchange().
3892
+ * Useful for debugging, documentation, or building dynamic UIs.
3893
+ *
3894
+ * @returns Array of exchange schemas with their configurations
3895
+ *
3896
+ * @example
3897
+ * ```typescript
3898
+ * import { listExchanges, addExchange } from "backtest-kit";
3899
+ *
3900
+ * addExchange({
3901
+ * exchangeName: "binance",
3902
+ * note: "Binance cryptocurrency exchange",
3903
+ * getCandles: async (symbol, interval, since, limit) => [...],
3904
+ * formatPrice: async (symbol, price) => price.toFixed(2),
3905
+ * formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
3906
+ * });
3907
+ *
3908
+ * const exchanges = listExchanges();
3909
+ * console.log(exchanges);
3910
+ * // [{ exchangeName: "binance", note: "Binance cryptocurrency exchange", ... }]
3911
+ * ```
3912
+ */
3913
+ async function listExchanges() {
3914
+ backtest$1.loggerService.log(LIST_EXCHANGES_METHOD_NAME);
3915
+ return await backtest$1.exchangeValidationService.list();
3916
+ }
3917
+ /**
3918
+ * Returns a list of all registered strategy schemas.
3919
+ *
3920
+ * Retrieves all strategies that have been registered via addStrategy().
3921
+ * Useful for debugging, documentation, or building dynamic UIs.
3922
+ *
3923
+ * @returns Array of strategy schemas with their configurations
3924
+ *
3925
+ * @example
3926
+ * ```typescript
3927
+ * import { listStrategies, addStrategy } from "backtest-kit";
3928
+ *
3929
+ * addStrategy({
3930
+ * strategyName: "my-strategy",
3931
+ * note: "Simple moving average crossover strategy",
3932
+ * interval: "5m",
3933
+ * getSignal: async (symbol) => ({
3934
+ * position: "long",
3935
+ * priceOpen: 50000,
3936
+ * priceTakeProfit: 51000,
3937
+ * priceStopLoss: 49000,
3938
+ * minuteEstimatedTime: 60,
3939
+ * }),
3940
+ * });
3941
+ *
3942
+ * const strategies = listStrategies();
3943
+ * console.log(strategies);
3944
+ * // [{ strategyName: "my-strategy", note: "Simple moving average...", ... }]
3945
+ * ```
3946
+ */
3947
+ async function listStrategies() {
3948
+ backtest$1.loggerService.log(LIST_STRATEGIES_METHOD_NAME);
3949
+ return await backtest$1.strategyValidationService.list();
3950
+ }
3951
+ /**
3952
+ * Returns a list of all registered frame schemas.
3953
+ *
3954
+ * Retrieves all frames that have been registered via addFrame().
3955
+ * Useful for debugging, documentation, or building dynamic UIs.
3956
+ *
3957
+ * @returns Array of frame schemas with their configurations
3958
+ *
3959
+ * @example
3960
+ * ```typescript
3961
+ * import { listFrames, addFrame } from "backtest-kit";
3962
+ *
3963
+ * addFrame({
3964
+ * frameName: "1d-backtest",
3965
+ * note: "One day backtest period for testing",
3966
+ * interval: "1m",
3967
+ * startDate: new Date("2024-01-01T00:00:00Z"),
3968
+ * endDate: new Date("2024-01-02T00:00:00Z"),
3969
+ * });
3970
+ *
3971
+ * const frames = listFrames();
3972
+ * console.log(frames);
3973
+ * // [{ frameName: "1d-backtest", note: "One day backtest...", ... }]
3974
+ * ```
3975
+ */
3976
+ async function listFrames() {
3977
+ backtest$1.loggerService.log(LIST_FRAMES_METHOD_NAME);
3978
+ return await backtest$1.frameValidationService.list();
3979
+ }
3980
+
3829
3981
  const LISTEN_SIGNAL_METHOD_NAME = "event.listenSignal";
3830
3982
  const LISTEN_SIGNAL_ONCE_METHOD_NAME = "event.listenSignalOnce";
3831
3983
  const LISTEN_SIGNAL_LIVE_METHOD_NAME = "event.listenSignalLive";
@@ -3835,6 +3987,7 @@ const LISTEN_SIGNAL_BACKTEST_ONCE_METHOD_NAME = "event.listenSignalBacktestOnce"
3835
3987
  const LISTEN_ERROR_METHOD_NAME = "event.listenError";
3836
3988
  const LISTEN_DONE_METHOD_NAME = "event.listenDone";
3837
3989
  const LISTEN_DONE_ONCE_METHOD_NAME = "event.listenDoneOnce";
3990
+ const LISTEN_PROGRESS_METHOD_NAME = "event.listenProgress";
3838
3991
  /**
3839
3992
  * Subscribes to all signal events with queued async processing.
3840
3993
  *
@@ -4090,6 +4243,40 @@ function listenDoneOnce(filterFn, fn) {
4090
4243
  backtest$1.loggerService.log(LISTEN_DONE_ONCE_METHOD_NAME);
4091
4244
  return doneEmitter.filter(filterFn).once(fn);
4092
4245
  }
4246
+ /**
4247
+ * Subscribes to backtest progress events with queued async processing.
4248
+ *
4249
+ * Emits during Backtest.background() execution to track progress.
4250
+ * Events are processed sequentially in order received, even if callback is async.
4251
+ * Uses queued wrapper to prevent concurrent execution of the callback.
4252
+ *
4253
+ * @param fn - Callback function to handle progress events
4254
+ * @returns Unsubscribe function to stop listening to events
4255
+ *
4256
+ * @example
4257
+ * ```typescript
4258
+ * import { listenProgress, Backtest } from "backtest-kit";
4259
+ *
4260
+ * const unsubscribe = listenProgress((event) => {
4261
+ * console.log(`Progress: ${(event.progress * 100).toFixed(2)}%`);
4262
+ * console.log(`${event.processedFrames} / ${event.totalFrames} frames`);
4263
+ * console.log(`Strategy: ${event.strategyName}, Symbol: ${event.symbol}`);
4264
+ * });
4265
+ *
4266
+ * Backtest.background("BTCUSDT", {
4267
+ * strategyName: "my-strategy",
4268
+ * exchangeName: "binance",
4269
+ * frameName: "1d-backtest"
4270
+ * });
4271
+ *
4272
+ * // Later: stop listening
4273
+ * unsubscribe();
4274
+ * ```
4275
+ */
4276
+ function listenProgress(fn) {
4277
+ backtest$1.loggerService.log(LISTEN_PROGRESS_METHOD_NAME);
4278
+ return progressEmitter.subscribe(queued(async (event) => fn(event)));
4279
+ }
4093
4280
 
4094
4281
  const GET_CANDLES_METHOD_NAME = "exchange.getCandles";
4095
4282
  const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
@@ -4538,4 +4725,4 @@ class LiveUtils {
4538
4725
  */
4539
4726
  const Live = new LiveUtils();
4540
4727
 
4541
- 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 };
4728
+ export { Backtest, ExecutionContextService, Live, MethodContextService, PersistBase, PersistSignalAdaper, addExchange, addFrame, addStrategy, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listExchanges, listFrames, listStrategies, listenDone, listenDoneOnce, listenError, listenProgress, 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.4",
3
+ "version": "1.1.5",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
package/types.d.ts CHANGED
@@ -140,6 +140,8 @@ interface IExchangeCallbacks {
140
140
  interface IExchangeSchema {
141
141
  /** Unique exchange identifier for registration */
142
142
  exchangeName: ExchangeName;
143
+ /** Optional developer note for documentation */
144
+ note?: string;
143
145
  /**
144
146
  * Fetch candles from data source (API or database).
145
147
  *
@@ -278,6 +280,8 @@ interface IFrameCallbacks {
278
280
  interface IFrameSchema {
279
281
  /** Unique identifier for this frame */
280
282
  frameName: FrameName;
283
+ /** Optional developer note for documentation */
284
+ note?: string;
281
285
  /** Interval for timestamp generation */
282
286
  interval: FrameInterval;
283
287
  /** Start of backtest period (inclusive) */
@@ -413,6 +417,8 @@ interface IStrategyCallbacks {
413
417
  interface IStrategySchema {
414
418
  /** Unique strategy identifier for registration */
415
419
  strategyName: StrategyName;
420
+ /** Optional developer note for documentation */
421
+ note?: string;
416
422
  /** Minimum interval between getSignal calls (throttling) */
417
423
  interval: SignalInterval;
418
424
  /** Signal generation function (returns null if no signal, validated DTO if signal) */
@@ -666,6 +672,90 @@ declare function addExchange(exchangeSchema: IExchangeSchema): void;
666
672
  */
667
673
  declare function addFrame(frameSchema: IFrameSchema): void;
668
674
 
675
+ /**
676
+ * Returns a list of all registered exchange schemas.
677
+ *
678
+ * Retrieves all exchanges that have been registered via addExchange().
679
+ * Useful for debugging, documentation, or building dynamic UIs.
680
+ *
681
+ * @returns Array of exchange schemas with their configurations
682
+ *
683
+ * @example
684
+ * ```typescript
685
+ * import { listExchanges, addExchange } from "backtest-kit";
686
+ *
687
+ * addExchange({
688
+ * exchangeName: "binance",
689
+ * note: "Binance cryptocurrency exchange",
690
+ * getCandles: async (symbol, interval, since, limit) => [...],
691
+ * formatPrice: async (symbol, price) => price.toFixed(2),
692
+ * formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
693
+ * });
694
+ *
695
+ * const exchanges = listExchanges();
696
+ * console.log(exchanges);
697
+ * // [{ exchangeName: "binance", note: "Binance cryptocurrency exchange", ... }]
698
+ * ```
699
+ */
700
+ declare function listExchanges(): Promise<IExchangeSchema[]>;
701
+ /**
702
+ * Returns a list of all registered strategy schemas.
703
+ *
704
+ * Retrieves all strategies that have been registered via addStrategy().
705
+ * Useful for debugging, documentation, or building dynamic UIs.
706
+ *
707
+ * @returns Array of strategy schemas with their configurations
708
+ *
709
+ * @example
710
+ * ```typescript
711
+ * import { listStrategies, addStrategy } from "backtest-kit";
712
+ *
713
+ * addStrategy({
714
+ * strategyName: "my-strategy",
715
+ * note: "Simple moving average crossover strategy",
716
+ * interval: "5m",
717
+ * getSignal: async (symbol) => ({
718
+ * position: "long",
719
+ * priceOpen: 50000,
720
+ * priceTakeProfit: 51000,
721
+ * priceStopLoss: 49000,
722
+ * minuteEstimatedTime: 60,
723
+ * }),
724
+ * });
725
+ *
726
+ * const strategies = listStrategies();
727
+ * console.log(strategies);
728
+ * // [{ strategyName: "my-strategy", note: "Simple moving average...", ... }]
729
+ * ```
730
+ */
731
+ declare function listStrategies(): Promise<IStrategySchema[]>;
732
+ /**
733
+ * Returns a list of all registered frame schemas.
734
+ *
735
+ * Retrieves all frames that have been registered via addFrame().
736
+ * Useful for debugging, documentation, or building dynamic UIs.
737
+ *
738
+ * @returns Array of frame schemas with their configurations
739
+ *
740
+ * @example
741
+ * ```typescript
742
+ * import { listFrames, addFrame } from "backtest-kit";
743
+ *
744
+ * addFrame({
745
+ * frameName: "1d-backtest",
746
+ * note: "One day backtest period for testing",
747
+ * interval: "1m",
748
+ * startDate: new Date("2024-01-01T00:00:00Z"),
749
+ * endDate: new Date("2024-01-02T00:00:00Z"),
750
+ * });
751
+ *
752
+ * const frames = listFrames();
753
+ * console.log(frames);
754
+ * // [{ frameName: "1d-backtest", note: "One day backtest...", ... }]
755
+ * ```
756
+ */
757
+ declare function listFrames(): Promise<IFrameSchema[]>;
758
+
669
759
  /**
670
760
  * Contract for background execution completion events.
671
761
  *
@@ -696,6 +786,37 @@ interface DoneContract {
696
786
  symbol: string;
697
787
  }
698
788
 
789
+ /**
790
+ * Contract for backtest progress events.
791
+ *
792
+ * Emitted during Backtest.background() execution to track progress.
793
+ * Contains information about total frames, processed frames, and completion percentage.
794
+ *
795
+ * @example
796
+ * ```typescript
797
+ * import { listenProgress } from "backtest-kit";
798
+ *
799
+ * listenProgress((event) => {
800
+ * console.log(`Progress: ${(event.progress * 100).toFixed(2)}%`);
801
+ * console.log(`Processed: ${event.processedFrames} / ${event.totalFrames}`);
802
+ * });
803
+ * ```
804
+ */
805
+ interface ProgressContract {
806
+ /** exchangeName - Name of the exchange used in execution */
807
+ exchangeName: string;
808
+ /** strategyName - Name of the strategy being executed */
809
+ strategyName: string;
810
+ /** symbol - Trading symbol (e.g., "BTCUSDT") */
811
+ symbol: string;
812
+ /** totalFrames - Total number of frames to process */
813
+ totalFrames: number;
814
+ /** processedFrames - Number of frames processed so far */
815
+ processedFrames: number;
816
+ /** progress - Completion percentage from 0.0 to 1.0 */
817
+ progress: number;
818
+ }
819
+
699
820
  /**
700
821
  * Subscribes to all signal events with queued async processing.
701
822
  *
@@ -924,6 +1045,37 @@ declare function listenDone(fn: (event: DoneContract) => void): () => void;
924
1045
  * ```
925
1046
  */
926
1047
  declare function listenDoneOnce(filterFn: (event: DoneContract) => boolean, fn: (event: DoneContract) => void): () => void;
1048
+ /**
1049
+ * Subscribes to backtest progress events with queued async processing.
1050
+ *
1051
+ * Emits during Backtest.background() execution to track progress.
1052
+ * Events are processed sequentially in order received, even if callback is async.
1053
+ * Uses queued wrapper to prevent concurrent execution of the callback.
1054
+ *
1055
+ * @param fn - Callback function to handle progress events
1056
+ * @returns Unsubscribe function to stop listening to events
1057
+ *
1058
+ * @example
1059
+ * ```typescript
1060
+ * import { listenProgress, Backtest } from "backtest-kit";
1061
+ *
1062
+ * const unsubscribe = listenProgress((event) => {
1063
+ * console.log(`Progress: ${(event.progress * 100).toFixed(2)}%`);
1064
+ * console.log(`${event.processedFrames} / ${event.totalFrames} frames`);
1065
+ * console.log(`Strategy: ${event.strategyName}, Symbol: ${event.symbol}`);
1066
+ * });
1067
+ *
1068
+ * Backtest.background("BTCUSDT", {
1069
+ * strategyName: "my-strategy",
1070
+ * exchangeName: "binance",
1071
+ * frameName: "1d-backtest"
1072
+ * });
1073
+ *
1074
+ * // Later: stop listening
1075
+ * unsubscribe();
1076
+ * ```
1077
+ */
1078
+ declare function listenProgress(fn: (event: ProgressContract) => void): () => void;
927
1079
 
928
1080
  /**
929
1081
  * Fetches historical candle data from the registered exchange.
@@ -2154,6 +2306,7 @@ declare class BacktestLogicPrivateService {
2154
2306
  private readonly strategyGlobalService;
2155
2307
  private readonly exchangeGlobalService;
2156
2308
  private readonly frameGlobalService;
2309
+ private readonly methodContextService;
2157
2310
  /**
2158
2311
  * Runs backtest for a symbol, streaming closed signals as async generator.
2159
2312
  *
@@ -2642,6 +2795,12 @@ declare class ExchangeValidationService {
2642
2795
  * Memoized function to cache validation results
2643
2796
  */
2644
2797
  validate: (exchangeName: ExchangeName, source: string) => void;
2798
+ /**
2799
+ * Returns a list of all registered exchange schemas
2800
+ * @public
2801
+ * @returns Array of exchange schemas with their configurations
2802
+ */
2803
+ list: () => Promise<IExchangeSchema[]>;
2645
2804
  }
2646
2805
 
2647
2806
  /**
@@ -2673,6 +2832,12 @@ declare class StrategyValidationService {
2673
2832
  * Memoized function to cache validation results
2674
2833
  */
2675
2834
  validate: (strategyName: StrategyName, source: string) => void;
2835
+ /**
2836
+ * Returns a list of all registered strategy schemas
2837
+ * @public
2838
+ * @returns Array of strategy schemas with their configurations
2839
+ */
2840
+ list: () => Promise<IStrategySchema[]>;
2676
2841
  }
2677
2842
 
2678
2843
  /**
@@ -2704,6 +2869,12 @@ declare class FrameValidationService {
2704
2869
  * Memoized function to cache validation results
2705
2870
  */
2706
2871
  validate: (frameName: FrameName, source: string) => void;
2872
+ /**
2873
+ * Returns a list of all registered frame schemas
2874
+ * @public
2875
+ * @returns Array of frame schemas with their configurations
2876
+ */
2877
+ list: () => Promise<IFrameSchema[]>;
2707
2878
  }
2708
2879
 
2709
2880
  declare const backtest: {
@@ -2736,4 +2907,4 @@ declare const backtest: {
2736
2907
  loggerService: LoggerService;
2737
2908
  };
2738
2909
 
2739
- 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 };
2910
+ export { Backtest, type CandleInterval, type DoneContract, 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 ProgressContract, type SignalInterval, type TPersistBase, type TPersistBaseCtor, addExchange, addFrame, addStrategy, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listExchanges, listFrames, listStrategies, listenDone, listenDoneOnce, listenError, listenProgress, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, setLogger };