backtest-kit 6.15.0 → 6.16.0

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
@@ -714,6 +714,11 @@ const schedulePingSubject = new Subject();
714
714
  * Allows users to track active signal lifecycle and implement custom dynamic management logic.
715
715
  */
716
716
  const activePingSubject = new Subject();
717
+ /**
718
+ * Idle ping emitter for strategy idle state events.
719
+ * Emits every tick when there is no pending or scheduled signal being monitored.
720
+ */
721
+ const idlePingSubject = new Subject();
717
722
  /**
718
723
  * Strategy management signal emitter.
719
724
  * Emits when strategy management actions are executed:
@@ -765,6 +770,7 @@ var emitters = /*#__PURE__*/Object.freeze({
765
770
  errorEmitter: errorEmitter,
766
771
  exitEmitter: exitEmitter,
767
772
  highestProfitSubject: highestProfitSubject,
773
+ idlePingSubject: idlePingSubject,
768
774
  maxDrawdownSubject: maxDrawdownSubject,
769
775
  partialLossSubject: partialLossSubject,
770
776
  partialProfitSubject: partialProfitSubject,
@@ -6028,6 +6034,27 @@ const CALL_ACTIVE_PING_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, pe
6028
6034
  errorEmitter.next(error);
6029
6035
  },
6030
6036
  });
6037
+ const CALL_IDLE_PING_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, timestamp, backtest, currentPrice) => {
6038
+ await ExecutionContextService.runInContext(async () => {
6039
+ // Call system onIdlePing callback (emits to idlePingSubject)
6040
+ await self.params.onIdlePing(self.params.execution.context.symbol, self.params.method.context.strategyName, self.params.method.context.exchangeName, currentPrice, self.params.execution.context.backtest, timestamp);
6041
+ }, {
6042
+ when: new Date(timestamp),
6043
+ symbol: symbol,
6044
+ backtest: backtest,
6045
+ });
6046
+ }), {
6047
+ fallback: (error, self) => {
6048
+ const message = "ClientStrategy CALL_IDLE_PING_CALLBACKS_FN thrown";
6049
+ const payload = {
6050
+ error: errorData(error),
6051
+ message: getErrorMessage(error),
6052
+ };
6053
+ self.params.logger.warn(message, payload);
6054
+ console.warn(message, payload);
6055
+ errorEmitter.next(error);
6056
+ },
6057
+ });
6031
6058
  const CALL_ACTIVE_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
6032
6059
  await ExecutionContextService.runInContext(async () => {
6033
6060
  if (self.params.callbacks?.onActive) {
@@ -6683,6 +6710,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice, backt
6683
6710
  };
6684
6711
  const RETURN_IDLE_FN = async (self, currentPrice) => {
6685
6712
  const currentTime = self.params.execution.context.when.getTime();
6713
+ await CALL_IDLE_PING_CALLBACKS_FN(self, self.params.execution.context.symbol, currentTime, self.params.execution.context.backtest, currentPrice);
6686
6714
  await CALL_IDLE_CALLBACKS_FN(self, self.params.execution.context.symbol, currentPrice, currentTime, self.params.execution.context.backtest);
6687
6715
  const result = {
6688
6716
  action: "idle",
@@ -10355,11 +10383,44 @@ const CREATE_COMMIT_SCHEDULE_PING_FN = (self) => trycatch(async (symbol, strateg
10355
10383
  },
10356
10384
  defaultValue: null,
10357
10385
  });
10386
+ /**
10387
+ * Creates a callback function for emitting idle ping events.
10388
+ *
10389
+ * Called by ClientStrategy when no active or scheduled signals are present.
10390
+ *
10391
+ * @param self - Reference to StrategyConnectionService instance
10392
+ * @returns Callback function for idle ping events
10393
+ */
10394
+ const CREATE_COMMIT_IDLE_PING_FN = (self) => trycatch(async (symbol, strategyName, exchangeName, currentPrice, backtest, timestamp) => {
10395
+ const frameName = self.methodContextService.context.frameName;
10396
+ const event = {
10397
+ symbol,
10398
+ strategyName,
10399
+ exchangeName,
10400
+ frameName,
10401
+ currentPrice,
10402
+ backtest,
10403
+ timestamp,
10404
+ };
10405
+ await idlePingSubject.next(event);
10406
+ await self.actionCoreService.pingIdle(backtest, event, { strategyName, exchangeName, frameName });
10407
+ }, {
10408
+ fallback: (error) => {
10409
+ const message = "StrategyConnectionService CREATE_COMMIT_IDLE_PING_FN thrown";
10410
+ const payload = {
10411
+ error: errorData(error),
10412
+ message: getErrorMessage(error),
10413
+ };
10414
+ self.loggerService.warn(message, payload);
10415
+ console.warn(message, payload);
10416
+ errorEmitter.next(error);
10417
+ },
10418
+ defaultValue: null,
10419
+ });
10358
10420
  /**
10359
10421
  * Creates a callback function for emitting active ping events.
10360
10422
  *
10361
10423
  * Called by ClientStrategy when an active pending signal is being monitored every minute.
10362
- * Placeholder for future activePingSubject implementation.
10363
10424
  *
10364
10425
  * @param self - Reference to StrategyConnectionService instance
10365
10426
  * @returns Callback function for active ping events
@@ -10606,6 +10667,7 @@ class StrategyConnectionService {
10606
10667
  onInit: CREATE_COMMIT_INIT_FN(this),
10607
10668
  onSchedulePing: CREATE_COMMIT_SCHEDULE_PING_FN(this),
10608
10669
  onActivePing: CREATE_COMMIT_ACTIVE_PING_FN(this),
10670
+ onIdlePing: CREATE_COMMIT_IDLE_PING_FN(this),
10609
10671
  onDispose: CREATE_COMMIT_DISPOSE_FN(this),
10610
10672
  onCommit: CREATE_COMMIT_FN(this),
10611
10673
  onSignalSync: CREATE_SYNC_FN(this, strategyName, exchangeName, frameName, backtest),
@@ -13084,6 +13146,34 @@ const CALL_PING_SCHEDULED_FN = trycatch(async (event, self) => {
13084
13146
  },
13085
13147
  defaultValue: null,
13086
13148
  });
13149
+ /**
13150
+ * Wrapper to call pingIdle method with error capture.
13151
+ */
13152
+ const CALL_PING_IDLE_FN = trycatch(async (event, self) => {
13153
+ if (!self._target.pingIdle) {
13154
+ return;
13155
+ }
13156
+ if (await self.params.strategy.hasPendingSignal(event.backtest, event.symbol, {
13157
+ strategyName: event.strategyName,
13158
+ exchangeName: event.exchangeName,
13159
+ frameName: event.frameName,
13160
+ })) {
13161
+ return;
13162
+ }
13163
+ return await self._target.pingIdle(event);
13164
+ }, {
13165
+ fallback: (error) => {
13166
+ const message = "ActionProxy.pingIdle thrown";
13167
+ const payload = {
13168
+ error: errorData(error),
13169
+ message: getErrorMessage(error),
13170
+ };
13171
+ LOGGER_SERVICE$4.warn(message, payload);
13172
+ console.warn(message, payload);
13173
+ errorEmitter.next(error);
13174
+ },
13175
+ defaultValue: null,
13176
+ });
13087
13177
  /**
13088
13178
  * Wrapper to call pingActive method with error capture.
13089
13179
  */
@@ -13322,6 +13412,18 @@ class ActionProxy {
13322
13412
  async pingActive(event) {
13323
13413
  return await CALL_PING_ACTIVE_FN(event, this);
13324
13414
  }
13415
+ /**
13416
+ * Handles idle ping events with error capture.
13417
+ *
13418
+ * Wraps the user's pingIdle() method to catch and log any errors.
13419
+ * Called every tick while no signal is pending or scheduled.
13420
+ *
13421
+ * @param event - Idle ping data with symbol, strategy info, current price, timestamp
13422
+ * @returns Promise resolving to user's pingIdle() result or null on error
13423
+ */
13424
+ async pingIdle(event) {
13425
+ return await CALL_PING_IDLE_FN(event, this);
13426
+ }
13325
13427
  /**
13326
13428
  * Handles risk rejection events with error capture.
13327
13429
  *
@@ -13509,6 +13611,23 @@ const CALL_PING_SCHEDULED_CALLBACK_FN = trycatch(async (self, event, strategyNam
13509
13611
  errorEmitter.next(error);
13510
13612
  },
13511
13613
  });
13614
+ /** Wrapper to call idle ping callback with error handling */
13615
+ const CALL_PING_IDLE_CALLBACK_FN = trycatch(async (self, event, strategyName, frameName, backtest) => {
13616
+ if (self.params.callbacks?.onPingIdle) {
13617
+ await self.params.callbacks.onPingIdle(event, self.params.actionName, strategyName, frameName, backtest);
13618
+ }
13619
+ }, {
13620
+ fallback: (error, self) => {
13621
+ const message = "ClientAction CALL_PING_IDLE_CALLBACK_FN thrown";
13622
+ const payload = {
13623
+ error: errorData(error),
13624
+ message: getErrorMessage(error),
13625
+ };
13626
+ self.params.logger.warn(message, payload);
13627
+ console.warn(message, payload);
13628
+ errorEmitter.next(error);
13629
+ },
13630
+ });
13512
13631
  /** Wrapper to call active ping callback with error handling */
13513
13632
  const CALL_PING_ACTIVE_CALLBACK_FN = trycatch(async (self, event, strategyName, frameName, backtest) => {
13514
13633
  if (self.params.callbacks?.onPingActive) {
@@ -13875,6 +13994,24 @@ class ClientAction {
13875
13994
  await CALL_PING_ACTIVE_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
13876
13995
  }
13877
13996
  ;
13997
+ /**
13998
+ * Handles idle ping events when no signal is active.
13999
+ */
14000
+ async pingIdle(event) {
14001
+ this.params.logger.debug("ClientAction pingIdle", {
14002
+ actionName: this.params.actionName,
14003
+ strategyName: this.params.strategyName,
14004
+ frameName: this.params.frameName,
14005
+ });
14006
+ if (!this._handlerInstance) {
14007
+ await this.waitForInit();
14008
+ }
14009
+ // Call handler method if defined
14010
+ await this._handlerInstance?.pingIdle(event);
14011
+ // Call callback if defined
14012
+ await CALL_PING_IDLE_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
14013
+ }
14014
+ ;
13878
14015
  /**
13879
14016
  * Handles risk rejection events when signals fail risk validation.
13880
14017
  */
@@ -14127,6 +14264,21 @@ class ActionConnectionService {
14127
14264
  const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
14128
14265
  await action.pingActive(event);
14129
14266
  };
14267
+ /**
14268
+ * Routes idle ping event to appropriate ClientAction instance.
14269
+ *
14270
+ * @param event - Idle ping event data
14271
+ * @param backtest - Whether running in backtest mode
14272
+ * @param context - Execution context with action name, strategy name, exchange name, frame name
14273
+ */
14274
+ this.pingIdle = async (event, backtest, context) => {
14275
+ this.loggerService.log("actionConnectionService pingIdle", {
14276
+ backtest,
14277
+ context,
14278
+ });
14279
+ const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
14280
+ await action.pingIdle(event);
14281
+ };
14130
14282
  /**
14131
14283
  * Routes riskRejection event to appropriate ClientAction instance.
14132
14284
  *
@@ -16230,6 +16382,27 @@ class ActionCoreService {
16230
16382
  await this.actionConnectionService.pingActive(event, backtest, { actionName, ...context });
16231
16383
  }
16232
16384
  };
16385
+ /**
16386
+ * Routes idle ping event to all registered actions for the strategy.
16387
+ *
16388
+ * Retrieves action list from strategy schema (IStrategySchema.actions)
16389
+ * and invokes the pingIdle handler on each ClientAction instance sequentially.
16390
+ * Called every tick when there is no pending or scheduled signal being monitored.
16391
+ *
16392
+ * @param backtest - Whether running in backtest mode (true) or live mode (false)
16393
+ * @param event - Idle state monitoring data
16394
+ * @param context - Strategy execution context with strategyName, exchangeName, frameName
16395
+ */
16396
+ this.pingIdle = async (backtest, event, context) => {
16397
+ this.loggerService.log("actionCoreService pingIdle", {
16398
+ context,
16399
+ });
16400
+ await this.validate(context);
16401
+ const { actions = [] } = this.strategySchemaService.get(context.strategyName);
16402
+ for (const actionName of actions) {
16403
+ await this.actionConnectionService.pingIdle(event, backtest, { actionName, ...context });
16404
+ }
16405
+ };
16233
16406
  /**
16234
16407
  * Routes risk rejection event to all registered actions for the strategy.
16235
16408
  *
@@ -37800,6 +37973,8 @@ const LISTEN_SCHEDULE_PING_METHOD_NAME = "event.listenSchedulePing";
37800
37973
  const LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME = "event.listenSchedulePingOnce";
37801
37974
  const LISTEN_ACTIVE_PING_METHOD_NAME = "event.listenActivePing";
37802
37975
  const LISTEN_ACTIVE_PING_ONCE_METHOD_NAME = "event.listenActivePingOnce";
37976
+ const LISTEN_IDLE_PING_METHOD_NAME = "event.listenIdlePing";
37977
+ const LISTEN_IDLE_PING_ONCE_METHOD_NAME = "event.listenIdlePingOnce";
37803
37978
  const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
37804
37979
  const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
37805
37980
  const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
@@ -38975,6 +39150,45 @@ function listenActivePingOnce(filterFn, fn) {
38975
39150
  };
38976
39151
  return disposeFn = listenActivePing(wrappedFn);
38977
39152
  }
39153
+ /**
39154
+ * Subscribes to idle ping events with queued async processing.
39155
+ *
39156
+ * Emits every tick when there is no pending or scheduled signal being monitored.
39157
+ *
39158
+ * @param fn - Callback function to handle idle ping events
39159
+ * @returns Unsubscribe function to stop listening
39160
+ */
39161
+ function listenIdlePing(fn) {
39162
+ backtest.loggerService.log(LISTEN_IDLE_PING_METHOD_NAME);
39163
+ const wrappedFn = async (event) => {
39164
+ if (await not(backtest.strategyCoreService.hasPendingSignal(event.backtest, event.symbol, {
39165
+ strategyName: event.strategyName,
39166
+ exchangeName: event.exchangeName,
39167
+ frameName: event.frameName,
39168
+ }))) {
39169
+ await fn(event);
39170
+ }
39171
+ };
39172
+ return idlePingSubject.subscribe(queued(wrappedFn));
39173
+ }
39174
+ /**
39175
+ * Subscribes to filtered idle ping events with one-time execution.
39176
+ *
39177
+ * @param filterFn - Predicate to filter events
39178
+ * @param fn - Callback function to handle the matching event
39179
+ * @returns Unsubscribe function to cancel the listener before it fires
39180
+ */
39181
+ function listenIdlePingOnce(filterFn, fn) {
39182
+ backtest.loggerService.log(LISTEN_IDLE_PING_ONCE_METHOD_NAME);
39183
+ let disposeFn;
39184
+ const wrappedFn = async (event) => {
39185
+ if (filterFn(event)) {
39186
+ await fn(event);
39187
+ disposeFn && disposeFn();
39188
+ }
39189
+ };
39190
+ return disposeFn = listenIdlePing(wrappedFn);
39191
+ }
38978
39192
  /**
38979
39193
  * Subscribes to strategy management events with queued async processing.
38980
39194
  *
@@ -45932,6 +46146,7 @@ const RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR = "RecentLiveAdapter.clear";
45932
46146
  const RECENT_ADAPTER_METHOD_NAME_ENABLE = "RecentAdapter.enable";
45933
46147
  const RECENT_ADAPTER_METHOD_NAME_DISABLE = "RecentAdapter.disable";
45934
46148
  const RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentAdapter.getLatestSignal";
46149
+ const RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL = "RecentAdapter.getMinutesSinceLatestSignalCreated";
45935
46150
  /**
45936
46151
  * Builds a composite storage key from context parts.
45937
46152
  * Includes backtest flag as the last segment to prevent live/backtest collisions.
@@ -45990,6 +46205,23 @@ class RecentPersistBacktestUtils {
45990
46205
  });
45991
46206
  return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45992
46207
  };
46208
+ /**
46209
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46210
+ * @param timestamp - Current timestamp in milliseconds
46211
+ * @param symbol - Trading pair symbol
46212
+ * @param strategyName - Strategy identifier
46213
+ * @param exchangeName - Exchange identifier
46214
+ * @param frameName - Frame identifier
46215
+ * @param backtest - Flag indicating if the context is backtest or live
46216
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46217
+ */
46218
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
46219
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
46220
+ if (!signal) {
46221
+ return null;
46222
+ }
46223
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46224
+ };
45993
46225
  }
45994
46226
  }
45995
46227
  /**
@@ -46032,6 +46264,23 @@ class RecentMemoryBacktestUtils {
46032
46264
  backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
46033
46265
  return this._signals.get(key) ?? null;
46034
46266
  };
46267
+ /**
46268
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46269
+ * @param timestamp - Current timestamp in milliseconds
46270
+ * @param symbol - Trading pair symbol
46271
+ * @param strategyName - Strategy identifier
46272
+ * @param exchangeName - Exchange identifier
46273
+ * @param frameName - Frame identifier
46274
+ * @param backtest - Flag indicating if the context is backtest or live
46275
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46276
+ */
46277
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
46278
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
46279
+ if (!signal) {
46280
+ return null;
46281
+ }
46282
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46283
+ };
46035
46284
  }
46036
46285
  }
46037
46286
  /**
@@ -46075,6 +46324,23 @@ class RecentPersistLiveUtils {
46075
46324
  });
46076
46325
  return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
46077
46326
  };
46327
+ /**
46328
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46329
+ * @param timestamp - Current timestamp in milliseconds
46330
+ * @param symbol - Trading pair symbol
46331
+ * @param strategyName - Strategy identifier
46332
+ * @param exchangeName - Exchange identifier
46333
+ * @param frameName - Frame identifier
46334
+ * @param backtest - Flag indicating if the context is backtest or live
46335
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46336
+ */
46337
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
46338
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
46339
+ if (!signal) {
46340
+ return null;
46341
+ }
46342
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46343
+ };
46078
46344
  }
46079
46345
  }
46080
46346
  /**
@@ -46117,6 +46383,23 @@ class RecentMemoryLiveUtils {
46117
46383
  backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
46118
46384
  return this._signals.get(key) ?? null;
46119
46385
  };
46386
+ /**
46387
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46388
+ * @param timestamp - Current timestamp in milliseconds
46389
+ * @param symbol - Trading pair symbol
46390
+ * @param strategyName - Strategy identifier
46391
+ * @param exchangeName - Exchange identifier
46392
+ * @param frameName - Frame identifier
46393
+ * @param backtest - Flag indicating if the context is backtest or live
46394
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46395
+ */
46396
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
46397
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
46398
+ if (!signal) {
46399
+ return null;
46400
+ }
46401
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46402
+ };
46120
46403
  }
46121
46404
  }
46122
46405
  /**
@@ -46163,6 +46446,32 @@ class RecentBacktestAdapter {
46163
46446
  });
46164
46447
  return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
46165
46448
  };
46449
+ /**
46450
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46451
+ * Proxies call to the underlying storage adapter.
46452
+ * @param timestamp - Current timestamp in milliseconds
46453
+ * @param symbol - Trading pair symbol
46454
+ * @param strategyName - Strategy identifier
46455
+ * @param exchangeName - Exchange identifier
46456
+ * @param frameName - Frame identifier
46457
+ * @param backtest - Flag indicating if the context is backtest or live
46458
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46459
+ */
46460
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
46461
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
46462
+ symbol,
46463
+ strategyName,
46464
+ exchangeName,
46465
+ frameName,
46466
+ backtest: backtest$1,
46467
+ timestamp,
46468
+ });
46469
+ const signal = await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
46470
+ if (!signal) {
46471
+ return null;
46472
+ }
46473
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46474
+ };
46166
46475
  /**
46167
46476
  * Sets the storage adapter constructor.
46168
46477
  * All future storage operations will use this adapter.
@@ -46241,6 +46550,32 @@ class RecentLiveAdapter {
46241
46550
  });
46242
46551
  return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
46243
46552
  };
46553
+ /**
46554
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46555
+ * Proxies call to the underlying storage adapter.
46556
+ * @param timestamp - Current timestamp in milliseconds
46557
+ * @param symbol - Trading pair symbol
46558
+ * @param strategyName - Strategy identifier
46559
+ * @param exchangeName - Exchange identifier
46560
+ * @param frameName - Frame identifier
46561
+ * @param backtest - Flag indicating if the context is backtest or live
46562
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46563
+ */
46564
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
46565
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
46566
+ symbol,
46567
+ strategyName,
46568
+ exchangeName,
46569
+ frameName,
46570
+ backtest: backtest$1,
46571
+ timestamp,
46572
+ });
46573
+ const signal = await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
46574
+ if (!signal) {
46575
+ return null;
46576
+ }
46577
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46578
+ };
46244
46579
  /**
46245
46580
  * Sets the storage adapter constructor.
46246
46581
  * All future storage operations will use this adapter.
@@ -46349,6 +46684,27 @@ class RecentAdapter {
46349
46684
  }
46350
46685
  return null;
46351
46686
  };
46687
+ /**
46688
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46689
+ * Searches backtest storage first, then live storage.
46690
+ * @param timestamp - Current timestamp in milliseconds
46691
+ * @param symbol - Trading pair symbol
46692
+ * @param context - Execution context with strategyName, exchangeName, and frameName
46693
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46694
+ * @throws Error if RecentAdapter is not enabled
46695
+ */
46696
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, context) => {
46697
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL, {
46698
+ symbol,
46699
+ context,
46700
+ timestamp,
46701
+ });
46702
+ const signal = await this.getLatestSignal(symbol, context);
46703
+ if (!signal) {
46704
+ return null;
46705
+ }
46706
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46707
+ };
46352
46708
  }
46353
46709
  }
46354
46710
  /**
@@ -46368,6 +46724,7 @@ const RecentLive = new RecentLiveAdapter();
46368
46724
  const RecentBacktest = new RecentBacktestAdapter();
46369
46725
 
46370
46726
  const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
46727
+ const GET_MINUTES_SINCE_LATEST_SIGNAL_CREATED_METHOD_NAME = "signal.getMinutesSinceLatestSignalCreated";
46371
46728
  /**
46372
46729
  * Returns the latest signal (pending or closed) for the current strategy context.
46373
46730
  *
@@ -46405,6 +46762,42 @@ async function getLatestSignal(symbol) {
46405
46762
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
46406
46763
  return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
46407
46764
  }
46765
+ /**
46766
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46767
+ *
46768
+ * Does not distinguish between active and closed signals — measures time since
46769
+ * whichever signal was recorded last. Useful for cooldown logic after a stop-loss.
46770
+ *
46771
+ * Searches backtest storage first, then live storage.
46772
+ * Returns null if no signal exists at all.
46773
+ *
46774
+ * Automatically detects backtest/live mode from execution context.
46775
+ *
46776
+ * @param symbol - Trading pair symbol
46777
+ * @param timestamp - Current timestamp in milliseconds
46778
+ * @returns Promise resolving to whole minutes since the latest signal was created, or null
46779
+ *
46780
+ * @example
46781
+ * ```typescript
46782
+ * import { getMinutesSinceLatestSignalCreated } from "backtest-kit";
46783
+ *
46784
+ * const minutes = await getMinutesSinceLatestSignalCreated("BTCUSDT", Date.now());
46785
+ * if (minutes !== null && minutes < 24 * 60) {
46786
+ * return; // cooldown — skip new signal for 24 hours after last signal
46787
+ * }
46788
+ * ```
46789
+ */
46790
+ async function getMinutesSinceLatestSignalCreated(symbol, timestamp) {
46791
+ backtest.loggerService.info(GET_MINUTES_SINCE_LATEST_SIGNAL_CREATED_METHOD_NAME, { symbol, timestamp });
46792
+ if (!ExecutionContextService.hasContext()) {
46793
+ throw new Error("getMinutesSinceLatestSignalCreated requires an execution context");
46794
+ }
46795
+ if (!MethodContextService.hasContext()) {
46796
+ throw new Error("getMinutesSinceLatestSignalCreated requires a method context");
46797
+ }
46798
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
46799
+ return await Recent.getMinutesSinceLatestSignalCreated(timestamp, symbol, { exchangeName, frameName, strategyName });
46800
+ }
46408
46801
 
46409
46802
  const DEFAULT_BM25_K1 = 1.5;
46410
46803
  const DEFAULT_BM25_B = 0.75;
@@ -49732,6 +50125,7 @@ const METHOD_NAME_CREATE_SNAPSHOT = "SessionUtils.createSnapshot";
49732
50125
  /** List of all global subjects whose listeners should be snapshotted for session isolation */
49733
50126
  const SUBJECT_ISOLATION_LIST = [
49734
50127
  activePingSubject,
50128
+ idlePingSubject,
49735
50129
  backtestScheduleOpenSubject,
49736
50130
  breakevenSubject,
49737
50131
  doneBacktestSubject,
@@ -57852,6 +58246,7 @@ const METHOD_NAME_PARTIAL_PROFIT_AVAILABLE = "ActionBase.partialProfitAvailable"
57852
58246
  const METHOD_NAME_PARTIAL_LOSS_AVAILABLE = "ActionBase.partialLossAvailable";
57853
58247
  const METHOD_NAME_PING_SCHEDULED = "ActionBase.pingScheduled";
57854
58248
  const METHOD_NAME_PING_ACTIVE = "ActionBase.pingActive";
58249
+ const METHOD_NAME_PING_IDLE = "ActionBase.pingIdle";
57855
58250
  const METHOD_NAME_RISK_REJECTION = "ActionBase.riskRejection";
57856
58251
  const METHOD_NAME_DISPOSE = "ActionBase.dispose";
57857
58252
  const DEFAULT_SOURCE = "default";
@@ -58245,6 +58640,26 @@ class ActionBase {
58245
58640
  source,
58246
58641
  });
58247
58642
  }
58643
+ /**
58644
+ * Handles idle ping events when no signal is active.
58645
+ *
58646
+ * Called every tick while no signal is pending or scheduled.
58647
+ * Use to monitor idle strategy state and implement entry condition logic.
58648
+ *
58649
+ * Triggered by: ActionCoreService.pingIdle() via StrategyConnectionService
58650
+ * Source: idlePingSubject.next() in CREATE_COMMIT_IDLE_PING_FN callback
58651
+ * Frequency: Every tick while no signal is pending or scheduled
58652
+ *
58653
+ * Default implementation: Logs idle ping event.
58654
+ *
58655
+ * @param event - Idle ping data with symbol, strategy info, current price, timestamp
58656
+ */
58657
+ pingIdle(event, source = DEFAULT_SOURCE) {
58658
+ LOGGER_SERVICE.info(METHOD_NAME_PING_IDLE, {
58659
+ event,
58660
+ source,
58661
+ });
58662
+ }
58248
58663
  /**
58249
58664
  * Handles risk rejection events when signals fail risk validation.
58250
58665
  *
@@ -58578,4 +58993,4 @@ const validateSignal = (signal, currentPrice) => {
58578
58993
  return !errors.length;
58579
58994
  };
58580
58995
 
58581
- export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRecentAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, 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, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, 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, 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, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
58996
+ export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRecentAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitSignalNotify, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, 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, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, 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, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "6.15.0",
3
+ "version": "6.16.0",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",