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.mjs CHANGED
@@ -788,11 +788,23 @@ const maxDrawdownSubject = new Subject();
788
788
  * Emits when a strategy calls commitSignalInfo() to broadcast a custom annotation.
789
789
  */
790
790
  const signalNotifySubject = new Subject();
791
+ /**
792
+ * Before start emitter for strategy initialization events.
793
+ * Emits when the engine is about to start a new strategy execution.
794
+ */
795
+ const beforeStartSubject = new Subject();
796
+ /**
797
+ * After end emitter for strategy completion events.
798
+ * Emits when the engine has completed processing a signal.
799
+ */
800
+ const afterEndSubject = new Subject();
791
801
 
792
802
  var emitters = /*#__PURE__*/Object.freeze({
793
803
  __proto__: null,
794
804
  activePingSubject: activePingSubject,
805
+ afterEndSubject: afterEndSubject,
795
806
  backtestScheduleOpenSubject: backtestScheduleOpenSubject,
807
+ beforeStartSubject: beforeStartSubject,
796
808
  breakevenSubject: breakevenSubject,
797
809
  doneBacktestSubject: doneBacktestSubject,
798
810
  doneLiveSubject: doneLiveSubject,
@@ -20400,6 +20412,110 @@ class WalkerLogicPrivateService {
20400
20412
  }
20401
20413
  }
20402
20414
 
20415
+ /**
20416
+ * Run iterator function for backtest logic.
20417
+ *
20418
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20419
+ * @param context - Execution context with strategy, exchange, and frame names
20420
+ * @param self - Instance of BacktestLogicPublicService
20421
+ * @returns Async iterator for backtest results
20422
+ */
20423
+ const RUN_ITERATOR_FN$1 = (self, symbol, context) => {
20424
+ return MethodContextService.runAsyncIterator(self.backtestLogicPrivateService.run(symbol), {
20425
+ exchangeName: context.exchangeName,
20426
+ strategyName: context.strategyName,
20427
+ frameName: context.frameName,
20428
+ });
20429
+ };
20430
+ /**
20431
+ * Call before start execution for backtest logic.
20432
+ * This function is responsible for triggering the beforeStartSubject
20433
+ * with the appropriate context and symbol information.
20434
+ */
20435
+ const CALL_BEFORE_START_FN$1 = trycatch(async (self, symbol, context) => {
20436
+ const { startDate } = self.frameSchemaService.get(context.frameName);
20437
+ const when = alignToInterval(startDate, "1m");
20438
+ await MethodContextService.runInContext(async () => {
20439
+ await ExecutionContextService.runInContext(async () => {
20440
+ const currentPrice = await self.exchangeConnectionService.getAveragePrice(symbol);
20441
+ await beforeStartSubject.next({
20442
+ symbol,
20443
+ exchangeName: context.exchangeName,
20444
+ strategyName: context.strategyName,
20445
+ frameName: context.frameName,
20446
+ backtest: true,
20447
+ when,
20448
+ timestamp: when.getTime(),
20449
+ currentPrice,
20450
+ });
20451
+ }, {
20452
+ symbol,
20453
+ when,
20454
+ backtest: true,
20455
+ });
20456
+ }, {
20457
+ exchangeName: context.exchangeName,
20458
+ strategyName: context.strategyName,
20459
+ frameName: context.frameName,
20460
+ });
20461
+ }, {
20462
+ fallback: (error, self) => {
20463
+ const message = "BacktestLogicPublicService CALL_BEFORE_START_FN thrown";
20464
+ const payload = {
20465
+ error: errorData(error),
20466
+ message: getErrorMessage(error),
20467
+ };
20468
+ self.loggerService.warn(message, payload);
20469
+ console.error(message, payload);
20470
+ errorEmitter.next(error);
20471
+ },
20472
+ });
20473
+ /**
20474
+ * Call after end execution for backtest logic.
20475
+ * This function is responsible for triggering the afterEndSubject
20476
+ * with the appropriate context and symbol information.
20477
+ */
20478
+ const CALL_AFTER_END_FN$1 = trycatch(async (self, symbol, context) => {
20479
+ const { startDate } = self.frameSchemaService.get(context.frameName);
20480
+ const timestamp = self.timeMetaService.hasTimestamp(symbol, context, true)
20481
+ ? await self.timeMetaService.getTimestamp(symbol, context, true)
20482
+ : startDate.getTime();
20483
+ const when = new Date(timestamp);
20484
+ await MethodContextService.runInContext(async () => {
20485
+ await ExecutionContextService.runInContext(async () => {
20486
+ const currentPrice = await self.exchangeConnectionService.getAveragePrice(symbol);
20487
+ await afterEndSubject.next({
20488
+ symbol,
20489
+ exchangeName: context.exchangeName,
20490
+ strategyName: context.strategyName,
20491
+ frameName: context.frameName,
20492
+ backtest: true,
20493
+ when,
20494
+ timestamp,
20495
+ currentPrice,
20496
+ });
20497
+ }, {
20498
+ symbol,
20499
+ when,
20500
+ backtest: true,
20501
+ });
20502
+ }, {
20503
+ exchangeName: context.exchangeName,
20504
+ strategyName: context.strategyName,
20505
+ frameName: context.frameName,
20506
+ });
20507
+ }, {
20508
+ fallback: (error, self) => {
20509
+ const message = "BacktestLogicPublicService CALL_AFTER_END_FN thrown";
20510
+ const payload = {
20511
+ error: errorData(error),
20512
+ message: getErrorMessage(error),
20513
+ };
20514
+ self.loggerService.warn(message, payload);
20515
+ console.error(message, payload);
20516
+ errorEmitter.next(error);
20517
+ },
20518
+ });
20403
20519
  /**
20404
20520
  * Public service for backtest orchestration with context management.
20405
20521
  *
@@ -20428,30 +20544,134 @@ class BacktestLogicPublicService {
20428
20544
  constructor() {
20429
20545
  this.loggerService = inject(TYPES.loggerService);
20430
20546
  this.backtestLogicPrivateService = inject(TYPES.backtestLogicPrivateService);
20431
- /**
20432
- * Runs backtest for a symbol with context propagation.
20433
- *
20434
- * Streams closed signals as async generator. Context is automatically
20435
- * injected into all framework functions called during iteration.
20436
- *
20437
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20438
- * @param context - Execution context with strategy, exchange, and frame names
20439
- * @returns Async generator yielding closed signals with PNL
20440
- */
20441
- this.run = (symbol, context) => {
20442
- this.loggerService.log("backtestLogicPublicService run", {
20547
+ this.timeMetaService = inject(TYPES.timeMetaService);
20548
+ this.frameSchemaService = inject(TYPES.frameSchemaService);
20549
+ this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
20550
+ }
20551
+ /**
20552
+ * Runs backtest for a symbol with context propagation.
20553
+ *
20554
+ * Streams closed signals as async generator. Context is automatically
20555
+ * injected into all framework functions called during iteration.
20556
+ *
20557
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20558
+ * @param context - Execution context with strategy, exchange, and frame names
20559
+ * @returns Async generator yielding closed signals with PNL
20560
+ */
20561
+ async *run(symbol, context) {
20562
+ this.loggerService.log("backtestLogicPublicService run", {
20563
+ symbol,
20564
+ context,
20565
+ });
20566
+ await CALL_BEFORE_START_FN$1(this, symbol, context);
20567
+ try {
20568
+ yield* RUN_ITERATOR_FN$1(this, symbol, context);
20569
+ }
20570
+ finally {
20571
+ await CALL_AFTER_END_FN$1(this, symbol, context);
20572
+ }
20573
+ }
20574
+ }
20575
+
20576
+ /**
20577
+ * Run iterator function for live logic.
20578
+ *
20579
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20580
+ * @param context - Execution context with strategy and exchange names
20581
+ * @param self - Instance of LiveLogicPublicService
20582
+ * @returns Async iterator for live trading results
20583
+ */
20584
+ const RUN_ITERATOR_FN = (self, symbol, context) => {
20585
+ return MethodContextService.runAsyncIterator(self.liveLogicPrivateService.run(symbol), {
20586
+ exchangeName: context.exchangeName,
20587
+ strategyName: context.strategyName,
20588
+ frameName: "",
20589
+ });
20590
+ };
20591
+ /**
20592
+ * Call before start execution for live logic.
20593
+ * This function is responsible for triggering the beforeStartSubject
20594
+ * with the appropriate context and symbol information.
20595
+ */
20596
+ const CALL_BEFORE_START_FN = trycatch(async (self, symbol, context) => {
20597
+ const when = alignToInterval(new Date(), "1m");
20598
+ await MethodContextService.runInContext(async () => {
20599
+ await ExecutionContextService.runInContext(async () => {
20600
+ const currentPrice = await self.exchangeConnectionService.getAveragePrice(symbol);
20601
+ await beforeStartSubject.next({
20443
20602
  symbol,
20444
- context,
20603
+ exchangeName: context.exchangeName,
20604
+ strategyName: context.strategyName,
20605
+ frameName: "",
20606
+ backtest: false,
20607
+ currentPrice,
20608
+ when,
20609
+ timestamp: when.getTime(),
20445
20610
  });
20446
- return MethodContextService.runAsyncIterator(this.backtestLogicPrivateService.run(symbol), {
20611
+ }, {
20612
+ symbol,
20613
+ when,
20614
+ backtest: false,
20615
+ });
20616
+ }, {
20617
+ exchangeName: context.exchangeName,
20618
+ strategyName: context.strategyName,
20619
+ frameName: "",
20620
+ });
20621
+ }, {
20622
+ fallback: (error, self) => {
20623
+ const message = "LiveLogicPublicService CALL_BEFORE_START_FN thrown";
20624
+ const payload = {
20625
+ error: errorData(error),
20626
+ message: getErrorMessage(error),
20627
+ };
20628
+ self.loggerService.warn(message, payload);
20629
+ console.error(message, payload);
20630
+ errorEmitter.next(error);
20631
+ },
20632
+ });
20633
+ /**
20634
+ * Call after end execution for live logic.
20635
+ * This function is responsible for triggering the afterEndSubject
20636
+ * with the appropriate context and symbol information.
20637
+ */
20638
+ const CALL_AFTER_END_FN = trycatch(async (self, symbol, context) => {
20639
+ const when = alignToInterval(new Date(), "1m");
20640
+ await MethodContextService.runInContext(async () => {
20641
+ await ExecutionContextService.runInContext(async () => {
20642
+ const currentPrice = await self.exchangeConnectionService.getAveragePrice(symbol);
20643
+ await afterEndSubject.next({
20644
+ symbol,
20447
20645
  exchangeName: context.exchangeName,
20448
20646
  strategyName: context.strategyName,
20449
- frameName: context.frameName,
20647
+ frameName: "",
20648
+ backtest: false,
20649
+ currentPrice,
20650
+ when,
20651
+ timestamp: when.getTime(),
20450
20652
  });
20653
+ }, {
20654
+ symbol,
20655
+ when,
20656
+ backtest: false,
20657
+ });
20658
+ }, {
20659
+ exchangeName: context.exchangeName,
20660
+ strategyName: context.strategyName,
20661
+ frameName: "",
20662
+ });
20663
+ }, {
20664
+ fallback: (error, self) => {
20665
+ const message = "LiveLogicPublicService CALL_AFTER_END_FN thrown";
20666
+ const payload = {
20667
+ error: errorData(error),
20668
+ message: getErrorMessage(error),
20451
20669
  };
20452
- }
20453
- }
20454
-
20670
+ self.loggerService.warn(message, payload);
20671
+ console.error(message, payload);
20672
+ errorEmitter.next(error);
20673
+ },
20674
+ });
20455
20675
  /**
20456
20676
  * Public service for live trading orchestration with context management.
20457
20677
  *
@@ -20487,28 +20707,31 @@ class LiveLogicPublicService {
20487
20707
  constructor() {
20488
20708
  this.loggerService = inject(TYPES.loggerService);
20489
20709
  this.liveLogicPrivateService = inject(TYPES.liveLogicPrivateService);
20490
- /**
20491
- * Runs live trading for a symbol with context propagation.
20492
- *
20493
- * Streams opened and closed signals as infinite async generator.
20494
- * Context is automatically injected into all framework functions.
20495
- * Process can crash and restart - state will be recovered from disk.
20496
- *
20497
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20498
- * @param context - Execution context with strategy and exchange names
20499
- * @returns Infinite async generator yielding opened and closed signals
20500
- */
20501
- this.run = (symbol, context) => {
20502
- this.loggerService.log("liveLogicPublicService run", {
20503
- symbol,
20504
- context,
20505
- });
20506
- return MethodContextService.runAsyncIterator(this.liveLogicPrivateService.run(symbol), {
20507
- exchangeName: context.exchangeName,
20508
- strategyName: context.strategyName,
20509
- frameName: "",
20510
- });
20511
- };
20710
+ this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
20711
+ }
20712
+ /**
20713
+ * Runs live trading for a symbol with context propagation.
20714
+ *
20715
+ * Streams opened and closed signals as infinite async generator.
20716
+ * Context is automatically injected into all framework functions.
20717
+ * Process can crash and restart - state will be recovered from disk.
20718
+ *
20719
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
20720
+ * @param context - Execution context with strategy and exchange names
20721
+ * @returns Infinite async generator yielding opened and closed signals
20722
+ */
20723
+ async *run(symbol, context) {
20724
+ this.loggerService.log("liveLogicPublicService run", {
20725
+ symbol,
20726
+ context,
20727
+ });
20728
+ await CALL_BEFORE_START_FN(this, symbol, context);
20729
+ try {
20730
+ yield* RUN_ITERATOR_FN(this, symbol, context);
20731
+ }
20732
+ finally {
20733
+ await CALL_AFTER_END_FN(this, symbol, context);
20734
+ }
20512
20735
  }
20513
20736
  }
20514
20737
 
@@ -34712,6 +34935,21 @@ class TimeMetaService {
34712
34935
  * Instances are cached until clear() is called.
34713
34936
  */
34714
34937
  this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
34938
+ /**
34939
+ * Checks if a timestamp exists for the given symbol and context.
34940
+ *
34941
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
34942
+ * @param context - Strategy, exchange, and frame identifiers
34943
+ * @param backtest - True if backtest mode, false if live mode
34944
+ * @returns True if a timestamp is available, false otherwise
34945
+ */
34946
+ this.hasTimestamp = (symbol, context, backtest) => {
34947
+ const key = CREATE_KEY_FN$a(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
34948
+ if (!this.getSource.has(key)) {
34949
+ return false;
34950
+ }
34951
+ return !!this.getSource.get(key)?.data;
34952
+ };
34715
34953
  /**
34716
34954
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
34717
34955
  *
@@ -36382,7 +36620,7 @@ const PRINT_PROGRESS_FN = (fetched, total, symbol, interval) => {
36382
36620
  */
36383
36621
  const CACHE_CANDLES_FN = retry(async (interval, dto, onWarmStart, onCheckStart) => {
36384
36622
  try {
36385
- onWarmStart && onWarmStart(dto.symbol, interval, dto.from, dto.to);
36623
+ onCheckStart && onCheckStart(dto.symbol, interval, dto.from, dto.to);
36386
36624
  await checkCandles({
36387
36625
  exchangeName: dto.exchangeName,
36388
36626
  from: dto.from,
@@ -36392,7 +36630,7 @@ const CACHE_CANDLES_FN = retry(async (interval, dto, onWarmStart, onCheckStart)
36392
36630
  });
36393
36631
  }
36394
36632
  catch (error) {
36395
- onCheckStart && onCheckStart(dto.symbol, interval, dto.from, dto.to);
36633
+ onWarmStart && onWarmStart(dto.symbol, interval, dto.from, dto.to);
36396
36634
  await warmCandles({
36397
36635
  symbol: dto.symbol,
36398
36636
  exchangeName: dto.exchangeName,
@@ -36925,11 +37163,6 @@ const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
36925
37163
  const GET_CLOSE_PRICE_METHOD_NAME = "exchange.getClosePrice";
36926
37164
  const FORMAT_PRICE_METHOD_NAME = "exchange.formatPrice";
36927
37165
  const FORMAT_QUANTITY_METHOD_NAME = "exchange.formatQuantity";
36928
- const GET_DATE_METHOD_NAME = "exchange.getDate";
36929
- const GET_TIMESTAMP_METHOD_NAME = "exchange.getTimestamp";
36930
- const GET_MODE_METHOD_NAME = "exchange.getMode";
36931
- const GET_SYMBOL_METHOD_NAME = "exchange.getSymbol";
36932
- const GET_CONTEXT_METHOD_NAME = "exchange.getContext";
36933
37166
  const HAS_TRADE_CONTEXT_METHOD_NAME = "exchange.hasTradeContext";
36934
37167
  const GET_ORDER_BOOK_METHOD_NAME = "exchange.getOrderBook";
36935
37168
  const GET_RAW_CANDLES_METHOD_NAME = "exchange.getRawCandles";
@@ -37096,113 +37329,6 @@ async function formatQuantity(symbol, quantity) {
37096
37329
  }
37097
37330
  return await backtest.exchangeConnectionService.formatQuantity(symbol, quantity);
37098
37331
  }
37099
- /**
37100
- * Gets the current date from execution context.
37101
- *
37102
- * In backtest mode: returns the current timeframe date being processed
37103
- * In live mode: returns current real-time date
37104
- *
37105
- * @returns Promise resolving to current execution context date
37106
- *
37107
- * @example
37108
- * ```typescript
37109
- * const date = await getDate();
37110
- * console.log(date); // 2024-01-01T12:00:00.000Z
37111
- * ```
37112
- */
37113
- async function getDate() {
37114
- backtest.loggerService.info(GET_DATE_METHOD_NAME);
37115
- if (!ExecutionContextService.hasContext()) {
37116
- throw new Error("getDate requires an execution context");
37117
- }
37118
- const { when } = backtest.executionContextService.context;
37119
- return new Date(when.getTime());
37120
- }
37121
- /**
37122
- * Gets the current timestamp from execution context.
37123
- *
37124
- * In backtest mode: returns the current timeframe timestamp being processed
37125
- * In live mode: returns current real-time timestamp
37126
- *
37127
- * @returns Promise resolving to current execution context timestamp in milliseconds
37128
- * @example
37129
- * ```typescript
37130
- * const timestamp = await getTimestamp();
37131
- * console.log(timestamp); // 1700000000000
37132
- * ```
37133
- */
37134
- async function getTimestamp() {
37135
- backtest.loggerService.info(GET_TIMESTAMP_METHOD_NAME);
37136
- if (!ExecutionContextService.hasContext()) {
37137
- throw new Error("getTimestamp requires an execution context");
37138
- }
37139
- return getContextTimestamp();
37140
- }
37141
- /**
37142
- * Gets the current execution mode.
37143
- *
37144
- * @returns Promise resolving to "backtest" or "live"
37145
- *
37146
- * @example
37147
- * ```typescript
37148
- * const mode = await getMode();
37149
- * if (mode === "backtest") {
37150
- * console.log("Running in backtest mode");
37151
- * } else {
37152
- * console.log("Running in live mode");
37153
- * }
37154
- * ```
37155
- */
37156
- async function getMode() {
37157
- backtest.loggerService.info(GET_MODE_METHOD_NAME);
37158
- if (!ExecutionContextService.hasContext()) {
37159
- throw new Error("getMode requires an execution context");
37160
- }
37161
- const { backtest: bt } = backtest.executionContextService.context;
37162
- return bt ? "backtest" : "live";
37163
- }
37164
- /**
37165
- * Gets the current trading symbol from execution context.
37166
- *
37167
- * @returns Promise resolving to the current trading symbol (e.g., "BTCUSDT")
37168
- * @throws Error if execution context is not active
37169
- *
37170
- * @example
37171
- * ```typescript
37172
- * const symbol = await getSymbol();
37173
- * console.log(symbol); // "BTCUSDT"
37174
- * ```
37175
- */
37176
- async function getSymbol() {
37177
- backtest.loggerService.info(GET_SYMBOL_METHOD_NAME);
37178
- if (!ExecutionContextService.hasContext()) {
37179
- throw new Error("getSymbol requires an execution context");
37180
- }
37181
- const { symbol } = backtest.executionContextService.context;
37182
- return symbol;
37183
- }
37184
- /**
37185
- * Gets the current method context.
37186
- *
37187
- * Returns the context object from the method context service, which contains
37188
- * information about the current method execution environment.
37189
- *
37190
- * @returns Promise resolving to the current method context object
37191
- * @throws Error if method context is not active
37192
- *
37193
- * @example
37194
- * ```typescript
37195
- * const context = await getContext();
37196
- * console.log(context); // { ...method context data... }
37197
- * ```
37198
- */
37199
- async function getContext() {
37200
- backtest.loggerService.info(GET_CONTEXT_METHOD_NAME);
37201
- if (!MethodContextService.hasContext()) {
37202
- throw new Error("getContext requires a method context");
37203
- }
37204
- return backtest.methodContextService.context;
37205
- }
37206
37332
  /**
37207
37333
  * Fetches order book for a trading pair from the registered exchange.
37208
37334
  *
@@ -40743,6 +40869,10 @@ const LISTEN_MAX_DRAWDOWN_METHOD_NAME = "event.listenMaxDrawdown";
40743
40869
  const LISTEN_MAX_DRAWDOWN_ONCE_METHOD_NAME = "event.listenMaxDrawdownOnce";
40744
40870
  const LISTEN_SIGNAL_NOTIFY_METHOD_NAME = "event.listenSignalNotify";
40745
40871
  const LISTEN_SIGNAL_NOTIFY_ONCE_METHOD_NAME = "event.listenSignalNotifyOnce";
40872
+ const LISTEN_BEFORE_START_METHOD_NAME = "event.listenBeforeStart";
40873
+ const LISTEN_BEFORE_START_ONCE_METHOD_NAME = "event.listenBeforeStartOnce";
40874
+ const LISTEN_AFTER_END_METHOD_NAME = "event.listenAfterEnd";
40875
+ const LISTEN_AFTER_END_ONCE_METHOD_NAME = "event.listenAfterEndOnce";
40746
40876
  /**
40747
40877
  * Subscribes to all signal events with queued async processing.
40748
40878
  *
@@ -42213,6 +42343,68 @@ function listenSignalNotifyOnce(filterFn, fn) {
42213
42343
  };
42214
42344
  return disposeFn = listenSignalNotify(wrappedFn);
42215
42345
  }
42346
+ /**
42347
+ * Subscribes to before start events with queued async processing.
42348
+ * Emits when the engine is about to start a new strategy execution for a symbol.
42349
+ * Events are processed sequentially in order received, even if callback is async.
42350
+ * Uses queued wrapper to prevent concurrent execution of the callback.
42351
+ * @param fn - Callback function to handle before start events
42352
+ * @return Unsubscribe function to stop listening to events
42353
+ */
42354
+ function listenBeforeStart(fn) {
42355
+ backtest.loggerService.log(LISTEN_BEFORE_START_METHOD_NAME);
42356
+ return beforeStartSubject.subscribe(queued(async (event) => fn(event)));
42357
+ }
42358
+ /**
42359
+ * Subscribes to filtered before start events with one-time execution.
42360
+ * Listens for events matching the filter predicate, then executes callback once
42361
+ * and automatically unsubscribes.
42362
+ * @param filterFn - Predicate to filter which events trigger the callback
42363
+ * @param fn - Callback function to handle the filtered event (called only once)
42364
+ * @return Unsubscribe function to cancel the listener before it fires
42365
+ */
42366
+ function listenBeforeStartOnce(filterFn, fn) {
42367
+ backtest.loggerService.log(LISTEN_BEFORE_START_ONCE_METHOD_NAME);
42368
+ let disposeFn;
42369
+ const wrappedFn = async (event) => {
42370
+ if (filterFn(event)) {
42371
+ await fn(event);
42372
+ disposeFn && disposeFn();
42373
+ }
42374
+ };
42375
+ return disposeFn = listenBeforeStart(wrappedFn);
42376
+ }
42377
+ /**
42378
+ * Subscribes to after end events with queued async processing.
42379
+ * Emits when the engine has completed processing a strategy execution for a symbol.
42380
+ * Events are processed sequentially in order received, even if callback is async.
42381
+ * Uses queued wrapper to prevent concurrent execution of the callback.
42382
+ * @param fn - Callback function to handle after end events
42383
+ * @return Unsubscribe function to stop listening to events
42384
+ */
42385
+ function listenAfterEnd(fn) {
42386
+ backtest.loggerService.log(LISTEN_AFTER_END_METHOD_NAME);
42387
+ return afterEndSubject.subscribe(queued(async (event) => fn(event)));
42388
+ }
42389
+ /**
42390
+ * Subscribes to filtered after end events with one-time execution.
42391
+ * Listens for events matching the filter predicate, then executes callback once
42392
+ * and automatically unsubscribes.
42393
+ * @param filterFn - Predicate to filter which events trigger the callback
42394
+ * @param fn - Callback function to handle the filtered event (called only once)
42395
+ * @return Unsubscribe function to cancel the listener before it fires
42396
+ */
42397
+ function listenAfterEndOnce(filterFn, fn) {
42398
+ backtest.loggerService.log(LISTEN_AFTER_END_ONCE_METHOD_NAME);
42399
+ let disposeFn;
42400
+ const wrappedFn = async (event) => {
42401
+ if (filterFn(event)) {
42402
+ await fn(event);
42403
+ disposeFn && disposeFn();
42404
+ }
42405
+ };
42406
+ return disposeFn = listenAfterEnd(wrappedFn);
42407
+ }
42216
42408
 
42217
42409
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
42218
42410
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -48901,6 +49093,128 @@ async function listRiskSchema() {
48901
49093
  return await backtest.riskValidationService.list();
48902
49094
  }
48903
49095
 
49096
+ const GET_DATE_METHOD_NAME = "meta.getDate";
49097
+ const GET_TIMESTAMP_METHOD_NAME = "meta.getTimestamp";
49098
+ const GET_MODE_METHOD_NAME = "meta.getMode";
49099
+ const GET_SYMBOL_METHOD_NAME = "meta.getSymbol";
49100
+ const GET_CONTEXT_METHOD_NAME = "meta.getContext";
49101
+ /**
49102
+ * Gets the current date from execution context.
49103
+ *
49104
+ * In backtest mode: returns the current timeframe date being processed
49105
+ * In live mode: returns current real-time date
49106
+ *
49107
+ * @returns Promise resolving to current execution context date
49108
+ *
49109
+ * @example
49110
+ * ```typescript
49111
+ * const date = await getDate();
49112
+ * console.log(date); // 2024-01-01T12:00:00.000Z
49113
+ * ```
49114
+ */
49115
+ async function getDate() {
49116
+ backtest.loggerService.info(GET_DATE_METHOD_NAME);
49117
+ if (!ExecutionContextService.hasContext()) {
49118
+ throw new Error("getDate requires an execution context");
49119
+ }
49120
+ const { when } = backtest.executionContextService.context;
49121
+ return new Date(when.getTime());
49122
+ }
49123
+ /**
49124
+ * Gets the current timestamp from execution context.
49125
+ *
49126
+ * In backtest mode: returns the current timeframe timestamp being processed
49127
+ * In live mode: returns current real-time timestamp
49128
+ *
49129
+ * @returns Promise resolving to current execution context timestamp in milliseconds
49130
+ * @example
49131
+ * ```typescript
49132
+ * const timestamp = await getTimestamp();
49133
+ * console.log(timestamp); // 1700000000000
49134
+ * ```
49135
+ */
49136
+ async function getTimestamp() {
49137
+ backtest.loggerService.info(GET_TIMESTAMP_METHOD_NAME);
49138
+ if (!ExecutionContextService.hasContext()) {
49139
+ throw new Error("getTimestamp requires an execution context");
49140
+ }
49141
+ if (!MethodContextService.hasContext()) {
49142
+ throw new Error("getTimestamp requires a method context");
49143
+ }
49144
+ const { symbol, backtest: isBacktest } = backtest.executionContextService.context;
49145
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
49146
+ return backtest.timeMetaService.getTimestamp(symbol, {
49147
+ exchangeName,
49148
+ frameName,
49149
+ strategyName,
49150
+ }, isBacktest);
49151
+ }
49152
+ /**
49153
+ * Gets the current execution mode.
49154
+ *
49155
+ * @returns Promise resolving to "backtest" or "live"
49156
+ *
49157
+ * @example
49158
+ * ```typescript
49159
+ * const mode = await getMode();
49160
+ * if (mode === "backtest") {
49161
+ * console.log("Running in backtest mode");
49162
+ * } else {
49163
+ * console.log("Running in live mode");
49164
+ * }
49165
+ * ```
49166
+ */
49167
+ async function getMode() {
49168
+ backtest.loggerService.info(GET_MODE_METHOD_NAME);
49169
+ if (!ExecutionContextService.hasContext()) {
49170
+ throw new Error("getMode requires an execution context");
49171
+ }
49172
+ const { backtest: bt } = backtest.executionContextService.context;
49173
+ return bt ? "backtest" : "live";
49174
+ }
49175
+ /**
49176
+ * Gets the current trading symbol from execution context.
49177
+ *
49178
+ * @returns Promise resolving to the current trading symbol (e.g., "BTCUSDT")
49179
+ * @throws Error if execution context is not active
49180
+ *
49181
+ * @example
49182
+ * ```typescript
49183
+ * const symbol = await getSymbol();
49184
+ * console.log(symbol); // "BTCUSDT"
49185
+ * ```
49186
+ */
49187
+ async function getSymbol() {
49188
+ backtest.loggerService.info(GET_SYMBOL_METHOD_NAME);
49189
+ if (!ExecutionContextService.hasContext()) {
49190
+ throw new Error("getSymbol requires an execution context");
49191
+ }
49192
+ const { symbol } = backtest.executionContextService.context;
49193
+ return symbol;
49194
+ }
49195
+ /**
49196
+ * Gets the current method context.
49197
+ *
49198
+ * Returns the context object from the method context service, which contains
49199
+ * information about the current method execution environment.
49200
+ *
49201
+ * @returns Promise resolving to the current method context object
49202
+ * @throws Error if method context is not active
49203
+ *
49204
+ * @example
49205
+ * ```typescript
49206
+ * const context = await getContext();
49207
+ * console.log(context); // { ...method context data... }
49208
+ * ```
49209
+ */
49210
+ async function getContext() {
49211
+ backtest.loggerService.info(GET_CONTEXT_METHOD_NAME);
49212
+ if (!MethodContextService.hasContext()) {
49213
+ throw new Error("getContext requires a method context");
49214
+ }
49215
+ return backtest.methodContextService.context;
49216
+ }
49217
+
48904
49218
  const RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistBacktestUtils.handleActivePing";
48905
49219
  const RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistBacktestUtils.getLatestSignal";
48906
49220
  const RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistLiveUtils.handleActivePing";
@@ -54945,6 +55259,8 @@ const SUBJECT_ISOLATION_LIST = [
54945
55259
  syncSubject,
54946
55260
  validationSubject,
54947
55261
  signalNotifySubject,
55262
+ beforeStartSubject,
55263
+ afterEndSubject
54948
55264
  ];
54949
55265
  /**
54950
55266
  * Creates a snapshot function for a given subject by clearing its internal
@@ -64077,4 +64393,4 @@ const validateSignal = (signal, currentPrice) => {
64077
64393
  return !errors.length;
64078
64394
  };
64079
64395
 
64080
- export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Lookup, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistBreakevenInstance, PersistCandleAdapter, PersistCandleInstance, PersistIntervalAdapter, PersistIntervalInstance, PersistLogAdapter, PersistLogInstance, PersistMeasureAdapter, PersistMeasureInstance, PersistMemoryAdapter, PersistMemoryInstance, PersistNotificationAdapter, PersistNotificationInstance, PersistPartialAdapter, PersistPartialInstance, PersistRecentAdapter, PersistRecentInstance, PersistRiskAdapter, PersistRiskInstance, PersistScheduleAdapter, PersistScheduleInstance, PersistSessionAdapter, PersistSessionInstance, PersistSignalAdapter, PersistSignalInstance, PersistStateAdapter, PersistStateInstance, PersistStorageAdapter, PersistStorageInstance, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, SessionBacktest, SessionLive, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, System, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, cacheCandles, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getClosePrice, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getRawCandles, getRiskSchema, getScheduledSignal, getSessionData, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, intervalStepMs, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSessionData, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, waitForReady, warmCandles, writeMemory };
64396
+ export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Lookup, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MemoryBacktest, MemoryBacktestAdapter, MemoryLive, MemoryLiveAdapter, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistBreakevenInstance, PersistCandleAdapter, PersistCandleInstance, PersistIntervalAdapter, PersistIntervalInstance, PersistLogAdapter, PersistLogInstance, PersistMeasureAdapter, PersistMeasureInstance, PersistMemoryAdapter, PersistMemoryInstance, PersistNotificationAdapter, PersistNotificationInstance, PersistPartialAdapter, PersistPartialInstance, PersistRecentAdapter, PersistRecentInstance, PersistRiskAdapter, PersistRiskInstance, PersistScheduleAdapter, PersistScheduleInstance, PersistSessionAdapter, PersistSessionInstance, PersistSignalAdapter, PersistSignalInstance, PersistStateAdapter, PersistStateInstance, PersistStorageAdapter, PersistStorageInstance, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, SessionBacktest, SessionLive, State, StateBacktest, StateBacktestAdapter, StateLive, StateLiveAdapter, Storage, StorageBacktest, StorageLive, Strategy, Sync, System, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, cacheCandles, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, createSignalState, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getClosePrice, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMinutesSinceLatestSignalCreated, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionActiveMinutes, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getPositionWaitingMinutes, getRawCandles, getRiskSchema, getScheduledSignal, getSessionData, getSignalState, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, intervalStepMs, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenAfterEnd, listenAfterEndOnce, listenBacktestProgress, listenBeforeStart, listenBeforeStartOnce, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenIdlePing, listenIdlePingOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalNotify, listenSignalNotifyOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, setSessionData, setSignalState, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, waitForReady, warmCandles, writeMemory };