backtest-kit 9.8.2 → 9.8.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/build/index.cjs CHANGED
@@ -808,11 +808,23 @@ const maxDrawdownSubject = new functoolsKit.Subject();
808
808
  * Emits when a strategy calls commitSignalInfo() to broadcast a custom annotation.
809
809
  */
810
810
  const signalNotifySubject = new functoolsKit.Subject();
811
+ /**
812
+ * Before start emitter for strategy initialization events.
813
+ * Emits when the engine is about to start a new strategy execution.
814
+ */
815
+ const beforeStartSubject = new functoolsKit.Subject();
816
+ /**
817
+ * After end emitter for strategy completion events.
818
+ * Emits when the engine has completed processing a signal.
819
+ */
820
+ const afterEndSubject = new functoolsKit.Subject();
811
821
 
812
822
  var emitters = /*#__PURE__*/Object.freeze({
813
823
  __proto__: null,
814
824
  activePingSubject: activePingSubject,
825
+ afterEndSubject: afterEndSubject,
815
826
  backtestScheduleOpenSubject: backtestScheduleOpenSubject,
827
+ beforeStartSubject: beforeStartSubject,
816
828
  breakevenSubject: breakevenSubject,
817
829
  doneBacktestSubject: doneBacktestSubject,
818
830
  doneLiveSubject: doneLiveSubject,
@@ -20420,6 +20432,110 @@ class WalkerLogicPrivateService {
20420
20432
  }
20421
20433
  }
20422
20434
 
20435
+ /**
20436
+ * Run iterator function for backtest logic.
20437
+ *
20438
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20439
+ * @param context - Execution context with strategy, exchange, and frame names
20440
+ * @param self - Instance of BacktestLogicPublicService
20441
+ * @returns Async iterator for backtest results
20442
+ */
20443
+ const RUN_ITERATOR_FN$1 = (self, symbol, context) => {
20444
+ return MethodContextService.runAsyncIterator(self.backtestLogicPrivateService.run(symbol), {
20445
+ exchangeName: context.exchangeName,
20446
+ strategyName: context.strategyName,
20447
+ frameName: context.frameName,
20448
+ });
20449
+ };
20450
+ /**
20451
+ * Call before start execution for backtest logic.
20452
+ * This function is responsible for triggering the beforeStartSubject
20453
+ * with the appropriate context and symbol information.
20454
+ */
20455
+ const CALL_BEFORE_START_FN$1 = functoolsKit.trycatch(async (self, symbol, context) => {
20456
+ const { startDate } = self.frameSchemaService.get(context.frameName);
20457
+ const when = alignToInterval(startDate, "1m");
20458
+ await MethodContextService.runInContext(async () => {
20459
+ await ExecutionContextService.runInContext(async () => {
20460
+ const currentPrice = await self.exchangeConnectionService.getAveragePrice(symbol);
20461
+ await beforeStartSubject.next({
20462
+ symbol,
20463
+ exchangeName: context.exchangeName,
20464
+ strategyName: context.strategyName,
20465
+ frameName: context.frameName,
20466
+ backtest: true,
20467
+ when,
20468
+ timestamp: when.getTime(),
20469
+ currentPrice,
20470
+ });
20471
+ }, {
20472
+ symbol,
20473
+ when,
20474
+ backtest: true,
20475
+ });
20476
+ }, {
20477
+ exchangeName: context.exchangeName,
20478
+ strategyName: context.strategyName,
20479
+ frameName: context.frameName,
20480
+ });
20481
+ }, {
20482
+ fallback: (error, self) => {
20483
+ const message = "BacktestLogicPublicService CALL_BEFORE_START_FN thrown";
20484
+ const payload = {
20485
+ error: functoolsKit.errorData(error),
20486
+ message: functoolsKit.getErrorMessage(error),
20487
+ };
20488
+ self.loggerService.warn(message, payload);
20489
+ console.error(message, payload);
20490
+ errorEmitter.next(error);
20491
+ },
20492
+ });
20493
+ /**
20494
+ * Call after end execution for backtest logic.
20495
+ * This function is responsible for triggering the afterEndSubject
20496
+ * with the appropriate context and symbol information.
20497
+ */
20498
+ const CALL_AFTER_END_FN$1 = functoolsKit.trycatch(async (self, symbol, context) => {
20499
+ const { startDate } = self.frameSchemaService.get(context.frameName);
20500
+ const timestamp = self.timeMetaService.hasTimestamp(symbol, context, true)
20501
+ ? await self.timeMetaService.getTimestamp(symbol, context, true)
20502
+ : startDate.getTime();
20503
+ const when = new Date(timestamp);
20504
+ await MethodContextService.runInContext(async () => {
20505
+ await ExecutionContextService.runInContext(async () => {
20506
+ const currentPrice = await self.exchangeConnectionService.getAveragePrice(symbol);
20507
+ await afterEndSubject.next({
20508
+ symbol,
20509
+ exchangeName: context.exchangeName,
20510
+ strategyName: context.strategyName,
20511
+ frameName: context.frameName,
20512
+ backtest: true,
20513
+ when,
20514
+ timestamp,
20515
+ currentPrice,
20516
+ });
20517
+ }, {
20518
+ symbol,
20519
+ when,
20520
+ backtest: true,
20521
+ });
20522
+ }, {
20523
+ exchangeName: context.exchangeName,
20524
+ strategyName: context.strategyName,
20525
+ frameName: context.frameName,
20526
+ });
20527
+ }, {
20528
+ fallback: (error, self) => {
20529
+ const message = "BacktestLogicPublicService CALL_AFTER_END_FN thrown";
20530
+ const payload = {
20531
+ error: functoolsKit.errorData(error),
20532
+ message: functoolsKit.getErrorMessage(error),
20533
+ };
20534
+ self.loggerService.warn(message, payload);
20535
+ console.error(message, payload);
20536
+ errorEmitter.next(error);
20537
+ },
20538
+ });
20423
20539
  /**
20424
20540
  * Public service for backtest orchestration with context management.
20425
20541
  *
@@ -20448,30 +20564,134 @@ class BacktestLogicPublicService {
20448
20564
  constructor() {
20449
20565
  this.loggerService = inject(TYPES.loggerService);
20450
20566
  this.backtestLogicPrivateService = inject(TYPES.backtestLogicPrivateService);
20451
- /**
20452
- * Runs backtest for a symbol with context propagation.
20453
- *
20454
- * Streams closed signals as async generator. Context is automatically
20455
- * injected into all framework functions called during iteration.
20456
- *
20457
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20458
- * @param context - Execution context with strategy, exchange, and frame names
20459
- * @returns Async generator yielding closed signals with PNL
20460
- */
20461
- this.run = (symbol, context) => {
20462
- this.loggerService.log("backtestLogicPublicService run", {
20567
+ this.timeMetaService = inject(TYPES.timeMetaService);
20568
+ this.frameSchemaService = inject(TYPES.frameSchemaService);
20569
+ this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
20570
+ }
20571
+ /**
20572
+ * Runs backtest for a symbol with context propagation.
20573
+ *
20574
+ * Streams closed signals as async generator. Context is automatically
20575
+ * injected into all framework functions called during iteration.
20576
+ *
20577
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20578
+ * @param context - Execution context with strategy, exchange, and frame names
20579
+ * @returns Async generator yielding closed signals with PNL
20580
+ */
20581
+ async *run(symbol, context) {
20582
+ this.loggerService.log("backtestLogicPublicService run", {
20583
+ symbol,
20584
+ context,
20585
+ });
20586
+ await CALL_BEFORE_START_FN$1(this, symbol, context);
20587
+ try {
20588
+ yield* RUN_ITERATOR_FN$1(this, symbol, context);
20589
+ }
20590
+ finally {
20591
+ await CALL_AFTER_END_FN$1(this, symbol, context);
20592
+ }
20593
+ }
20594
+ }
20595
+
20596
+ /**
20597
+ * Run iterator function for live logic.
20598
+ *
20599
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20600
+ * @param context - Execution context with strategy and exchange names
20601
+ * @param self - Instance of LiveLogicPublicService
20602
+ * @returns Async iterator for live trading results
20603
+ */
20604
+ const RUN_ITERATOR_FN = (self, symbol, context) => {
20605
+ return MethodContextService.runAsyncIterator(self.liveLogicPrivateService.run(symbol), {
20606
+ exchangeName: context.exchangeName,
20607
+ strategyName: context.strategyName,
20608
+ frameName: "",
20609
+ });
20610
+ };
20611
+ /**
20612
+ * Call before start execution for live logic.
20613
+ * This function is responsible for triggering the beforeStartSubject
20614
+ * with the appropriate context and symbol information.
20615
+ */
20616
+ const CALL_BEFORE_START_FN = functoolsKit.trycatch(async (self, symbol, context) => {
20617
+ const when = alignToInterval(new Date(), "1m");
20618
+ await MethodContextService.runInContext(async () => {
20619
+ await ExecutionContextService.runInContext(async () => {
20620
+ const currentPrice = await self.exchangeConnectionService.getAveragePrice(symbol);
20621
+ await beforeStartSubject.next({
20463
20622
  symbol,
20464
- context,
20623
+ exchangeName: context.exchangeName,
20624
+ strategyName: context.strategyName,
20625
+ frameName: "",
20626
+ backtest: false,
20627
+ currentPrice,
20628
+ when,
20629
+ timestamp: when.getTime(),
20465
20630
  });
20466
- return MethodContextService.runAsyncIterator(this.backtestLogicPrivateService.run(symbol), {
20631
+ }, {
20632
+ symbol,
20633
+ when,
20634
+ backtest: false,
20635
+ });
20636
+ }, {
20637
+ exchangeName: context.exchangeName,
20638
+ strategyName: context.strategyName,
20639
+ frameName: "",
20640
+ });
20641
+ }, {
20642
+ fallback: (error, self) => {
20643
+ const message = "LiveLogicPublicService CALL_BEFORE_START_FN thrown";
20644
+ const payload = {
20645
+ error: functoolsKit.errorData(error),
20646
+ message: functoolsKit.getErrorMessage(error),
20647
+ };
20648
+ self.loggerService.warn(message, payload);
20649
+ console.error(message, payload);
20650
+ errorEmitter.next(error);
20651
+ },
20652
+ });
20653
+ /**
20654
+ * Call after end execution for live logic.
20655
+ * This function is responsible for triggering the afterEndSubject
20656
+ * with the appropriate context and symbol information.
20657
+ */
20658
+ const CALL_AFTER_END_FN = functoolsKit.trycatch(async (self, symbol, context) => {
20659
+ const when = alignToInterval(new Date(), "1m");
20660
+ await MethodContextService.runInContext(async () => {
20661
+ await ExecutionContextService.runInContext(async () => {
20662
+ const currentPrice = await self.exchangeConnectionService.getAveragePrice(symbol);
20663
+ await afterEndSubject.next({
20664
+ symbol,
20467
20665
  exchangeName: context.exchangeName,
20468
20666
  strategyName: context.strategyName,
20469
- frameName: context.frameName,
20667
+ frameName: "",
20668
+ backtest: false,
20669
+ currentPrice,
20670
+ when,
20671
+ timestamp: when.getTime(),
20470
20672
  });
20673
+ }, {
20674
+ symbol,
20675
+ when,
20676
+ backtest: false,
20677
+ });
20678
+ }, {
20679
+ exchangeName: context.exchangeName,
20680
+ strategyName: context.strategyName,
20681
+ frameName: "",
20682
+ });
20683
+ }, {
20684
+ fallback: (error, self) => {
20685
+ const message = "LiveLogicPublicService CALL_AFTER_END_FN thrown";
20686
+ const payload = {
20687
+ error: functoolsKit.errorData(error),
20688
+ message: functoolsKit.getErrorMessage(error),
20471
20689
  };
20472
- }
20473
- }
20474
-
20690
+ self.loggerService.warn(message, payload);
20691
+ console.error(message, payload);
20692
+ errorEmitter.next(error);
20693
+ },
20694
+ });
20475
20695
  /**
20476
20696
  * Public service for live trading orchestration with context management.
20477
20697
  *
@@ -20507,28 +20727,31 @@ class LiveLogicPublicService {
20507
20727
  constructor() {
20508
20728
  this.loggerService = inject(TYPES.loggerService);
20509
20729
  this.liveLogicPrivateService = inject(TYPES.liveLogicPrivateService);
20510
- /**
20511
- * Runs live trading for a symbol with context propagation.
20512
- *
20513
- * Streams opened and closed signals as infinite async generator.
20514
- * Context is automatically injected into all framework functions.
20515
- * Process can crash and restart - state will be recovered from disk.
20516
- *
20517
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20518
- * @param context - Execution context with strategy and exchange names
20519
- * @returns Infinite async generator yielding opened and closed signals
20520
- */
20521
- this.run = (symbol, context) => {
20522
- this.loggerService.log("liveLogicPublicService run", {
20523
- symbol,
20524
- context,
20525
- });
20526
- return MethodContextService.runAsyncIterator(this.liveLogicPrivateService.run(symbol), {
20527
- exchangeName: context.exchangeName,
20528
- strategyName: context.strategyName,
20529
- frameName: "",
20530
- });
20531
- };
20730
+ this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
20731
+ }
20732
+ /**
20733
+ * Runs live trading for a symbol with context propagation.
20734
+ *
20735
+ * Streams opened and closed signals as infinite async generator.
20736
+ * Context is automatically injected into all framework functions.
20737
+ * Process can crash and restart - state will be recovered from disk.
20738
+ *
20739
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20740
+ * @param context - Execution context with strategy and exchange names
20741
+ * @returns Infinite async generator yielding opened and closed signals
20742
+ */
20743
+ async *run(symbol, context) {
20744
+ this.loggerService.log("liveLogicPublicService run", {
20745
+ symbol,
20746
+ context,
20747
+ });
20748
+ await CALL_BEFORE_START_FN(this, symbol, context);
20749
+ try {
20750
+ yield* RUN_ITERATOR_FN(this, symbol, context);
20751
+ }
20752
+ finally {
20753
+ await CALL_AFTER_END_FN(this, symbol, context);
20754
+ }
20532
20755
  }
20533
20756
  }
20534
20757
 
@@ -34732,6 +34955,21 @@ class TimeMetaService {
34732
34955
  * Instances are cached until clear() is called.
34733
34956
  */
34734
34957
  this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
34958
+ /**
34959
+ * Checks if a timestamp exists for the given symbol and context.
34960
+ *
34961
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
34962
+ * @param context - Strategy, exchange, and frame identifiers
34963
+ * @param backtest - True if backtest mode, false if live mode
34964
+ * @returns True if a timestamp is available, false otherwise
34965
+ */
34966
+ this.hasTimestamp = (symbol, context, backtest) => {
34967
+ const key = CREATE_KEY_FN$a(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
34968
+ if (!this.getSource.has(key)) {
34969
+ return false;
34970
+ }
34971
+ return !!this.getSource.get(key)?.data;
34972
+ };
34735
34973
  /**
34736
34974
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
34737
34975
  *
@@ -36402,7 +36640,7 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
36402
36640
  */
36403
36641
  const CACHE_CANDLES_FN = functoolsKit.retry(async (interval, dto, onWarmStart, onCheckStart) => {
36404
36642
  try {
36405
- onWarmStart && onWarmStart(dto.symbol, interval, dto.from, dto.to);
36643
+ onCheckStart && onCheckStart(dto.symbol, interval, dto.from, dto.to);
36406
36644
  await checkCandles({
36407
36645
  exchangeName: dto.exchangeName,
36408
36646
  from: dto.from,
@@ -36412,7 +36650,7 @@ const CACHE_CANDLES_FN = functoolsKit.retry(async (interval, dto, onWarmStart, o
36412
36650
  });
36413
36651
  }
36414
36652
  catch (error) {
36415
- onCheckStart && onCheckStart(dto.symbol, interval, dto.from, dto.to);
36653
+ onWarmStart && onWarmStart(dto.symbol, interval, dto.from, dto.to);
36416
36654
  await warmCandles({
36417
36655
  symbol: dto.symbol,
36418
36656
  exchangeName: dto.exchangeName,
@@ -36945,11 +37183,6 @@ const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
36945
37183
  const GET_CLOSE_PRICE_METHOD_NAME = "exchange.getClosePrice";
36946
37184
  const FORMAT_PRICE_METHOD_NAME = "exchange.formatPrice";
36947
37185
  const FORMAT_QUANTITY_METHOD_NAME = "exchange.formatQuantity";
36948
- const GET_DATE_METHOD_NAME = "exchange.getDate";
36949
- const GET_TIMESTAMP_METHOD_NAME = "exchange.getTimestamp";
36950
- const GET_MODE_METHOD_NAME = "exchange.getMode";
36951
- const GET_SYMBOL_METHOD_NAME = "exchange.getSymbol";
36952
- const GET_CONTEXT_METHOD_NAME = "exchange.getContext";
36953
37186
  const HAS_TRADE_CONTEXT_METHOD_NAME = "exchange.hasTradeContext";
36954
37187
  const GET_ORDER_BOOK_METHOD_NAME = "exchange.getOrderBook";
36955
37188
  const GET_RAW_CANDLES_METHOD_NAME = "exchange.getRawCandles";
@@ -37116,113 +37349,6 @@ async function formatQuantity(symbol, quantity) {
37116
37349
  }
37117
37350
  return await backtest.exchangeConnectionService.formatQuantity(symbol, quantity);
37118
37351
  }
37119
- /**
37120
- * Gets the current date from execution context.
37121
- *
37122
- * In backtest mode: returns the current timeframe date being processed
37123
- * In live mode: returns current real-time date
37124
- *
37125
- * @returns Promise resolving to current execution context date
37126
- *
37127
- * @example
37128
- * ```typescript
37129
- * const date = await getDate();
37130
- * console.log(date); // 2024-01-01T12:00:00.000Z
37131
- * ```
37132
- */
37133
- async function getDate() {
37134
- backtest.loggerService.info(GET_DATE_METHOD_NAME);
37135
- if (!ExecutionContextService.hasContext()) {
37136
- throw new Error("getDate requires an execution context");
37137
- }
37138
- const { when } = backtest.executionContextService.context;
37139
- return new Date(when.getTime());
37140
- }
37141
- /**
37142
- * Gets the current timestamp from execution context.
37143
- *
37144
- * In backtest mode: returns the current timeframe timestamp being processed
37145
- * In live mode: returns current real-time timestamp
37146
- *
37147
- * @returns Promise resolving to current execution context timestamp in milliseconds
37148
- * @example
37149
- * ```typescript
37150
- * const timestamp = await getTimestamp();
37151
- * console.log(timestamp); // 1700000000000
37152
- * ```
37153
- */
37154
- async function getTimestamp() {
37155
- backtest.loggerService.info(GET_TIMESTAMP_METHOD_NAME);
37156
- if (!ExecutionContextService.hasContext()) {
37157
- throw new Error("getTimestamp requires an execution context");
37158
- }
37159
- return getContextTimestamp();
37160
- }
37161
- /**
37162
- * Gets the current execution mode.
37163
- *
37164
- * @returns Promise resolving to "backtest" or "live"
37165
- *
37166
- * @example
37167
- * ```typescript
37168
- * const mode = await getMode();
37169
- * if (mode === "backtest") {
37170
- * console.log("Running in backtest mode");
37171
- * } else {
37172
- * console.log("Running in live mode");
37173
- * }
37174
- * ```
37175
- */
37176
- async function getMode() {
37177
- backtest.loggerService.info(GET_MODE_METHOD_NAME);
37178
- if (!ExecutionContextService.hasContext()) {
37179
- throw new Error("getMode requires an execution context");
37180
- }
37181
- const { backtest: bt } = backtest.executionContextService.context;
37182
- return bt ? "backtest" : "live";
37183
- }
37184
- /**
37185
- * Gets the current trading symbol from execution context.
37186
- *
37187
- * @returns Promise resolving to the current trading symbol (e.g., "BTCUSDT")
37188
- * @throws Error if execution context is not active
37189
- *
37190
- * @example
37191
- * ```typescript
37192
- * const symbol = await getSymbol();
37193
- * console.log(symbol); // "BTCUSDT"
37194
- * ```
37195
- */
37196
- async function getSymbol() {
37197
- backtest.loggerService.info(GET_SYMBOL_METHOD_NAME);
37198
- if (!ExecutionContextService.hasContext()) {
37199
- throw new Error("getSymbol requires an execution context");
37200
- }
37201
- const { symbol } = backtest.executionContextService.context;
37202
- return symbol;
37203
- }
37204
- /**
37205
- * Gets the current method context.
37206
- *
37207
- * Returns the context object from the method context service, which contains
37208
- * information about the current method execution environment.
37209
- *
37210
- * @returns Promise resolving to the current method context object
37211
- * @throws Error if method context is not active
37212
- *
37213
- * @example
37214
- * ```typescript
37215
- * const context = await getContext();
37216
- * console.log(context); // { ...method context data... }
37217
- * ```
37218
- */
37219
- async function getContext() {
37220
- backtest.loggerService.info(GET_CONTEXT_METHOD_NAME);
37221
- if (!MethodContextService.hasContext()) {
37222
- throw new Error("getContext requires a method context");
37223
- }
37224
- return backtest.methodContextService.context;
37225
- }
37226
37352
  /**
37227
37353
  * Fetches order book for a trading pair from the registered exchange.
37228
37354
  *
@@ -40763,6 +40889,10 @@ const LISTEN_MAX_DRAWDOWN_METHOD_NAME = "event.listenMaxDrawdown";
40763
40889
  const LISTEN_MAX_DRAWDOWN_ONCE_METHOD_NAME = "event.listenMaxDrawdownOnce";
40764
40890
  const LISTEN_SIGNAL_NOTIFY_METHOD_NAME = "event.listenSignalNotify";
40765
40891
  const LISTEN_SIGNAL_NOTIFY_ONCE_METHOD_NAME = "event.listenSignalNotifyOnce";
40892
+ const LISTEN_BEFORE_START_METHOD_NAME = "event.listenBeforeStart";
40893
+ const LISTEN_BEFORE_START_ONCE_METHOD_NAME = "event.listenBeforeStartOnce";
40894
+ const LISTEN_AFTER_END_METHOD_NAME = "event.listenAfterEnd";
40895
+ const LISTEN_AFTER_END_ONCE_METHOD_NAME = "event.listenAfterEndOnce";
40766
40896
  /**
40767
40897
  * Subscribes to all signal events with queued async processing.
40768
40898
  *
@@ -42233,6 +42363,68 @@ function listenSignalNotifyOnce(filterFn, fn) {
42233
42363
  };
42234
42364
  return disposeFn = listenSignalNotify(wrappedFn);
42235
42365
  }
42366
+ /**
42367
+ * Subscribes to before start events with queued async processing.
42368
+ * Emits when the engine is about to start a new strategy execution for a symbol.
42369
+ * Events are processed sequentially in order received, even if callback is async.
42370
+ * Uses queued wrapper to prevent concurrent execution of the callback.
42371
+ * @param fn - Callback function to handle before start events
42372
+ * @return Unsubscribe function to stop listening to events
42373
+ */
42374
+ function listenBeforeStart(fn) {
42375
+ backtest.loggerService.log(LISTEN_BEFORE_START_METHOD_NAME);
42376
+ return beforeStartSubject.subscribe(functoolsKit.queued(async (event) => fn(event)));
42377
+ }
42378
+ /**
42379
+ * Subscribes to filtered before start events with one-time execution.
42380
+ * Listens for events matching the filter predicate, then executes callback once
42381
+ * and automatically unsubscribes.
42382
+ * @param filterFn - Predicate to filter which events trigger the callback
42383
+ * @param fn - Callback function to handle the filtered event (called only once)
42384
+ * @return Unsubscribe function to cancel the listener before it fires
42385
+ */
42386
+ function listenBeforeStartOnce(filterFn, fn) {
42387
+ backtest.loggerService.log(LISTEN_BEFORE_START_ONCE_METHOD_NAME);
42388
+ let disposeFn;
42389
+ const wrappedFn = async (event) => {
42390
+ if (filterFn(event)) {
42391
+ await fn(event);
42392
+ disposeFn && disposeFn();
42393
+ }
42394
+ };
42395
+ return disposeFn = listenBeforeStart(wrappedFn);
42396
+ }
42397
+ /**
42398
+ * Subscribes to after end events with queued async processing.
42399
+ * Emits when the engine has completed processing a strategy execution for a symbol.
42400
+ * Events are processed sequentially in order received, even if callback is async.
42401
+ * Uses queued wrapper to prevent concurrent execution of the callback.
42402
+ * @param fn - Callback function to handle after end events
42403
+ * @return Unsubscribe function to stop listening to events
42404
+ */
42405
+ function listenAfterEnd(fn) {
42406
+ backtest.loggerService.log(LISTEN_AFTER_END_METHOD_NAME);
42407
+ return afterEndSubject.subscribe(functoolsKit.queued(async (event) => fn(event)));
42408
+ }
42409
+ /**
42410
+ * Subscribes to filtered after end events with one-time execution.
42411
+ * Listens for events matching the filter predicate, then executes callback once
42412
+ * and automatically unsubscribes.
42413
+ * @param filterFn - Predicate to filter which events trigger the callback
42414
+ * @param fn - Callback function to handle the filtered event (called only once)
42415
+ * @return Unsubscribe function to cancel the listener before it fires
42416
+ */
42417
+ function listenAfterEndOnce(filterFn, fn) {
42418
+ backtest.loggerService.log(LISTEN_AFTER_END_ONCE_METHOD_NAME);
42419
+ let disposeFn;
42420
+ const wrappedFn = async (event) => {
42421
+ if (filterFn(event)) {
42422
+ await fn(event);
42423
+ disposeFn && disposeFn();
42424
+ }
42425
+ };
42426
+ return disposeFn = listenAfterEnd(wrappedFn);
42427
+ }
42236
42428
 
42237
42429
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
42238
42430
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -48921,6 +49113,128 @@ async function listRiskSchema() {
48921
49113
  return await backtest.riskValidationService.list();
48922
49114
  }
48923
49115
 
49116
+ const GET_DATE_METHOD_NAME = "meta.getDate";
49117
+ const GET_TIMESTAMP_METHOD_NAME = "meta.getTimestamp";
49118
+ const GET_MODE_METHOD_NAME = "meta.getMode";
49119
+ const GET_SYMBOL_METHOD_NAME = "meta.getSymbol";
49120
+ const GET_CONTEXT_METHOD_NAME = "meta.getContext";
49121
+ /**
49122
+ * Gets the current date from execution context.
49123
+ *
49124
+ * In backtest mode: returns the current timeframe date being processed
49125
+ * In live mode: returns current real-time date
49126
+ *
49127
+ * @returns Promise resolving to current execution context date
49128
+ *
49129
+ * @example
49130
+ * ```typescript
49131
+ * const date = await getDate();
49132
+ * console.log(date); // 2024-01-01T12:00:00.000Z
49133
+ * ```
49134
+ */
49135
+ async function getDate() {
49136
+ backtest.loggerService.info(GET_DATE_METHOD_NAME);
49137
+ if (!ExecutionContextService.hasContext()) {
49138
+ throw new Error("getDate requires an execution context");
49139
+ }
49140
+ const { when } = backtest.executionContextService.context;
49141
+ return new Date(when.getTime());
49142
+ }
49143
+ /**
49144
+ * Gets the current timestamp from execution context.
49145
+ *
49146
+ * In backtest mode: returns the current timeframe timestamp being processed
49147
+ * In live mode: returns current real-time timestamp
49148
+ *
49149
+ * @returns Promise resolving to current execution context timestamp in milliseconds
49150
+ * @example
49151
+ * ```typescript
49152
+ * const timestamp = await getTimestamp();
49153
+ * console.log(timestamp); // 1700000000000
49154
+ * ```
49155
+ */
49156
+ async function getTimestamp() {
49157
+ backtest.loggerService.info(GET_TIMESTAMP_METHOD_NAME);
49158
+ if (!ExecutionContextService.hasContext()) {
49159
+ throw new Error("getTimestamp requires an execution context");
49160
+ }
49161
+ if (!MethodContextService.hasContext()) {
49162
+ throw new Error("getTimestamp requires a method context");
49163
+ }
49164
+ const { symbol, backtest: isBacktest } = backtest.executionContextService.context;
49165
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49166
+ return backtest.timeMetaService.getTimestamp(symbol, {
49167
+ exchangeName,
49168
+ frameName,
49169
+ strategyName,
49170
+ }, isBacktest);
49171
+ }
49172
+ /**
49173
+ * Gets the current execution mode.
49174
+ *
49175
+ * @returns Promise resolving to "backtest" or "live"
49176
+ *
49177
+ * @example
49178
+ * ```typescript
49179
+ * const mode = await getMode();
49180
+ * if (mode === "backtest") {
49181
+ * console.log("Running in backtest mode");
49182
+ * } else {
49183
+ * console.log("Running in live mode");
49184
+ * }
49185
+ * ```
49186
+ */
49187
+ async function getMode() {
49188
+ backtest.loggerService.info(GET_MODE_METHOD_NAME);
49189
+ if (!ExecutionContextService.hasContext()) {
49190
+ throw new Error("getMode requires an execution context");
49191
+ }
49192
+ const { backtest: bt } = backtest.executionContextService.context;
49193
+ return bt ? "backtest" : "live";
49194
+ }
49195
+ /**
49196
+ * Gets the current trading symbol from execution context.
49197
+ *
49198
+ * @returns Promise resolving to the current trading symbol (e.g., "BTCUSDT")
49199
+ * @throws Error if execution context is not active
49200
+ *
49201
+ * @example
49202
+ * ```typescript
49203
+ * const symbol = await getSymbol();
49204
+ * console.log(symbol); // "BTCUSDT"
49205
+ * ```
49206
+ */
49207
+ async function getSymbol() {
49208
+ backtest.loggerService.info(GET_SYMBOL_METHOD_NAME);
49209
+ if (!ExecutionContextService.hasContext()) {
49210
+ throw new Error("getSymbol requires an execution context");
49211
+ }
49212
+ const { symbol } = backtest.executionContextService.context;
49213
+ return symbol;
49214
+ }
49215
+ /**
49216
+ * Gets the current method context.
49217
+ *
49218
+ * Returns the context object from the method context service, which contains
49219
+ * information about the current method execution environment.
49220
+ *
49221
+ * @returns Promise resolving to the current method context object
49222
+ * @throws Error if method context is not active
49223
+ *
49224
+ * @example
49225
+ * ```typescript
49226
+ * const context = await getContext();
49227
+ * console.log(context); // { ...method context data... }
49228
+ * ```
49229
+ */
49230
+ async function getContext() {
49231
+ backtest.loggerService.info(GET_CONTEXT_METHOD_NAME);
49232
+ if (!MethodContextService.hasContext()) {
49233
+ throw new Error("getContext requires a method context");
49234
+ }
49235
+ return backtest.methodContextService.context;
49236
+ }
49237
+
48924
49238
  const RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistBacktestUtils.handleActivePing";
48925
49239
  const RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistBacktestUtils.getLatestSignal";
48926
49240
  const RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistLiveUtils.handleActivePing";
@@ -54965,6 +55279,8 @@ const SUBJECT_ISOLATION_LIST = [
54965
55279
  syncSubject,
54966
55280
  validationSubject,
54967
55281
  signalNotifySubject,
55282
+ beforeStartSubject,
55283
+ afterEndSubject
54968
55284
  ];
54969
55285
  /**
54970
55286
  * Creates a snapshot function for a given subject by clearing its internal
@@ -64303,7 +64619,11 @@ exports.listStrategySchema = listStrategySchema;
64303
64619
  exports.listWalkerSchema = listWalkerSchema;
64304
64620
  exports.listenActivePing = listenActivePing;
64305
64621
  exports.listenActivePingOnce = listenActivePingOnce;
64622
+ exports.listenAfterEnd = listenAfterEnd;
64623
+ exports.listenAfterEndOnce = listenAfterEndOnce;
64306
64624
  exports.listenBacktestProgress = listenBacktestProgress;
64625
+ exports.listenBeforeStart = listenBeforeStart;
64626
+ exports.listenBeforeStartOnce = listenBeforeStartOnce;
64307
64627
  exports.listenBreakevenAvailable = listenBreakevenAvailable;
64308
64628
  exports.listenBreakevenAvailableOnce = listenBreakevenAvailableOnce;
64309
64629
  exports.listenDoneBacktest = listenDoneBacktest;