backtest-kit 6.15.0 → 7.0.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,
@@ -910,7 +916,6 @@ const INTERVAL_MINUTES$9 = {
910
916
  "6h": 360,
911
917
  "8h": 480,
912
918
  "1d": 1440,
913
- "1w": 10080,
914
919
  };
915
920
  const MS_PER_MINUTE$7 = 60000;
916
921
  const PERSIST_SIGNAL_UTILS_METHOD_NAME_USE_PERSIST_SIGNAL_ADAPTER = "PersistSignalUtils.usePersistSignalAdapter";
@@ -3090,7 +3095,6 @@ const INTERVAL_MINUTES$8 = {
3090
3095
  "6h": 360,
3091
3096
  "8h": 480,
3092
3097
  "1d": 1440,
3093
- "1w": 10080,
3094
3098
  };
3095
3099
  /**
3096
3100
  * Aligns timestamp down to the nearest interval boundary.
@@ -6028,6 +6032,27 @@ const CALL_ACTIVE_PING_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, pe
6028
6032
  errorEmitter.next(error);
6029
6033
  },
6030
6034
  });
6035
+ const CALL_IDLE_PING_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, timestamp, backtest, currentPrice) => {
6036
+ await ExecutionContextService.runInContext(async () => {
6037
+ // Call system onIdlePing callback (emits to idlePingSubject)
6038
+ 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);
6039
+ }, {
6040
+ when: new Date(timestamp),
6041
+ symbol: symbol,
6042
+ backtest: backtest,
6043
+ });
6044
+ }), {
6045
+ fallback: (error, self) => {
6046
+ const message = "ClientStrategy CALL_IDLE_PING_CALLBACKS_FN thrown";
6047
+ const payload = {
6048
+ error: errorData(error),
6049
+ message: getErrorMessage(error),
6050
+ };
6051
+ self.params.logger.warn(message, payload);
6052
+ console.warn(message, payload);
6053
+ errorEmitter.next(error);
6054
+ },
6055
+ });
6031
6056
  const CALL_ACTIVE_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
6032
6057
  await ExecutionContextService.runInContext(async () => {
6033
6058
  if (self.params.callbacks?.onActive) {
@@ -6683,6 +6708,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice, backt
6683
6708
  };
6684
6709
  const RETURN_IDLE_FN = async (self, currentPrice) => {
6685
6710
  const currentTime = self.params.execution.context.when.getTime();
6711
+ await CALL_IDLE_PING_CALLBACKS_FN(self, self.params.execution.context.symbol, currentTime, self.params.execution.context.backtest, currentPrice);
6686
6712
  await CALL_IDLE_CALLBACKS_FN(self, self.params.execution.context.symbol, currentPrice, currentTime, self.params.execution.context.backtest);
6687
6713
  const result = {
6688
6714
  action: "idle",
@@ -10355,11 +10381,44 @@ const CREATE_COMMIT_SCHEDULE_PING_FN = (self) => trycatch(async (symbol, strateg
10355
10381
  },
10356
10382
  defaultValue: null,
10357
10383
  });
10384
+ /**
10385
+ * Creates a callback function for emitting idle ping events.
10386
+ *
10387
+ * Called by ClientStrategy when no active or scheduled signals are present.
10388
+ *
10389
+ * @param self - Reference to StrategyConnectionService instance
10390
+ * @returns Callback function for idle ping events
10391
+ */
10392
+ const CREATE_COMMIT_IDLE_PING_FN = (self) => trycatch(async (symbol, strategyName, exchangeName, currentPrice, backtest, timestamp) => {
10393
+ const frameName = self.methodContextService.context.frameName;
10394
+ const event = {
10395
+ symbol,
10396
+ strategyName,
10397
+ exchangeName,
10398
+ frameName,
10399
+ currentPrice,
10400
+ backtest,
10401
+ timestamp,
10402
+ };
10403
+ await idlePingSubject.next(event);
10404
+ await self.actionCoreService.pingIdle(backtest, event, { strategyName, exchangeName, frameName });
10405
+ }, {
10406
+ fallback: (error) => {
10407
+ const message = "StrategyConnectionService CREATE_COMMIT_IDLE_PING_FN thrown";
10408
+ const payload = {
10409
+ error: errorData(error),
10410
+ message: getErrorMessage(error),
10411
+ };
10412
+ self.loggerService.warn(message, payload);
10413
+ console.warn(message, payload);
10414
+ errorEmitter.next(error);
10415
+ },
10416
+ defaultValue: null,
10417
+ });
10358
10418
  /**
10359
10419
  * Creates a callback function for emitting active ping events.
10360
10420
  *
10361
10421
  * Called by ClientStrategy when an active pending signal is being monitored every minute.
10362
- * Placeholder for future activePingSubject implementation.
10363
10422
  *
10364
10423
  * @param self - Reference to StrategyConnectionService instance
10365
10424
  * @returns Callback function for active ping events
@@ -10606,6 +10665,7 @@ class StrategyConnectionService {
10606
10665
  onInit: CREATE_COMMIT_INIT_FN(this),
10607
10666
  onSchedulePing: CREATE_COMMIT_SCHEDULE_PING_FN(this),
10608
10667
  onActivePing: CREATE_COMMIT_ACTIVE_PING_FN(this),
10668
+ onIdlePing: CREATE_COMMIT_IDLE_PING_FN(this),
10609
10669
  onDispose: CREATE_COMMIT_DISPOSE_FN(this),
10610
10670
  onCommit: CREATE_COMMIT_FN(this),
10611
10671
  onSignalSync: CREATE_SYNC_FN(this, strategyName, exchangeName, frameName, backtest),
@@ -11957,8 +12017,6 @@ const INTERVAL_MINUTES$6 = {
11957
12017
  "8h": 480,
11958
12018
  "12h": 720,
11959
12019
  "1d": 1440,
11960
- "1w": 10080,
11961
- "1M": 43200,
11962
12020
  };
11963
12021
  /**
11964
12022
  * Wrapper to call onTimeframe callback with error handling.
@@ -12024,7 +12082,7 @@ const GET_TIMEFRAME_FN = async (symbol, self) => {
12024
12082
  * Features:
12025
12083
  * - Generates timestamp arrays for backtest iteration
12026
12084
  * - Singleshot caching prevents redundant generation
12027
- * - Configurable interval spacing (1m to 3d)
12085
+ * - Configurable interval spacing (1m to 1d)
12028
12086
  * - Callback support for validation and logging
12029
12087
  *
12030
12088
  * Used by BacktestLogicPrivateService to iterate through historical periods.
@@ -12378,7 +12436,6 @@ const INTERVAL_MINUTES$5 = {
12378
12436
  "6h": 360,
12379
12437
  "8h": 480,
12380
12438
  "1d": 1440,
12381
- "1w": 10080,
12382
12439
  };
12383
12440
  /**
12384
12441
  * Aligns timestamp down to the nearest interval boundary.
@@ -13084,6 +13141,34 @@ const CALL_PING_SCHEDULED_FN = trycatch(async (event, self) => {
13084
13141
  },
13085
13142
  defaultValue: null,
13086
13143
  });
13144
+ /**
13145
+ * Wrapper to call pingIdle method with error capture.
13146
+ */
13147
+ const CALL_PING_IDLE_FN = trycatch(async (event, self) => {
13148
+ if (!self._target.pingIdle) {
13149
+ return;
13150
+ }
13151
+ if (await self.params.strategy.hasPendingSignal(event.backtest, event.symbol, {
13152
+ strategyName: event.strategyName,
13153
+ exchangeName: event.exchangeName,
13154
+ frameName: event.frameName,
13155
+ })) {
13156
+ return;
13157
+ }
13158
+ return await self._target.pingIdle(event);
13159
+ }, {
13160
+ fallback: (error) => {
13161
+ const message = "ActionProxy.pingIdle thrown";
13162
+ const payload = {
13163
+ error: errorData(error),
13164
+ message: getErrorMessage(error),
13165
+ };
13166
+ LOGGER_SERVICE$4.warn(message, payload);
13167
+ console.warn(message, payload);
13168
+ errorEmitter.next(error);
13169
+ },
13170
+ defaultValue: null,
13171
+ });
13087
13172
  /**
13088
13173
  * Wrapper to call pingActive method with error capture.
13089
13174
  */
@@ -13322,6 +13407,18 @@ class ActionProxy {
13322
13407
  async pingActive(event) {
13323
13408
  return await CALL_PING_ACTIVE_FN(event, this);
13324
13409
  }
13410
+ /**
13411
+ * Handles idle ping events with error capture.
13412
+ *
13413
+ * Wraps the user's pingIdle() method to catch and log any errors.
13414
+ * Called every tick while no signal is pending or scheduled.
13415
+ *
13416
+ * @param event - Idle ping data with symbol, strategy info, current price, timestamp
13417
+ * @returns Promise resolving to user's pingIdle() result or null on error
13418
+ */
13419
+ async pingIdle(event) {
13420
+ return await CALL_PING_IDLE_FN(event, this);
13421
+ }
13325
13422
  /**
13326
13423
  * Handles risk rejection events with error capture.
13327
13424
  *
@@ -13509,6 +13606,23 @@ const CALL_PING_SCHEDULED_CALLBACK_FN = trycatch(async (self, event, strategyNam
13509
13606
  errorEmitter.next(error);
13510
13607
  },
13511
13608
  });
13609
+ /** Wrapper to call idle ping callback with error handling */
13610
+ const CALL_PING_IDLE_CALLBACK_FN = trycatch(async (self, event, strategyName, frameName, backtest) => {
13611
+ if (self.params.callbacks?.onPingIdle) {
13612
+ await self.params.callbacks.onPingIdle(event, self.params.actionName, strategyName, frameName, backtest);
13613
+ }
13614
+ }, {
13615
+ fallback: (error, self) => {
13616
+ const message = "ClientAction CALL_PING_IDLE_CALLBACK_FN thrown";
13617
+ const payload = {
13618
+ error: errorData(error),
13619
+ message: getErrorMessage(error),
13620
+ };
13621
+ self.params.logger.warn(message, payload);
13622
+ console.warn(message, payload);
13623
+ errorEmitter.next(error);
13624
+ },
13625
+ });
13512
13626
  /** Wrapper to call active ping callback with error handling */
13513
13627
  const CALL_PING_ACTIVE_CALLBACK_FN = trycatch(async (self, event, strategyName, frameName, backtest) => {
13514
13628
  if (self.params.callbacks?.onPingActive) {
@@ -13875,6 +13989,24 @@ class ClientAction {
13875
13989
  await CALL_PING_ACTIVE_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
13876
13990
  }
13877
13991
  ;
13992
+ /**
13993
+ * Handles idle ping events when no signal is active.
13994
+ */
13995
+ async pingIdle(event) {
13996
+ this.params.logger.debug("ClientAction pingIdle", {
13997
+ actionName: this.params.actionName,
13998
+ strategyName: this.params.strategyName,
13999
+ frameName: this.params.frameName,
14000
+ });
14001
+ if (!this._handlerInstance) {
14002
+ await this.waitForInit();
14003
+ }
14004
+ // Call handler method if defined
14005
+ await this._handlerInstance?.pingIdle(event);
14006
+ // Call callback if defined
14007
+ await CALL_PING_IDLE_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
14008
+ }
14009
+ ;
13878
14010
  /**
13879
14011
  * Handles risk rejection events when signals fail risk validation.
13880
14012
  */
@@ -14127,6 +14259,21 @@ class ActionConnectionService {
14127
14259
  const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
14128
14260
  await action.pingActive(event);
14129
14261
  };
14262
+ /**
14263
+ * Routes idle ping event to appropriate ClientAction instance.
14264
+ *
14265
+ * @param event - Idle ping event data
14266
+ * @param backtest - Whether running in backtest mode
14267
+ * @param context - Execution context with action name, strategy name, exchange name, frame name
14268
+ */
14269
+ this.pingIdle = async (event, backtest, context) => {
14270
+ this.loggerService.log("actionConnectionService pingIdle", {
14271
+ backtest,
14272
+ context,
14273
+ });
14274
+ const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
14275
+ await action.pingIdle(event);
14276
+ };
14130
14277
  /**
14131
14278
  * Routes riskRejection event to appropriate ClientAction instance.
14132
14279
  *
@@ -16230,6 +16377,27 @@ class ActionCoreService {
16230
16377
  await this.actionConnectionService.pingActive(event, backtest, { actionName, ...context });
16231
16378
  }
16232
16379
  };
16380
+ /**
16381
+ * Routes idle ping event to all registered actions for the strategy.
16382
+ *
16383
+ * Retrieves action list from strategy schema (IStrategySchema.actions)
16384
+ * and invokes the pingIdle handler on each ClientAction instance sequentially.
16385
+ * Called every tick when there is no pending or scheduled signal being monitored.
16386
+ *
16387
+ * @param backtest - Whether running in backtest mode (true) or live mode (false)
16388
+ * @param event - Idle state monitoring data
16389
+ * @param context - Strategy execution context with strategyName, exchangeName, frameName
16390
+ */
16391
+ this.pingIdle = async (backtest, event, context) => {
16392
+ this.loggerService.log("actionCoreService pingIdle", {
16393
+ context,
16394
+ });
16395
+ await this.validate(context);
16396
+ const { actions = [] } = this.strategySchemaService.get(context.strategyName);
16397
+ for (const actionName of actions) {
16398
+ await this.actionConnectionService.pingIdle(event, backtest, { actionName, ...context });
16399
+ }
16400
+ };
16233
16401
  /**
16234
16402
  * Routes risk rejection event to all registered actions for the strategy.
16235
16403
  *
@@ -17639,7 +17807,6 @@ const INTERVAL_MINUTES$4 = {
17639
17807
  "6h": 360,
17640
17808
  "8h": 480,
17641
17809
  "1d": 1440,
17642
- "1w": 10080,
17643
17810
  };
17644
17811
  const createEmitter = memoize(([interval]) => `${interval}`, (interval) => {
17645
17812
  const tickSubject = new Subject();
@@ -32852,7 +33019,6 @@ const INTERVAL_MINUTES$3 = {
32852
33019
  "6h": 360,
32853
33020
  "8h": 480,
32854
33021
  "1d": 1440,
32855
- "1w": 10080,
32856
33022
  };
32857
33023
  /**
32858
33024
  * Aligns timestamp down to the nearest interval boundary.
@@ -33603,7 +33769,6 @@ const INTERVAL_MINUTES$2 = {
33603
33769
  "6h": 360,
33604
33770
  "8h": 480,
33605
33771
  "1d": 1440,
33606
- "1w": 10080,
33607
33772
  };
33608
33773
  const ALIGN_TO_INTERVAL_FN = (timestamp, intervalMinutes) => {
33609
33774
  const intervalMs = intervalMinutes * MS_PER_MINUTE$2;
@@ -37800,6 +37965,8 @@ const LISTEN_SCHEDULE_PING_METHOD_NAME = "event.listenSchedulePing";
37800
37965
  const LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME = "event.listenSchedulePingOnce";
37801
37966
  const LISTEN_ACTIVE_PING_METHOD_NAME = "event.listenActivePing";
37802
37967
  const LISTEN_ACTIVE_PING_ONCE_METHOD_NAME = "event.listenActivePingOnce";
37968
+ const LISTEN_IDLE_PING_METHOD_NAME = "event.listenIdlePing";
37969
+ const LISTEN_IDLE_PING_ONCE_METHOD_NAME = "event.listenIdlePingOnce";
37803
37970
  const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
37804
37971
  const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
37805
37972
  const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
@@ -38975,6 +39142,45 @@ function listenActivePingOnce(filterFn, fn) {
38975
39142
  };
38976
39143
  return disposeFn = listenActivePing(wrappedFn);
38977
39144
  }
39145
+ /**
39146
+ * Subscribes to idle ping events with queued async processing.
39147
+ *
39148
+ * Emits every tick when there is no pending or scheduled signal being monitored.
39149
+ *
39150
+ * @param fn - Callback function to handle idle ping events
39151
+ * @returns Unsubscribe function to stop listening
39152
+ */
39153
+ function listenIdlePing(fn) {
39154
+ backtest.loggerService.log(LISTEN_IDLE_PING_METHOD_NAME);
39155
+ const wrappedFn = async (event) => {
39156
+ if (await not(backtest.strategyCoreService.hasPendingSignal(event.backtest, event.symbol, {
39157
+ strategyName: event.strategyName,
39158
+ exchangeName: event.exchangeName,
39159
+ frameName: event.frameName,
39160
+ }))) {
39161
+ await fn(event);
39162
+ }
39163
+ };
39164
+ return idlePingSubject.subscribe(queued(wrappedFn));
39165
+ }
39166
+ /**
39167
+ * Subscribes to filtered idle ping events with one-time execution.
39168
+ *
39169
+ * @param filterFn - Predicate to filter events
39170
+ * @param fn - Callback function to handle the matching event
39171
+ * @returns Unsubscribe function to cancel the listener before it fires
39172
+ */
39173
+ function listenIdlePingOnce(filterFn, fn) {
39174
+ backtest.loggerService.log(LISTEN_IDLE_PING_ONCE_METHOD_NAME);
39175
+ let disposeFn;
39176
+ const wrappedFn = async (event) => {
39177
+ if (filterFn(event)) {
39178
+ await fn(event);
39179
+ disposeFn && disposeFn();
39180
+ }
39181
+ };
39182
+ return disposeFn = listenIdlePing(wrappedFn);
39183
+ }
38978
39184
  /**
38979
39185
  * Subscribes to strategy management events with queued async processing.
38980
39186
  *
@@ -45185,7 +45391,7 @@ function addExchangeSchema(exchangeSchema) {
45185
45391
  *
45186
45392
  * @param frameSchema - Frame configuration object
45187
45393
  * @param frameSchema.frameName - Unique frame identifier
45188
- * @param frameSchema.interval - Timeframe interval ("1m" | "3m" | "5m" | "15m" | "30m" | "1h" | "2h" | "4h" | "6h" | "8h" | "12h" | "1d" | "3d")
45394
+ * @param frameSchema.interval - Timeframe interval ("1m" | "3m" | "5m" | "15m" | "30m" | "1h" | "2h" | "4h" | "6h" | "8h" | "12h" | "1d")
45189
45395
  * @param frameSchema.startDate - Start date for timeframe generation
45190
45396
  * @param frameSchema.endDate - End date for timeframe generation
45191
45397
  * @param frameSchema.callbacks - Optional callback for timeframe events
@@ -45932,6 +46138,7 @@ const RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR = "RecentLiveAdapter.clear";
45932
46138
  const RECENT_ADAPTER_METHOD_NAME_ENABLE = "RecentAdapter.enable";
45933
46139
  const RECENT_ADAPTER_METHOD_NAME_DISABLE = "RecentAdapter.disable";
45934
46140
  const RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentAdapter.getLatestSignal";
46141
+ const RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL = "RecentAdapter.getMinutesSinceLatestSignalCreated";
45935
46142
  /**
45936
46143
  * Builds a composite storage key from context parts.
45937
46144
  * Includes backtest flag as the last segment to prevent live/backtest collisions.
@@ -45990,6 +46197,23 @@ class RecentPersistBacktestUtils {
45990
46197
  });
45991
46198
  return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45992
46199
  };
46200
+ /**
46201
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46202
+ * @param timestamp - Current timestamp in milliseconds
46203
+ * @param symbol - Trading pair symbol
46204
+ * @param strategyName - Strategy identifier
46205
+ * @param exchangeName - Exchange identifier
46206
+ * @param frameName - Frame identifier
46207
+ * @param backtest - Flag indicating if the context is backtest or live
46208
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46209
+ */
46210
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
46211
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
46212
+ if (!signal) {
46213
+ return null;
46214
+ }
46215
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46216
+ };
45993
46217
  }
45994
46218
  }
45995
46219
  /**
@@ -46032,6 +46256,23 @@ class RecentMemoryBacktestUtils {
46032
46256
  backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
46033
46257
  return this._signals.get(key) ?? null;
46034
46258
  };
46259
+ /**
46260
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46261
+ * @param timestamp - Current timestamp in milliseconds
46262
+ * @param symbol - Trading pair symbol
46263
+ * @param strategyName - Strategy identifier
46264
+ * @param exchangeName - Exchange identifier
46265
+ * @param frameName - Frame identifier
46266
+ * @param backtest - Flag indicating if the context is backtest or live
46267
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46268
+ */
46269
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
46270
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
46271
+ if (!signal) {
46272
+ return null;
46273
+ }
46274
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46275
+ };
46035
46276
  }
46036
46277
  }
46037
46278
  /**
@@ -46075,6 +46316,23 @@ class RecentPersistLiveUtils {
46075
46316
  });
46076
46317
  return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
46077
46318
  };
46319
+ /**
46320
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46321
+ * @param timestamp - Current timestamp in milliseconds
46322
+ * @param symbol - Trading pair symbol
46323
+ * @param strategyName - Strategy identifier
46324
+ * @param exchangeName - Exchange identifier
46325
+ * @param frameName - Frame identifier
46326
+ * @param backtest - Flag indicating if the context is backtest or live
46327
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46328
+ */
46329
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
46330
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
46331
+ if (!signal) {
46332
+ return null;
46333
+ }
46334
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46335
+ };
46078
46336
  }
46079
46337
  }
46080
46338
  /**
@@ -46117,6 +46375,23 @@ class RecentMemoryLiveUtils {
46117
46375
  backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
46118
46376
  return this._signals.get(key) ?? null;
46119
46377
  };
46378
+ /**
46379
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46380
+ * @param timestamp - Current timestamp in milliseconds
46381
+ * @param symbol - Trading pair symbol
46382
+ * @param strategyName - Strategy identifier
46383
+ * @param exchangeName - Exchange identifier
46384
+ * @param frameName - Frame identifier
46385
+ * @param backtest - Flag indicating if the context is backtest or live
46386
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46387
+ */
46388
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
46389
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
46390
+ if (!signal) {
46391
+ return null;
46392
+ }
46393
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46394
+ };
46120
46395
  }
46121
46396
  }
46122
46397
  /**
@@ -46163,6 +46438,32 @@ class RecentBacktestAdapter {
46163
46438
  });
46164
46439
  return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
46165
46440
  };
46441
+ /**
46442
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46443
+ * Proxies call to the underlying storage adapter.
46444
+ * @param timestamp - Current timestamp in milliseconds
46445
+ * @param symbol - Trading pair symbol
46446
+ * @param strategyName - Strategy identifier
46447
+ * @param exchangeName - Exchange identifier
46448
+ * @param frameName - Frame identifier
46449
+ * @param backtest - Flag indicating if the context is backtest or live
46450
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46451
+ */
46452
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
46453
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
46454
+ symbol,
46455
+ strategyName,
46456
+ exchangeName,
46457
+ frameName,
46458
+ backtest: backtest$1,
46459
+ timestamp,
46460
+ });
46461
+ const signal = await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
46462
+ if (!signal) {
46463
+ return null;
46464
+ }
46465
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46466
+ };
46166
46467
  /**
46167
46468
  * Sets the storage adapter constructor.
46168
46469
  * All future storage operations will use this adapter.
@@ -46241,6 +46542,32 @@ class RecentLiveAdapter {
46241
46542
  });
46242
46543
  return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
46243
46544
  };
46545
+ /**
46546
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46547
+ * Proxies call to the underlying storage adapter.
46548
+ * @param timestamp - Current timestamp in milliseconds
46549
+ * @param symbol - Trading pair symbol
46550
+ * @param strategyName - Strategy identifier
46551
+ * @param exchangeName - Exchange identifier
46552
+ * @param frameName - Frame identifier
46553
+ * @param backtest - Flag indicating if the context is backtest or live
46554
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46555
+ */
46556
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
46557
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
46558
+ symbol,
46559
+ strategyName,
46560
+ exchangeName,
46561
+ frameName,
46562
+ backtest: backtest$1,
46563
+ timestamp,
46564
+ });
46565
+ const signal = await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
46566
+ if (!signal) {
46567
+ return null;
46568
+ }
46569
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46570
+ };
46244
46571
  /**
46245
46572
  * Sets the storage adapter constructor.
46246
46573
  * All future storage operations will use this adapter.
@@ -46349,6 +46676,27 @@ class RecentAdapter {
46349
46676
  }
46350
46677
  return null;
46351
46678
  };
46679
+ /**
46680
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46681
+ * Searches backtest storage first, then live storage.
46682
+ * @param timestamp - Current timestamp in milliseconds
46683
+ * @param symbol - Trading pair symbol
46684
+ * @param context - Execution context with strategyName, exchangeName, and frameName
46685
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46686
+ * @throws Error if RecentAdapter is not enabled
46687
+ */
46688
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, context) => {
46689
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL, {
46690
+ symbol,
46691
+ context,
46692
+ timestamp,
46693
+ });
46694
+ const signal = await this.getLatestSignal(symbol, context);
46695
+ if (!signal) {
46696
+ return null;
46697
+ }
46698
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46699
+ };
46352
46700
  }
46353
46701
  }
46354
46702
  /**
@@ -46368,6 +46716,7 @@ const RecentLive = new RecentLiveAdapter();
46368
46716
  const RecentBacktest = new RecentBacktestAdapter();
46369
46717
 
46370
46718
  const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
46719
+ const GET_MINUTES_SINCE_LATEST_SIGNAL_CREATED_METHOD_NAME = "signal.getMinutesSinceLatestSignalCreated";
46371
46720
  /**
46372
46721
  * Returns the latest signal (pending or closed) for the current strategy context.
46373
46722
  *
@@ -46405,6 +46754,43 @@ async function getLatestSignal(symbol) {
46405
46754
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
46406
46755
  return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
46407
46756
  }
46757
+ /**
46758
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46759
+ *
46760
+ * Does not distinguish between active and closed signals — measures time since
46761
+ * whichever signal was recorded last. Useful for cooldown logic after a stop-loss.
46762
+ *
46763
+ * Searches backtest storage first, then live storage.
46764
+ * Returns null if no signal exists at all.
46765
+ *
46766
+ * Automatically detects backtest/live mode from execution context.
46767
+ *
46768
+ * @param symbol - Trading pair symbol
46769
+ * @param timestamp - Current timestamp in milliseconds
46770
+ * @returns Promise resolving to whole minutes since the latest signal was created, or null
46771
+ *
46772
+ * @example
46773
+ * ```typescript
46774
+ * import { getMinutesSinceLatestSignalCreated } from "backtest-kit";
46775
+ *
46776
+ * const minutes = await getMinutesSinceLatestSignalCreated("BTCUSDT");
46777
+ * if (minutes !== null && minutes < 24 * 60) {
46778
+ * return; // cooldown — skip new signal for 24 hours after last signal
46779
+ * }
46780
+ * ```
46781
+ */
46782
+ async function getMinutesSinceLatestSignalCreated(symbol) {
46783
+ backtest.loggerService.info(GET_MINUTES_SINCE_LATEST_SIGNAL_CREATED_METHOD_NAME, { symbol });
46784
+ if (!ExecutionContextService.hasContext()) {
46785
+ throw new Error("getMinutesSinceLatestSignalCreated requires an execution context");
46786
+ }
46787
+ if (!MethodContextService.hasContext()) {
46788
+ throw new Error("getMinutesSinceLatestSignalCreated requires a method context");
46789
+ }
46790
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
46791
+ const { when } = backtest.executionContextService.context;
46792
+ return await Recent.getMinutesSinceLatestSignalCreated(when.getTime(), symbol, { exchangeName, frameName, strategyName });
46793
+ }
46408
46794
 
46409
46795
  const DEFAULT_BM25_K1 = 1.5;
46410
46796
  const DEFAULT_BM25_B = 0.75;
@@ -49732,6 +50118,7 @@ const METHOD_NAME_CREATE_SNAPSHOT = "SessionUtils.createSnapshot";
49732
50118
  /** List of all global subjects whose listeners should be snapshotted for session isolation */
49733
50119
  const SUBJECT_ISOLATION_LIST = [
49734
50120
  activePingSubject,
50121
+ idlePingSubject,
49735
50122
  backtestScheduleOpenSubject,
49736
50123
  breakevenSubject,
49737
50124
  doneBacktestSubject,
@@ -56286,7 +56673,6 @@ const INTERVAL_MINUTES$1 = {
56286
56673
  "6h": 360,
56287
56674
  "8h": 480,
56288
56675
  "1d": 1440,
56289
- "1w": 10080,
56290
56676
  };
56291
56677
  /**
56292
56678
  * Aligns timestamp down to the nearest interval boundary.
@@ -56921,7 +57307,6 @@ const INTERVAL_MINUTES = {
56921
57307
  "6h": 360,
56922
57308
  "8h": 480,
56923
57309
  "1d": 1440,
56924
- "1w": 10080,
56925
57310
  };
56926
57311
  /**
56927
57312
  * Aligns timestamp down to the nearest interval boundary.
@@ -57852,6 +58237,7 @@ const METHOD_NAME_PARTIAL_PROFIT_AVAILABLE = "ActionBase.partialProfitAvailable"
57852
58237
  const METHOD_NAME_PARTIAL_LOSS_AVAILABLE = "ActionBase.partialLossAvailable";
57853
58238
  const METHOD_NAME_PING_SCHEDULED = "ActionBase.pingScheduled";
57854
58239
  const METHOD_NAME_PING_ACTIVE = "ActionBase.pingActive";
58240
+ const METHOD_NAME_PING_IDLE = "ActionBase.pingIdle";
57855
58241
  const METHOD_NAME_RISK_REJECTION = "ActionBase.riskRejection";
57856
58242
  const METHOD_NAME_DISPOSE = "ActionBase.dispose";
57857
58243
  const DEFAULT_SOURCE = "default";
@@ -58245,6 +58631,26 @@ class ActionBase {
58245
58631
  source,
58246
58632
  });
58247
58633
  }
58634
+ /**
58635
+ * Handles idle ping events when no signal is active.
58636
+ *
58637
+ * Called every tick while no signal is pending or scheduled.
58638
+ * Use to monitor idle strategy state and implement entry condition logic.
58639
+ *
58640
+ * Triggered by: ActionCoreService.pingIdle() via StrategyConnectionService
58641
+ * Source: idlePingSubject.next() in CREATE_COMMIT_IDLE_PING_FN callback
58642
+ * Frequency: Every tick while no signal is pending or scheduled
58643
+ *
58644
+ * Default implementation: Logs idle ping event.
58645
+ *
58646
+ * @param event - Idle ping data with symbol, strategy info, current price, timestamp
58647
+ */
58648
+ pingIdle(event, source = DEFAULT_SOURCE) {
58649
+ LOGGER_SERVICE.info(METHOD_NAME_PING_IDLE, {
58650
+ event,
58651
+ source,
58652
+ });
58653
+ }
58248
58654
  /**
58249
58655
  * Handles risk rejection events when signals fail risk validation.
58250
58656
  *
@@ -58578,4 +58984,4 @@ const validateSignal = (signal, currentPrice) => {
58578
58984
  return !errors.length;
58579
58985
  };
58580
58986
 
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 };
58987
+ 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 };